Documentação Api de SyncPlay, Radio Play e Sync Manager
SyncPlay (SyncPlayController)
Endpoints usados pelo aplicativo SyncPlay para baixar acervo (músicas/mídias), buscar capas,
resolver o roteamento MQTT de rede de rádios e gravar textos de IA (artista/música) gerados
localmente. Não usam AuthComponent (o controller estende Cake\Controller\Controller
diretamente) — a segurança é feita via authcode na própria URL ou, no caso de
updateMusicInfo, via header X-SyncPlay-SN.
GET /api/syncplay/acervo/:authcode/:scope/:page Buscar acervo (músicas ou mídias) de uma filial, paginado
Resolve o authcode para a filial (branch) vinculada e retorna a lista de
músicas (MUSICS_DOWNLOAD) ou mídias (MEDIAS_DOWNLOAD) disponíveis
para download, já com metadados (artista, ritmo, estilo, categoria, waveform/mix, textos de IA etc).
Parâmetros (path)
| authcodeobrigatório | Código de autorização vinculado à filial (branch). |
| scopeobrigatório | MUSICS_DOWNLOAD ou MEDIAS_DOWNLOAD. |
| page | Índice da página (paginação simples, sem tamanho fixo de página nesse endpoint). Opcional, padrão 0. |
Autenticação
Sem autenticação por sessão/token; a validade do authcode é a própria autorização.
200Sucesso
{
"data": [
{
"id": 284018,
"title": "Música Exemplo",
"audio": "musica-exemplo.mp3",
"directory": "/acervo/musicas",
"released": 2020,
"time": { "time": "00:03:24", "milliseconds": 204000 },
"artist": { "id": 12, "name": "Artista Exemplo" },
"rhythm": { "id": 3, "name": "Pop" },
"style": { "id": 5, "name": "Romântico" },
"category": { "id": 1, "name": "Nacional" },
"nationality": { "id": 1, "name": "Nacional" },
"collection": ["Coleção A"],
"mix": { "duration_total": 204000, "duration_real": 194000, "mix_init": 0, "mix_end": 194000 },
"info": { "artista": "Texto IA do artista", "musica": "Texto IA da música" }
}
],
"_pageable": {
"first": "https://host/api/syncplay/acervo/AUTHCODE/MUSICS_DOWNLOAD/0",
"previous": null,
"next": "https://host/api/syncplay/acervo/AUTHCODE/MUSICS_DOWNLOAD/1",
"last": null,
"size": 1
}
}
400Scope not allowed
{
"status": "error",
"code": 400,
"message": "Scope not allowed"
}
400Authcode inválido/inexistente
{
"status": "error",
"code": 400,
"message": "Client not found"
}
Não há tratamento de 401/403 nem 500: qualquer \Exception vira 400
(usa $ex->getCode() ?: 400). Um authcode vazio/malformado
pode gerar um warning de PHP antes da exceção de negócio, mas o resultado final
observável é sempre 400.
GET /api/syncplay/music-cover/:id Buscar a URL de capa de uma música ou mídia
Busca o upload de capa (MusicCovers) vinculado ao ID informado; se não houver
e o tipo for MediaCovers, cai para a capa do tipo de mídia (MediasTypes).
Parâmetros
| idobrigatório | ID da música ou mídia (path). |
| typeobrigatório | MusicCovers ou MediaCovers (querystring). |
Exemplo: /api/syncplay/music-cover/284018?type=MusicCovers
200Sucesso
{
"data": "https://storage.exemplo.com/covers/284018.jpg"
}
Quando não existe capa, data retorna null com status 200 mesmo assim.
400ID inválido
{
"status": "error",
"code": 400,
"message": "Invalid music id"
}
O parâmetro type é lido diretamente de $_GET['type'] (sem
isset); se for omitido, o PHP emite um notice em vez de um erro JSON
tratado — trate type como obrigatório na prática. Sem 401/403; qualquer
outra falha (ex.: storage indisponível) tende a cair em 400 pelo mesmo catch genérico.
GET /api/syncplay/network/broadcast-routing/:authcode Resolver os tópicos MQTT de roteamento de rede de rádios (network broadcast) de uma filial
A partir do authcode, resolve a filial vinculada e retorna os tópicos MQTT
(comandos, ack, config) usados para sincronizar comandos entre rádios de uma mesma rede.
Parâmetros (path)
| authcodeobrigatório | Código de autorização vinculado à filial. |
200Sucesso
{
"status": "success",
"data": {
"listen_commands_topic": ["superaudio/network/broadcast/10"],
"publish_commands_topics": ["superaudio/network/broadcast/25"],
"listen_ack_topic": "superaudio/network/ack/25",
"publish_ack_topics": ["superaudio/network/ack/10"],
"listen_config_topic": "superaudio/network/config/25"
}
}
400Auth Code inválido
{
"status": "error",
"code": 400,
"message": "Invalid Auth Code configuration"
}
400Filial não encontrada
{
"status": "error",
"code": 400,
"message": "Branch not found for the given Auth Code"
}
Sem tratamento de 401/403/500 — todo erro de negócio cai em 400.
PATCH /api/syncplay/music-info Gravar textos de IA (artista/música) gerados pelo SyncPlay
Salva a descrição de IA da música e/ou do artista apenas se ainda não existir uma descrição
prévia (não sobrescreve conteúdo já preenchido). Se a música tiver music_store_id,
a atualização é propagada para as rádios que importaram a música da loja.
Headers
| X-SyncPlay-SNobrigatório | Serial number do dispositivo SyncPlay. Sem ele, 401. |
Body (JSON)
| music_idobrigatório | ID da música. |
| info.musica | Texto de IA da música. Opcional, mas ao menos um dos dois campos (musica/artista) é obrigatório. |
| info.artista | Texto de IA do artista. Opcional, mas ao menos um dos dois campos (musica/artista) é obrigatório. |
200Sucesso (todos os campos enviados foram salvos)
{
"status": "success",
"message": "Informações salvas com sucesso.",
"details": { "musica": "saved", "artista": "saved" },
"propagated": true
}
200Parcial (parte foi salva e parte já existia)
{
"status": "partial",
"message": "Algumas descrições foram salvas; outras já existiam e foram mantidas.",
"details": { "musica": "saved", "artista": "kept_original" }
}
400Payload inválido
{
"status": "error",
"message": "Payload inválido: music_id ausente ou inválido."
}
400Nenhum texto informado
{
"status": "error",
"message": "Informe info.artista ou info.musica."
}
401Header X-SyncPlay-SN ausente
{
"status": "error",
"message": "Header Faltando"
}
403Música não liberada para a filial do serial number
{
"status": "error",
"message": "Música não disponível para esta filial."
}
404Música não encontrada
{
"status": "error",
"message": "Música não encontrada."
}
409Descrições já existentes mantidas
{
"status": "error",
"message": "Descrição já existente foi mantida."
}
500 não é lançado explicitamente pelo controller; uma falha inesperada (ex.: banco
indisponível) escapa do catch (\Exception) apenas se não for uma
\Exception (ex.: \Error/\TypeError), resultando
em 500 padrão do framework.
Radio Play Web (RadioPlayWebController)
Endpoints do player web da rádio (superaudio-web): listagem/consulta de playlists geradas
para o site e resolução de dados de RDS por slug. Controller de Auth->allow()
total — nenhum dos três endpoints exige autenticação.
GET /api/radio-play/web/playlists/:branch/ Listar playlists (superaudio-web) de uma filial
Retorna todas as playlists do tipo superaudio-web cadastradas para a filial.
Parâmetros (path)
| branchobrigatório | ID da filial (branch). |
Endpoint público, sem autenticação.
200Sucesso
[
{
"id": 501,
"branch_id": 10,
"date": "2026-07-28",
"type": "superaudio-web"
}
]
A resposta é um array "cru" da tabela Controla.Playlists, sem envelope data/status.
200Corpo indica erro, mas HTTP status NÃO é alterado
{
"status": "error",
"message": "mensagem da exceção",
"code": 0
}
Atenção: a busca da filial ($Branchs->get($branchId)) acontece
fora do bloco try/catch. Se branch não existir, o controller lança
RecordNotFoundException não capturada, e o CakePHP responde com o erro
padrão de framework (404), não com o JSON {status:error,...} acima.
Esse JSON só aparece para outras falhas (ex.: parâmetro de método inválido).
GET /api/radio-play/web/playlist/:branch/:date Buscar uma playlist (superaudio-web) específica, por ID ou por filial+data
Se id for informado, busca direto por ID (ignorando filial/data). Caso contrário,
busca pela combinação filial + data. Em ambos os casos inclui o upload (Upload) vinculado.
Rotas
/api/radio-play/web/playlist/:branch/:date
/api/radio-play/web/playlist/:id/
Parâmetros (path)
| id | ID da playlist. Se enviado, tem prioridade sobre branch/date. Opcional. |
| branchcondicional | ID da filial. Obrigatório se id não for enviado. |
| datecondicional | Data da playlist (formato usado no cadastro, ex.: YYYY-MM-DD). Obrigatório se id não for enviado. |
Endpoint público, sem autenticação.
200Sucesso
{
"id": 501,
"branch_id": 10,
"date": "2026-07-28",
"type": "superaudio-web",
"upload": {
"id": 9001,
"filename": "playlist-2026-07-28.json"
}
}
Se nada for encontrado, retorna null com HTTP 200 (find "first" sem resultado, sem erro).
200Corpo indica erro, mas HTTP status permanece 200
{
"status": "error",
"message": "mensagem da exceção",
"code": 0
}
Diferente de getPlaylists, aqui a busca da filial fica dentro do
try/catch, então uma filial inexistente é capturada e devolvida nesse formato — mas
o controller nunca chama withStatus(), então o HTTP status continua 200
mesmo em erro. Sem 400/401/403/500 explícitos.
GET /api/radio/:slug/rds Buscar dados de RDS (Radio Data System) de uma rádio/cliente por slug
Resolve link_streaming, link_server_rds e type_server_rds
de uma filial (Sal.Branchs) ou de um cliente RadioStore (RadioStore.Clients),
conforme m. Resultado fica em cache Redis por 600 segundos
(chave radio-play-rds-{slug}).
Parâmetros
| slugobrigatório | Valor de link_radio_data_system cadastrado na filial/cliente (path). |
| m | branchs (Sal.Branchs) ou clients (RadioStore.Clients). Querystring, opcional, padrão branchs. |
Endpoint público, sem autenticação.
200Sucesso
{
"code": 200,
"data": {
"link_streaming": "https://stream.exemplo.com/radio",
"link_server_rds": "https://rds.exemplo.com",
"type_server_rds": "generic"
}
}
Se o slug não existir, data vem null com HTTP 200 (busca é "first" sem exceção).
400Valor de "m" diferente de branchs/clients
{
"status": "error",
"message": "Model not allowed"
}
405Método diferente de GET/OPTIONS
{
"status": "error",
"message": "Method Not Allowed"
}
Sem 401/403 (endpoint público). Uma falha inesperada fora de \Exception
(ex.: \Throwable não capturado) resulta em 500 padrão do framework, já
que o catch aqui só cobre \Exception.
Sync Manager (plugin Controla — SyncManagerApiController)
API interna usada pela tela de Sync Manager (monitoramento de sincronizadores/SyncPad) do
painel administrativo. As rotas não estão declaradas explicitamente em
plugins/Controla/config/routes.php — são resolvidas pelo fallback
$routes->fallbacks(DashedRoute::class) daquele arquivo, então a URL segue o
padrão /controla/<controller-em-dash-case>/<action-em-dash-case>/...
(SyncManagerApiController → sync-manager-api). O controller chama
$this->Auth->allow(), mas internamente ainda depende da sessão web logada
(UserLogged.companyID, super.is) para escopo por empresa — não é
uma API pública para integração externa.
GET /controla/sync-manager-api/stations/:clientId Listar estações (sincronizadores vinculados a filiais) e seus códigos de autorização
Lista os ApiClients do modelo Branchs disponíveis para a empresa
logada (ou de todas as filiais, se superadmin/gestor), com os authorization_codes
ativos de cada um e o status de conexão (online/offline/waiting).
Parâmetros (path)
| clientId | Filtra por um client_id específico; se omitido, lista todas as filiais disponíveis para a empresa da sessão. Opcional. |
Depende de sessão web autenticada (não valida token/Authorization).
200Sucesso
{
"data": [
{
"client_id": "abc123",
"foreign_key": 10,
"branch": { "id": 10, "name_fantasy": "Filial Centro" },
"authorization_codes": [
{
"authorization_code": "AUTHCODE1",
"connection": "online",
"connection_player": "online",
"last_connection_player": "28/07/2026 - 10:00:00"
}
]
}
],
"pagination": {
"current": false,
"pages": false,
"seed": false,
"next": false,
"prev": false
}
}
pagination sempre vem com valores false nesse endpoint — a paginação (setPagination) nunca é chamada de fato pelo core.
400Clients empty
{
"message": "Clients empty"
}
Atenção: a exceção "Nenhuma empresa ativa/encontrada" (lançada
quando a empresa não tem filial ativa) não define código HTTP (fica 0). Como o
controller faz $this->response->withStatus($exc->getCode()) sem
validar, isso tende a resultar em erro 500 do framework (status HTTP inválido) em vez
de um 400 tratado. Sem 401/403 explícitos.
GET /controla/sync-manager-api/stations-store/:clientId Listar estações vinculadas a Clientes (RadioStore) e seus códigos de autorização
Mesma lógica de stations, porém para ApiClients do modelo
Clients (RadioStore) da empresa logada.
Parâmetros (path)
| clientId | Filtra por um client_id específico. Opcional. |
Depende de sessão web autenticada.
200Sucesso
{
"data": [
{
"client_id": "xyz789",
"foreign_key": 55,
"client": { "id": 55, "name_fantasy": "Cliente Exemplo" },
"authorization_codes": [
{
"authorization_code": "AUTHCODE2",
"nickname": "Sem Apelido",
"connection": "online"
}
]
}
],
"pagination": { "current": false, "pages": false, "seed": false, "next": false, "prev": false }
}
200Corpo indica erro, mas HTTP status NÃO é alterado
{
"message": "Clients empty"
}
Atenção: ao contrário de stations, aqui o
$this->response->withStatus() está comentado no código-fonte
("estava quebrando o request em /radio-store/clients/list-apps"), então qualquer
erro de negócio retorna HTTP 200 com o corpo {message: ...}. Sem
400/401/403/500 reais na prática.
GET /controla/sync-manager-api/all-stations-clients/:clientId Listar todos os Clientes (RadioStore) elegíveis globalmente
Usa a tabela ClientAvailables (model ProductsMediasTypes) para
montar a lista de clientes disponíveis, em vez de restringir pela empresa logada (não
restrito à empresa da sessão).
Parâmetros (path)
| clientId | Filtra por um client_id específico. Opcional. |
Depende de sessão web autenticada.
200Sucesso
{
"data": [
{
"client_id": "xyz789",
"foreign_key": 55,
"client": { "id": 55, "name_fantasy": "Cliente Exemplo" },
"authorization_codes": [
{ "authorization_code": "AUTHCODE2", "nickname": "Sem Apelido", "connection": "offline" }
]
}
],
"pagination": { "current": false, "pages": false, "seed": false, "next": false, "prev": false }
}
200Corpo indica erro, mas HTTP status NÃO é alterado (mesmo padrão de stations-store)
{
"message": "Clients empty"
}
Mesmas ressalvas de stations-store: sem HTTP status de erro real, sem 400/401/403/500 tratados.
GET /controla/sync-manager-api/get-tasks-scheduling/:authCode Buscar tarefas agendadas (task scheduling) de um código de autorização
Retorna, por tarefa, o horário agendado (schedule) das
ApiTaskSchedulings vinculadas ao authorization_code informado.
Parâmetros (path)
| authCodeobrigatório | Authorization code do sincronizador. |
Depende de sessão web autenticada.
200Sucesso
{
"code": 200,
"tasks": {
"generate-playlist": { "scheduled": "28/07/2026 03:00:00" }
}
}
404authCode vazio ou "0"
{
"message": "Parâmetro não encontrados",
"code": 404
}
500Código de autorização não encontrado
{
"message": "Código não encontrado.",
"code": 500
}
Usado como 500 no código-fonte, apesar de semanticamente ser um caso de "não encontrado" (deveria ser 404).
404Nenhuma tarefa agendada para o código
{
"message": "Não foram encontradas tarefas agendadas.",
"code": 404
}
405Método diferente de GET
{
"message": "Method Not Allowed",
"code": 405
}
Sem 401/403 explícitos. O código HTTP retornado é sempre o mesmo valor de code do corpo (withStatus($exc->getCode())).
GET /controla/sync-manager-api/syncpad-devices Listar dispositivos SyncPad conectados/desconectados
Lê a tabela local syncpad_device_connections (populada por job a partir do
EMQX) e devolve um dispositivo por device_key, com filial, empresa, usuário,
status online/offline e "uptime"/"lastSeen". Escopo por empresa da sessão, salvo quando
superadmin usa all=1.
Parâmetros GET (querystring)
| all | 1 para superadmin visualizar dispositivos de todas as empresas. Opcional. |
| company_id | Filtra por uma empresa específica. Opcional, apenas superadmin. |
Depende de sessão web autenticada (companyID/super.is da sessão).
200Sucesso
{
"devices": [
{
"id": "syncpad:client:55",
"type": "syncpad",
"online": true,
"deviceName": "Filial Centro",
"branchId": 10,
"branchName": "Filial Centro",
"companyName": "Rádio Exemplo",
"cityName": "São Paulo",
"stateName": "SP",
"userId": 42,
"userName": "João Silva",
"radioStoreClientId": 55,
"ip": "203.0.113.10",
"version": "",
"uptime": "2h 15m",
"lastSeen": "28/07/2026 09:45",
"profileName": "",
"serialNumber": null
}
]
}
405Método diferente de GET
{
"message": "Method Not Allowed",
"devices": []
}
500Falha inesperada (ex.: banco/EMQX indisponível)
{
"message": "mensagem da exceção",
"devices": []
}
Sem 400/401/403 explícitos nesta action — o catch cobre \Throwable e usa o código da exceção se estiver entre 100 e 599, senão cai em 500.
GET /controla/sync-manager-api/syncpad-user-history Histórico de conexões SyncPad de um usuário
Retorna o histórico de conexões (dia, hora, duração) de um usuário no SyncPad, opcionalmente
filtrado por filial, com paginação simples via limit/offset.
Parâmetros GET (querystring)
| user_idobrigatório | ID do usuário. |
| branch_id | Filtra pelo histórico de uma filial específica. Opcional. |
| limit | Quantidade máxima de registros. Opcional, padrão 50. |
| offset | Deslocamento para paginação. Opcional, padrão 0. |
Depende de sessão web autenticada.
200Sucesso
{
"cityName": "São Paulo",
"stateName": "SP",
"history": [
{
"date": "28/07/2026",
"time": "09:30",
"durationFormatted": "2h 15m",
"stillConnected": true
}
]
}
400Parâmetro user_id ausente
{
"message": "Parâmetro user_id é obrigatório",
"history": []
}
405Método diferente de GET
{
"message": "Method Not Allowed",
"history": []
}
500Falha inesperada
{
"message": "mensagem da exceção",
"history": []
}
Sem 401/403 explícitos.