API da NuvemFlex Rádio
Ligue o seu sistema aos pontos de som e à web rádio: status dos players, o que tocou, avisos, certificados e webhooks. A API segue o padrão de todos os sistemas da Brotas System.
Começar
- No painel, o dono da conta abre Equipe e segurança > Tokens de API e cria uma chave com só as permissões que o sistema precisa.
- A chave aparece uma vez. Guarde num cofre ou no
.envdo seu servidor. Nunca coloque no navegador nem no app. - Teste:
GET https://nuvemflex.com.br/api/v1/medevolve a chave, a conta e os escopos.
curl https://nuvemflex.com.br/api/v1/ping
curl https://nuvemflex.com.br/api/v1/me \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
Endereço base: https://nuvemflex.com.br/api/v1. Só HTTPS. Especificação OpenAPI 3.1: https://nuvemflex.com.br/api/v1/openapi.json. Guias comuns da Brotas System (paginação, erros, webhooks): portal de desenvolvedores.
Autenticação e escopos
Envie a chave no cabeçalho Authorization: Bearer. Chave na URL é recusada (400 key_in_query). Formato: bsk_nuvemflex_<live|test>_<id>_<segredo>. Guardamos só uma impressão do segredo (HMAC), por isso não dá para mostrar a chave de novo: se perdeu, crie outra e revogue a antiga.
Tokens antigos, que começam com nfx_, continuam valendo com as mesmas permissões. Recomendamos trocar por uma chave nova quando puder.
Cada chave pertence a uma conta e só enxerga os dados dela. Pode ter validade, lista de IPs (CIDR) e limite próprio por minuto.
| Escopo | Libera |
|---|---|
*:read | Leitura de todos os recursos (menos os escopos .sensitive e os de api_brotas.wildcard_excluded) |
points:read | Pontos de som — leitura |
plays:read | Execuções (o que tocou) — leitura |
announcements:read | Avisos — leitura |
announcements:write | Avisos — escrita |
certificates:read | Certificados de repertório — leitura |
stations:read | Web rádio — leitura |
billing:read | Assinatura e faturas — leitura |
*:read lê tudo, menos billing (assinatura e faturas), que precisa do próprio escopo.
Limites, paginação e datas
- Padrão de 120 requisições por minuto por chave. Toda resposta traz
RateLimit-Limit,RateLimit-RemainingeRateLimit-Reset. Estourou:429comRetry-After. - 10 tentativas com chave errada do mesmo IP em 5 minutos bloqueiam o IP por 15 minutos.
- Listas:
?page=1&per_page=50(máximo 100; acima disso,422),sort=-created_atefilter[campo]=valor. A resposta trazmeta.page,per_page,totalelast_page. - Datas em ISO 8601 no fuso da conta (ex.:
2026-10-17T14:30:00-03:00). Valores em texto com duas casas ecurrency: "BRL". - POST aceita
Idempotency-Key: repetir a mesma chave com o mesmo corpo devolve a mesma resposta por 24 h.
Erros
Formato RFC 9457 (application/problem+json), com code e request_id. Informe o request_id ao suporte.
{
"type": "https://brotassystem.com.br/desenvolvedores/erros#insufficient_scope",
"title": "Escopo insuficiente",
"status": 403,
"code": "insufficient_scope",
"detail": "Esta chave não tem o escopo \"announcements:write\".",
"request_id": "req_8f3a1c2b9d0e4f11"
}
Códigos: unauthenticated 401, insufficient_scope, ip_not_allowed, https_required e tenant_suspended 403, not_found 404, idempotency_conflict 409, payload_too_large 413, unsupported_media_type 415, validation_failed 422 (com errors), rate_limited 429, server_error 500.
Pontos de som
| Rota | Escopo | O que faz |
|---|---|---|
GET /points | points:read | Lista os pontos. Filtros: filter[online]=true|false, filter[tag]=Shopping. Ordem: name, last_signal_at. |
GET /points/{id} | points:read | Um ponto, com ambiente e etiquetas. |
GET /points/{id}/status | points:read | Status agora: playing, stopped, paused, closed (fora do horário), offline, never_connected. |
curl "https://nuvemflex.com.br/api/v1/points?filter[online]=false" \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
Execuções
GET /plays (plays:read): o que tocou, música ou aviso. Filtros: filter[from] e filter[to] (datas da conta, AAAA-MM-DD, até 93 dias; sem período, os últimos 7 dias), filter[point_id], filter[type]=music|announcement, filter[announcement_id]. As execuções ficam guardadas por 24 meses; os totais por dia continuam nos relatórios do painel.
curl "https://nuvemflex.com.br/api/v1/plays?filter[from]=2026-10-01&filter[to]=2026-10-15&filter[type]=announcement&per_page=100" \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
Avisos
| Rota | Escopo | O que faz |
|---|---|---|
GET /announcements | announcements:read | Lista. Filtros: filter[status]=draft|approved, filter[archived], filter[point_id]. |
GET /announcements/{id} | announcements:read | Um aviso. |
POST /announcements | announcements:write | Cria a partir de texto (voz de IA, gasta uma locução do plano no mês) ou de audio_url (MP3 em https, até 8 MB). |
POST /announcements/{id}/approve | announcements:write | Libera o aviso para tocar. |
Todo aviso criado pela API nasce em rascunho, como no painel: só toca depois de liberado (aqui ou no painel, pelo "Ouvir e liberar"). Sem saldo de locuções no mês, a resposta é 422.
curl -X POST https://nuvemflex.com.br/api/v1/announcements \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: promo-sexta-001" \
-d '{"title":"Promoção de sexta","text":"Hoje tem 20% de desconto em toda a loja.","voice":"coral","point_ids":[12,15],"start_date":"2026-10-17","end_date":"2026-10-31"}'
curl -X POST https://nuvemflex.com.br/api/v1/announcements/88/approve \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
Certificados
GET /certificates(certificates:read): certificados e declarações já emitidos, com o código e o link de verificação pública.GET /certificates/{id}/pdf: o PDF de um certificado da lista.GET /certificates/monthly/AAAA-MM/pdf: gera o certificado mensal de repertório e devolve o PDF.
curl -o certificado.pdf https://nuvemflex.com.br/api/v1/certificates/monthly/2026-09/pdf \
-H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
Também há GET /stations (stations:read, web rádio com o link do stream) e GET /billing (billing:read, plano, situação e últimas faturas).
Webhooks
Cadastre o endereço em Equipe e segurança > Webhooks no painel. Só https na porta 443, com IP público. O segredo aparece uma vez.
| Evento | Quando |
|---|---|
point.offline | Ponto de som sem sinal no horário de funcionamento |
invoice.paid | Pagamento confirmado |
announcement.played | Aviso tocado num ponto |
POST https://seu-sistema.com.br/webhooks/nuvemflex
Content-Type: application/json
X-Brotas-Event: announcement.played
X-Brotas-Delivery: 1532
X-Brotas-Signature: t=1760711400,v1=5d1f...e9
{"id":"evt_...","type":"announcement.played","created_at":"2026-10-17T11:30:00-03:00",
"data":{"announcement":{"id":88,"title":"Promoção de sexta"},"point":{"id":12,"name":"Loja Centro"},"played_at":"2026-10-17T11:29:58-03:00"}}
Confira a assinatura: v1 = HMAC-SHA256 do texto t + "." + corpo com o segredo do webhook. Recuse se a diferença entre agora e t passar de 300 segundos. Responda 2xx em até 10 s; sem isso, tentamos de novo em 1 min, 5 min, 30 min, 2 h e 6 h. Depois de 20 falhas seguidas o webhook é desligado.
// PHP
[$t, $v1] = [null, null];
foreach (explode(',', $_SERVER['HTTP_X_BROTAS_SIGNATURE'] ?? '') as $p) {
[$k, $v] = array_pad(explode('=', $p, 2), 2, '');
if ($k === 't') $t = (int) $v; elseif ($k === 'v1') $v1 = $v;
}
$corpo = file_get_contents('php://input');
$ok = $t && abs(time() - $t) <= 300 && hash_equals(hash_hmac('sha256', $t.'.'.$corpo, $segredo), (string) $v1);
// Node.js
const [t, v1] = ['t', 'v1'].map(k => (req.headers['x-brotas-signature'] || '').split(',').find(p => p.startsWith(k + '='))?.slice(k.length + 1));
const esperado = crypto.createHmac('sha256', segredo).update(t + '.' + corpoCru).digest('hex');
const ok = Math.abs(Date.now() / 1000 - Number(t)) <= 300 && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1 || ''));
Integração com o Hub Brotas
Preparada para quando o Hub Brotas ligar a NuvemFlex:
- Faturamento: o Hub lê
GET /billingcom uma chave de escopobilling:read(situação, plano, vencimento e últimas faturas). - Entrada sem senha (SSO): o Hub abre
https://nuvemflex.com.br/hub/sso?token=.... O token ébase64url(json)+"."+base64url(HMAC-SHA256(segredo, base64url(json))), comemail,cliente_id(opcional),iat,exp(no máximo 5 minutos) ejti(uso único). O segredo é combinado entre os dois sistemas. Quem usa verificação em duas etapas entra pelo login normal.
Alexa, ESP32 e caixas de som
O player do ponto de som roda numa página (TV, computador, celular), porque monta a programação e os avisos de cada ponto na hora. Ele não gera um MP3 contínuo. Para aparelhos que só tocam um link de rádio, como Alexa (skill de rádio), ESP32 com decodificador MP3 ou caixas Wi-Fi, use a web rádio da conta: o link do stream sai em GET /stations (stream_url) e no arquivo /r/<nome-da-radio>/radio.m3u.
curl https://nuvemflex.com.br/api/v1/stations -H "Authorization: Bearer bsk_nuvemflex_live_XXXXXXXXXXXXXXXX_SEGREDO"
# "stream_url": "https://radio.brotastv.com.br/listen/minharadio/radio.mp3"
Segurança
A API é de servidor para servidor: não tem CORS e não aceita cookie de login. Chave vazou? Revogue no painel na hora; o sistema que usava para de funcionar. Dúvidas: contato@nuvemflex.com.br.
