# Juba Player — API de Revenda

Versão do contrato: 1.1.0. Atualizado em 11/11/2026.

Contrato público para integrações servidor a servidor de revendedores aprovados. Os endpoints internos de admin, app, portal e provedor não fazem parte deste contrato. Gestão de playlists remotas com autorização explícita do cliente ou vínculo pelo administrador global. Ativação e gestão são independentes.

## Começar

1. Cadastre-se em https://jubaplayer.com/revenda e aguarde aprovação.
2. Entre em https://jubaplayer.com/parceiro/conta e gere uma chave de API. Ela aparece uma única vez.
3. Guarde a chave no gerenciador de segredos ou variável de ambiente do seu backend (JUBA_API_KEY).
4. Consulte GET /partner/me para validar acesso e saldo sem gastar créditos.
5. Abra o app na TV para registrar o aparelho. Use o ID informado pelo cliente, nunca invente IDs ou faça varreduras.
6. Para ativar, tenha créditos, crie um UUID de pedido e envie Idempotency-Key. Essa chamada gasta saldo real.

Base: https://jubaplayer.com/api/v1. Autenticação: cabeçalho x-api-key. HTTPS obrigatório. Turnstile protege o painel, mas não é usado nesta API servidor a servidor. Não use JWT do painel para esta integração.

## Segurança, acesso e limites

- Nunca coloque a chave em navegador, app distribuído, query string, Git, capturas ou prompts. Uma única chave ativa por revendedor; gerar outra revoga imediatamente a anterior. Suspensão ou cadastro pendente bloqueiam a API.
- Leituras: 60 requisições/minuto por conta, compartilhadas entre todos os GETs de integração e gestão no painel. Escritas de gestão: 20/minuto por conta (incluindo convites, API e painel). Ativações: 10/minuto por conta, incluindo retries. Contadores de conta persistem no banco e são compartilhados entre instâncias.
- Proteção anterior à autenticação: 120 requisições/minuto por IP, compartilhadas entre todas as rotas de integração, por instância da API. Pode atingir clientes atrás do mesmo NAT. Este contador em memória reinicia no deploy; o limite por conta continua.
- Outras rotas da plataforma têm limite padrão de 300/minuto por IP/instância, exceto limites próprios de login/cadastro e exceções de health/webhook. Isso não torna essas rotas parte do contrato público.
- JSON de ativação limitado a 4096 bytes; gestão, a 8192 bytes. Limites excedidos retornam HTTP 429 + Retry-After em segundos. O limite por conta aparece em x-ratelimit-limit, x-ratelimit-remaining e x-ratelimit-reset após autenticação; em rejeições por IP os cabeçalhos representam o limite de IP.
- Respeite Retry-After; depois aplique retentativa com espera crescente e aleatoriedade. Não tente contornar limites alternando IPs/chaves. Cacheie leituras; não consulte a cada segundo.
- GET de aparelho só permite consultar aparelhos já ativados pela própria conta. Um 404 não diferencia inexistência de falta de vínculo. POST pode fazer a primeira ativação de um aparelho existente informado pelo cliente.
- Logs de integração registram ator, rota, método, resultado, IP e x-request-id para suporte. Não envie segredos em campos livres.

## Idempotência e renovação

Persista uma Idempotency-Key por pedido ANTES da primeira chamada. Depois de timeout, desconexão ou 5xx, repita com a mesma chave e o mesmo aparelho. Mesmo sucesso retorna o JSON original, com saldo/vencimento daquele momento, sem novo débito. Para consultar o estado atual use os GETs. Uma nova renovação requer nova chave. Não gere outro UUID a cada tentativa.

A chave tem 8–128 caracteres ASCII (letras, números, ponto, sublinhado, dois-pontos e hífen). O registro de sucesso permanece enquanto a conta existir, mesmo se a chave de API for trocada. Usar a mesma chave para outro aparelho retorna 409. Falhas não reservam a chave. Autenticação e limites continuam valendo em replays. Sem Idempotency-Key, cada POST válido consome outro crédito — compatibilidade com integrações anteriores.

## Testes e suporte

Não há sandbox público desta API. Valide primeiro /partner/me. Para ensaio de ativação, combine uma conta/aparelho/crédito de teste com o administrador; o endpoint não tem dry-run. A consulta de um aparelho ainda sem vínculo retorna 404 até sua primeira ativação.

Ao solicitar suporte, informe x-request-id, horário, rota e status HTTP; não envie chave ou senha. Datas são ISO 8601 UTC. Credenciais inválidas retornam 401; crédito insuficiente 402. Campos adicionais em respostas podem ser introduzidos; ignore os desconhecidos.

## Provedores e escopo

Provedores gerenciam código, DNS e plano pelo painel web. Ainda não existe API pública por chave para provedores, compra de créditos, vouchers ou automação do admin. Não use engenharia reversa das sessões de painel como contrato de integração. A API pública não fornece listas/canais nem transporta mídia.

## Gestão de playlists e consentimento

Ativar uma licença não autoriza gerenciar listas. Crie um convite, entregue approvalUrl ao cliente e aguarde sua aprovação no portal usando ID e chave. O link sozinho não concede acesso. Alternativamente, o administrador global pode vincular, transferir ou revogar com motivo obrigatório e auditoria, sem consentimento prévio. Há somente um gestor por aparelho. O cliente vê o vínculo e pode revogá-lo.

A revenda só gerencia listas adicionadas durante o vínculo atual. Revogar ou transferir mantém listas e licença, mas corta acesso à revenda anterior. A nova revenda não herda listas antigas; nem a mesma revenda recupera acesso a vínculos encerrados. Cliente e admin continuam podendo excluir as listas remotas. Listas locais da TV estão fora desta API. O app recebe mudanças na próxima sincronização.

POST de lista não implementa Idempotency-Key: salve o ID retornado; após timeout consulte as listas antes de repetir. PUT substitui todos os campos e preserva ID. DELETE retorna 204 sem corpo (404 se já removida). GET nunca devolve URLs, usuários ou senhas. Não envie credenciais em nomes de listas. A consulta de licença continua restrita ao histórico de ativações; vínculo de gestão é consultado em managed-devices.

Exemplos de corpos completos, válidos tanto em POST quanto em PUT:

```json
{"type":"m3u","name":"Minha lista","url":"https://example.com/lista.m3u","epgUrl":"https://example.com/epg.xml"}
{"type":"xtream","name":"Minha lista","server":"https://example.com","username":"usuario","password":"senha"}
{"type":"provider","name":"Minha lista","code":"ABC123","username":"usuario","password":"senha"}
```

## GET /partner/me

Consultar conta e saldo. Retorna nome e créditos disponíveis da conta autenticada. Não altera saldo.


```bash
curl --fail-with-body -X GET 'https://jubaplayer.com/api/v1/partner/me' -H "x-api-key: $JUBA_API_KEY"
```

Resposta HTTP 200 (exemplo fictício):

```json
{
  "name": "Revenda Exemplo",
  "credits": 30
}
```

## GET /partner/devices/{deviceId}

Consultar licença de aparelho vinculado. Somente aparelhos com pelo menos uma ativação deste revendedor (API ou painel). Aparelhos inexistentes e sem vínculo retornam o mesmo 404. A resposta não contém listas, senhas ou chave do aparelho.

- path **deviceId** (obrigatório): ID do aparelho.

```bash
curl --fail-with-body -X GET 'https://jubaplayer.com/api/v1/partner/devices/aa:bb:cc:dd:ee:ff' -H "x-api-key: $JUBA_API_KEY"
```

Resposta HTTP 200 (exemplo fictício):

```json
{
  "deviceId": "aa:bb:cc:dd:ee:ff",
  "license": {
    "status": "active",
    "plan": "annual",
    "registeredAt": "2026-11-11T12:00:00.000Z",
    "expiresAt": "2027-11-11T12:00:00.000Z"
  }
}
```

## POST /partner/activate

Ativar ou renovar um aparelho. Consome 1 crédito e acrescenta 365 dias na configuração atual. Se a licença ainda for válida, soma ao vencimento; se expirada, começa agora. O aparelho precisa existir (abrir o app e sincronizar antes). Use Idempotency-Key em toda ativação. A operação real debita saldo; não há modo de simulação neste endpoint.

- header **Idempotency-Key** (opcional): Fortemente recomendado. 8–128 caracteres: letras ASCII, números, ponto, sublinhado, dois-pontos ou hífen. Use um UUID por pedido e persista antes de enviar. Mesmo revendedor + mesma chave + mesmo aparelho retorna a resposta original sem novo débito, inclusive após rotação da API key. Mesma chave com outro aparelho retorna 409. Registro de sucesso conservado enquanto a conta existir; respostas de falha não reservam a chave. Sem cabeçalho, cada POST é uma nova ativação (compatibilidade).

```bash
curl --fail-with-body -X POST 'https://jubaplayer.com/api/v1/partner/activate' -H "x-api-key: $JUBA_API_KEY" -H 'Idempotency-Key: a4939de8-394c-4fa5-aa09-21f78433b6db' -H 'Content-Type: application/json' --data '{"deviceId":"aa:bb:cc:dd:ee:ff"}'
```

Resposta HTTP 200 (exemplo fictício):

```json
{
  "deviceId": "aa:bb:cc:dd:ee:ff",
  "license": {
    "status": "active",
    "plan": "annual",
    "registeredAt": "2026-11-11T12:00:00.000Z",
    "expiresAt": "2027-11-11T12:00:00.000Z"
  },
  "creditsLeft": 29
}
```

## POST /partner/management-invitations

Solicitar autorização de gestão. Cria link de uso único válido por 30 minutos. Entregue approvalUrl ao cliente, que deve autenticar o próprio aparelho e aprovar explicitamente. Nunca solicite sua chave de seis dígitos. Reemitir expira o convite anterior da mesma conta/aparelho. Não concede acesso por si só. Limite de escrita: 20/min por conta.


```bash
curl --fail-with-body -X POST 'https://jubaplayer.com/api/v1/partner/management-invitations' -H "x-api-key: $JUBA_API_KEY" -H 'Content-Type: application/json' --data '{"deviceId":"aa:bb:cc:dd:ee:ff"}'
```

Resposta HTTP 201 (exemplo fictício):

```json
{
  "id": "d6f7d632-522c-445a-9664-2cf7adef2f62",
  "expiresAt": "2026-11-11T12:00:00.000Z",
  "approvalUrl": "https://jubaplayer.com/entrar#gestao=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
```

## GET /partner/managed-devices

Listar aparelhos sob gestão. Somente vínculos atuais; gestão não implica histórico de ativação. Página de 50 itens, ordenada por vínculo mais recente.

- query **page** (opcional): Página, começando em 1.

```bash
curl --fail-with-body -X GET 'https://jubaplayer.com/api/v1/partner/managed-devices' -H "x-api-key: $JUBA_API_KEY"
```

Resposta HTTP 200 (exemplo fictício):

```json
{
  "total": 1,
  "pageSize": 50,
  "items": [
    {
      "deviceId": "aa:bb:cc:dd:ee:ff",
      "managementId": "d6f7d632-522c-445a-9664-2cf7adef2f62",
      "grantedAt": "2026-11-11T12:00:00.000Z",
      "grantedBy": "admin"
    }
  ]
}
```

## GET /partner/devices/{deviceId}/playlists

Listar metadados das próprias listas. Exige vínculo atual. Retorna somente listas criadas durante este vínculo, sem URLs ou credenciais. Listas do cliente, do admin e de outros vínculos não aparecem. Limite de leitura: 60/min por conta.

- path **deviceId** (obrigatório): ID do aparelho autorizado.

```bash
curl --fail-with-body -X GET 'https://jubaplayer.com/api/v1/partner/devices/aa:bb:cc:dd:ee:ff/playlists' -H "x-api-key: $JUBA_API_KEY"
```

Resposta HTTP 200 (exemplo fictício):

```json
[
  {
    "id": "d6f7d632-522c-445a-9664-2cf7adef2f62",
    "name": "Minha lista",
    "type": "m3u",
    "createdAt": "2026-11-11T12:00:00.000Z",
    "managementId": "d6f7d632-522c-445a-9664-2cf7adef2f62"
  }
]
```

## POST /partner/devices/{deviceId}/playlists

Adicionar lista remota. Exige vínculo atual. Só listas criadas pela revenda durante este vínculo podem ser alteradas. Revogação/transferência preserva listas, mas corta acesso; nova autorização não recupera acesso a listas de vínculos antigos. Máximo de 20 listas remotas por aparelho (inclui todas as origens). Corpo completo M3U, Xtream ou código de provedor ativo. URLs e credenciais são somente de escrita. Limite de escrita: 20/min por conta, compartilhado com convites e painel. POST não implementa Idempotency-Key: após timeout consulte as listas antes de repetir, pois pode duplicar. Salve o id retornado.

- path **deviceId** (obrigatório): ID do aparelho autorizado.

```bash
curl --fail-with-body -X POST 'https://jubaplayer.com/api/v1/partner/devices/aa:bb:cc:dd:ee:ff/playlists' -H "x-api-key: $JUBA_API_KEY" -H 'Content-Type: application/json' --data '{"type":"m3u","name":"Minha lista","url":"https://example.com/lista.m3u"}'
```

Resposta HTTP 201 (exemplo fictício):

```json
{
  "id": "d6f7d632-522c-445a-9664-2cf7adef2f62"
}
```

## PUT /partner/devices/{deviceId}/playlists/{id}

Substituir dados da própria lista. Exige vínculo atual. Só listas criadas pela revenda durante este vínculo podem ser alteradas. Revogação/transferência preserva listas, mas corta acesso; nova autorização não recupera acesso a listas de vínculos antigos. Máximo de 20 listas remotas por aparelho (inclui todas as origens). Corpo completo M3U, Xtream ou código de provedor ativo. URLs e credenciais são somente de escrita. Limite de escrita: 20/min por conta, compartilhado com convites e painel. Envie todos os campos; preserva o ID. Repetir PUT com o mesmo conteúdo mantém o estado.

- path **deviceId** (obrigatório): ID do aparelho autorizado.
- path **id** (obrigatório): UUID da lista retornado na criação.

```bash
curl --fail-with-body -X PUT 'https://jubaplayer.com/api/v1/partner/devices/aa:bb:cc:dd:ee:ff/playlists/d6f7d632-522c-445a-9664-2cf7adef2f62' -H "x-api-key: $JUBA_API_KEY" -H 'Content-Type: application/json' --data '{"type":"m3u","name":"Minha lista","url":"https://example.com/lista.m3u"}'
```

Resposta HTTP 200 (exemplo fictício):

```json
{
  "id": "d6f7d632-522c-445a-9664-2cf7adef2f62"
}
```

## DELETE /partner/devices/{deviceId}/playlists/{id}

Remover a própria lista. Exige vínculo atual e lista criada nele. Sem corpo JSON. Retorna 204; repetir após sucesso retorna 404. Não remove listas locais da TV.

- path **deviceId** (obrigatório): ID do aparelho autorizado.
- path **id** (obrigatório): UUID da lista retornado na criação.

```bash
curl --fail-with-body -X DELETE 'https://jubaplayer.com/api/v1/partner/devices/aa:bb:cc:dd:ee:ff/playlists/d6f7d632-522c-445a-9664-2cf7adef2f62' -H "x-api-key: $JUBA_API_KEY"
```

Resposta HTTP 204 (exemplo fictício):

Sem corpo.

## Exemplo JavaScript (Node.js, backend)

O pedidoId vem do seu banco e deve ser reaproveitado em retries. Não execute este código no navegador.

```javascript
async function ativar(deviceId, pedidoId) {
  const response = await fetch('https://jubaplayer.com/api/v1/partner/activate', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.JUBA_API_KEY,
      'Content-Type': 'application/json',
      'Idempotency-Key': pedidoId,
    },
    body: JSON.stringify({ deviceId }),
    signal: AbortSignal.timeout(15000),
  });
  const body = await response.json();
  if (!response.ok) {
    throw Object.assign(new Error(body.error), {
      status: response.status,
      requestId: response.headers.get('x-request-id'),
      retryAfter: response.headers.get('retry-after'),
    });
  }
  return body;
}
```

## Erros

| HTTP | error (exemplo) | Como agir |
| --- | --- | --- |
| 400 | validation | Formato inválido de ID, JSON ou Idempotency-Key. Corrija a entrada antes de repetir. |
| 401 | invalid_api_key | Chave ausente, inválida, revogada ou conta suspensa/pendente. Confira a conta e a chave. |
| 402 | no_credits | Saldo insuficiente. Compre créditos no painel antes de tentar novamente. |
| 404 | device_not_found | Aparelho/lista não encontrado ou fora do escopo permitido. Consulta de licença exige ativação anterior pela própria conta. |
| 409 | idempotency_conflict | A mesma Idempotency-Key já foi concluída para outro aparelho. Não reutilize uma chave para operações diferentes. |
| 413 | FST_ERR_CTP_BODY_TOO_LARGE | JSON excede 4096 bytes em ativação ou 8192 bytes em gestão. |
| 415 | FST_ERR_CTP_INVALID_MEDIA_TYPE | Envie Content-Type: application/json. |
| 429 | rate_limited | Limite excedido. Aguarde os segundos do cabeçalho Retry-After antes de repetir. |
| 500 | internal | Erro interno. Em ativações, repita somente com a MESMA Idempotency-Key. Sem ela, confirme com suporte antes de repetir. |
| 403 | management_required | Gestão não autorizada, revogada ou conta indisponível. Solicite novo consentimento ou intervenção administrativa. |

O envelope de erro é {"error":"codigo"}; erros de validação também podem trazer details. Outros códigos de transporte/validação do Fastify podem aparecer em requisições malformadas. Não use o texto humano como contrato.

## Arquivos

- [OpenAPI 3.1](https://jubaplayer.com/developers/openapi.json)
- [Guia web](https://jubaplayer.com/desenvolvedores)
- [Índice para agentes](https://jubaplayer.com/llms.txt)

Referências de formato: [OpenAPI](https://spec.openapis.org/oas/v3.1.1.html) e [proposta llms.txt](https://llmstxt.org/).
