> **Esta página faz parte do roteiro de integração.** Ela detalha a seção 4 (como o projeto acessa a API) e as fases 3 a 8. Para pôr um jogo no plicflix do zero até ele aparecer no catálogo, siga o roteiro em ordem: https://developers.plicflix.com/docs/COMECE (fases 1 a 8, com quem faz cada passo). Índice para IAs: https://developers.plicflix.com/llms.txt

# A API do plicflix

- **Endereço:** `https://api.plicflix.com`
- **Formato:** JSON (`Content-Type: application/json`), UTF-8. Erros sempre no
  formato `{"erro": "mensagem"}` e, quando há um motivo para máquinas,
  `{"erro": "…", "codigo": "…"}`.
- **Corpo:** no máximo 64 KiB; campos desconhecidos são recusados (`400`).
- **Dinheiro:** em centavos. **Mês:** `AAAA-MM`, fuso de Brasília.
- **Limites:** cadastro, login e troca pelo navegador têm limite por IP (`429`
  ao passar).
- **Para máquinas:** [`/openapi.json`](/openapi.json) (OpenAPI 3.1).

## Rotas do estúdio (o SEU servidor)

Autenticação: `Authorization: Bearer pk_…` (a chave do painel). **Só no
servidor.**

### `POST /v1/ingresso/trocar` — a troca do token

```http
POST /v1/ingresso/trocar
Authorization: Bearer pk_…
Content-Type: application/json

{"token":"pft_…"}
```

| status | corpo |
|---|---|
| `200` | `{"jogador":{"id":"pf_…","nome":"Ana"},"jogo":"seu-slug","partida":"pt_…","assinante":true,"contexto":{"ref":"plicflix","idioma":"pt-BR","plataforma":"web"}}` |
| `401` | token inválido, vencido (5 min), já trocado, de jogo de outro estúdio, ou chave inválida/estúdio inativo. Sempre a mesma mensagem, de propósito. |

- `jogador.id`: `pf_` + 24 caracteres; **fixo** para essa pessoa no seu jogo e
  diferente em cada jogo.
- `jogador.nome`: o apelido (até 24 letras); pode mudar.
- `assinante`: `false` para testadores sem assinatura.

### `GET /v1/parceiro/jogos`

Os seus jogos: `{"parceiro":"Seu Estúdio","jogos":[{"slug","nome","url","tipo","ativo","situacao","motivo_recusa","status","status_url"}]}`.
`situacao`: `rascunho`, `em_revisao`, `recusado` ou `publicado`. `status`: o
que a vigia viu (`online`, `instavel`, `offline` ou vazio).

### `PUT /v1/parceiro/jogos/{slug}`

Edita a vitrine. **Parcial:** só os campos enviados mudam; os omitidos ficam
como estão. As imagens e o vídeo só mudam pelo painel (onde a qualidade é
conferida). Campos:
`nome, subtitulo, descricao, descricao_longa, estudio, generos[], tags[],
classificacao, plataformas[], multijogador, cor, status_url`. Regras em
[VITRINE](VITRINE). Não muda `url`, `slug` nem a situação.

### `POST /v1/parceiro/jogos/{slug}/midia/{campo}` — as imagens e o vídeo da vitrine

A montagem da vitrine inteira sem abrir o portal, para scripts e IAs. O corpo é
o **próprio arquivo**, com o `Content-Type` dele (`image/png`, `image/jpeg`,
`image/webp`, `video/mp4`, `video/webm`). Os `campo`s são `banner`, `poster`,
`logo`, `captura` (cada envio acrescenta uma, até 12) e `trailer`.

A conferência é a **mesma do portal** (tamanhos, proporções e logo
transparente: [VITRINE](VITRINE)).
- **Aceito:** `201 {"campo","url","largura","altura","duracao"?}`. O arquivo
  anterior do mesmo campo é apagado.
- **Recusado:** `422 {"erro","problemas":[…],"regra"}`, dizendo o que
  corrigir.

```bash
curl -X POST https://api.plicflix.com/v1/parceiro/jogos/SEU-SLUG/midia/banner \
  -H "Authorization: Bearer pk_…" -H "Content-Type: image/jpeg" --data-binary @banner.jpg
```

Para tirar uma captura, use `DELETE /v1/parceiro/jogos/{slug}/capturas?url=<a url da captura>`.

### `GET /v1/parceiro/jogos/{slug}/conferencia` — o que falta para publicar

A lista de conferência (os mesmos itens do portal) e o que ainda falta para
enviar à revisão. Com `?verificar=1`, roda também a verificação automática (o
plicflix visita o jogo: https, a página, o `frame-ancestors`, o SDK, a rota de
status).

```json
{"jogo":"seu-slug","situacao":"rascunho","motivo_recusa":"","pode_enviar":false,
 "faltando":["Integração: Entrada pelo SDK (token trocado no seu servidor)"],
 "itens":[{"grupo":"Vitrine","item":"Banner","ok":true,"detalhe":"…"}, …]}
```

### `POST /v1/parceiro/jogos/{slug}/revisao` — enviar para revisão

Sem corpo. `200 {"ok":true,"situacao":"em_revisao"}`; ainda faltando algo:
`409 {"erro":"…","faltando":[…]}`. A equipe recebe o aviso e responde por
e-mail (publicado, ou recusado com o motivo). Roteiro completo: [COMECE](COMECE), fases 7 e 8.

### `GET /v1/parceiro/casos`

Os casos de reclamação: `{"casos":[{"id","jogo","mes","situacao","aberto_em","prazo","prova","prova_em","decidido_em","motivos":[…]}]}`.
`situacao`: `aberto`, `em_analise`, `corrigido` ou `perdido`.

### `POST /v1/parceiro/casos/{id}/prova`

`{"prova":"o que mudou e como conferir"}` → `200 {"situacao":"em_analise"}`. Fora do prazo: `409`.

### `GET /v1/parceiro/repasses`

O extrato: `{"repasses":[{"mes":"2026-11","jogo":"seu-slug","jogadores":120,"segundos":431000,"pago_centavos":53210,"retido_centavos":0}]}`.

## Rota do navegador (jogos sem servidor)

### `POST /v1/ingresso/navegador`

`{"token":"pft_…"}`, chamada **pela página do jogo** (o `Plicflix.jogador()`
faz isso). Sem chave; o cabeçalho `Origin` precisa ser a origem do endereço
cadastrado do jogo (senão `403`). Resposta igual à da troca, mais
`"pelo_navegador": true`. Veja [MOTORES](MOTORES#jogo-sem-servidor).

## Entrar com Plicflix (OAuth 2.0 + PKCE)

O login **fora do app** (o seu site, o seu app nativo). O guia completo, com
exemplos, está em [OAUTH](OAUTH.md).

| rota | o quê |
|---|---|
| `GET https://plicflix.com/oauth/authorize` | a página de autorizar: `response_type=code`, `client_id` (o slug), `redirect_uri` (cadastrado), `state`, `code_challenge` (S256, **obrigatório**), `scope=perfil`, `prompt=consent` (opcional) |
| `POST /v1/oauth/token` | `grant_type=authorization_code`, `code`, `redirect_uri`, `code_verifier`, `client_id`. A chave `pk_` é opcional (Basic, `client_secret` ou Bearer). Devolve `{jogador:{id,nome}, jogo, assinante, sub, name, access_token, token_type, expires_in, scope}`. |
| `GET /v1/oauth/userinfo` | `Authorization: Bearer pfa_…` → as mesmas chaves, sem o token |
| `POST /v1/oauth/revogar` | `token=pfa_…` → `200` (sempre) |
| `GET /v1/parceiro/jogos/{slug}/oauth` / `PUT` | os `redirect_uris` do jogo (`{"redirect_uris":[…]}`, até 10), com a chave `pk_` |
| `GET https://plicflix.com/.well-known/oauth-authorization-server` | a descoberta (RFC 8414); também em `/v1/oauth/descoberta` |

## Pública

| rota | o quê |
|---|---|
| `GET /saude` | `{"ok":true}` se a API está de pé. |
| `GET /v1/jogos/{slug}/status` | `{"status":"online"\|"instavel"\|"offline"\|"desconhecido","ms","em"}` (o que a vigia viu). |

## Testar pela API

Para um teste automático do seu jogo **sem o app**, faça a parte do jogador
pela API com uma conta **testadora** do seu jogo (passo 8 do [COMECE](COMECE)).
Autenticação do jogador: `Authorization: Bearer <token da conta>`.

```bash
API=https://api.plicflix.com
# 1. entrar (a conta já criada e com o e-mail confirmado; testadora do jogo)
TOK=$(curl -s -X POST $API/v1/entrar -d '{"email":"voce@estudio.com","senha":"…"}' | jq -r .token)
# 2. "tocar em Jogar": devolve a URL que o app abriria
curl -s -X POST $API/v1/jogos/SEU-SLUG/jogar -H "Authorization: Bearer $TOK" -d '{"plataforma":"web"}'
# → {"partida":"pt_…","url":"https://seu-jogo/?ref=plicflix#plicflix=pft_…&pf_idioma=pt-BR&pf_plataforma=web&pf_api=…", …}
# 3. abra essa URL num navegador automatizado (Playwright, Puppeteer) DENTRO de um iframe
#    numa página https de outro site, ou mande o token direto para a sua rota:
curl -s -X POST https://seu-jogo/api/plicflix/entrar -d '{"token":"pft_…"}'
```

O que o app faz durante a partida (para reproduzir num teste):

| rota | corpo | quando |
|---|---|---|
| `POST /v1/partidas/{partida}/carregou` | `{"ms":1800,"falha":""}` | quando o jogo chama `pronto()` (`falha`: `""`, `demorou`, `erro`, `desistiu`) |
| `POST /v1/partidas/{partida}/onde` | `{"texto":"…","area":"…","ponto":{…}}` | quando o jogo chama `onde()` |
| `POST /v1/partidas/{partida}/pulso` | `{"ativo_ms":30000}` | a cada 30 s de jogo |
| `POST /v1/partidas/{partida}/fim` | — | ao sair |

Uma partida vale até o `fim` ou até a mesma conta abrir **outra** (um jogador
só joga uma coisa por vez): depois disso, as rotas dela respondem
`410 {"erro":"partida encerrada"}`.

As rotas da conta do jogador (`POST /v1/contas`, `/v1/eu/confirmar`,
`/v1/senha/esqueci` etc.) estão no [`/openapi.json`](/openapi.json).

## Os códigos de erro mais comuns

| status | quando |
|---|---|
| `400` | JSON inválido, campo desconhecido ou valor fora da regra (a mensagem diz qual) |
| `401` | sem chave/token, chave inválida, token do jogador vencido |
| `403` | sem permissão (ex.: troca pelo navegador de outra origem; e-mail do jogador não confirmado: `codigo: "email_nao_confirmado"`) |
| `404` | jogo não existe, ou não publicado e você não é testador |
| `409` | conflito (ex.: prova fora do prazo) |
| `410` | partida encerrada (houve `fim` ou a conta abriu outra partida) |
| `429` | limite por IP; espere um minuto |
| `5xx` | erro nosso; tente de novo com espera |
