NuvemFlex Cadastre-se
Desenvolvedores

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

  1. 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.
  2. A chave aparece uma vez. Guarde num cofre ou no .env do seu servidor. Nunca coloque no navegador nem no app.
  3. Teste: GET https://nuvemflex.com.br/api/v1/me devolve 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.

EscopoLibera
*:readLeitura de todos os recursos (menos os escopos .sensitive e os de api_brotas.wildcard_excluded)
points:readPontos de som — leitura
plays:readExecuções (o que tocou) — leitura
announcements:readAvisos — leitura
announcements:writeAvisos — escrita
certificates:readCertificados de repertório — leitura
stations:readWeb rádio — leitura
billing:readAssinatura 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-Remaining e RateLimit-Reset. Estourou: 429 com Retry-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_at e filter[campo]=valor. A resposta traz meta.page, per_page, total e last_page.
  • Datas em ISO 8601 no fuso da conta (ex.: 2026-10-17T14:30:00-03:00). Valores em texto com duas casas e currency: "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

RotaEscopoO que faz
GET /pointspoints:readLista os pontos. Filtros: filter[online]=true|false, filter[tag]=Shopping. Ordem: name, last_signal_at.
GET /points/{id}points:readUm ponto, com ambiente e etiquetas.
GET /points/{id}/statuspoints:readStatus 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

RotaEscopoO que faz
GET /announcementsannouncements:readLista. Filtros: filter[status]=draft|approved, filter[archived], filter[point_id].
GET /announcements/{id}announcements:readUm aviso.
POST /announcementsannouncements:writeCria 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}/approveannouncements:writeLibera 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.

EventoQuando
point.offlinePonto de som sem sinal no horário de funcionamento
invoice.paidPagamento confirmado
announcement.playedAviso 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 /billing com uma chave de escopo billing: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))), com email, cliente_id (opcional), iat, exp (no máximo 5 minutos) e jti (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.