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órioUsername ou e-mail do usuário Controla.
passwordobrigatórioSenha 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órioToken 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órioToken JWT, formato Bearer <token>.

Parâmetros GET (querystring)

serialNumberSerial number do dispositivo; resolve automaticamente branchId/companyId. Opcional.
branchIdID da rádio (branch), para usuários Controla. Opcional se serialNumber for informado.
clientIdID 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órioToken JWT, formato Bearer <token>.

Body (JSON)

(corpo inteiro)obrigatórioSalvo como o JSON de configuração da botoneira (estrutura livre, definida pelo app). Não pode ser vazio, limitado a 5MB.
serialNumber / branchId / clientIdInformados 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órioToken JWT, formato Bearer <token>.

Body (JSON)

(corpo inteiro)obrigatórioSubstitui o JSON de configuração da botoneira atual. Não pode ser vazio, limitado a 5MB.
serialNumber / branchId / clientIdInformados 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órioToken JWT, formato Bearer <token>.

Parâmetros (querystring ou body)

serialNumber / branchId / clientIdPara 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órioToken JWT, formato Bearer <token>.

Body (multipart/form-data)

audioobrigatórioArquivo 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órioToken JWT, formato Bearer <token>.

Parâmetros GET (querystring)

branchIdID 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.
clientIdOverride 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."
}