# Let2Play — API de desenvolvedores v1.0

Publique e administre jogos HTML5 no Let2Play por HTTP. Esta página é a referência completa;
a versão navegável fica em https://let2play.com/desenvolvedores/api e o OpenAPI em https://let2play.com/api/v1/openapi.json.

## Autenticação

Cada estúdio tem um **Team ID** (`TM-…`) e chaves criadas em https://let2play.com/estudio/api.
A chave tem o formato `l2p_live_<prefixo>_<segredo>` e aparece uma única vez, na criação.

```http
Authorization: Bearer l2p_live_a1b2c3d4e5f6_<segredo>
X-Team-Id: TM-9F2C71A4B8        # opcional; se enviado, precisa ser o time da chave
Content-Type: application/json
```

Base de todas as rotas: `https://let2play.com`. Só HTTPS. Corpo e resposta em JSON UTF-8.
Nunca coloque a chave em HTML, repositório ou front-end: ela publica jogos em nome do estúdio.

## Escopos

| Escopo | O que libera |
| --- | --- |
| `jogos:ler` | Listar seus jogos, ver status de revisão e baixar métricas. |
| `jogos:escrever` | Criar rascunhos, editar textos, enviar o .zip, a capa e os prints. |
| `produtos:escrever` | Criar e alterar preço do jogo, moedas, itens e assinaturas. |
| `publicar` | Mandar o jogo para a revisão humana do portal. |

## Limites

- 600 chamadas por hora por chave; 60 uploads por hora.
- Estouro: HTTP 429 com `Retry-After` em segundos.
- .zip até 100 MB (300 MB descompactado); imagens até 8 MB; 8 prints por jogo.

## Erros

Formato fixo: `{"erro": "mensagem", "codigo": "slug", "status": 422}`.
Em erro de validação vem também `campos`, com o motivo por campo.

| codigo | HTTP | Quando acontece |
| --- | --- | --- |
| `chave_ausente` | 401 | Não veio o cabeçalho Authorization: Bearer … |
| `chave_invalida` | 401 | Chave com formato errado, revogada ou inexistente. |
| `team_divergente` | 403 | O X-Team-Id enviado não é o do dono da chave. |
| `estudio_suspenso` | 403 | Estúdio suspenso: fale com o suporte. |
| `escopo_faltando` | 403 | A chave não tem o escopo que o endpoint exige. |
| `nao_encontrado` | 404 | Jogo, print ou produto não existe (ou não é seu). |
| `metodo_invalido` | 405 | Método HTTP errado para essa rota. |
| `json_invalido` | 422 | O corpo não é um JSON de objeto válido. |
| `validacao` | 422 | Um ou mais campos inválidos — veja o objeto "campos". |
| `limite` | 429 | Passou do limite de chamadas por hora (cabeçalho Retry-After). |
| `erro_interno` | 500 | Falha nossa. Tente de novo; se insistir, fale com o suporte. |

## Ciclo de vida de um jogo

1. `POST /api/v1/jogos` cria o **rascunho** (nada aparece no portal).
2. `POST …/pacote` (.zip com `index.html` na raiz) e `POST …/capa` (keyart 16:9).
3. `POST …/prints` para a galeria e `POST …/produtos` para o preço (sem produto, o jogo é grátis).
4. `POST …/enviar` manda para **revisão humana** (`em_revisao`).
5. Aprovado, o jogo entra no portal (`no_ar: true`). Reprovado, leia `revisao_motivo`, corrija e envie de novo.
6. Pacote novo em jogo aprovado volta sozinho para revisão; mudar texto ou imagem entra no ar na hora.

## Endpoints

### Conta

#### `GET /api/v1/eu`

Quem sou eu — qualquer escopo

Confere a chave e devolve o time, os escopos e os limites. Use isto como teste de conexão.

Resposta:

```json
{
  "team_id": "TM-9F2C71A4B8",
  "estudio": {"nome": "Estúdio Neon", "slug": "estudio-neon", "url": "https://let2play.com/criador/estudio-neon"},
  "chave": {"nome": "Claude Code", "prefixo": "a1b2c3d4e5f6", "escopos": ["jogos:ler","jogos:escrever"]},
  "limites": {"chamadas_por_hora": 600, "uploads_por_hora": 60},
  "taxa_vendas_pct": 20,
  "modo_demo": true,
  "api": "1.0"
}
```

#### `GET /api/v1/categorias`

Categorias disponíveis — qualquer escopo

Lista os slugs válidos para o campo `categorias` do jogo. Todo jogo precisa de pelo menos uma.

Resposta:

```json
{"categorias": [{"slug": "acao", "nome": "Ação"}, {"slug": "puzzle", "nome": "Puzzle"}]}
```

### Jogos

#### `GET /api/v1/jogos`

Listar meus jogos — escopo `jogos:ler`

Todos os jogos do time, do mais recente para o mais antigo.

- query `revisao`: filtra por rascunho | em_revisao | aprovado | reprovado

Resposta:

```json
{"jogos": [ … objeto de jogo … ], "total": 3}
```

#### `POST /api/v1/jogos`

Criar jogo (rascunho) — escopo `jogos:escrever`

Cria o jogo como rascunho e devolve o `slug` gerado a partir do título. Nada fica visível no portal até você enviar para revisão e a revisão aprovar. Obrigatórios: `titulo` (2–60), `descricao` (20–300) e `categorias` (1+).

Corpo:

```json
{
  "titulo": "Coletor de Cristais",
  "subtitulo": "Corra, pule, colete",
  "descricao": "Pule entre plataformas e colete cristais antes do tempo acabar.",
  "descricao_longa": "Texto longo da página do jogo (até 6000 caracteres).",
  "como_jogar": "Use as setas para andar e espaço para pular.",
  "controles": [["←/→", "Mover"], ["Espaço", "Pular"]],
  "tags": ["plataforma", "arcade"],
  "categorias": ["acao", "arcade"],
  "youtube": "https://youtu.be/XXXXXXXXXXX",
  "dois_jogadores": false,
  "mobile": true,
  "orientacao": "paisagem",
  "i18n": {"en": {"titulo": "Crystal Collector", "descricao": "Jump and collect…"}}
}
```

Resposta:

```json
{
  "slug": "coletor-de-cristais",
  "titulo": "Coletor de Cristais",
  "descricao": "Pule entre plataformas e colete cristais antes do tempo acabar.",
  "categorias": ["acao", "arcade"],
  "revisao": "rascunho",
  "status": "oculto",
  "no_ar": false,
  "preco_centavos": 0,
  "tem_pacote": false,
  "capa": null,
  "prints": [],
  "pendencias": ["pacote", "capa"],
  "url": "https://let2play.com/jogo/coletor-de-cristais"
}
```

#### `GET /api/v1/jogos/{slug}`

Ver um jogo — escopo `jogos:ler`

Estado completo: textos, mídias, preço, situação da revisão e o que falta (`pendencias`).

Resposta:

```json
{
  "slug": "coletor-de-cristais",
  "titulo": "Coletor de Cristais",
  "descricao": "Pule entre plataformas e colete cristais antes do tempo acabar.",
  "categorias": ["acao", "arcade"],
  "revisao": "rascunho",
  "status": "oculto",
  "no_ar": false,
  "preco_centavos": 0,
  "tem_pacote": false,
  "capa": null,
  "prints": [],
  "pendencias": ["pacote", "capa"],
  "url": "https://let2play.com/jogo/coletor-de-cristais"
}
```

#### `PATCH /api/v1/jogos/{slug}`

Editar jogo — escopo `jogos:escrever`

Só os campos enviados mudam. O `slug` nunca muda (é a URL pública). Se o jogo já está aprovado, textos e imagens entram no ar na hora. Aceita também `POST` na mesma rota, para clientes que não fazem PATCH.

Corpo:

```json
{"descricao": "Nova descrição curta, com mais de vinte caracteres.", "tags": ["arcade","retro"]}
```

Resposta:

```json
{
  "slug": "coletor-de-cristais",
  "titulo": "Coletor de Cristais",
  "descricao": "Pule entre plataformas e colete cristais antes do tempo acabar.",
  "categorias": ["acao", "arcade"],
  "revisao": "rascunho",
  "status": "oculto",
  "no_ar": false,
  "preco_centavos": 0,
  "tem_pacote": false,
  "capa": null,
  "prints": [],
  "pendencias": ["pacote", "capa"],
  "url": "https://let2play.com/jogo/coletor-de-cristais"
}
```

#### `POST /api/v1/jogos/{slug}/pacote`

Enviar o .zip do jogo — escopo `jogos:escrever`

O jogo em si. Mande o corpo cru do arquivo com `Content-Type: application/zip` (ou `multipart/form-data` no campo `pacote`). O .zip precisa ter `index.html` na raiz (ou dentro de uma única pasta), máximo 100 MB compactado / 300 MB descompactado. Versão nova em jogo já aprovado volta para revisão automaticamente.

Corpo:

```text
<corpo binário do arquivo .zip>
```

Resposta:

```json
{"ok": true, "jogo": { … }, "tem_pacote": true, "revisao": "rascunho"}
```

#### `POST /api/v1/jogos/{slug}/capa`

Enviar a capa — escopo `jogos:escrever`

Capa do card (obrigatória para publicar). JPG, PNG ou WebP, até 8 MB; convertemos para WebP e geramos as variantes. Proporção recomendada 16:9 (ex.: 1280×720). Três jeitos de mandar: corpo cru da imagem, `multipart/form-data` no campo `capa`, ou JSON com a imagem em base64 — este último é o caminho mais fácil para uma IA que acabou de gerar a arte.

Corpo:

```json
{"imagem_base64": "iVBORw0KGgoAAAANSUhEUg…"}
```

Resposta:

```json
{"ok": true, "capa": "https://let2play.com/capas/coletor-de-cristais-a1b2c3.webp"}
```

#### `GET /api/v1/jogos/{slug}/prints`

Listar prints — escopo `jogos:ler`

As imagens da galeria da página do jogo.

Resposta:

```json
{"prints": [{"id": 12, "url": "https://let2play.com/capas/coletor-de-cristais-tela-aa11bb.webp"}]}
```

#### `POST /api/v1/jogos/{slug}/prints`

Adicionar print — escopo `jogos:escrever`

Uma imagem por chamada, até 8 por jogo. Mesmos formatos e mesmos três jeitos de envio da capa (corpo cru, multipart no campo `imagem`, ou `imagem_base64`).

Corpo:

```json
{"imagem_base64": "iVBORw0KGgoAAAANSUhEUg…"}
```

Resposta:

```json
{"ok": true, "print": {"id": 13, "url": "https://let2play.com/capas/…webp"}, "total": 2}
```

#### `DELETE /api/v1/jogos/{slug}/prints/{id}`

Apagar print — escopo `jogos:escrever`

Remove a imagem da galeria e do disco.

Resposta:

```json
{"ok": true, "total": 1}
```

### Preços e produtos

#### `GET /api/v1/jogos/{slug}/produtos`

Listar produtos — escopo `jogos:ler`

Preço do jogo e itens da loja interna, com o total vendido de cada um.

Resposta:

```json
{"produtos": [{"codigo": "jogo", "tipo": "jogo", "nome": "Coletor de Cristais", "preco_centavos": 990, "ativo": true, "vendas": 0}]}
```

#### `POST /api/v1/jogos/{slug}/produtos`

Criar ou atualizar produto (definir preço) — escopo `produtos:escrever`

Upsert pelo `codigo`: se já existe, atualiza. Para dar preço ao jogo, use `"tipo": "jogo"` (um por jogo) — sem esse produto o jogo é grátis. Preço em centavos, de 100 a 100000 (R$ 1,00 a R$ 1.000,00). Tipos: `jogo`, `moedas`, `item`, `consumivel`, `assinatura`. `moedas` exige quantas moedas o pacote dá; `assinatura` exige `intervalo` (month|year). Use `"ativo": false` para tirar de venda. Produto já vendido não pode mudar de `codigo` nem de `tipo`.

Corpo:

```json
{
  "codigo": "moedas-500",
  "tipo": "moedas",
  "nome": "500 moedas",
  "descricao": "Pacote inicial",
  "preco_centavos": 990,
  "moedas": 500,
  "icone": "paid",
  "ativo": true,
  "i18n": {"en": {"nome": "500 coins"}}
}
```

Resposta:

```json
{"ok": true, "produto": {"codigo": "moedas-500", "preco_centavos": 990, …}, "criado": true}
```

#### `DELETE /api/v1/jogos/{slug}/produtos/{codigo}`

Apagar produto — escopo `produtos:escrever`

Só dá para apagar produto que nunca foi vendido. Vendido, use `"ativo": false`.

Resposta:

```json
{"ok": true}
```

### Publicação e métricas

#### `POST /api/v1/jogos/{slug}/enviar`

Enviar para revisão — escopo `publicar`

Equivale ao botão "Enviar para revisão" do estúdio. Exige pacote, capa e categoria (se faltar algo, vem 422 com a lista em `pendencias`). Depois o jogo fica `em_revisao` até uma pessoa do portal aprovar ou reprovar — consulte `GET /api/v1/jogos/{slug}` para ver o resultado e o `revisao_motivo`.

Resposta:

```json
{"ok": true, "revisao": "em_revisao", "enviado_em": "2026-11-08 14:02:11"}
```

#### `GET /api/v1/jogos/{slug}/metricas`

Métricas do jogo — escopo `jogos:ler`

Série diária e totais do período: impressões, cliques, páginas vistas, jogadas, tempo, anúncios e receita (em centavos, já descontada a taxa).

- query `periodo`: dias: 7, 30 (padrão) ou 90

Resposta:

```json
{
  "periodo": {"dias": 30, "de": "2026-10-10", "ate": "2026-11-08"},
  "totais": {"jogadas": 1240, "views": 3100, "segundos": 86400, "anuncios": 900,
             "receita_vendas": 15400, "receita_anuncios": 3600, "receita": 19000},
  "dias": [{"dia": "2026-10-10", "jogadas": 12, "views": 40, "receita": 120}]
}
```

#### `GET /api/v1/openapi.json`

OpenAPI — qualquer escopo

Especificação OpenAPI 3.1 desta API, gerada do mesmo catálogo que gera esta página. Útil para gerar cliente ou alimentar uma IA.

Resposta:

```json
{"openapi": "3.1.0", "info": {"title": "Let2Play Developer API", "version": "1.0"}, …}
```

## Tipos de produto

| tipo | O que é |
| --- | --- |
| `jogo` | Jogo completo — Compra única que libera o jogo |
| `moedas` | Pacote de moedas — Moedas creditadas na carteira do jogo |
| `item` | Item permanente — Compra única (skin, fase, personagem…) |
| `consumivel` | Item consumível — Pode comprar várias vezes; o jogo consome |
| `assinatura` | Assinatura — Cobrança recorrente (VIP, passe…) |

Preço em centavos de real (BRL), de 100 a 100000. A plataforma fica com 20% de cada venda; o resto vai ao estúdio via Stripe Connect (cadastre o recebedor em https://let2play.com/estudio/recebedor antes de vender).

## Dentro do jogo (SDK)

O jogo roda num iframe isolado, sem acesso a cookies nem ao `localStorage` do portal.
Conta, moedas, itens, ranking, save na nuvem e compras passam pelo SDK:

```html
<script src="https://let2play.com/sdk/let2play.js"></script>
```

```js
const eu = await Let2Play.iniciar();      // {logado, apelido, moedas, itens, assinaturas, jogo_comprado}
Let2Play.inicioPartida(); Let2Play.fimPartida(pontos);
await Let2Play.salvar({fase: 7});  const save = await Let2Play.carregar();
const r = await Let2Play.comprar("moedas-500");   // abre o checkout no portal
const g = await Let2Play.gastar(100, "continuar"); // moedas compradas: o servidor confere
```

Regras do pacote do jogo: https://let2play.com/desenvolvedores/contrato.md

## Roteiro pronto para colar na sua IA

```text
Você vai publicar um jogo HTML5 no portal Let2Play usando a API de desenvolvedores.

Base: https://let2play.com
Documentação completa (leia primeiro): https://let2play.com/desenvolvedores/api.md
OpenAPI: https://let2play.com/api/v1/openapi.json

Credenciais (em toda chamada):
  Authorization: Bearer $LET2PLAY_API_KEY
  X-Team-Id: SEU_TEAM_ID
A chave está na variável de ambiente LET2PLAY_API_KEY. Nunca escreva a chave em arquivo, commit ou log.

Roteiro:
1. GET /api/v1/eu  -> confirme o time e os escopos da chave.
2. GET /api/v1/categorias  -> escolha 1 a 3 slugs reais.
3. POST /api/v1/jogos  -> crie o rascunho (titulo 2-60, descricao 20-300, categorias obrigatórias).
   Guarde o "slug" devolvido: ele é o identificador de todas as chamadas seguintes.
4. POST /api/v1/jogos/{slug}/pacote  -> o jogo compactado (.zip com index.html na raiz, até 100 MB),
   corpo cru com Content-Type: application/zip.
5. POST /api/v1/jogos/{slug}/capa  -> keyart 16:9 (1280x720), JSON {"imagem_base64": "..."}.
   A capa é arte, não print de gameplay.
6. POST /api/v1/jogos/{slug}/prints  -> 2 a 4 prints reais do jogo (uma chamada por imagem).
7. (opcional) POST /api/v1/jogos/{slug}/produtos  -> preço do jogo ({"codigo":"jogo","tipo":"jogo",
   "nome":"...","preco_centavos":990}) e/ou moedas, itens, assinatura. Sem isso o jogo é grátis.
8. POST /api/v1/jogos/{slug}/enviar  -> manda para revisão humana.
9. GET /api/v1/jogos/{slug}  -> acompanhe "revisao" (em_revisao -> aprovado | reprovado) e
   "revisao_motivo" se reprovar. Corrija e envie de novo.

Regras:
- Antes do passo 8, confira que "pendencias" está vazio em GET /api/v1/jogos/{slug}.
- Erro tem o formato {"erro","codigo","status"}; em 422 de validação vem também "campos" com o motivo
  por campo. Corrija o campo apontado em vez de repetir a chamada.
- Em 429 respeite o cabeçalho Retry-After. Limite: 600 chamadas/hora e 60 uploads/hora por chave.
- O jogo roda isolado num iframe; para conta, moedas, ranking e compras dentro do jogo use o SDK
  (https://let2play.com/sdk/let2play.js), documentado em https://let2play.com/desenvolvedores/api#sdk.
```

