{"openapi":"3.1.0","info":{"title":"Jogo de Boteco API","version":"1.0.0","description":"Dados de jogadores e partidas do **Jogo de Boteco** (truco, cacheta, dominó, buraco e palitinho) pra integrações parceiras, como a Tipspace.\n\n### Autenticação\nToda rota `/v1` pede o header **`x-api-key`** com a chave que a gente te passou. É chamada **servidor-pra-servidor**: nunca coloque a chave num app ou site.\n\n### Identificadores\n- **`user_id`**: UUID da conta do jogador no Jogo de Boteco. Não muda nunca (nem quando ele troca de apelido ou entra com Google/Apple), então é ele que você guarda pra ligar a conta.\n- **`match_id`**: id da partida no servidor (UUID + `.boteco`).\n\n### Modos de jogo (`game_mode`)\n`truco-paulista-sujo`, `truco-paulista-limpo`, `truco-mineiro`, `truco-mineiro-limpo`, `caxeta`, `domino-batida`, `domino-pontos`, `buraco-coringas`, `buraco-so2`, `palitinho`. Nos filtros, dá pra mandar só o jogo (`truco`) pra pegar todos os modos dele.\n\n### Ranqueada e MMR\nSó a ranqueada mexe no MMR (Elo, começa em 1000). Os rankings são mensais: `truco-paulista-mano`, `truco-mineiro-mano`, `truco-paulista-dupla`, `truco-mineiro-dupla`, `caxeta-mano`, `domino-mano`. Nas partidas casuais e com amigos, os campos de MMR vêm `null`.\n\n### Bots\nQuando falta gente, a casa completa a mesa com bots. Na ranqueada eles aparecem com nome de gente pros jogadores, mas aqui vêm sempre com **`is_bot: true`**.\n\n### Datas\nISO 8601 em UTC. `match_finished_at` é `null` enquanto a partida está rolando.\n\n### Partida terminada (webhook)\nNo fim de cada partida no servidor, o jogo manda um **POST** pra URL que vocês cadastrarem, com o header `token`. Veja *Webhooks* abaixo. É só um aviso pra vocês conferirem: o resultado vem de `GET /v1/matches/{match_id}`.","contact":{"name":"Jogo de Boteco","email":"contato@jogodeboteco.com","url":"https://jogodeboteco.com"}},"servers":[{"url":"https://api.jogodeboteco.com","description":"Produção"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Jogadores","description":"Conta, partidas jogadas e MMR."},{"name":"Partidas","description":"Histórico e detalhe das partidas no servidor."}],"paths":{"/v1/users/{user_id}":{"get":{"tags":["Jogadores"],"operationId":"getUser","summary":"Jogador e quantas partidas ele já jogou","description":"Conta do jogador, total de partidas (vitórias, derrotas, em andamento) por modo e o MMR em cada ranking.","parameters":[{"$ref":"#/components/parameters/UserId"}],"responses":{"200":{"description":"Jogador","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"user_id não é um UUID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"user_id inválido"}}}},"401":{"description":"Sem chave ou chave errada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"chave da API ausente ou inválida (header x-api-key)"}}}},"404":{"description":"Conta não existe (ou foi apagada)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"jogador não encontrado"}}}}}}},"/v1/users/{user_id}/match-history":{"get":{"tags":["Partidas"],"operationId":"getMatchHistory","summary":"Histórico de partidas do jogador","description":"Partidas no servidor, da mais recente pra mais antiga, com resultado, placar, MMR antes/depois e quem estava na mesa. Inclui a partida em andamento (`status: in_progress`, `match_finished_at: null`).","parameters":[{"$ref":"#/components/parameters/UserId"},{"name":"game_mode","in":"query","description":"Modo exato ou só o jogo.","schema":{"type":"string","examples":["truco-paulista-sujo","truco"]},"example":"truco"},{"name":"ranked","in":"query","description":"Só ranqueadas (`true`) ou só casuais/com amigos (`false`).","schema":{"type":"boolean"}},{"name":"limit","in":"query","description":"Itens por página (1 a 100).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","description":"O `next_cursor` da página anterior.","schema":{"type":"string"}}],"responses":{"200":{"description":"Página do histórico","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchHistory"}}}},"400":{"description":"user_id inválido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"user_id inválido"}}}},"401":{"description":"Sem chave ou chave errada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"chave da API ausente ou inválida (header x-api-key)"}}}}}}},"/v1/matches/{match_id}":{"get":{"tags":["Partidas"],"operationId":"getMatch","summary":"Detalhe de uma partida","description":"A partida inteira, com todas as cadeiras e o MMR de cada humano. É a fonte pra conferir resultado (não depende do lado de um jogador).","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string"},"example":"85c061a2-a9d2-4d89-b3db-4428d9af1802.boteco"}],"responses":{"200":{"description":"Partida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchDetail"}}}},"401":{"description":"Sem chave ou chave errada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"chave da API ausente ou inválida (header x-api-key)"}}}},"404":{"description":"Partida não existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"partida não encontrada"}}}}}}}},"webhooks":{"matchFinished":{"post":{"tags":["Partidas"],"operationId":"matchFinished","summary":"Partida terminou (o jogo chama vocês)","description":"Enviado quando uma partida no servidor termina (normal ou por desistência). Responda 2xx. Não tem reenvio: se falhar, a partida aparece na próxima consulta ao histórico. Partidas não ranqueadas também chegam (com `ranked: false`).","parameters":[{"name":"token","in":"header","required":true,"description":"Token combinado do webhook (compare em tempo constante).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["matchId","ranked","gameMode","playerUserIds","finishedAt"],"properties":{"matchId":{"type":"string","example":"85c061a2-a9d2-4d89-b3db-4428d9af1802.boteco"},"ranked":{"type":"boolean"},"gameMode":{"$ref":"#/components/schemas/GameMode"},"playerUserIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Os humanos da mesa (user_id). Bot não entra."},"finishedAt":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Recebido"}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Chave da integração (servidor-pra-servidor)."}},"parameters":{"UserId":{"name":"user_id","in":"path","required":true,"description":"UUID da conta no Jogo de Boteco.","schema":{"type":"string","format":"uuid"},"example":"6cfd22fe-e410-4c64-b57e-5e250f6846ca"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"Game":{"type":"string","enum":["truco","caxeta","domino","buraco","palitinho"],"description":"`caxeta` = cacheta."},"GameMode":{"type":"string","enum":["truco-paulista-sujo","truco-paulista-limpo","truco-mineiro","truco-mineiro-limpo","caxeta","domino-batida","domino-pontos","buraco-coringas","buraco-so2","palitinho"]},"User":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","example":"6cfd22fe-e410-4c64-b57e-5e250f6846ca"},"name":{"type":"string","description":"Apelido atual.","example":"Zeca Truqueiro"},"created_at":{"type":"string","format":"date-time"},"login":{"type":"object","description":"Contas ligadas. Ranqueada exige uma delas.","properties":{"google":{"type":"boolean"},"apple":{"type":"boolean"}}},"matches":{"type":"object","properties":{"total":{"type":"integer","example":42},"wins":{"type":"integer","example":25},"losses":{"type":"integer","example":16},"in_progress":{"type":"integer","example":1},"vs_bots_on_device":{"type":"integer","description":"Partidas offline contra os bots (fora do histórico).","example":120},"by_game_mode":{"type":"array","items":{"type":"object","properties":{"game_mode":{"$ref":"#/components/schemas/GameMode"},"ranked":{"type":"boolean"},"total":{"type":"integer"},"wins":{"type":"integer"},"losses":{"type":"integer"}}}}}},"ratings":{"type":"array","items":{"type":"object","properties":{"board":{"type":"string","examples":["truco-paulista-mano","truco-mineiro-mano","truco-paulista-dupla","truco-mineiro-dupla","caxeta-mano","domino-mano"],"description":"Ranking. `truco-mano` é o antigo, de antes de separar paulista e mineiro."},"mmr":{"type":"integer","example":1087},"games":{"type":"integer","example":31},"season_rank":{"type":["integer","null"],"description":"Posição no ranking do mês.","example":12}}}}}},"Player":{"type":"object","properties":{"seat":{"type":"integer","description":"Cadeira (0 a 5). Truco e dominó: dupla = cadeira % 2."},"team":{"type":"integer","description":"Time (na cacheta e no palitinho, cada um é seu próprio time = cadeira)."},"name":{"type":"string"},"is_bot":{"type":"boolean"},"user_id":{"type":["string","null"],"format":"uuid","description":"`null` pra bot (ou conta apagada)."},"won":{"type":["boolean","null"],"description":"`null` enquanto não terminou."}}},"Match":{"type":"object","description":"Partida vista por um jogador.","properties":{"match_id":{"type":"string","example":"85c061a2-a9d2-4d89-b3db-4428d9af1802.boteco"},"legacy":{"type":"boolean","description":"Partida de antes de 01/10/2026 (histórico antigo): sem id real da partida, sem hora de início (vem igual ao fim), sem cadeira/time/placar e sem user_id dos outros jogadores."},"game":{"$ref":"#/components/schemas/Game"},"mode":{"type":["string","null"],"example":"paulista-sujo"},"game_mode":{"$ref":"#/components/schemas/GameMode"},"queue":{"type":"string","enum":["casual","amigos","mano","dupla"],"description":"`mano` = ranqueada 1x1, `dupla` = ranqueada 2x2, `amigos` = mesa por link."},"ranked":{"type":"boolean"},"status":{"type":"string","enum":["in_progress","finished","abandoned"]},"end_reason":{"type":["string","null"],"enum":["normal","forfeit","abandoned",null],"description":"`forfeit` = alguém desistiu (perde ele e a dupla)."},"match_started_at":{"type":"string","format":"date-time"},"match_finished_at":{"type":["string","null"],"format":"date-time","description":"`null` = em andamento."},"seat":{"type":"integer"},"team":{"type":"integer"},"result":{"type":["string","null"],"enum":["win","loss",null]},"forfeited":{"type":"boolean","description":"Foi ele quem desistiu."},"mmr":{"type":["integer","null"],"description":"MMR dele quando a partida começou.","example":1000},"mmr_after":{"type":["integer","null"],"example":1012},"mmr_delta":{"type":["integer","null"],"example":12},"score":{"type":["array","null"],"items":{"type":"integer"},"description":"Placar final por time (truco, dominó, buraco).","example":[12,7]},"winner_team":{"type":["integer","null"]},"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}}}},"MatchHistory":{"type":"object","properties":{"matches":{"type":"array","items":{"$ref":"#/components/schemas/Match"}},"next_cursor":{"type":["string","null"],"description":"`null` = acabou."}}},"MatchDetail":{"type":"object","properties":{"match":{"allOf":[{"$ref":"#/components/schemas/Match"},{"type":"object","properties":{"players":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Player"},{"type":"object","properties":{"mmr":{"type":["integer","null"]},"mmr_after":{"type":["integer","null"]},"mmr_delta":{"type":["integer","null"]}}}]}}}}],"description":"Mesmo formato da partida do histórico, sem os campos de um jogador só (seat, team, result, mmr...): eles ficam em cada item de `players`."}}}}}}