Em 1 minuto
Crie a chave do aparelho em Meus Mupli (uma chave por aparelho, mostrada uma vez) e mande:
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Visitas","value":1280}'A resposta diz o que a tela mostra:
{"ok":true,"seq":4822,"changed":true,"accepted_at":"…","fresh_until":"…","expires_at":"…",
"pages":[{"layout":"number","shown":["title","value"],"not_shown":[]}],
"warnings":[],"device_bytes":100,"alert":{"fired":false,"seq":null,"replaced":false},
"device":{"online":true,"rom":"whitelabel","delivered_seq":4821,"next_fetch_in_s":42},"next_in_s":5}O que você manda é seu e aparece numa tela que fica na mesa, à vista de quem passa. Não mande dados sensíveis (saúde, religião, opinião política, biometria, origem racial), dados de crianças e de adolescentes, senhas, documentos nem dados de cartão.
Tudo é opcional
Até o vazio vale. {} responde 200 e a tela volta à mesa, com o relógio e o bichinho:
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{}'- Campo que não veio não aparece.
nullvale o mesmo que não mandar. - Cada envio troca a tela inteira: o que não veio sai.
- Sem
pages, os campos de página escritos direto no objeto são a página 1 (o atalho do exemplo acima). - O servidor recusa pouco e avisa sempre: o que não cabe sai da tela e a resposta diz o quê.
A página e os blocos
Até 4 páginas, que passam em rotação. Cada uma tem um título com ícone e estado, e até quatro blocos: o número grande (value), o gráfico (chart), as linhas (rows) e um recado (text). Uma página aparece se tiver ao menos um número, um gráfico, uma linha, um recado ou um estado.
Um painel de loja com 3 páginas e um aviso:
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
--data @- <<'JSON'
{
"name": "Loja Centro", "ttl": 900,
"pages": [
{"title": "Vendas hoje", "icon": "cart", "value": 12840.50, "prefix": "R$", "label": "38 pedidos",
"delta": 3.4, "delta_unit": "%",
"chart": {"type": "line", "points": [8, 9, 9, 12, 11, 14, 13, 15], "labels": ["8 h", "agora"]}},
{"title": "Serviços", "icon": "server", "status": "warn",
"rows": [{"label": "API", "value": "120 ms", "status": "ok"},
{"label": "Fila", "value": 1204, "status": "warn"},
{"label": "Pagamento", "value": "fora", "status": "bad"}]},
{"title": "Recado", "text": "Reunião às 15h na sala 2"}
],
"alert": {"id": "pedido-4822", "level": "info", "text": "Novo pedido!"}
}
JSONOs gráficos
Um objeto chart só, seis tipos. O servidor acerta a escala e o aparelho desenha com as cores da casa.
| type | Precisa | Desenha |
|---|---|---|
line | 2 pontos ou mais (até 48; passou, ficam os últimos) | linha com o último ponto marcado, máximo e mínimo |
area | 2 pontos ou mais (até 48) | a linha com a área cheia até a base |
sparkline | 2 pontos ou mais (até 48) | só a linha, sem números: cabe ao lado de linhas |
bar | 1 ponto ou mais (até 24); negativos descem do zero | barras, um rótulo por barra e a meta tracejada |
gauge | um valor: chart.value, o value da página ou o último ponto | meio círculo de min a max (padrão 0 a 100), o valor no centro |
donut | 1 a 4 partes de valor 0 ou mais (mais: "Outros"); com 1, o max | anel com a legenda e a porcentagem de cada parte |
Linha: número e gráfico
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Visitas","icon":"users","value":1280,"delta":12,"delta_unit":"%","chart":{"type":"line","points":[820,940,910,1100,1180,1280],"labels":["há 6 h","agora"]}}'Área: só o gráfico, em verde, com unidade
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Energia solar","icon":"bolt","chart":{"type":"area","points":[0,0.4,1.8,3.2,3.6,2.9,1.1,0.2],"unit":"kW","color":"green"}}'Barras: um rótulo por barra e a meta tracejada
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Pedidos","icon":"cart","chart":{"type":"bar","points":[12,18,9,22,30,26,14],"labels":["S","T","Q","Q","S","S","D"],"target":20}}'Sparkline: linhas e um gráfico sem legenda
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Fila","icon":"server","status":"warn","rows":[{"label":"Agora","value":42,"status":"warn"},{"label":"Média","value":31}],"chart":{"type":"sparkline","points":[30,28,35,41,39,42]}}'Medidor: a meta do mês, com o valor no centro
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Meta do mês","icon":"money","prefix":"R$","chart":{"type":"gauge","value":72300,"max":100000,"target":90000}}'Rosca: partes de um todo
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"De onde vêm","icon":"chart","chart":{"type":"donut","points":[540,230,120],"labels":["Busca","Direto","Redes"]}}'Os layouts e a escolha automática
O layout sai do que veio preenchido; a primeira linha que casa vence. A dica layout pede outro, se a página tiver o bloco principal dele.
| Tem | Layout | Sai, com aviso |
|---|---|---|
| número e gráfico | number_chart | linhas e texto |
| número | number | o que não couber embaixo (até 4 linhas, ou 2 linhas e 2 de texto) |
| gráfico e linhas | rows_chart | a 4ª linha e o texto |
| gráfico | chart | nada |
| linhas | rows | nada |
| texto ou estado | message | nada |
| nada disso | nenhum | a página não aparece (empty_page) |
Os campos
O objeto
v | inteiro | só 1 (padrão 1) |
|---|---|---|
name | texto | até 16 letras: o nome do painel, na mesa |
ttl | inteiro (s) | 150 a 604800, padrão 900: por quanto tempo o dado é fresco |
stale_alert | booleano | padrão false: dado velho dispara uma vez o aviso "Sem notícias" |
pages | lista | até 4 páginas, na ordem da rotação |
alert | objeto | { text, level, id }: o aviso do aparelho |
A página
title | texto | até 18 letras e a largura da linha do título |
|---|---|---|
icon | nome | cart, money, user, users, server, cloud, bolt, temp, drop, home, code, clock, star, heart, bell, chart |
status | nome | ok, warn, bad, info, off |
layout | nome | auto (padrão), number, number_chart, chart, rows, rows_chart, message |
value | número | o número grande; com as casas decimais, até 9 dígitos |
decimals | inteiro | 0 a 3; padrão: as casas do número como veio |
prefix | texto | até 4 letras ("R$") |
unit | texto | até 10 letras ("pedidos", "kWh") |
label | texto | até 22 letras, embaixo do número |
delta | número | a variação, com seta |
delta_unit | texto | "%" ou nada |
good | nome | up (padrão) ou down: qual direção é boa |
chart | objeto | o gráfico (abaixo) |
rows | lista | até 4 linhas { label, value, status } |
text | texto | até 60 letras, até 2 linhas |
O gráfico
type | nome | line, area, sparkline, bar, gauge, donut |
|---|---|---|
points | lista de números | null marca uma falta |
value | número | só no gauge |
labels | lista de textos | rótulos do gráfico |
min, max | número | ampliam a faixa, nunca a cortam |
target | número | a meta, tracejada |
color | nome | auto, violet, blue, green, red, pink, gray, white |
unit | texto | até 6 letras |
decimals | inteiro | 0 a 3 |
Texto: as letras do português e do espanhol entram (ç, ã, é); emoji e símbolos que a tela não tem tiram o campo da tela, com aviso. Nada é cortado com reticências: o texto que não cabe sai.
Limites e em quanto tempo a tela muda
- 1 envio aceito a cada 5 segundos por aparelho; acima disso, 429 com
Retry-After. O último vence: nunca há fila. - Corpo de até 8 KiB e 6 níveis.
- A tela mostra o envio mais novo em até 70 segundos.
- O dado fica fresco pelo
ttl(padrão 15 minutos); depois, a tela o mostra apagado, com a idade, e ele some depois de 4 vezes ottl(no mínimo 24 horas, nunca mais de 7 dias). - Até 2 chaves por aparelho, para trocar sem buraco. A chave vai só no cabeçalho
Authorization: escrita nos parâmetros do endereço (depois do?), ela é recusada e revogada na hora, salvo se o limite de chaves inválidas já tiver barrado o endereço de origem (então revogue-a você em Meus Mupli). - Um aviso (
alert) por minuto por aparelho; o mesmoidnão volta em 24 horas. Acima disso, o envio é aceito sem o aviso. Oidnunca aparece na tela: qualquer texto serve nele. - O aviso que tocou chega ao aparelho mesmo que você mande outro número logo depois: ele vai junto nos envios seguintes até o aparelho confirmar que o recebeu (na busca seguinte). Enquanto nenhum número muda, ele espera o aparelho voltar, o tempo que for; com um número novo, vai junto por até 3 minutos depois de tocar. Um aviso novo antes disso troca o anterior, e a resposta diz (
"replaced":true). - Na resposta,
alert.fireddiz se o aviso deste envio tocou ealert.seqqualseqo leva: quandodevice.delivered_seqfor igual ou maior, o servidor já o entregou ao aparelho (se essa entrega se perder no caminho, o aviso segue nos envios seguintes até o aparelho confirmar).device.next_fetch_in_sé quando o aparelho busca de novo, mais ou menos (ele olha a cada minuto: mandar mais rápido não muda a tela);nullcom o aparelho desligado. - Contra abuso: até 600 pedidos por minuto de um mesmo endereço de origem (no IPv6, a mesma rede /64; no ritmo máximo, cerca de 50 aparelhos por servidor), 10 tentativas com chave inválida por minuto pelo mesmo endereço (ou rede /64), 60 leituras por minuto por chave e 3.000 pedidos por minuto no serviço inteiro, somados todos os clientes; acima deste, 503 com
Retry-After. São os limites da cláusula 3.3 dos Termos da API.
Mandar o mesmo de novo responde "changed":false e renova a validade do que já está na tela. Repita um envio com segurança com o cabeçalho Idempotency-Key.
Recusas e avisos
Recusa (422): nada muda na tela, e a resposta diz cada campo.
{"error":"validation_failed","errors":[{"path":"pages[0].chart.type","code":"enum","allowed":["line","area","sparkline","bar","gauge","donut"]}]}invalid_json | JSON malformado (400, com "at": a posição) |
|---|---|
unsupported_version | v diferente de 1 |
unknown_field | um campo que o formato não tem, em qualquer nível |
duplicate_key | a mesma chave duas vezes no mesmo objeto |
too_deep | mais de 6 níveis |
type | tipo errado ("value":"12", pages que não é lista, 1e400) |
enum | um nome fora da lista (vem com "allowed") |
too_many | mais de 4 páginas, ou mais de 4 linhas numa página |
pages_and_page_fields | o atalho de uma página e pages juntos |
control_char | caractere de controle num texto |
invisible_char | caractere invisível num texto |
link_not_allowed | um endereço num texto (:// ou www.) |
system_phrase | o nome (name) ou o texto do aviso (alert.text) igual a uma frase do sistema do aparelho ("Sem dados", "Atualizando"…): em outro campo é só um aviso |
Aviso (200): a tela muda sem aquele campo.
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" \
-d '{"title":"Pedidos de hoje no balcão","value":1280,"rows":[{"label":"Ontem","value":1100}],"chart":{"type":"line","points":[1100,1280]},"text":"Aberto até as 22h"}'too_long | texto acima do limite de letras: o campo sai |
|---|---|
too_wide | texto ou número que não cabe na tela (vem com "px" e "max_px"): o campo sai |
char_unsupported | uma letra que a tela não tem (vem com "char"): o campo sai |
transliterated | uma letra trocada pela sem acento (ł vira l) |
decimals_reduced | o número só coube com menos casas |
range | número grande demais para a escala, ou min maior que max |
clamped | ttl ou valor do gauge fora da faixa: encosta na ponta |
not_meaningful | gráfico sem o que precisa: o gráfico sai |
trimmed | pontos demais: ficam os últimos |
merged | partes demais na rosca: o resto vira "Outros" |
ignored | um campo que aquele gráfico não usa |
layout_ignored | a dica layout pede um bloco que a página não tem |
not_shown | um bloco que não coube no layout (vem com "layout") |
empty_page | página sem nada para mostrar: sai da rotação |
alert_empty | aviso sem text |
alert_repeated | o mesmo aviso de novo (o mesmo id em 24 horas, ou o mesmo texto sem id) |
alert_rate_limited | mais de um aviso por minuto |
device_budget | o conjunto passou do que o aparelho guarda: sai o texto, depois as linhas (toda tela guarda lugar para o maior aviso, então uma tela no limite pode perder um texto ou uma linha mesmo sem aviso) |
system_phrase | um rótulo, uma linha ou um texto igual a uma frase do sistema ("Offline", "Erro", "Link"…): só aquele campo sai, o resto do envio vale |
As respostas
| 200 | aceito: seq, changed, as horas, o layout de cada página, os avisos, o aviso (alert: se tocou e o seq que o leva) e o estado do aparelho (com next_fetch_in_s) |
|---|---|
| 400 | JSON malformado, ou a chave escrita no endereço (token_in_query: a chave é revogada na hora) |
| 401 | chave ausente, errada ou revogada, ou o Mupli foi desvinculado (sempre {"error":"invalid_token"}) |
| 404 | outro caminho, ou um endereço com "?" |
| 409 | a mesma Idempotency-Key com outro corpo |
| 413 | corpo acima de 8 KiB |
| 415 | sem Content-Type: application/json |
| 422 | recusa: o corpo diz cada campo e o motivo |
| 429 | mais de 1 envio a cada 5 segundos (Retry-After diz quanto esperar) |
| 503 | manutenção ou teto do serviço (Retry-After) |
Exemplos recusados
- JSON quebrado:
{"title":"Visitas",→ 400invalid_json - Campo que o formato não tem:
{"titel":"Visitas","value":1280}→ 422unknown_field - Número escrito como texto:
{"title":"Visitas","value":"1280"}→ 422type - Tipo de gráfico fora da lista:
{"title":"Visitas","chart":{"type":"pizza","points":[1,2]}}→ 422enum - Endereço num texto:
{"title":"Visitas","text":"Veja em www.exemplo.com"}→ 422link_not_allowed - O atalho de uma página junto com pages:
{"title":"Visitas","pages":[{"title":"Vendas","value":3}]}→ 422pages_and_page_fields
Ler, conferir e apagar
O que está na tela agora, como o aparelho mostra:
curl https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI"
Conferir um corpo sem guardar nem mudar a tela (1 por segundo):
curl -X POST https://mupli.io/api/apps/whitelabel/v1/screen/validate -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI" -H "Content-Type: application/json" -d @painel.json
Apagar a tela (o aparelho volta à mesa):
curl -X DELETE https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer mupli_wl1_SUA_CHAVE_AQUI"
Receitas
Mande quando o dado mudar, e um envio a cada poucos minutos para manter a tela fresca. Não repita um 4xx sem mudar nada. Guarde a chave como segredo, no servidor: nunca num navegador.
Shell e cron
# a cada minuto (cron), o mesmo corpo do primeiro exemplo
* * * * * curl -fsS -X POST https://mupli.io/api/apps/whitelabel/v1/screen -H "Authorization: Bearer $MUPLI_WL_KEY" -H "Content-Type: application/json" -d '{"title":"Visitas","value":1280}'Python
import os, requests
s = requests.Session() # uma conexão só, reaproveitada
s.headers.update({"Authorization": "Bearer " + os.environ["MUPLI_WL_KEY"]})
r = s.post("https://mupli.io/api/apps/whitelabel/v1/screen", json={"title":"Visitas","value":1280})
print(r.status_code, r.json().get("warnings"))Node.js
const r = await fetch("https://mupli.io/api/apps/whitelabel/v1/screen", {
method: "POST",
headers: { "Authorization": `Bearer ${process.env.MUPLI_WL_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({"title":"Visitas","value":1280}),
})
console.log(r.status, (await r.json()).warnings)Versões
Este é o formato v 1. Um campo novo vem numa versão nova, e a v1 continua valendo.