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órioCódigo de autorização vinculado à filial (branch).
scopeobrigatórioMUSICS_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órioID da música ou mídia (path).
typeobrigatórioMusicCovers 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órioCó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órioSerial number do dispositivo SyncPlay. Sem ele, 401.

Body (JSON)

music_idobrigatórioID da música.
info.musicaTexto de IA da música. Opcional, mas ao menos um dos dois campos (musica/artista) é obrigatório.
info.artistaTexto 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órioID 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)

idID da playlist. Se enviado, tem prioridade sobre branch/date. Opcional.
branchcondicionalID da filial. Obrigatório se id não for enviado.
datecondicionalData 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órioValor de link_radio_data_system cadastrado na filial/cliente (path).
mbranchs (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>/... (SyncManagerApiControllersync-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)

clientIdFiltra 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)

clientIdFiltra 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)

clientIdFiltra 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órioAuthorization 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)

all1 para superadmin visualizar dispositivos de todas as empresas. Opcional.
company_idFiltra 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órioID do usuário.
branch_idFiltra pelo histórico de uma filial específica. Opcional.
limitQuantidade máxima de registros. Opcional, padrão 50.
offsetDeslocamento 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.