{"openapi":"3.1.0","info":{"title":"brawlstars-api — LEAGUETV","version":"1.0.0","description":"Serviço sobre a API oficial do Brawl Stars: cadastro por **tag**, captura automática\ndas partidas, histórico persistido e as consultas que alimentam as peças da transmissão.\n\n## Autenticação\n`/health`, `/` e `/docs.json` são abertas. **Ler** aceita `x-api-key: <SERVICE_KEY>`\n(ou `?key=`) **ou** a sessão do dashboard. **Escrever exige sessão** — a `x-api-key`\npode estar visível no JS de um overlay.\n\n## Leia antes de prometer um número no ar\nLevantado por censo de **858 batalhas** da API oficial, não pela documentação dela.\nO battlelog é **placar, não scoreboard**: veja `x-indisponivel` na raiz desta spec."},"servers":[{"url":"https://brawlstars-api.ottospike.com"}],"x-indisponivel":{"aviso":"Estes dados NÃO EXISTEM na API oficial do Brawl Stars. Não há endpoint, não é limitação de plano, e nenhuma rota aqui vai devolvê-los.","campos":[{"pedido":"ranking de kills / abates","situacao":"sem fonte"},{"pedido":"ranking de assists / assistências","situacao":"sem fonte"},{"pedido":"ranking de damage / dano causado","situacao":"sem fonte"},{"pedido":"ranking de heals / cura","situacao":"sem fonte"},{"pedido":"mortes, gols (Brawl Ball), gemas (Gem Grab), tempo em zona (Hot Zone), supers, gadget/star power/gear usado","situacao":"sem fonte"},{"pedido":"bans de draft","situacao":"sem fonte — os tipos existem no swagger da Supercell (BannedBrawlerEntry, Match, RegisterMatchRequest) mas nenhum path os expõe"},{"pedido":"partida em andamento / ao vivo","situacao":"sem fonte — o battlelog só devolve batalha TERMINADA. 'Em andamento' só existe no evento (fim nulo) ou no DB_PARTIDA do bundle NodeCG"},{"pedido":"melhor jogador do time vencedor quando o star player é do time perdedor","situacao":"não derivável — a API elege UM star player por partida e não há estatística individual para inferir um segundo"}],"provaMedida":{"metodo":"censo de todos os campos presentes em 858 batalhas de 35 jogadores","camposPorJogador":["tag","name","brawler{id,name,power,trophies}"],"camposPorPartida":["event{id,mode,modeId,map}","battleTime","battle{mode,type,result,duration,starPlayer,teams,trophyChange,rank,level}"]},"saidaPossivel":"Só com OCR da tela de fim de partida, que é outro projeto e outra fonte."},"x-receitas":[{"peca":"Pós-queda (recap do mapa que acabou)","chamada":"GET /api/partidas?limite=1","devolve":"mapa, modo, duração, lado vencedor, os 6 picks com tag/nick/brawler e quem foi star player","nota":"A partida só aparece DEPOIS de terminar — é o único momento em que a API publica. Para o recap de uma série inteira use ?dia= ou ?evento=."},{"peca":"Tabela geral / classificação","chamada":"GET /api/classificacao?evento=<id>","devolve":"por time: jogos, vitórias, derrotas, saldo, aproveitamento e sequência dos 5 últimos (V/D, mais recente primeiro)","nota":"Não há coluna de PONTOS de propósito: cada torneio pesa mapa e série do seu jeito. Multiplique vitorias pelo seu critério."},{"peca":"MVP / melhor da partida","chamada":"GET /api/ranking?ordem=mvps  ·  GET /api/ranking?ordem=mvpsVitoria","devolve":"mvps (star player geral), mvpsVitoria, mvpsDerrota e taxaMvp","nota":"A API elege UM star player por partida e ele PODE ser do time perdedor. Use mvps para 'quem mais brilhou' e mvpsVitoria para 'quem mais converteu'. taxaMvp compara quem jogou 40 mapas com quem jogou 8."},{"peca":"Ranking de kills / assists / damage / heals","chamada":null,"devolve":null,"nota":"IMPOSSÍVEL — ver x-indisponivel. /api/ranking devolve a lista desses campos em `indisponivel[]` junto com o motivo, para o consumidor não descobrir na marra."},{"peca":"Versus / confronto direto","chamada":"GET /api/confronto?a=<idTime>&b=<idTime>&evento=<id>","devolve":"placar do confronto (a × b) e a lista dos mapas com o vencedor de cada"},{"peca":"Card de jogador","chamada":"GET /api/jogador/%23TAG","devolve":"nick, time, partidas, vitórias, star players e os brawlers mais usados","nota":"A # da tag precisa ir como %23 na URL."},{"peca":"Veto / escolha de mapa","chamada":"GET /api/mapas?evento=<id>","devolve":"por mapa: quantas partidas e as vitórias de cada time nele"},{"peca":"Série ao vivo durante o programa","chamada":"GET /api/aovivo?evento=<id>","devolve":"os dois times da série que está rolando, o placar acumulado, cada set com o placar depois dele, e o `frescor` (quando o último mapa acabou, quando foi capturado e a latência entre os dois)","nota":"Detecta a série pelo ÚLTIMO mapa que caiu — sem palpite. Cada mapa entra alguns instantes DEPOIS de terminar: NÃO existe placar de mapa em andamento. Sempre mostre o `frescor` junto do placar, senão o overlay continua afirmando o número velho com cara de atual quando a captura trava."},{"peca":"Draft ao vivo (picks e bans em tempo real)","chamada":null,"nota":"IMPOSSÍVEL pela API. Picks só saem depois da partida; bans nunca. O draft ao vivo continua sendo entrada manual no bundle NodeCG."}],"paths":{"/health":{"get":{"tags":["serviço"],"summary":"Vivo. Aberta — é o healthcheck do container.","security":[],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"ts":{"type":"string"}}}}}}}}},"/docs.json":{"get":{"tags":["serviço"],"summary":"Esta spec.","security":[],"responses":{"200":{"description":"OpenAPI","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/brawlers":{"get":{"tags":["upstream ao vivo"],"summary":"Lista de brawlers, com slug que casa com o nome de arquivo do fankit.","description":"108 hoje; eram 106 em 19/08 — a lista muda, então não congele num manifest.","responses":{"200":{"description":"brawlers","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"}}}}}}}}}}}},"/modes":{"get":{"tags":["upstream ao vivo"],"summary":"Modos, sem os sem-nome e sem repetição.","description":"O upstream devolve 66 com 2 `name:null` e 4 nomes repetidos em ids diferentes. Aqui saem 62, e `ids[]` traz TODOS os ids daquele nome — a rotação referencia o id, não o nome.","responses":{"200":{"description":"modos","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"ids":{"type":"array","items":{"type":"integer"}}}}}}}}}}}}},"/rotation":{"get":{"tags":["upstream ao vivo"],"summary":"Rotação de eventos, com o modo resolvido por modeId.","description":"Nunca casamos modo pela string: `airHockey`(45) é BRAWL HOCKEY e `deathmatch5v5`(31) é WIPEOUT 5V5.","responses":{"200":{"description":"rotação","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"slotId":{"type":"integer"},"startTime":{"type":"string"},"endTime":{"type":"string"},"modo":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true},"slug":{"type":"string"}}},"mapa":{"type":"string","nullable":true},"mapaSlug":{"type":"string"}}}}}}}}}}}},"/players/{tag}":{"get":{"tags":["upstream ao vivo"],"summary":"Resolve uma tag no jogador. Direto do upstream, sem passar pelo banco.","parameters":[{"name":"tag","in":"path","required":true,"schema":{"type":"string"},"description":"Com # (use %23). Aceita minúscula."}],"responses":{"200":{"description":"jogador","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string"},"nome":{"type":"string"},"clube":{"type":"string","nullable":true},"icone":{"type":"integer","nullable":true}}}}}}}}},"/series":{"get":{"tags":["upstream ao vivo"],"summary":"Reconstrói uma série juntando os battlelogs dos dois elencos.","description":"Recebe os DOIS elencos separados porque o battlelog não tem 'time A': tem `teams[0]`/`teams[1]`\nem ordem arbitrária (medido: 308× × 302×) e um `result` relativo a quem foi consultado.\n\nConsulta os 6 battlelogs e deduplica, então o log atrasado de um jogador não custa o set.\n**Use sempre `desde`** — sem ele vêm as últimas 25 partidas de cada um, que raramente são só a série.","parameters":[{"name":"a","in":"query","required":true,"schema":{"type":"string"},"description":"Tags do elenco A, separadas por vírgula (# = %23)."},{"name":"b","in":"query","required":true,"schema":{"type":"string"},"description":"Tags do elenco B."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"ISO 8601. Recorta o início da série."},{"name":"tipo","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"série","content":{"application/json":{"schema":{"type":"object","properties":{"a":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}},"b":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}},"placar":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"}}},"sets":{"type":"array","items":{"type":"object","properties":{"set":{"type":"integer"},"battleTime":{"type":"string"},"tipo":{"type":"string","nullable":true},"modo":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true},"slug":{"type":"string"}}},"mapa":{"type":"string","nullable":true},"mapaSlug":{"type":"string"},"duracao":{"type":"integer","nullable":true},"vencedor":{"type":"string","enum":["a","b"],"nullable":true},"mvp":{"type":"object","properties":{"tag":{"type":"string"},"nome":{"type":"string"},"brawler":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"}}},"lado":{"type":"string"}}},"picks":{"type":"object","properties":{"a":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"nome":{"type":"string"},"brawler":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"}}}}}},"b":{"type":"array","items":{"type":"object","properties":{}}}}},"fontes":{"type":"array","items":{"type":"string"}}}}},"ignoradas":{"type":"integer"},"falhas":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"erro":{"type":"string"}}}}}}}}}}}},"/api/classificacao":{"get":{"tags":["histórico"],"summary":"A TABELA. Ordenada por vitórias, depois saldo, depois sigla.","description":"Sem coluna de pontos de propósito — cada torneio pesa mapa e série do seu jeito. Só entra confronto entre dois times cadastrados, com vencedor, e com `contabiliza = 1`.","parameters":[{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."},{"name":"formato","in":"query","schema":{"type":"string","enum":["csv"]},"description":"csv devolve text/csv com BOM e separador ';' (abre no Excel PT-BR)."}],"responses":{"200":{"description":"tabela","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"sigla":{"type":"string"},"nome":{"type":"string"},"jogos":{"type":"integer"},"vitorias":{"type":"integer"},"derrotas":{"type":"integer"},"saldo":{"type":"integer"},"aproveitamento":{"type":"integer","nullable":true},"sequencia":{"type":"string","description":"Até 5 caracteres V/D, mais recente primeiro."}}}},"janela":{"type":"object","properties":{}}}}}}}}}},"/api/ranking":{"get":{"tags":["histórico"],"summary":"Ranking de jogadores. Star player em três recortes.","description":"`indisponivel[]` na resposta lista o que a API não publica (kills, dano, cura, assistências…) com o motivo.","parameters":[{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."},{"name":"formato","in":"query","schema":{"type":"string","enum":["csv"]},"description":"csv devolve text/csv com BOM e separador ';' (abre no Excel PT-BR)."},{"name":"ordem","in":"query","schema":{"type":"string","enum":["mvps","mvpsVitoria","vitorias","partidas","aproveitamento"],"default":"mvps"}},{"name":"time","in":"query","schema":{"type":"integer"},"description":"Só os jogadores deste time."}],"responses":{"200":{"description":"ranking","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"nick":{"type":"string"},"time_sigla":{"type":"string","nullable":true},"partidas":{"type":"integer"},"vitorias":{"type":"integer"},"derrotas":{"type":"integer"},"aproveitamento":{"type":"integer","nullable":true},"mvps":{"type":"integer","description":"Star player geral — inclui as vezes em que o time dele perdeu."},"mvpsVitoria":{"type":"integer","description":"Star player com o time vencendo."},"mvpsDerrota":{"type":"integer","description":"Star player carregando time que caiu."},"taxaMvp":{"type":"integer","nullable":true,"description":"mvps / partidas, em %."},"brawlers":{"type":"integer","description":"Quantos brawlers distintos ele usou."}}}},"indisponivel":{"type":"array","items":{"type":"string"}},"motivo":{"type":"string"}}}}}}}}},"/api/mapas":{"get":{"tags":["histórico"],"summary":"Aproveitamento por mapa, por time.","parameters":[{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."},{"name":"formato","in":"query","schema":{"type":"string","enum":["csv"]},"description":"csv devolve text/csv com BOM e separador ';' (abre no Excel PT-BR)."}],"responses":{"200":{"description":"mapas","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"modo":{"type":"string","nullable":true},"mapa":{"type":"string","nullable":true},"partidas":{"type":"integer"},"vitorias":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string"},"vitorias":{"type":"integer"}}}}}}}}}}}}}}},"/api/partidas":{"get":{"tags":["histórico"],"summary":"Histórico de partidas. É a fonte do pós-queda.","description":"`time0`/`time1` são as siglas atribuídas a cada lado, ou null quando o lado não é de time cadastrado (treino contra desconhecido continua no banco).","parameters":[{"name":"dia","in":"query","schema":{"type":"string"},"description":"AAAA-MM-DD."},{"name":"time","in":"query","schema":{"type":"integer"}},{"name":"limite","in":"query","schema":{"type":"integer","default":100,"maximum":500}},{"name":"formato","in":"query","schema":{"type":"string","enum":["csv"]},"description":"csv devolve text/csv com BOM e separador ';' (abre no Excel PT-BR)."}],"responses":{"200":{"description":"partidas","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"battle_time":{"type":"string"},"dia":{"type":"string"},"tipo":{"type":"string","nullable":true},"modo":{"type":"string","nullable":true},"mapa":{"type":"string","nullable":true},"duracao":{"type":"integer","nullable":true},"vencedor_idx":{"type":"integer","nullable":true,"description":"0, 1 ou null (empate). Refere-se ao lado, não ao time."},"contabiliza":{"type":"integer","description":"1 conta nas estatísticas, 0 fica só no histórico."},"time0":{"type":"string","nullable":true},"time1":{"type":"string","nullable":true},"picks":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"nick":{"type":"string","nullable":true},"idx":{"type":"integer"},"brawler":{"type":"string","nullable":true},"mvp":{"type":"integer"}}}}}}}}}}}}}}},"/api/confronto":{"get":{"tags":["histórico"],"summary":"Confronto direto entre dois times.","parameters":[{"name":"a","in":"query","required":true,"schema":{"type":"integer"}},{"name":"b","in":"query","required":true,"schema":{"type":"integer"}},{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."}],"responses":{"200":{"description":"h2h","content":{"application/json":{"schema":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"},"partidas":{"type":"integer"},"mapas":{"type":"array","items":{"type":"object","properties":{"battle_time":{"type":"string"},"modo":{"type":"string","nullable":true},"mapa":{"type":"string","nullable":true},"vencedor":{"type":"string"}}}}}}}}}}}},"/api/jogador/{tag}":{"get":{"tags":["histórico"],"summary":"Ficha do jogador (card de transmissão).","parameters":[{"name":"tag","in":"path","required":true,"schema":{"type":"string"},"description":"Com # como %23."}],"responses":{"200":{"description":"ficha","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string"},"nick":{"type":"string"},"nick_api":{"type":"string","nullable":true},"time_sigla":{"type":"string","nullable":true},"time_nome":{"type":"string","nullable":true},"partidas":{"type":"integer"},"vitorias":{"type":"integer"},"mvps":{"type":"integer"},"brawlers":{"type":"array","items":{"type":"object","properties":{"brawler":{"type":"string"},"vezes":{"type":"integer"},"vitorias":{"type":"integer"}}}}}}}}}}}},"/api/geral":{"get":{"tags":["histórico"],"summary":"Resumo + classificação + por dia + por brawler, numa chamada.","parameters":[{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."}],"responses":{"200":{"description":"geral","content":{"application/json":{"schema":{"type":"object","properties":{"resumo":{"type":"object","properties":{}},"times":{"type":"array","items":{"type":"object","properties":{}}},"dias":{"type":"array","items":{"type":"object","properties":{"dia":{"type":"string"},"partidas":{"type":"integer"},"amistosos":{"type":"integer"},"excluidas":{"type":"integer"},"mapas":{"type":"integer"},"primeira":{"type":"string"},"ultima":{"type":"string"}}}},"brawlers":{"type":"array","items":{"type":"object","properties":{}}}}}}}}}}},"/api/aovivo":{"get":{"tags":["histórico"],"summary":"Estado da série que está rolando agora, com frescor.","description":"A série é detectada pelo **último mapa que caiu** entre dois times cadastrados — não há palpite.\n\n`serie` vem `null` quando não há confronto nenhum na janela. É de propósito: devolver 0×0\nfaria o overlay anunciar uma série que não começou.\n\n⚠️ **Não é placar de mapa em andamento.** A API só publica batalha terminada. Este endpoint\nmostra a série se atualizando **a cada mapa que termina**, e o `frescor` diz de quando é o número.","parameters":[{"name":"evento","in":"query","schema":{"type":"integer"},"description":"Recorta pela janela do evento. Atalho para desde/ate/tipo — não combine com eles."},{"name":"desde","in":"query","schema":{"type":"string"},"description":"Data inicial, AAAA-MM-DD (inclusive)."},{"name":"ate","in":"query","schema":{"type":"string"},"description":"Data final, AAAA-MM-DD (inclusive)."},{"name":"tipo","in":"query","schema":{"type":"string"},"description":"friendly (torneio/scrim) · ranked · soloRanked."}],"responses":{"200":{"description":"ao vivo","content":{"application/json":{"schema":{"type":"object","properties":{"serie":{"type":"object","properties":{"a":{"type":"object","properties":{"id":{"type":"integer"},"sigla":{"type":"string"},"nome":{"type":"string"}}},"b":{"type":"object","properties":{"id":{"type":"integer"},"sigla":{"type":"string"},"nome":{"type":"string"}}},"placar":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"}}},"sets":{"type":"array","items":{"type":"object","properties":{"set":{"type":"integer"},"battleTime":{"type":"string"},"modo":{"type":"string","nullable":true},"mapa":{"type":"string","nullable":true},"duracao":{"type":"integer","nullable":true},"vencedor":{"type":"string","enum":["a","b"]},"placarApos":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"}}}}}}}},"frescor":{"type":"object","properties":{"ultimoMapaEm":{"type":"string","description":"Quando o último mapa TERMINOU."},"capturadoEm":{"type":"string","description":"Quando o worker gravou."},"latenciaSegundos":{"type":"integer","nullable":true,"description":"Diferença entre os dois — a latência real da fonte, medida."}}},"confrontos":{"type":"integer"}}}}}}}}},"/api/latencia":{"get":{"tags":["histórico"],"summary":"Quanto tempo a fonte leva entre a partida acabar e aparecer.","description":"Média/mín/máx de `capturado_em − battle_time`. Amostras acima do teto são descartadas: a primeira carga de um jogador novo traz 25 partidas velhas de uma vez e inflaria tudo. Serve para decidir o intervalo do worker.","responses":{"200":{"description":"latência","content":{"application/json":{"schema":{"type":"object","properties":{"amostras":{"type":"integer"},"media":{"type":"integer","nullable":true},"minimo":{"type":"number","nullable":true},"maximo":{"type":"number","nullable":true},"tetoSegundos":{"type":"integer"},"nota":{"type":"string"}}}}}}}}},"/api/cadastro":{"get":{"tags":["cadastro"],"summary":"Times e jogadores.","responses":{"200":{"description":"cadastro","content":{"application/json":{"schema":{"type":"object","properties":{"times":{"type":"array","items":{"type":"object","properties":{}}},"jogadores":{"type":"array","items":{"type":"object","properties":{}}}}}}}}}}},"/api/time":{"post":{"tags":["cadastro"],"summary":"Cria ou atualiza time. Exige sessão.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"sigla":{"type":"string"},"nome":{"type":"string"}}}}}},"responses":{"200":{"description":"id","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"}}}}}}}}},"/api/time/{id}":{"delete":{"tags":["cadastro"],"summary":"Remove o time. NÃO apaga jogadores nem histórico — só a atribuição.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/jogador":{"post":{"tags":["cadastro"],"summary":"Cadastra jogador pela TAG. Exige sessão.","description":"A tag é a única chave da API — não existe busca por nome. Alfabeto real medido: `0289CGJLPQRUVY`, 3 a 10 caracteres. Não existem O, I, S, B nem 1.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string"},"nick":{"type":"string"},"time_id":{"type":"integer","nullable":true},"ativo":{"type":"boolean"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string"}}}}}},"400":{"description":"tag fora do alfabeto do Brawl Stars"}}}},"/api/jogador/{tag}/":{"delete":{"tags":["cadastro"],"summary":"Remove o jogador.","parameters":[{"name":"tag","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/eventos":{"get":{"tags":["config"],"summary":"Janelas nomeadas.","responses":{"200":{"description":"eventos","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"nome":{"type":"string"},"inicio":{"type":"string"},"fim":{"type":"string","nullable":true},"tipo":{"type":"string","nullable":true}}}}}}}}}}}},"/api/evento":{"post":{"tags":["config"],"summary":"Cria ou atualiza um evento. Exige sessão.","description":"A partida NÃO guarda a que evento pertence — o vínculo sai do instante dela, na leitura. Corrigir a data reclassifica o histórico inteiro na hora, e partida capturada antes do evento existir entra sozinha quando ele for criado. `fim` vazio = evento aberto.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"nome":{"type":"string"},"inicio":{"type":"string"},"fim":{"type":"string","nullable":true},"tipo":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"id","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"}}}}}}}}},"/api/evento/{id}":{"delete":{"tags":["config"],"summary":"Remove o recorte. Nenhuma partida é tocada.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/contabilizar":{"post":{"tags":["config"],"summary":"Liga/desliga a contabilidade de partidas. Exige sessão.","description":"A regra automática não distingue jogo oficial de aquecimento: o reserva do time A esquentando contra o reserva do B passa em toda atribuição e mexe no placar sozinho. Excluída, a partida sai da classificação, do ranking, do pick rate e da ficha — mas CONTINUA no histórico, porque a API não devolve batalha antiga de novo.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"integer"}},"contabiliza":{"type":"boolean"}}}}}},"responses":{"200":{"description":"alteradas","content":{"application/json":{"schema":{"type":"object","properties":{"alteradas":{"type":"integer"}}}}}}}}},"/api/worker":{"get":{"tags":["config"],"summary":"Estado do laço de captura.","responses":{"200":{"description":"worker","content":{"application/json":{"schema":{"type":"object","properties":{"ligado":{"type":"boolean"},"intervalo":{"type":"integer"},"rodando":{"type":"boolean"},"ultimaVolta":{"type":"string","nullable":true},"ultimoErro":{"type":"string","nullable":true},"novasNaUltima":{"type":"integer"},"totalNovas":{"type":"integer"},"voltas":{"type":"integer"}}}}}}}}},"/api/worker/config":{"post":{"tags":["config"],"summary":"Liga/desliga a captura e ajusta o intervalo, sem redeploy. Exige sessão.","description":"Grava no banco. A env var (`WORKER`, `WORKER_INTERVALO`) passa a ser só o valor **inicial**:\nsemeia na primeira subida e nunca mais sobrescreve, senão um restart desfaria o que foi\nmudado aqui. Vale a partir da próxima volta — o ciclo relê o banco a cada iteração.\n\nIntervalo mínimo 30s. Segredos (`BRAWLSTARS_TOKEN`, `SERVICE_KEY`, `ACCESS_PASSWORD`)\n**não** são configuráveis por aqui: no banco, o painel viraria um cofre de credenciais\nprotegido pela própria senha que ele guarda.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"ligado":{"type":"boolean"},"intervalo":{"type":"integer"}}}}}},"responses":{"200":{"description":"config gravada","content":{"application/json":{"schema":{"type":"object","properties":{"ligado":{"type":"boolean"},"intervalo":{"type":"integer"}}}}}}}}},"/api/worker/volta":{"post":{"tags":["config"],"summary":"Força uma volta agora. Exige sessão.","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}}},"components":{"securitySchemes":{"chave":{"type":"apiKey","in":"header","name":"x-api-key","description":"SERVICE_KEY. Só LÊ."},"sessao":{"type":"apiKey","in":"cookie","name":"sess","description":"Cookie do dashboard (POST /login). Necessário para ESCREVER."}}},"security":[{"chave":[]},{"sessao":[]}],"tags":[{"name":"upstream ao vivo","description":"Vai direto na API da Supercell, não passa pelo banco."},{"name":"histórico","description":"Sai do banco local. Aceita recorte por evento ou por data, e ?formato=csv."},{"name":"cadastro","description":"Times e jogadores. Escrita exige sessão."},{"name":"config","description":"Eventos, contabilidade e worker."},{"name":"serviço","description":"Aberto, sem autenticação."}]}