{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública do Biscoiteiro",
    "summary": "O ranking do Biscoiteiro em JSON. Só leitura.",
    "description": "O Biscoiteiro é um ranking brasileiro de sites onde quem paga mais fica no topo; cada lance derrete até zero em 7 dias. Esta API entrega o mesmo que o site mostra: o ranking de agora, os dados de cada link, a fornada congelada de cada dia e o Hall da Fama.\n\nTudo é somente leitura (`GET`) e responde JSON em UTF-8. Toda resposta de sucesso vem no envelope `{ dados, gerado_em }`; todo erro vem como `{ erro: { codigo, mensagem } }`. CORS é aberto: dá para chamar direto do navegador.\n\nConvenções: campos em snake_case e em português; datas em ISO 8601 UTC; dinheiro em centavos inteiros, arredondado para baixo (`85714` = R$ 857,14); durações em segundos inteiros. Só aparecem links ativos e só texto que já passou pela moderação.\n\nEstabilidade: a v1 pode ganhar campos novos sem aviso, então ignore o que você não conhece. Nenhum campo existente muda de nome, muda de tipo ou some; isso só numa v2.",
    "version": "1.0.0",
    "termsOfService": "https://biscoiteiro.com.br/termos",
    "contact": {
      "name": "Biscoiteiro",
      "url": "https://biscoiteiro.com.br/docs/api",
      "email": "oi@biscoiteiro.com.br"
    }
  },
  "servers": [
    {
      "url": "https://biscoiteiro.com.br",
      "description": "Produção"
    }
  ],
  "externalDocs": {
    "description": "Como o ranking funciona: lances, decaimento de 7 dias e desempate",
    "url": "https://biscoiteiro.com.br/como-funciona"
  },
  "tags": [
    { "name": "Ranking", "description": "O ranking de agora e as fornadas fechadas de cada dia." },
    { "name": "Links", "description": "Dados públicos, posição e estatísticas de um link." },
    { "name": "Hall da Fama", "description": "Todo mundo que já foi Biscoiteiro-mor, e os recordes." },
    { "name": "Selo", "description": "Imagem SVG para o dono do link colocar no próprio site." }
  ],
  "security": [{}, { "ChaveBearer": [] }, { "ChaveNoCabecalho": [] }],
  "x-limites": {
    "com_chave": { "requisicoes": 60, "janela_segundos": 60, "por": "chave" },
    "sem_chave": { "requisicoes": 10, "janela_segundos": 60, "por": "IP" }
  },
  "paths": {
    "/api/v1/ranking": {
      "get": {
        "tags": ["Ranking"],
        "operationId": "obterRanking",
        "summary": "Ranking atual",
        "description": "A fornada de agora, do #1 para baixo: posição, valor efetivo, quando cada link zera e quanto custa passar cada um. O valor efetivo derrete o tempo todo, então a resposta sai de um cache de 5 segundos; `gerado_em` diz de quando é a foto.",
        "parameters": [{ "$ref": "#/components/parameters/Limite" }],
        "responses": {
          "200": {
            "description": "O ranking atual.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RespostaRanking" },
                "example": {
                  "dados": {
                    "total": 42,
                    "reinando_desde": "2026-09-17T13:05:00.000Z",
                    "itens": [
                      {
                        "posicao": 1,
                        "slug": "sualoja-com-br",
                        "titulo": "Sua Loja",
                        "descricao": "Camisetas que não desbotam, entrega pra todo o Brasil.",
                        "dominio": "sualoja.com.br",
                        "url": "https://sualoja.com.br",
                        "instagram": "sualoja",
                        "favicon_url": "https://biscoiteiro.com.br/imagens/favicons/0b9d2f6e-6c1d-4a63-9f39-5d2a0c1e7a11.png",
                        "valor_efetivo_centavos": 85714,
                        "zera_em": "2026-09-24T13:05:00.000Z",
                        "ultimo_lance_em": "2026-09-17T13:05:00.000Z",
                        "custo_para_passar_centavos": 85900,
                        "cliques": 312,
                        "pagina_url": "https://biscoiteiro.com.br/p/sualoja-com-br"
                      },
                      {
                        "posicao": 2,
                        "slug": "instagram-fulano",
                        "titulo": "@fulano",
                        "descricao": null,
                        "dominio": "instagram.com",
                        "url": "https://instagram.com/fulano",
                        "instagram": "fulano",
                        "favicon_url": null,
                        "valor_efetivo_centavos": 20100,
                        "zera_em": "2026-09-25T09:40:12.000Z",
                        "ultimo_lance_em": "2026-09-18T09:40:12.000Z",
                        "custo_para_passar_centavos": 20200,
                        "cliques": 57,
                        "pagina_url": "https://biscoiteiro.com.br/p/instagram-fulano"
                      }
                    ]
                  },
                  "gerado_em": "2026-09-18T15:00:02.000Z"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/ChaveRecusada" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/ranking/{data}": {
      "get": {
        "tags": ["Ranking"],
        "operationId": "obterRankingDoDia",
        "summary": "Ranking congelado de um dia",
        "description": "A fornada fechada de um dia (o mesmo conteúdo de `/ranking/AAAA-MM-DD` no site e do carrossel do Instagram), com os números do dia. A data é a de Brasília. Link que saiu do ar depois some da lista, e as posições não são renumeradas: o buraco fica, inclusive no #1 (a lista pode começar no 2).",
        "parameters": [{ "$ref": "#/components/parameters/Data" }],
        "responses": {
          "200": {
            "description": "A fornada daquele dia.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RespostaRankingDoDia" },
                "example": {
                  "dados": {
                    "data": "2026-09-17",
                    "fechada_em": "2026-09-17T23:00:04.000Z",
                    "pagina_url": "https://biscoiteiro.com.br/ranking/2026-09-17",
                    "itens": [
                      {
                        "posicao": 1,
                        "slug": "sualoja-com-br",
                        "titulo": "Sua Loja",
                        "dominio": "sualoja.com.br",
                        "url": "https://sualoja.com.br",
                        "instagram": "sualoja",
                        "favicon_url": "https://biscoiteiro.com.br/imagens/favicons/0b9d2f6e-6c1d-4a63-9f39-5d2a0c1e7a11.png",
                        "valor_efetivo_centavos": 94100,
                        "pagina_url": "https://biscoiteiro.com.br/p/sualoja-com-br"
                      },
                      {
                        "posicao": 2,
                        "slug": "meusaas-com-br",
                        "titulo": "MeuSaaS",
                        "dominio": "meusaas.com.br",
                        "url": "https://meusaas.com.br",
                        "instagram": null,
                        "favicon_url": null,
                        "valor_efetivo_centavos": 31250,
                        "pagina_url": "https://biscoiteiro.com.br/p/meusaas-com-br"
                      }
                    ],
                    "numeros": {
                      "lances_no_dia": 18,
                      "trocas_de_trono": 3,
                      "maior_lance_centavos": 100000,
                      "maior_subida": { "slug": "meusaas-com-br", "titulo": "MeuSaaS", "posicoes": 9 },
                      "reinou_hoje_segundos": 35700
                    }
                  },
                  "gerado_em": "2026-09-18T15:00:02.000Z"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/ChaveRecusada" },
          "404": { "$ref": "#/components/responses/FornadaNaoEncontrada" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/links/{slug}": {
      "get": {
        "tags": ["Links"],
        "operationId": "obterLink",
        "summary": "Dados, posição e estatísticas de um link",
        "description": "Tudo que a página `/p/{slug}` mostra: os dados públicos do link, a posição e o valor efetivo de agora, e as estatísticas históricas. Link que já zerou continua respondendo, com `no_ranking: false`. Link pendente de moderação, removido ou bloqueado responde 404, igual a link que nunca existiu.",
        "parameters": [{ "$ref": "#/components/parameters/Slug" }],
        "responses": {
          "200": {
            "description": "O link.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RespostaLink" },
                "example": {
                  "dados": {
                    "slug": "sualoja-com-br",
                    "titulo": "Sua Loja",
                    "descricao": "Camisetas que não desbotam, entrega pra todo o Brasil.",
                    "dominio": "sualoja.com.br",
                    "url": "https://sualoja.com.br",
                    "instagram": "sualoja",
                    "favicon_url": "https://biscoiteiro.com.br/imagens/favicons/0b9d2f6e-6c1d-4a63-9f39-5d2a0c1e7a11.png",
                    "pagina_url": "https://biscoiteiro.com.br/p/sualoja-com-br",
                    "selo_url": "https://biscoiteiro.com.br/selo/sualoja-com-br.svg",
                    "criado_em": "2026-09-02T18:22:41.000Z",
                    "cliques": 312,
                    "no_ranking": true,
                    "posicao": 1,
                    "valor_efetivo_centavos": 85714,
                    "zera_em": "2026-09-24T13:05:00.000Z",
                    "custo_para_passar_centavos": 85900,
                    "estatisticas": {
                      "numero_de_reinados": 4,
                      "tempo_total_no_trono_segundos": 412380,
                      "maior_reinado_continuo_segundos": 187200,
                      "dias_no_trono": 6,
                      "melhor_posicao": 1,
                      "maior_lance_centavos": 100000,
                      "total_pago_centavos": 345000,
                      "numero_de_lances": 7,
                      "primeiro_lance_em": "2026-09-02T18:25:10.000Z"
                    }
                  },
                  "gerado_em": "2026-09-18T15:00:02.000Z"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/ChaveRecusada" },
          "404": { "$ref": "#/components/responses/LinkNaoEncontrado" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/hall-da-fama": {
      "get": {
        "tags": ["Hall da Fama"],
        "operationId": "obterHallDaFama",
        "summary": "Reinados e recordes",
        "description": "Todos os links que já foram #1, ordenados por tempo total no trono, e os três recordes da casa: maior reinado contínuo, maior lance e mais reinados. O reinado em andamento conta até agora. Recorde cujo dono saiu do ar vem `null`.",
        "responses": {
          "200": {
            "description": "O Hall da Fama.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RespostaHallDaFama" },
                "example": {
                  "dados": {
                    "total": 2,
                    "itens": [
                      {
                        "slug": "sualoja-com-br",
                        "titulo": "Sua Loja",
                        "dominio": "sualoja.com.br",
                        "favicon_url": "https://biscoiteiro.com.br/imagens/favicons/0b9d2f6e-6c1d-4a63-9f39-5d2a0c1e7a11.png",
                        "numero_de_reinados": 4,
                        "tempo_total_no_trono_segundos": 412380,
                        "maior_reinado_continuo_segundos": 187200,
                        "maior_lance_centavos": 100000,
                        "reinando_agora": true,
                        "pagina_url": "https://biscoiteiro.com.br/p/sualoja-com-br"
                      },
                      {
                        "slug": "meusaas-com-br",
                        "titulo": "MeuSaaS",
                        "dominio": "meusaas.com.br",
                        "favicon_url": null,
                        "numero_de_reinados": 6,
                        "tempo_total_no_trono_segundos": 96400,
                        "maior_reinado_continuo_segundos": 40120,
                        "maior_lance_centavos": 150000,
                        "reinando_agora": false,
                        "pagina_url": "https://biscoiteiro.com.br/p/meusaas-com-br"
                      }
                    ],
                    "recordes": {
                      "maior_reinado": { "slug": "sualoja-com-br", "titulo": "Sua Loja", "duracao_segundos": 187200 },
                      "maior_lance": { "slug": "meusaas-com-br", "titulo": "MeuSaaS", "valor_centavos": 150000 },
                      "mais_reinados": { "slug": "meusaas-com-br", "titulo": "MeuSaaS", "quantidade": 6 }
                    }
                  },
                  "gerado_em": "2026-09-18T15:00:02.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/ChaveRecusada" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/selo/{slug}.svg": {
      "get": {
        "tags": ["Selo"],
        "operationId": "obterSelo",
        "summary": "Selo \"Estou no #N do Biscoiteiro\"",
        "description": "Imagem SVG de 200×56 (vetorial: fica nítida em 2x e em qualquer tamanho) para o dono do link colocar no próprio site, dentro de um link para a página dele no Biscoiteiro. Com o link no ranking o selo diz \"Estou no #N do Biscoiteiro\"; depois que o link zera, \"Já estive no Biscoiteiro\". Não pede chave e não conta no limite da API; a imagem fica em cache por 5 minutos, então a posição pode demorar esse tanto para mudar.",
        "security": [],
        "parameters": [{ "$ref": "#/components/parameters/Slug" }, { "$ref": "#/components/parameters/Tema" }],
        "responses": {
          "200": {
            "description": "O selo em SVG.",
            "headers": {
              "Cache-Control": {
                "description": "Sempre `public, max-age=300`.",
                "schema": { "type": "string" },
                "example": "public, max-age=300"
              }
            },
            "content": {
              "image/svg+xml": {
                "schema": { "type": "string" },
                "example": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"56\" viewBox=\"0 0 200 56\" role=\"img\" aria-label=\"Estou no #3 do Biscoiteiro\">…</svg>"
              }
            }
          },
          "404": {
            "description": "Link inexistente ou fora do ar, ou caminho sem o sufixo `.svg`.",
            "content": {
              "text/plain": {
                "schema": { "type": "string" },
                "example": "Selo não encontrado."
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ChaveBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bsc_<token>",
        "description": "Chave de API no cabeçalho `Authorization: Bearer bsc_...`. É a forma recomendada."
      },
      "ChaveNoCabecalho": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A mesma chave no cabeçalho `X-API-Key: bsc_...`, para clientes que não deixam mexer no `Authorization`. Se os dois vierem, vale o `Authorization`."
      }
    },
    "parameters": {
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "Identificador do link: o mesmo da URL `/p/{slug}` no site.",
        "schema": { "$ref": "#/components/schemas/Slug" },
        "example": "sualoja-com-br"
      },
      "Data": {
        "name": "data",
        "in": "path",
        "required": true,
        "description": "Dia da fornada, no calendário de Brasília, no formato `AAAA-MM-DD`.",
        "schema": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
        "example": "2026-09-17"
      },
      "Limite": {
        "name": "limite",
        "in": "query",
        "required": false,
        "description": "Quantos links devolver, a partir do #1. `total` sempre diz quantos existem no ranking.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 },
        "example": 10
      },
      "Tema": {
        "name": "tema",
        "in": "query",
        "required": false,
        "description": "`escuro` troca o fundo para chocolate, para sites de fundo escuro. Qualquer outro valor cai no `claro`.",
        "schema": { "type": "string", "enum": ["claro", "escuro"], "default": "claro" },
        "example": "escuro"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Quantas requisições cabem na janela de um minuto: 60 com chave, 10 sem.",
        "schema": { "type": "integer" },
        "example": 60
      },
      "X-RateLimit-Remaining": {
        "description": "Quantas ainda cabem na janela atual, já contando esta.",
        "schema": { "type": "integer" },
        "example": 57
      },
      "X-RateLimit-Reset": {
        "description": "Em quantos segundos a janela atual vira e a contagem zera.",
        "schema": { "type": "integer" },
        "example": 41
      },
      "Retry-After": {
        "description": "Só no 429: em quantos segundos dá para tentar de novo.",
        "schema": { "type": "integer" },
        "example": 41
      },
      "WWW-Authenticate": {
        "description": "Só no 401: o esquema de autenticação esperado.",
        "schema": { "type": "string" },
        "example": "Bearer realm=\"Biscoiteiro API\""
      }
    },
    "responses": {
      "ParametroInvalido": {
        "description": "Parâmetro de rota ou de query fora do formato. A mensagem diz qual e por quê.",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "example": { "erro": { "codigo": "parametro_invalido", "mensagem": "data: use o formato AAAA-MM-DD" } }
          }
        }
      },
      "ChaveRecusada": {
        "description": "Veio uma chave e ela não vale: formato errado, desconhecida ou revogada. A API não cai para o limite anônimo em silêncio; para usar sem chave, não mande o cabeçalho.",
        "headers": {
          "WWW-Authenticate": { "$ref": "#/components/headers/WWW-Authenticate" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "examples": {
              "chave_invalida": {
                "summary": "Chave desconhecida ou malformada",
                "value": {
                  "erro": {
                    "codigo": "chave_invalida",
                    "mensagem": "Chave de API inválida. Mande `Authorization: Bearer bsc_...` ou `X-API-Key: bsc_...`, ou tire o cabeçalho para usar o limite anônimo."
                  }
                }
              },
              "chave_revogada": {
                "summary": "Chave revogada no painel",
                "value": { "erro": { "codigo": "chave_revogada", "mensagem": "Essa chave foi revogada. Gera outra no painel da sua conta." } }
              }
            }
          }
        }
      },
      "LinkNaoEncontrado": {
        "description": "Não existe link ativo com esse slug.",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "example": { "erro": { "codigo": "link_nao_encontrado", "mensagem": "Esse link não existe no Biscoiteiro ou não está no ar." } }
          }
        }
      },
      "FornadaNaoEncontrada": {
        "description": "Não há fornada fechada nessa data: o dia ainda não fechou ou é anterior ao lançamento.",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "example": {
              "erro": { "codigo": "fornada_nao_encontrada", "mensagem": "Não tem fornada fechada em 2026-09-30. O dia pode não ter fechado ainda." }
            }
          }
        }
      },
      "LimiteExcedido": {
        "description": "Estourou o limite da janela de um minuto. Espere o `Retry-After` e tente de novo.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" },
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "example": {
              "erro": {
                "codigo": "limite_excedido",
                "mensagem": "Calma, biscoiteiro: o limite é de 10 requisições por minuto. Tenta de novo em 41s."
              }
            }
          }
        }
      },
      "ErroInterno": {
        "description": "Falha do nosso lado. Pode tentar de novo.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erro" },
            "example": { "erro": { "codigo": "erro_interno", "mensagem": "Deu ruim do nosso lado. Tenta de novo daqui a pouco." } }
          }
        }
      }
    },
    "schemas": {
      "Instante": {
        "type": "string",
        "format": "date-time",
        "description": "Data e hora em ISO 8601, sempre em UTC.",
        "examples": ["2026-09-18T15:00:02.000Z"]
      },
      "Centavos": {
        "type": "integer",
        "minimum": 0,
        "description": "Dinheiro em centavos inteiros de real, arredondado para baixo.",
        "examples": [85714]
      },
      "Segundos": {
        "type": "integer",
        "minimum": 0,
        "description": "Duração em segundos inteiros."
      },
      "Slug": {
        "type": "string",
        "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
        "maxLength": 120,
        "description": "Identificador público do link.",
        "examples": ["sualoja-com-br"]
      },
      "RespostaRanking": {
        "type": "object",
        "required": ["dados", "gerado_em"],
        "properties": {
          "dados": { "$ref": "#/components/schemas/Ranking" },
          "gerado_em": { "$ref": "#/components/schemas/Instante", "description": "De quando é a foto do ranking (cache de 5 segundos)." }
        }
      },
      "Ranking": {
        "type": "object",
        "description": "O ranking de agora.",
        "required": ["total", "reinando_desde", "itens"],
        "properties": {
          "total": { "type": "integer", "minimum": 0, "description": "Quantos links estão no ranking agora, independente de `limite`." },
          "reinando_desde": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Desde quando o #1 atual está no trono. `null` com o ranking vazio."
          },
          "itens": { "type": "array", "items": { "$ref": "#/components/schemas/ItemDoRanking" }, "description": "Do #1 para baixo." }
        }
      },
      "ItemDoRanking": {
        "type": "object",
        "required": [
          "posicao",
          "slug",
          "titulo",
          "descricao",
          "dominio",
          "url",
          "instagram",
          "favicon_url",
          "valor_efetivo_centavos",
          "zera_em",
          "ultimo_lance_em",
          "custo_para_passar_centavos",
          "cliques",
          "pagina_url"
        ],
        "properties": {
          "posicao": { "type": "integer", "minimum": 1, "description": "1 é o Biscoiteiro-mor." },
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "descricao": { "type": ["string", "null"], "description": "Descrição moderada, quando existe." },
          "dominio": { "type": "string", "description": "Domínio sem `www`." },
          "url": { "type": "string", "format": "uri", "description": "Endereço normalizado do site." },
          "instagram": { "type": ["string", "null"], "description": "Usuário do Instagram, sem @." },
          "favicon_url": { "type": ["string", "null"], "format": "uri", "description": "Favicon guardado pelo Biscoiteiro (PNG 64×64)." },
          "valor_efetivo_centavos": { "$ref": "#/components/schemas/Centavos", "description": "Soma dos lances vivos, já derretidos até `gerado_em`." },
          "zera_em": { "$ref": "#/components/schemas/Instante", "description": "Quando o link sai do ranking se ninguém reforçar." },
          "ultimo_lance_em": { "$ref": "#/components/schemas/Instante", "description": "Confirmação do lance mais recente." },
          "custo_para_passar_centavos": {
            "$ref": "#/components/schemas/Centavos",
            "description": "Quanto um link novo paga agora para ficar logo acima deste: R$ 1 acima do valor efetivo, no mínimo R$ 10."
          },
          "cliques": { "type": "integer", "minimum": 0, "description": "Cliques de saída contados pelo Biscoiteiro (no máximo 1 por IP a cada 24 h)." },
          "pagina_url": { "type": "string", "format": "uri", "description": "Página do link no Biscoiteiro." }
        }
      },
      "RespostaLink": {
        "type": "object",
        "required": ["dados", "gerado_em"],
        "properties": {
          "dados": { "$ref": "#/components/schemas/Link" },
          "gerado_em": { "$ref": "#/components/schemas/Instante" }
        }
      },
      "Link": {
        "type": "object",
        "description": "Dados públicos de um link, com a situação dele no ranking de agora.",
        "required": [
          "slug",
          "titulo",
          "descricao",
          "dominio",
          "url",
          "instagram",
          "favicon_url",
          "pagina_url",
          "selo_url",
          "criado_em",
          "cliques",
          "no_ranking",
          "posicao",
          "valor_efetivo_centavos",
          "zera_em",
          "custo_para_passar_centavos",
          "estatisticas"
        ],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "descricao": { "type": ["string", "null"], "description": "Descrição moderada, quando existe." },
          "dominio": { "type": "string", "description": "Domínio sem `www`." },
          "url": { "type": "string", "format": "uri", "description": "Endereço normalizado do site." },
          "instagram": { "type": ["string", "null"], "description": "Usuário do Instagram, sem @." },
          "favicon_url": { "type": ["string", "null"], "format": "uri", "description": "Favicon guardado pelo Biscoiteiro (PNG 64×64)." },
          "pagina_url": { "type": "string", "format": "uri", "description": "Página do link no Biscoiteiro." },
          "selo_url": { "type": "string", "format": "uri", "description": "Selo SVG deste link." },
          "criado_em": { "$ref": "#/components/schemas/Instante", "description": "Quando o link entrou no Biscoiteiro." },
          "cliques": { "type": "integer", "minimum": 0, "description": "Cliques de saída contados pelo Biscoiteiro." },
          "no_ranking": { "type": "boolean", "description": "`false` quando o valor efetivo já zerou." },
          "posicao": { "type": ["integer", "null"], "minimum": 1, "description": "Posição de agora; `null` fora do ranking." },
          "valor_efetivo_centavos": { "$ref": "#/components/schemas/Centavos", "description": "0 fora do ranking." },
          "zera_em": { "type": ["string", "null"], "format": "date-time", "description": "Quando sai do ranking; `null` se já saiu." },
          "custo_para_passar_centavos": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Quanto um link novo paga agora para ficar logo acima deste; `null` fora do ranking."
          },
          "estatisticas": { "$ref": "#/components/schemas/EstatisticasDoLink" }
        }
      },
      "EstatisticasDoLink": {
        "type": "object",
        "description": "Números históricos do link: os mesmos da página dele no site.",
        "required": [
          "numero_de_reinados",
          "tempo_total_no_trono_segundos",
          "maior_reinado_continuo_segundos",
          "dias_no_trono",
          "melhor_posicao",
          "maior_lance_centavos",
          "total_pago_centavos",
          "numero_de_lances",
          "primeiro_lance_em"
        ],
        "properties": {
          "numero_de_reinados": { "type": "integer", "minimum": 0, "description": "Quantas vezes tomou o #1." },
          "tempo_total_no_trono_segundos": { "$ref": "#/components/schemas/Segundos", "description": "Soma de todos os reinados, contando o que está em andamento." },
          "maior_reinado_continuo_segundos": { "$ref": "#/components/schemas/Segundos", "description": "O reinado mais longo sem perder o trono." },
          "dias_no_trono": { "type": "integer", "minimum": 0, "description": "Dias distintos (Brasília) em que passou pelo #1." },
          "melhor_posicao": { "type": ["integer", "null"], "minimum": 1, "description": "Melhor posição já registrada; `null` se ainda não saiu em nenhuma foto do ranking." },
          "maior_lance_centavos": { "$ref": "#/components/schemas/Centavos", "description": "O maior lance confirmado do link." },
          "total_pago_centavos": { "$ref": "#/components/schemas/Centavos", "description": "Soma de todos os lances confirmados." },
          "numero_de_lances": { "type": "integer", "minimum": 0, "description": "Quantos lances confirmados o link já teve." },
          "primeiro_lance_em": { "type": ["string", "null"], "format": "date-time", "description": "Confirmação do primeiro lance." }
        }
      },
      "RespostaRankingDoDia": {
        "type": "object",
        "required": ["dados", "gerado_em"],
        "properties": {
          "dados": { "$ref": "#/components/schemas/RankingDoDia" },
          "gerado_em": { "$ref": "#/components/schemas/Instante" }
        }
      },
      "RankingDoDia": {
        "type": "object",
        "description": "A fornada fechada de um dia.",
        "required": ["data", "fechada_em", "pagina_url", "itens", "numeros"],
        "properties": {
          "data": { "type": "string", "format": "date", "description": "Dia da fornada (Brasília)." },
          "fechada_em": { "$ref": "#/components/schemas/Instante", "description": "Instante em que a fornada foi congelada." },
          "pagina_url": { "type": "string", "format": "uri", "description": "Página do arquivo desse dia no site." },
          "itens": { "type": "array", "items": { "$ref": "#/components/schemas/ItemDoRankingDoDia" }, "description": "Do #1 para baixo, como fechou." },
          "numeros": { "$ref": "#/components/schemas/NumerosDoDia" }
        }
      },
      "ItemDoRankingDoDia": {
        "type": "object",
        "required": ["posicao", "slug", "titulo", "dominio", "url", "instagram", "favicon_url", "valor_efetivo_centavos", "pagina_url"],
        "properties": {
          "posicao": { "type": "integer", "minimum": 1, "description": "Posição no fechamento." },
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "dominio": { "type": "string", "description": "Domínio sem `www`." },
          "url": { "type": "string", "format": "uri", "description": "Endereço normalizado do site." },
          "instagram": { "type": ["string", "null"], "description": "Usuário do Instagram, sem @." },
          "favicon_url": { "type": ["string", "null"], "format": "uri", "description": "Favicon guardado pelo Biscoiteiro." },
          "valor_efetivo_centavos": { "$ref": "#/components/schemas/Centavos", "description": "Valor efetivo no instante do fechamento." },
          "pagina_url": { "type": "string", "format": "uri", "description": "Página do link no Biscoiteiro." }
        }
      },
      "NumerosDoDia": {
        "type": "object",
        "description": "Os números daquele dia, congelados junto com a fornada.",
        "required": ["lances_no_dia", "trocas_de_trono", "maior_lance_centavos", "maior_subida", "reinou_hoje_segundos"],
        "properties": {
          "lances_no_dia": { "type": "integer", "minimum": 0, "description": "Lances confirmados naquele dia." },
          "trocas_de_trono": { "type": "integer", "minimum": 0, "description": "Quantas vezes o #1 mudou de dono." },
          "maior_lance_centavos": { "$ref": "#/components/schemas/Centavos", "description": "O maior lance do dia." },
          "maior_subida": {
            "anyOf": [{ "$ref": "#/components/schemas/MaiorSubida" }, { "type": "null" }],
            "description": "Quem mais subiu de posição no dia."
          },
          "reinou_hoje_segundos": { "$ref": "#/components/schemas/Segundos", "description": "Quanto tempo o #1 do fechamento passou no trono naquele dia. Zero se o #1 daquele dia saiu do ar depois." }
        }
      },
      "MaiorSubida": {
        "type": "object",
        "required": ["slug", "titulo", "posicoes"],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "posicoes": { "type": "integer", "minimum": 0, "description": "Quantas posições subiu." }
        }
      },
      "RespostaHallDaFama": {
        "type": "object",
        "required": ["dados", "gerado_em"],
        "properties": {
          "dados": { "$ref": "#/components/schemas/HallDaFama" },
          "gerado_em": { "$ref": "#/components/schemas/Instante" }
        }
      },
      "HallDaFama": {
        "type": "object",
        "description": "Todos que já foram #1 e os recordes da casa.",
        "required": ["total", "itens", "recordes"],
        "properties": {
          "total": { "type": "integer", "minimum": 0, "description": "Quantos links já foram #1." },
          "itens": { "type": "array", "items": { "$ref": "#/components/schemas/ItemDoHall" }, "description": "Ordenados por tempo total no trono, do maior para o menor." },
          "recordes": { "$ref": "#/components/schemas/Recordes" }
        }
      },
      "ItemDoHall": {
        "type": "object",
        "required": [
          "slug",
          "titulo",
          "dominio",
          "favicon_url",
          "numero_de_reinados",
          "tempo_total_no_trono_segundos",
          "maior_reinado_continuo_segundos",
          "maior_lance_centavos",
          "reinando_agora",
          "pagina_url"
        ],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "dominio": { "type": "string", "description": "Domínio sem `www`." },
          "favicon_url": { "type": ["string", "null"], "format": "uri", "description": "Favicon guardado pelo Biscoiteiro." },
          "numero_de_reinados": { "type": "integer", "minimum": 1, "description": "Quantas vezes tomou o #1." },
          "tempo_total_no_trono_segundos": { "$ref": "#/components/schemas/Segundos", "description": "Soma de todos os reinados, contando o que está em andamento." },
          "maior_reinado_continuo_segundos": { "$ref": "#/components/schemas/Segundos", "description": "O reinado mais longo sem perder o trono." },
          "maior_lance_centavos": { "$ref": "#/components/schemas/Centavos", "description": "O maior lance confirmado do link." },
          "reinando_agora": { "type": "boolean", "description": "`true` para o Biscoiteiro-mor do momento." },
          "pagina_url": { "type": "string", "format": "uri", "description": "Página do link no Biscoiteiro." }
        }
      },
      "Recordes": {
        "type": "object",
        "description": "Os três recordes da casa.",
        "required": ["maior_reinado", "maior_lance", "mais_reinados"],
        "properties": {
          "maior_reinado": {
            "anyOf": [{ "$ref": "#/components/schemas/RecordeDeReinado" }, { "type": "null" }],
            "description": "O reinado contínuo mais longo da história."
          },
          "maior_lance": {
            "anyOf": [{ "$ref": "#/components/schemas/RecordeDeLance" }, { "type": "null" }],
            "description": "O maior lance já confirmado."
          },
          "mais_reinados": {
            "anyOf": [{ "$ref": "#/components/schemas/RecordeDeQuantidade" }, { "type": "null" }],
            "description": "Quem mais vezes tomou o trono."
          }
        }
      },
      "RecordeDeReinado": {
        "type": "object",
        "required": ["slug", "titulo", "duracao_segundos"],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "duracao_segundos": { "$ref": "#/components/schemas/Segundos", "description": "Duração do reinado recordista." }
        }
      },
      "RecordeDeLance": {
        "type": "object",
        "required": ["slug", "titulo", "valor_centavos"],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "valor_centavos": { "$ref": "#/components/schemas/Centavos", "description": "Valor do lance recordista." }
        }
      },
      "RecordeDeQuantidade": {
        "type": "object",
        "required": ["slug", "titulo", "quantidade"],
        "properties": {
          "slug": { "$ref": "#/components/schemas/Slug" },
          "titulo": { "type": "string", "description": "Nome do site, já moderado." },
          "quantidade": { "type": "integer", "minimum": 1, "description": "Número de reinados." }
        }
      },
      "Erro": {
        "type": "object",
        "required": ["erro"],
        "properties": {
          "erro": {
            "type": "object",
            "required": ["codigo", "mensagem"],
            "properties": {
              "codigo": { "$ref": "#/components/schemas/CodigoDeErro" },
              "mensagem": { "type": "string", "description": "Texto para gente ler. Pode mudar; programe contra o `codigo`." }
            }
          }
        }
      },
      "CodigoDeErro": {
        "type": "string",
        "description": "Identificador estável do erro.",
        "enum": [
          "parametro_invalido",
          "chave_invalida",
          "chave_revogada",
          "link_nao_encontrado",
          "fornada_nao_encontrada",
          "rota_nao_encontrada",
          "limite_excedido",
          "erro_interno"
        ],
        "x-codigos": {
          "parametro_invalido": { "status": 400, "descricao": "Parâmetro de rota ou de query fora do formato. A mensagem diz qual." },
          "chave_invalida": { "status": 401, "descricao": "Veio uma chave malformada ou que não existe." },
          "chave_revogada": { "status": 401, "descricao": "A chave existia e foi revogada no painel." },
          "link_nao_encontrado": { "status": 404, "descricao": "Não existe link ativo com esse slug." },
          "fornada_nao_encontrada": { "status": 404, "descricao": "Não há fornada fechada nessa data." },
          "rota_nao_encontrada": { "status": 404, "descricao": "O caminho não existe na v1." },
          "limite_excedido": { "status": 429, "descricao": "Estourou o limite do minuto. Espere o `Retry-After`." },
          "erro_interno": { "status": 500, "descricao": "Falha do nosso lado. Pode tentar de novo." }
        }
      }
    }
  }
}
