{
  "openapi": "3.1.0",
  "info": {
    "title": "API de Medicamentos — medicamentos.api.br",
    "version": "1.0.0",
    "summary": "Registros da ANVISA, preços da lista CMED (PF, PMC e PMVG), princípios ativos, código de barras (EAN) e bula em seções.",
    "description": "API REST em JSON, só de leitura, com os medicamentos registrados na ANVISA e os preços máximos da lista CMED vigente: PF (preço fábrica), PMC (preço máximo ao consumidor) e PMVG (preço máximo de venda ao governo), por apresentação e por regime de ICMS. Traz ainda princípios ativos, código de barras (EAN) e a bula em seções.\n\nAutenticação: o header `X-API-Key` em toda requisição. A cota pertence à conta, e os limites de cada plano estão na documentação (https://medicamentos.api.br/api/docs).\n\nEscopo dos dados: só entram medicamentos com número de registro na ANVISA (os 9 dígitos do registro). Os preços são os da lista CMED vigente, cuja data de início vale está em `cmed.vigencia`; a API mostra só a vigência mais recente. O registro que saiu da lista CMED continua no catálogo, sem apresentações com preço, e `situacao` diz o que ele é hoje (`ativo`, `inativo` ou `removido`).\n\nCompatibilidade: as respostas só ganham campos novos; o que for aposentado fica marcado como `deprecated` por um tempo e vai para o changelog da documentação. Cliente deve ignorar campos que não conhece. Valor ausente é sempre `null`, nunca texto vazio, `0` nem `nan`.\n\nErros: todo erro é JSON, no formato `{ \"erro\", \"mensagem\" }`: `erro` é o código para o programa decidir (minúsculas separadas por sublinhado) e `mensagem` é o texto, em português, para pessoas. A lista completa dos códigos está em `x-erros`.",
    "termsOfService": "https://medicamentos.api.br/termos"
  },
  "servers": [
    { "url": "https://medicamentos.api.br", "description": "Produção" }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "tags": [
    { "name": "Medicamentos", "description": "Busca por nome, detalhe por registro (com os preços CMED) e bula." },
    { "name": "Princípios ativos", "description": "Lista de princípios ativos e os medicamentos de cada um. Planos Pro e Business." },
    { "name": "Código de barras (EAN)", "description": "Do código de barras à apresentação e ao registro." }
  ],
  "x-erros": [
    { "http": 400, "erro": "parametro_invalido", "quando": "Parâmetro ausente, repetido, fora do intervalo ou mal formado: `nome` (2 a 100 caracteres, contados sem acento nem espaço repetido), `busca`, `pagina`, `porPagina`, `situacao`, `registro`, `slug` e `icms`/`regime`. A `mensagem` diz qual. Não consome cota. Também é este código o de um `%` sem dois dígitos hexadecimais na URL, mas esse é recusado antes de a key ser lida: vem sem os headers `X-RateLimit-*` e até sem key." },
    { "http": 400, "erro": "ean_invalido", "quando": "Código de barras que não é um EAN-8, UPC-A (12 dígitos), EAN-13 ou GTIN-14 válido: só dígitos, com o dígito verificador GS1 correto, e que não seja só zeros nem o mesmo dígito repetido. Não consome cota." },
    { "http": 400, "erro": "corpo_invalido", "quando": "Corpo `application/json` malformado, em qualquer método (um `GET` com `Content-Type: application/json` e corpo quebrado também). A API é só de leitura e não precisa de corpo. O corpo é lido antes de qualquer rota, então este 400 sai antes de a key ser lida: **não há garantia** dos headers `X-RateLimit-*` nele (nem de `Access-Control-*`), mesmo com uma key válida." },
    { "http": 401, "erro": "nao_autenticado", "quando": "Falta o header `X-API-Key`." },
    { "http": 401, "erro": "chave_invalida", "quando": "Key que não tem 32 caracteres hexadecimais, que não existe ou que foi revogada." },
    { "http": 403, "erro": "tier_insuficiente", "quando": "O endpoint exige um plano maior que o da key (bula e princípios ativos pedem o Pro ou o Business). Vem antes da validação dos parâmetros e não consome cota." },
    { "http": 404, "erro": "nao_encontrado", "quando": "Registro ANVISA que não existe (a consulta foi feita: consome cota) ou caminho de `/api/v1` que não existe (não exige key e não consome cota)." },
    { "http": 404, "erro": "ean_nao_encontrado", "quando": "Código de barras válido que nenhum registro usa." },
    { "http": 404, "erro": "principio_ativo_nao_encontrado", "quando": "Slug que não é de nenhum princípio ativo, nem canônico nem alias antigo. O corpo traz `sugestoes`." },
    { "http": 404, "erro": "bula_nao_encontrada", "quando": "O registro não tem nenhuma seção real de bula. Se o PDF existe e a extração falhou, o corpo traz também `extracaoFalhou: true` e `urlBulaOficial`." },
    { "http": 405, "erro": "metodo_nao_permitido", "quando": "Método diferente de GET, HEAD e OPTIONS em qualquer caminho de `/api/v1`. O header `Allow` lista os aceitos." },
    { "http": 413, "erro": "corpo_muito_grande", "quando": "Corpo JSON acima do limite do servidor (100 kB)." },
    { "http": 415, "erro": "requisicao_invalida", "quando": "Outro erro do cliente detectado pelo servidor HTTP, com o status dele; por exemplo 415 para um corpo `application/json` com um `charset` que o servidor não lê." },
    { "http": 429, "erro": "limite_excedido", "quando": "Cota do plano esgotada, diária ou mensal. O corpo traz `upgradeUrl` e `reiniciaEm`, e o header `Retry-After` os segundos até o contador reiniciar." },
    { "http": 500, "erro": "erro_interno", "quando": "Falha do nosso lado. A mensagem é fixa; o motivo fica só no log do servidor. Tente de novo em instantes." }
  ],
  "paths": {
    "/api/v1/medicamentos": {
      "get": {
        "operationId": "buscarMedicamentos",
        "tags": ["Medicamentos"],
        "summary": "Buscar medicamentos por nome",
        "description": "Busca pelo nome comercial **ou** pelo princípio ativo, sem diferenciar maiúsculas, acentos nem espaços repetidos: `dipirona` acha NOVALGINA, e `analgésico` e `analgesico` são o mesmo termo. Todas as palavras do termo precisam aparecer. A ordem é por relevância (nome igual ao termo, nome que começa com ele, todas as palavras inteiras no nome, palavras dentro do nome, só pelo princípio ativo) e, dentro de cada grau, por nome e registro: a paginação é estável, nenhum registro repete nem se perde entre as páginas. Sem `situacao`, só registros ativos. Os itens não trazem preços; para os preços use o detalhe do registro.",
        "x-plano-minimo": "free",
        "parameters": [
          { "$ref": "#/components/parameters/Nome" },
          { "$ref": "#/components/parameters/Pagina" },
          { "$ref": "#/components/parameters/PorPagina" },
          { "$ref": "#/components/parameters/Situacao" }
        ],
        "responses": {
          "200": {
            "description": "A página de resultados, com o total e a paginação. Sem nenhum resultado, `total` é 0 e `resultados` vem vazio.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ListaMedicamentos" } } }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/medicamentos/{registro}": {
      "get": {
        "operationId": "obterMedicamento",
        "tags": ["Medicamentos"],
        "summary": "Detalhe de um medicamento, com os preços CMED",
        "description": "O medicamento pelo registro ANVISA, com os dados do catálogo e os preços da lista CMED vigente, só das apresentações **deste** registro. O objeto `cmed` traz todos os regimes de ICMS que a lista publica; `?icms=18` devolve só o regime pedido e reduz o corpo. `precosCMED` é a mesma informação em lista plana, mantida por compatibilidade (obsoleta). Responde também para registro inativo ou removido: `situacao` diz o que ele é.",
        "x-plano-minimo": "free",
        "parameters": [
          { "$ref": "#/components/parameters/Registro" },
          { "$ref": "#/components/parameters/Icms" },
          { "$ref": "#/components/parameters/Regime" }
        ],
        "responses": {
          "200": {
            "description": "O medicamento. Sem apresentação na lista CMED, `cmed.apresentacoes` e `precosCMED` vêm vazios.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Medicamento" } } }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "404": {
            "description": "`nao_encontrado`: nenhum medicamento tem esse registro. A requisição gasta cota: a consulta foi feita.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/medicamentos/{registro}/bula": {
      "get": {
        "operationId": "obterBula",
        "tags": ["Medicamentos"],
        "summary": "Bula do medicamento, em seções",
        "description": "A bula do Bulário Eletrônico da ANVISA, em seções e em JSON (não PDF), com o texto integral de cada seção. Só responde 200 quando há ao menos uma seção real: `secoesAusentes` lista as que a extração não encontrou, e a classe terapêutica nunca aparece no lugar de «para que serve». Sem nenhuma seção real a resposta é `404 bula_nao_encontrada`, e se o PDF existe mas a extração falhou o corpo diz isso (`extracaoFalhou`) e aponta a bula oficial (`urlBulaOficial`). Planos Pro e Business.",
        "x-plano-minimo": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/Registro" }
        ],
        "responses": {
          "200": {
            "description": "A bula, com as seções que foram extraídas.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Bula" } } }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "403": { "$ref": "#/components/responses/TierInsuficiente" },
          "404": {
            "description": "`bula_nao_encontrada`: o registro não tem nenhuma seção real de bula. Com `extracaoFalhou: true` o PDF existe e a extração automática não reconheceu nenhuma seção (não é o mesmo que sem bula publicada). A requisição gasta cota.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErroBulaNaoEncontrada" } } }
          },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/principios-ativos": {
      "get": {
        "operationId": "listarPrincipiosAtivos",
        "tags": ["Princípios ativos"],
        "summary": "Listar princípios ativos",
        "description": "Os princípios ativos (a substância-base, sem sal nem hidratação: `losartana`, não `losartana potássica`) que têm ao menos um medicamento ativo, em ordem alfabética do slug, com a quantidade de medicamentos ativos de cada um. `busca` filtra pelo slug, sem acento nem caixa; todas as palavras precisam aparecer. O `slug` daqui é o que `/api/v1/principios-ativos/{slug}` recebe. Planos Pro e Business.",
        "x-plano-minimo": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/Busca" },
          { "$ref": "#/components/parameters/Pagina" },
          { "$ref": "#/components/parameters/PorPagina" }
        ],
        "responses": {
          "200": {
            "description": "A página de princípios ativos.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ListaPrincipiosAtivos" } } }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "403": { "$ref": "#/components/responses/TierInsuficiente" },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/principios-ativos/{slug}": {
      "get": {
        "operationId": "obterPrincipioAtivo",
        "tags": ["Princípios ativos"],
        "summary": "Medicamentos de um princípio ativo",
        "description": "Todos os medicamentos que contêm o princípio ativo, paginados, por nome e registro: ideal para comparar genéricos (`classificacao`). O slug aceita maiúsculas, acento e o **alias antigo** (`dipirona-monoidratada` resolve para `dipirona`; uma associação como `losartana-potassica-hidroclorotiazida`, para o primeiro componente, `losartana`), e a resposta traz o slug canônico em `slug`. Sem `situacao`, só registros ativos. Um slug que não existe é `404` com `sugestoes`. Planos Pro e Business.",
        "x-plano-minimo": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/Slug" },
          { "$ref": "#/components/parameters/Situacao" },
          { "$ref": "#/components/parameters/Pagina" },
          { "$ref": "#/components/parameters/PorPagina" }
        ],
        "responses": {
          "200": {
            "description": "O princípio ativo e a página dos seus medicamentos. Um princípio ativo que só tem registros inativos existe: `total` é 0 com a `situacao` padrão.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MedicamentosDoPrincipio" } } }
          },
          "400": { "$ref": "#/components/responses/ParametroInvalido" },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "403": { "$ref": "#/components/responses/TierInsuficiente" },
          "404": {
            "description": "`principio_ativo_nao_encontrado`: o slug não é de nenhum princípio ativo. O corpo traz `sugestoes` (até 5 slugs parecidos, ou vazio). A requisição gasta cota.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErroPrincipioNaoEncontrado" } } }
          },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    },
    "/api/v1/ean/{codigo}": {
      "get": {
        "operationId": "buscarPorEan",
        "tags": ["Código de barras (EAN)"],
        "summary": "Medicamento pelo código de barras",
        "description": "Do código de barras ao medicamento, para PDV de farmácia e conferência do preço máximo no balcão. Confere o dígito verificador e aceita EAN-8, UPC-A (12 dígitos), EAN-13 e GTIN-14 (o EAN-13 com um zero à esquerda é o mesmo produto). A resposta é o detalhe do registro (o mesmo de `/api/v1/medicamentos/{registro}`, com `?icms=`) **mais** `apresentacaoLida`, a apresentação que o código identifica, com os preços só dela. Quando o mesmo código consta em mais de um registro ativo, `alternativas` lista os outros; o principal é o registro ativo.",
        "x-plano-minimo": "free",
        "parameters": [
          { "$ref": "#/components/parameters/Codigo" },
          { "$ref": "#/components/parameters/Icms" },
          { "$ref": "#/components/parameters/Regime" }
        ],
        "responses": {
          "200": {
            "description": "O medicamento, a apresentação lida e as alternativas. `apresentacaoLida` é `null` só quando o código consta apenas no índice antigo, que não sabe a apresentação.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MedicamentoPorEan" } } }
          },
          "400": {
            "description": "`ean_invalido`: o código não é um EAN-8, UPC-A, EAN-13 ou GTIN-14 válido (dígito verificador, placeholder). `parametro_invalido`: o `icms` ou o `regime` não é uma alíquota. Não consome cota; o saldo atual vai nos headers.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "401": { "$ref": "#/components/responses/NaoAutenticado" },
          "404": {
            "description": "`ean_nao_encontrado`: o código é válido, mas nenhum registro o usa. A requisição gasta cota.",
            "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "429": { "$ref": "#/components/responses/LimiteExcedido" },
          "500": { "$ref": "#/components/responses/ErroInterno" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A key de 32 caracteres hexadecimais, gerada em https://medicamentos.api.br/conta depois de confirmar o e-mail. Aparece uma única vez: guarde-a ao gerar."
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "O limite principal do plano. O período (dia ou mês) e o valor dependem do plano. No 429 é o limite do período que estourou.",
        "schema": { "type": "string", "pattern": "^[0-9]+$" }
      },
      "XRateLimitRemaining": {
        "description": "O saldo do mesmo período depois de gastar esta requisição (no 400, o saldo de antes: nada foi gasto), no mínimo 0.",
        "schema": { "type": "string", "pattern": "^[0-9]+$" }
      },
      "RetryAfter": {
        "description": "Segundos até o contador que bloqueou reiniciar: a próxima meia-noite de Brasília (limite diário) ou 00:00 do dia 1º do mês seguinte (limite mensal). O mesmo instante de `reiniciaEm`, no corpo.",
        "schema": { "type": "string", "pattern": "^[0-9]+$" }
      }
    },
    "parameters": {
      "Nome": {
        "name": "nome", "in": "query", "required": true,
        "description": "Termo de busca: nome comercial ou princípio ativo. De 2 a 100 caracteres (o mínimo vale depois de tirar acentos e espaços repetidos), sem byte NUL, uma vez só.",
        "schema": { "type": "string", "minLength": 2, "maxLength": 100 },
        "example": "losartana"
      },
      "Busca": {
        "name": "busca", "in": "query", "required": false,
        "description": "Filtra os princípios ativos pelo slug: todas as palavras precisam aparecer. De 2 a 100 caracteres. Sem ele, a lista vem inteira.",
        "schema": { "type": "string", "minLength": 2, "maxLength": 100 },
        "example": "losartana"
      },
      "Pagina": {
        "name": "pagina", "in": "query", "required": false,
        "description": "A página do resultado: inteiro de 1 a 1.000.000, só dígitos. Começa em 1.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 1000000, "default": 1 },
        "example": 1
      },
      "PorPagina": {
        "name": "porPagina", "in": "query", "required": false,
        "description": "Itens por página: inteiro de 1 a 100, só dígitos. Nunca é aparado em silêncio: fora do intervalo é 400.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 },
        "example": 20
      },
      "Situacao": {
        "name": "situacao", "in": "query", "required": false,
        "description": "Situação do registro: `ativo`, `inativo` ou `todos`. Sem o parâmetro, só os ativos.",
        "schema": { "type": "string", "enum": ["ativo", "inativo", "todos"], "default": "ativo" },
        "example": "ativo"
      },
      "Registro": {
        "name": "registro", "in": "path", "required": true,
        "description": "Registro ANVISA: os 9 dígitos do registro, os 13 impressos na embalagem (vale pelos 9 primeiros; os 4 finais, a apresentação e o controle, não são conferidos) ou qualquer um dos dois com pontuação (`1.0043.0911`, `1-0043-0911`). Qualquer outro formato é `parametro_invalido`. A resposta traz sempre o registro de 9 dígitos.",
        "schema": { "type": "string" },
        "example": "100430911"
      },
      "Slug": {
        "name": "slug", "in": "path", "required": true,
        "description": "O princípio ativo: o slug canônico (`losartana`) ou um alias antigo (`losartana-potassica`), sem diferenciar maiúsculas nem acentos. Até 120 caracteres.",
        "schema": { "type": "string", "maxLength": 120 },
        "example": "losartana"
      },
      "Codigo": {
        "name": "codigo", "in": "path", "required": true,
        "description": "O código de barras: EAN-8, UPC-A (12 dígitos), EAN-13 ou GTIN-14, só números, com o dígito verificador correto.",
        "schema": { "type": "string" },
        "example": "7891317427542"
      },
      "Icms": {
        "name": "icms", "in": "query", "required": false,
        "description": "Filtra os preços por um regime de ICMS: a alíquota (`18`, `17,5`, `12 ALC`) ou `sem-impostos`. Sem o parâmetro vêm todos os regimes que a lista CMED publica. Reduz muito o corpo.",
        "schema": { "type": "string" },
        "example": "18"
      },
      "Regime": {
        "name": "regime", "in": "query", "required": false,
        "description": "O mesmo parâmetro que `icms`, com outro nome (`regime=18%`). Se os dois forem enviados, precisam concordar.",
        "schema": { "type": "string" },
        "example": "18%"
      }
    },
    "responses": {
      "ParametroInvalido": {
        "description": "`parametro_invalido`: um parâmetro ausente, repetido, fora do intervalo ou mal formado (a `mensagem` diz qual). Não consome cota; o saldo atual vai nos headers (menos no `%` mal formado da URL, recusado antes de a key ser lida). Também é 400 o `corpo_invalido` (corpo JSON malformado), que sai do parse do corpo, antes de qualquer rota e de a key ser lida: para ele os headers `X-RateLimit-*` não são garantidos.",
        "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
      },
      "NaoAutenticado": {
        "description": "`nao_autenticado`: falta o header `X-API-Key`. `chave_invalida`: a key não tem o formato certo, não existe ou foi revogada.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
      },
      "TierInsuficiente": {
        "description": "`tier_insuficiente`: o endpoint exige um plano maior que o da key. Vem antes da validação dos parâmetros e não consome cota.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
      },
      "LimiteExcedido": {
        "description": "`limite_excedido`: a cota do plano acabou (a diária ou, no Free, a mensal). O corpo diz quando o contador reinicia (`reiniciaEm`) e para onde ampliar o plano (`upgradeUrl`). Os headers `X-RateLimit-*` descrevem o limite que estourou.",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" },
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErroLimite" } } }
      },
      "ErroInterno": {
        "description": "`erro_interno`: falha do nosso lado. Tente de novo em instantes.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
      }
    },
    "schemas": {
      "Paginacao": {
        "type": "object",
        "required": ["total", "pagina", "totalPaginas", "porPagina"],
        "properties": {
          "total": { "type": "integer", "minimum": 0, "description": "Quantos itens casam, em todas as páginas. Vale também para uma página além do fim." },
          "pagina": { "type": "integer", "minimum": 1, "description": "A página devolvida: a pedida." },
          "totalPaginas": { "type": "integer", "minimum": 1, "description": "Quantas páginas há; no mínimo 1, mesmo sem resultado." },
          "porPagina": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Itens por página." }
        }
      },
      "ItemMedicamento": {
        "type": "object",
        "additionalProperties": false,
        "required": ["registro", "nome", "fabricante", "principioAtivo", "principioAtivoSlug", "classeTerapeutica", "categoria", "classificacao", "situacao"],
        "properties": {
          "registro": { "type": "string", "pattern": "^[0-9]{9}$", "description": "Registro ANVISA, 9 dígitos." },
          "nome": { "type": "string", "description": "Nome comercial, como a ANVISA o registra." },
          "fabricante": { "type": ["string", "null"], "description": "Detentor do registro." },
          "principioAtivo": { "type": ["string", "null"], "description": "O princípio ativo como o registro o escreve, com sal e hidratação (`LOSARTANA POTÁSSICA`). Para a substância-base, veja `principioAtivoSlug`." },
          "principioAtivoSlug": { "type": ["string", "null"], "description": "Slug do princípio ativo (a substância-base, sem sal nem hidratação): o do primeiro componente nas associações. É o que `/api/v1/principios-ativos/{slug}` recebe." },
          "classeTerapeutica": { "type": ["string", "null"], "description": "Classe terapêutica da lista CMED (`ANTI-HIPERTENSIVOS`). Não é a categoria regulatória." },
          "categoria": { "type": ["string", "null"], "deprecated": true, "description": "Obsoleto: o mesmo valor de `classeTerapeutica` (a categoria regulatória está em `categoriaRegulatoria`, no detalhe). Use `classeTerapeutica`." },
          "classificacao": { "type": ["string", "null"], "description": "O que o medicamento é em relação ao de referência: `referencia`, `generico`, `similar`, `notificado`... Serve para comparar genéricos." },
          "situacao": { "type": ["string", "null"], "description": "`ativo`, `inativo` (a ANVISA não o lista mais como ativo, mas a lista CMED ainda o traz) ou `removido` (saiu das duas)." }
        }
      },
      "ListaMedicamentos": {
        "allOf": [
          { "$ref": "#/components/schemas/Paginacao" },
          {
            "type": "object",
            "required": ["resultados"],
            "properties": {
              "resultados": { "type": "array", "items": { "$ref": "#/components/schemas/ItemMedicamento" }, "description": "Os medicamentos da página, por relevância." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "PrincipioAtivoResumo": {
        "type": "object",
        "additionalProperties": false,
        "required": ["slug", "nome", "total"],
        "properties": {
          "slug": { "type": "string", "description": "O slug canônico, para `/api/v1/principios-ativos/{slug}`." },
          "nome": { "type": "string", "description": "O nome para exibir, com acento." },
          "total": { "type": "integer", "minimum": 1, "description": "Quantos medicamentos ativos contêm o princípio ativo." }
        }
      },
      "ListaPrincipiosAtivos": {
        "allOf": [
          { "$ref": "#/components/schemas/Paginacao" },
          {
            "type": "object",
            "required": ["resultados"],
            "properties": {
              "resultados": { "type": "array", "items": { "$ref": "#/components/schemas/PrincipioAtivoResumo" }, "description": "Os princípios ativos da página, em ordem alfabética do slug." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "MedicamentosDoPrincipio": {
        "allOf": [
          { "$ref": "#/components/schemas/Paginacao" },
          {
            "type": "object",
            "required": ["principioAtivo", "slug", "nome", "medicamentos"],
            "properties": {
              "principioAtivo": { "type": "string", "description": "O nome do princípio ativo (o mesmo de `nome`; o campo existe desde a primeira versão)." },
              "slug": { "type": "string", "description": "O slug canônico, mesmo quando o pedido usou um alias antigo ou outra grafia." },
              "nome": { "type": "string", "description": "O nome para exibir, com acento." },
              "medicamentos": { "type": "array", "items": { "$ref": "#/components/schemas/ItemMedicamento" }, "description": "Os medicamentos da página, por nome e registro." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "PrincipioAtivoRef": {
        "type": "object",
        "additionalProperties": false,
        "required": ["nome", "slug"],
        "properties": {
          "nome": { "type": "string", "description": "O nome para exibir, com acento." },
          "slug": { "type": "string", "description": "O slug canônico." }
        }
      },
      "Preco": {
        "type": "object",
        "additionalProperties": false,
        "required": ["regime", "pf", "pmc", "pmvg"],
        "properties": {
          "regime": { "type": "string", "description": "O regime de ICMS: `sem-impostos`, `0%`, `12%`, `12% ALC`, `17%`, `17,5%`, `18%`, `18% ALC`... (ALC: Áreas de Livre Comércio). Os regimes são os que a lista CMED publica." },
          "pf": { "type": ["number", "null"], "description": "Preço fábrica, em reais." },
          "pmc": { "type": ["number", "null"], "description": "Preço máximo ao consumidor, em reais. `null` quando a lista não publica PMC, como nas apresentações de restrição hospitalar." },
          "pmvg": { "type": ["number", "null"], "description": "Preço máximo de venda ao governo, em reais. Só é o limite legal onde `capNovo` da apresentação é `true`; nas demais vale o `pf`. `null` quando a lista não publica." }
        }
      },
      "Apresentacao": {
        "type": "object",
        "additionalProperties": false,
        "required": ["codigoGgrem", "laboratorio", "apresentacao", "ean", "isento", "capNovo", "precos"],
        "properties": {
          "codigoGgrem": { "type": "string", "description": "Código GGREM da apresentação na lista CMED, 15 dígitos: a chave da apresentação." },
          "laboratorio": { "type": "string", "description": "Laboratório, como a lista CMED o escreve." },
          "apresentacao": { "type": "string", "description": "Dosagem e embalagem, como a lista CMED as escreve." },
          "ean": { "type": "array", "items": { "type": "string", "pattern": "^[0-9]{8,14}$" }, "description": "Os códigos de barras da apresentação (até 3, só dígitos). Vazio quando a lista não traz nenhum." },
          "isento": { "type": "boolean", "description": "A apresentação é isenta de ICMS (coluna ICMS 0% da lista): os preços saem marcados com asterisco na lista CMED." },
          "capNovo": { "type": ["boolean", "null"], "description": "`true` quando o item é do Anexo I da Resolução CTE-CMED nº 1/2026: só então o `pmvg` é o limite legal nas vendas ao governo. `null` quando a lista não diz." },
          "precos": { "type": "array", "items": { "$ref": "#/components/schemas/Preco" }, "description": "Os preços da vigência, um por regime, em ordem de regime. Com `icms`, só o regime pedido (vazio se a apresentação não tem preço nele)." }
        }
      },
      "Cmed": {
        "type": "object",
        "additionalProperties": false,
        "required": ["vigencia", "apresentacoes"],
        "properties": {
          "vigencia": { "type": ["string", "null"], "format": "date", "description": "A data a partir da qual a lista CMED vigente vale (ISO). É a lista mais recente que a API carregou." },
          "apresentacoes": { "type": "array", "items": { "$ref": "#/components/schemas/Apresentacao" }, "description": "As apresentações do registro na lista CMED, por laboratório, apresentação e GGREM. Só as deste registro: nunca as de outro laboratório." }
        }
      },
      "PrecoPlano": {
        "type": "object",
        "additionalProperties": false,
        "required": ["uf", "regime", "pf", "pmc", "pmvg", "referencia", "laboratorio", "apresentacao", "matchConfidence", "codigoGgrem", "ean", "isento", "capNovo"],
        "properties": {
          "uf": { "const": "Nacional", "description": "Legado: a lista CMED é por regime de ICMS, não por UF; o valor é sempre `Nacional`." },
          "regime": { "type": "string", "description": "O regime de ICMS (só os principais: sem os `ALC` e sem `sem-impostos`)." },
          "pf": { "type": "number", "description": "Preço fábrica, em reais." },
          "pmc": { "type": ["number", "null"], "description": "Preço máximo ao consumidor, em reais." },
          "pmvg": { "type": ["number", "null"], "description": "Preço máximo de venda ao governo, em reais (veja `capNovo`)." },
          "referencia": { "type": ["string", "null"], "format": "date", "description": "A vigência da lista CMED (`cmed.vigencia`)." },
          "laboratorio": { "type": "string", "description": "Laboratório." },
          "apresentacao": { "type": "string", "description": "Dosagem e embalagem." },
          "matchConfidence": { "const": "alta", "description": "Legado: o preço é ligado ao registro de forma exata, então é sempre `alta`." },
          "codigoGgrem": { "type": "string", "description": "Código GGREM da apresentação." },
          "ean": { "type": "array", "items": { "type": "string" }, "description": "Os códigos de barras da apresentação." },
          "isento": { "type": "boolean", "description": "O preço vem com asterisco na lista CMED (isenção de ICMS possível)." },
          "capNovo": { "type": ["boolean", "null"], "description": "Veja `cmed.apresentacoes[].capNovo`." }
        }
      },
      "MedicamentoBase": {
        "type": "object",
        "required": ["registro", "nome", "fabricante", "principioAtivo", "principiosAtivos", "principioAtivoSlug", "classeTerapeutica", "categoria", "categoriaRegulatoria", "classificacao", "situacao", "validadeRegistro", "cmed", "precosCMED"],
        "properties": {
          "registro": { "type": "string", "pattern": "^[0-9]{9}$", "description": "Registro ANVISA, 9 dígitos (sempre, qualquer que seja o formato do pedido)." },
          "nome": { "type": "string", "description": "Nome comercial, como a ANVISA o registra." },
          "fabricante": { "type": ["string", "null"], "description": "Detentor do registro." },
          "principioAtivo": { "type": ["string", "null"], "description": "O princípio ativo como o registro o escreve, com sal e hidratação (`LOSARTANA POTÁSSICA`)." },
          "principiosAtivos": { "type": "array", "items": { "$ref": "#/components/schemas/PrincipioAtivoRef" }, "description": "Os princípios ativos (substância-base, sem sal nem hidratação) na ordem em que o ativo é escrito no registro: uma associação tem mais de um. Vazio quando o catálogo ainda não os calculou." },
          "principioAtivoSlug": { "type": ["string", "null"], "description": "O slug do primeiro princípio ativo: o do primeiro componente nas associações." },
          "classeTerapeutica": { "type": ["string", "null"], "description": "Classe terapêutica da lista CMED (`ANTI-HIPERTENSIVOS`)." },
          "categoria": { "type": ["string", "null"], "deprecated": true, "description": "Obsoleto: o mesmo valor de `classeTerapeutica`. Antes era descrita como categoria regulatória, mas sempre foi a classe terapêutica. Use `classeTerapeutica`." },
          "categoriaRegulatoria": { "type": ["string", "null"], "description": "A categoria regulatória na ANVISA: `generico`, `similar`, `novo`, `biologico`, `especifico`, `fitoterapico`..." },
          "classificacao": { "type": ["string", "null"], "description": "`referencia`, `generico`, `similar`, `notificado`... Serve para comparar genéricos." },
          "situacao": { "type": ["string", "null"], "description": "`ativo`, `inativo` (a ANVISA não o lista mais como ativo, mas a lista CMED ainda o traz) ou `removido` (saiu das duas)." },
          "validadeRegistro": { "type": ["string", "null"], "pattern": "^[0-9]{4}-[0-9]{2}$", "description": "Vencimento do registro, no formato `AAAA-MM` (a precisão é mensal). `null` quando a ANVISA não informa." },
          "cmed": { "$ref": "#/components/schemas/Cmed", "description": "Os preços da lista CMED vigente, por apresentação do registro e por regime de ICMS." },
          "precosCMED": { "type": "array", "deprecated": true, "items": { "$ref": "#/components/schemas/PrecoPlano" }, "description": "Obsoleto: os mesmos preços de `cmed` em lista plana (só os regimes principais, uma linha por apresentação e regime). Use `cmed`." }
        }
      },
      "Medicamento": {
        "$ref": "#/components/schemas/MedicamentoBase",
        "unevaluatedProperties": false
      },
      "ApresentacaoLida": {
        "type": ["object", "null"],
        "additionalProperties": false,
        "required": ["ean", "codigoGgrem", "apresentacao", "laboratorio", "isento", "capNovo", "precos"],
        "properties": {
          "ean": { "type": "string", "pattern": "^[0-9]{8,14}$", "description": "O código lido, na grafia canônica (a menor: sem zeros à esquerda a mais)." },
          "codigoGgrem": { "type": "string", "description": "Código GGREM da apresentação que o código identifica." },
          "apresentacao": { "type": "string", "description": "Dosagem e embalagem da apresentação." },
          "laboratorio": { "type": "string", "description": "Laboratório da apresentação." },
          "isento": { "type": "boolean", "description": "A apresentação é isenta de ICMS." },
          "capNovo": { "type": ["boolean", "null"], "description": "De que depende o PMVG: veja `cmed.apresentacoes[].capNovo`." },
          "precos": { "type": "array", "items": { "$ref": "#/components/schemas/Preco" }, "description": "Os preços só desta apresentação (filtrados por `icms`, se pedido)." }
        }
      },
      "Alternativa": {
        "type": "object",
        "additionalProperties": false,
        "required": ["registro", "nome", "fabricante", "situacao", "codigoGgrem", "apresentacao", "laboratorio"],
        "properties": {
          "registro": { "type": "string", "pattern": "^[0-9]{9}$", "description": "O outro registro ativo que usa o mesmo código de barras." },
          "nome": { "type": "string", "description": "Nome comercial." },
          "fabricante": { "type": ["string", "null"], "description": "Detentor do registro." },
          "situacao": { "type": ["string", "null"], "description": "Sempre `ativo`: o registro inativo ou removido não é alternativa." },
          "codigoGgrem": { "type": ["string", "null"], "description": "A apresentação desse registro que usa o código. `null` quando só o índice antigo o conhece." },
          "apresentacao": { "type": ["string", "null"], "description": "Dosagem e embalagem dessa apresentação." },
          "laboratorio": { "type": ["string", "null"], "description": "Laboratório dessa apresentação." }
        }
      },
      "MedicamentoPorEan": {
        "allOf": [
          { "$ref": "#/components/schemas/MedicamentoBase" },
          {
            "type": "object",
            "required": ["apresentacaoLida", "alternativas"],
            "properties": {
              "apresentacaoLida": { "$ref": "#/components/schemas/ApresentacaoLida", "description": "A apresentação que o código de barras identifica, com os preços só dela. `null` só quando o código consta apenas no índice antigo." },
              "alternativas": { "type": "array", "items": { "$ref": "#/components/schemas/Alternativa" }, "description": "Os outros registros ativos que usam o mesmo código (um produto repassado de um laboratório a outro). Vazio no caso comum." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "SecoesBula": {
        "type": "object",
        "additionalProperties": false,
        "required": ["paraQueServe", "comoTomar", "contraindicacoes", "efeitosColaterais", "composicao", "precaucoes"],
        "properties": {
          "paraQueServe": { "type": ["string", "null"], "description": "Para que este medicamento é indicado. Nunca a classe terapêutica no lugar da indicação." },
          "comoTomar": { "type": ["string", "null"], "description": "Como usar: dose, via e frequência." },
          "contraindicacoes": { "type": ["string", "null"], "description": "Quando não usar." },
          "efeitosColaterais": { "type": ["string", "null"], "description": "Reações adversas. O texto sai integral, sem corte." },
          "composicao": { "type": ["string", "null"], "description": "Composição." },
          "precaucoes": { "type": ["string", "null"], "description": "Advertências e precauções." }
        }
      },
      "Bula": {
        "type": "object",
        "additionalProperties": false,
        "required": ["registro", "secoes", "secoesAusentes", "extracaoFalhou", "atualizadoEm", "urlBulaOficial", "bula"],
        "properties": {
          "registro": { "type": "string", "pattern": "^[0-9]{9}$", "description": "Registro ANVISA, 9 dígitos." },
          "secoes": { "$ref": "#/components/schemas/SecoesBula", "description": "As seis seções, com o texto integral; `null` na que a extração não encontrou." },
          "secoesAusentes": { "type": "array", "items": { "type": "string", "enum": ["paraQueServe", "comoTomar", "contraindicacoes", "efeitosColaterais", "composicao", "precaucoes"] }, "description": "As chaves das seções que não foram encontradas: um registro com a bula incompleta é `200` com esta lista, não `404`." },
          "extracaoFalhou": { "type": "boolean", "description": "`true` quando o PDF da bula abriu mas a extração automática não reconheceu as seções: falha de extração, não ausência de bula publicada. Se não sobrou nenhuma seção real, a resposta é `404` com `extracaoFalhou: true` no corpo." },
          "atualizadoEm": { "type": ["string", "null"], "format": "date-time", "description": "Quando o texto da bula foi extraído do PDF da ANVISA (nas linhas mais antigas, quando foi gravado), em ISO 8601 (UTC)." },
          "urlBulaOficial": { "type": "string", "format": "uri", "description": "A busca do Bulário Eletrônico da ANVISA por este registro: a bula oficial, para conferir o texto." },
          "bula": {
            "type": "object",
            "deprecated": true,
            "additionalProperties": false,
            "required": ["paraQueServe", "comoTomar", "contraindicacoes", "efeitosColaterais", "composicao", "precaucoes", "extracaoFalhou"],
            "properties": {
              "paraQueServe": { "type": ["string", "null"] },
              "comoTomar": { "type": ["string", "null"] },
              "contraindicacoes": { "type": ["string", "null"] },
              "efeitosColaterais": { "type": ["string", "null"] },
              "composicao": { "type": ["string", "null"] },
              "precaucoes": { "type": ["string", "null"] },
              "extracaoFalhou": { "type": "boolean" }
            },
            "description": "Obsoleto: as mesmas seções de `secoes` com o `extracaoFalhou` dentro, o formato de antes. Use `secoes`."
          }
        }
      },
      "ErroBase": {
        "type": "object",
        "required": ["erro", "mensagem"],
        "properties": {
          "erro": { "type": "string", "pattern": "^[a-z_]+$", "description": "O código do erro, para o programa decidir. A lista está em `x-erros`." },
          "mensagem": { "type": "string", "description": "O texto, em português, para pessoas." }
        }
      },
      "Erro": {
        "$ref": "#/components/schemas/ErroBase",
        "unevaluatedProperties": false
      },
      "ErroPrincipioNaoEncontrado": {
        "allOf": [
          { "$ref": "#/components/schemas/ErroBase" },
          {
            "type": "object",
            "required": ["sugestoes"],
            "properties": {
              "sugestoes": { "type": "array", "items": { "type": "string" }, "maxItems": 5, "description": "Até 5 slugs de princípios ativos parecidos com o pedido, do mais para o menos próximo; vazio se nenhum se parece." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ErroBulaNaoEncontrada": {
        "allOf": [
          { "$ref": "#/components/schemas/ErroBase" },
          {
            "type": "object",
            "properties": {
              "extracaoFalhou": { "const": true, "description": "Só vem, com `true`, quando o PDF da bula existe mas a extração não reconheceu nenhuma seção." },
              "urlBulaOficial": { "type": "string", "format": "uri", "description": "Onde consultar a bula oficial; só vem junto de `extracaoFalhou`." }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ErroLimite": {
        "allOf": [
          { "$ref": "#/components/schemas/ErroBase" },
          {
            "type": "object",
            "required": ["upgradeUrl", "reiniciaEm"],
            "properties": {
              "upgradeUrl": { "type": "string", "format": "uri", "description": "Para onde ampliar o plano. O Free vai para a assinatura com o Pro já escolhido; Pro e Business, para a página de planos." },
              "reiniciaEm": { "type": "string", "format": "date-time", "description": "O instante em que o contador que bloqueou reinicia, em ISO 8601 no horário de Brasília (`-03:00`): a meia-noite do dia seguinte (limite diário) ou do dia 1º do mês seguinte (limite mensal). O mesmo momento do `Retry-After`." }
            }
          }
        ],
        "unevaluatedProperties": false
      }
    }
  }
}
