Documentação Api v2 (Autenticação Bearer/JWT)
A API v2 é a nova geração de endpoints da Superaudio/Controla. Diferente dos endpoints legados
(onde o access_token é enviado como parte da URL), todos os endpoints da v2 exigem
o token JWT enviado no header HTTP Authorization, no formato Bearer.
Authorization: Bearer <token>
O token nunca deve ser enviado via querystring ou corpo da requisição. Ele é obtido através do
endpoint de login (POST /api/v2/auth/login) e tem validade de 24 horas (86400 segundos).
Quando o header estiver ausente, mal formado, ou o token estiver inválido/expirado, o endpoint
responde 401 Unauthorized antes mesmo de processar a regra de negócio.
POST
/api/v2/auth/login
Login (autenticação de usuário Controla)
Autentica um usuário Controla por username/e-mail + senha (mesma regra do login web) e
retorna um token JWT, os dados do usuário, a empresa/contexto principal, as rádios
(branches) da empresa, o papel (role) e a lista completa de empresas/rádios que o usuário
pode acessar.
Parâmetros POST (JSON body)
| username_or_emailobrigatório | Username ou e-mail do usuário Controla. |
| passwordobrigatório | Senha do usuário. |
Autenticação
Não requer token — este é o endpoint que gera o token.
200Sucesso
{
"status": "success",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"expiresIn": 86400,
"user": {
"id": 42,
"username": "joao.silva",
"email": "joao@empresa.com",
"firstName": "João",
"lastName": "Silva"
},
"company": {
"id": 5,
"name": "Rádio Exemplo",
"image": "https:\/\/storage.exemplo.com\/companies\/5\/logo.png"
},
"branches": [
{
"id": 10,
"name": "Filial Centro",
"image": null
}
],
"role": {
"id": 3,
"name": "Administrador",
"priority": 10
},
"accessList": [
{
"companyId": 5,
"companyName": "Rádio Exemplo",
"companyImage": null,
"radios": [
{
"id": 10,
"name": "Filial Centro",
"image": null
}
]
}
]
}
400username_or_email/password ausentes
{
"status": "error",
"code": "BAD_REQUEST",
"message": "username_or_email e password são obrigatórios."
}
401Credenciais inválidas
{
"status": "error",
"code": "INVALID_CREDENTIALS",
"message": "Credenciais inválidas."
}
403Sem companhia associada
{
"status": "error",
"code": "ACCESS_DENIED",
"message": "Usuário sem companhia associada."
}
500Erro interno
{
"status": "error",
"code": "INTERNAL_ERROR",
"message": "Mensagem interna do erro."
}
GET
/api/v2/auth/me
Validar token / obter dados do usuário autenticado
Valida o token JWT enviado no header Authorization e retorna os dados do
usuário, empresa e papel (role) contidos no próprio token. Útil para o cliente confirmar
que a sessão continua válida.
Headers
| Authorizationobrigatório | Token JWT do usuário autenticado, no formato Bearer <token>. |
200Sucesso
{
"status": "success",
"user": {
"id": 42,
"username": "joao.silva",
"email": "joao@empresa.com"
},
"company": {
"id": 5
},
"role": {
"id": 3,
"priority": 10
}
}
401Token não enviado
{
"status": "error",
"code": "TOKEN_INVALID",
"message": "Token de autenticação não fornecido."
}
401Token inválido
{
"status": "error",
"code": "TOKEN_INVALID",
"message": "Token inválido."
}
401Token expirado
{
"status": "error",
"code": "TOKEN_INVALID",
"message": "Token expirado."
}
GET
/api/v2/botoneira/profiles
Buscar perfil da botoneira
Retorna o JSON de configuração da botoneira salvo para o contexto resolvido (rádio/branch
de um usuário Controla, ou cliente RadioStore).
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Parâmetros GET (querystring)
| serialNumber | Serial number do dispositivo; resolve automaticamente branchId/companyId. Opcional. |
| branchId | ID da rádio (branch), para usuários Controla. Opcional se serialNumber for informado. |
| clientId | ID do cliente RadioStore (contexto de cliente); tem precedência sobre branchId. Opcional. |
Um dos três (serialNumber, branchId ou clientId) é obrigatório — exceto para tokens de
contexto "client" (authType=client), onde o clientId é resolvido do próprio token.
200Sucesso
{
"status": "success",
"profile": {
"id": 15,
"data": {
"buttons": [
{
"id": 1,
"label": "Vinheta 1",
"audioUrl": "https:\/\/storage.exemplo.com\/botoneira\/audio\/abc123.mp3"
}
]
},
"version": 3,
"createdAt": "2026-01-10T14:32:00",
"updatedAt": "2026-02-15T09:12:00"
}
}
400Contexto não informado
{
"status": "error",
"message": "serialNumber, branchId ou clientId é obrigatório."
}
401Token ausente/inválido
{
"status": "error",
"message": "Token de autenticação não fornecido."
}
403Serial/rádio de outra companhia
{
"status": "error",
"message": "O serial number não pertence à sua companhia."
}
404Sem perfil salvo
{
"status": "error",
"message": "Nenhum perfil encontrado para este contexto."
}
POST
/api/v2/botoneira/profiles
Salvar (criar) perfil da botoneira
Cria um novo perfil, salvando o corpo inteiro da requisição como o JSON de configuração da
botoneira. Retorna erro se já existir um perfil para o contexto (use PUT para atualizar).
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Body (JSON)
| (corpo inteiro)obrigatório | Salvo como o JSON de configuração da botoneira (estrutura livre, definida pelo app). Não pode ser vazio, limitado a 5MB. |
| serialNumber / branchId / clientId | Informados via querystring ou no corpo, para resolver o contexto (mesma regra do GET). |
201Criado
{
"status": "success",
"profile": {
"id": 15,
"version": 1,
"createdAt": "2026-01-10T14:32:00"
}
}
400Corpo vazio
{
"status": "error",
"message": "O corpo da requisição não pode ser vazio."
}
400Perfil já existe
{
"status": "error",
"message": "Já existe um perfil para este contexto. Use PUT para atualizar."
}
400Payload muito grande
{
"status": "error",
"message": "Payload excede o limite de 5MB."
}
401Token ausente/inválido
{
"status": "error",
"message": "Token inválido ou expirado."
}
500Erro interno
{
"status": "error",
"message": "Não foi possível salvar o perfil."
}
PUT
/api/v2/botoneira/profiles
Substituir (atualizar) perfil da botoneira
Substitui integralmente o JSON de configuração salvo para o contexto. Retorna erro 404 se
ainda não existir um perfil (use POST para criar).
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Body (JSON)
| (corpo inteiro)obrigatório | Substitui o JSON de configuração da botoneira atual. Não pode ser vazio, limitado a 5MB. |
| serialNumber / branchId / clientId | Informados via querystring ou no corpo, para resolver o contexto (mesma regra do GET). |
200Sucesso
{
"status": "success",
"profile": {
"id": 15,
"version": 4,
"updatedAt": "2026-02-20T11:00:00"
}
}
400Corpo vazio
{
"status": "error",
"message": "O corpo da requisição não pode ser vazio."
}
400Payload muito grande
{
"status": "error",
"message": "Payload excede o limite de 5MB."
}
401Token ausente/inválido
{
"status": "error",
"message": "Token de autenticação não fornecido."
}
404Perfil inexistente
{
"status": "error",
"message": "Perfil não encontrado. Use POST para criar."
}
500Erro interno
{
"status": "error",
"message": "Não foi possível atualizar o perfil."
}
DELETE
/api/v2/botoneira/profiles
Remover (soft-delete) perfil da botoneira
Marca o perfil do contexto resolvido como excluído (soft-delete).
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Parâmetros (querystring ou body)
| serialNumber / branchId / clientId | Para resolver o contexto do perfil a ser removido (mesma regra do GET). |
200Sucesso
{
"status": "success",
"message": "Perfil removido com sucesso."
}
400Contexto não informado
{
"status": "error",
"message": "serialNumber, branchId ou clientId é obrigatório."
}
401Token ausente/inválido
{
"status": "error",
"message": "Token inválido ou expirado."
}
404Perfil inexistente
{
"status": "error",
"message": "Perfil não encontrado."
}
500Erro interno
{
"status": "error",
"message": "Não foi possível remover o perfil."
}
POST
/api/v2/botoneira/audio/upload
Upload de áudio da botoneira
Faz upload de um arquivo de áudio (ex.: vinheta gravada no app) para o storage configurado
(Minio/DigitalOcean/S3) e retorna a URL pública do arquivo, para ser referenciada em um
botão do perfil da botoneira.
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Body (multipart/form-data)
| audioobrigatório | Arquivo de áudio. Extensões aceitas: mp3, wav, ogg, webm, m4a, aac. Tamanho máximo: 50MB. |
201Criado
{
"status": "success",
"data": {
"url": "https:\/\/storage.exemplo.com\/bucket\/botoneira\/audio\/abc123def456.mp3",
"key": "botoneira\/audio\/abc123def456.mp3",
"filename": "vinheta_bom_dia.mp3",
"size": 245678,
"mime": "audio\/mpeg",
"md5": "d41d8cd98f00b204e9800998ecf8427e"
}
}
400Campo ausente ou falha no upload
{
"status": "error",
"code": "BAD_REQUEST",
"message": "Campo \"audio\" é obrigatório. Envie o arquivo de áudio como multipart\/form-data."
}
401Token ausente/inválido
{
"status": "error",
"code": "UNAUTHORIZED",
"message": "Token de autenticação não fornecido."
}
413Arquivo muito grande (limite de 50MB)
{
"status": "error",
"code": "FILE_TOO_LARGE",
"message": "Arquivo muito grande. Tamanho máximo permitido: 50MB."
}
415Tipo/extensão não suportados
{
"status": "error",
"code": "UNSUPPORTED_MEDIA_TYPE",
"message": "Tipo de arquivo não permitido: audio\/x-flac. Tipos aceitos: audio\/mpeg, audio\/mp3, audio\/wav, audio\/x-wav, audio\/wave, audio\/ogg, audio\/webm, audio\/mp4, audio\/x-m4a, audio\/aac"
}
500Erro interno
{
"status": "error",
"code": "INTERNAL_ERROR",
"message": "Mensagem interna do erro."
}
GET
/api/v2/config
Buscar configuração de sincronização (SyncPad/SyncPlay)
Retorna a configuração efetiva de SyncPad/SyncPlay resolvida para o usuário/cliente
autenticado, além do tópico MQTT que o dispositivo deve assinar para receber atualizações
em tempo real. Este endpoint é chamado pelo dispositivo na inicialização como fallback
garantido (fonte de verdade via REST); atualizações em tempo real chegam via mensagens
retidas no MQTT.
Headers
| Authorizationobrigatório | Token JWT, formato Bearer <token>. |
Parâmetros GET (querystring)
| branchId | ID da rádio (branch), para usuários Controla com contexto específico de rádio. Opcional; por padrão usa o branchId do próprio token, se houver. |
| clientId | Override explícito do contexto de cliente RadioStore. Opcional. |
200Sucesso
{
"status": "success",
"config": {
"max_buttons": 32
},
"mqttTopic": "botoneira/branch/5/user/42/config"
}
401Token ausente
{
"status": "error",
"message": "Token de autenticação não fornecido."
}
401Token inválido/expirado
{
"status": "error",
"message": "Token inválido."
}
400/403/404Erro de resolução de contexto
{
"status": "error",
"message": "Mensagem específica do erro."
}
500Erro interno
{
"status": "error",
"message": "Mensagem interna do erro."
}