# Fonteza

> API REST somente leitura e servidor MCP com dados públicos oficiais brasileiros: cadastro de empresas da Receita Federal (CNPJ, sócios), oportunidades de contratação do PNCP e despesas federais do Portal da Transparência. Cada resposta identifica a fonte, a publicação e a cobertura.

- URL base: https://api.fonteza.com.br
- Autenticação: header `X-Api-Key: fk_...`. Chave gratuita em https://fonteza.com.br/app/login, 1.000 chamadas por dia. /v1/meta e /healthz são públicas.
- MCP remoto: https://fonteza.com.br/mcp (streamable HTTP, header `Authorization: Bearer fk_...`)
- Contrato OpenAPI 3.1: https://fonteza.com.br/openapi.yaml
- Toda a documentação em um arquivo: https://fonteza.com.br/llms-full.txt
- Toda página de /docs tem versão Markdown: acrescente `.md` ao endereço ou envie `Accept: text/markdown`.

## Guias

- [API Fonteza](https://fonteza.com.br/docs/index.md): Empresas da Receita Federal, oportunidades do PNCP e despesas federais em JSON, por REST ou MCP.
- [Montar uma busca](https://fonteza.com.br/docs/construtor.md): Escolha os filtros de /v1/buscar e copie a requisição pronta em curl, Python ou JavaScript.
- [Agentes de IA](https://fonteza.com.br/docs/agentes.md): MCP remoto, llms.txt e páginas em Markdown para agentes que consultam ou integram a Fonteza.
- [Autenticação e cota](https://fonteza.com.br/docs/autenticacao.md): Header X-Api-Key, criação de chaves e como a cota diária é contada.
- [Paginação](https://fonteza.com.br/docs/paginacao.md): Offset na busca de empresas; cursor preso à publicação em oportunidades e despesas.
- [Erros](https://fonteza.com.br/docs/erros.md): Status HTTP, formatos do corpo e códigos estáveis de erro.
- [Cobertura e procedência](https://fonteza.com.br/docs/cobertura.md): Como ler fonte, publicação e cobertura antes de concluir que algo não existe.

## Referência

- [GET /v1/buscar](https://fonteza.com.br/docs/referencia/buscar.md): Buscar empresas
- [GET /v1/empresas/{cnpj}](https://fonteza.com.br/docs/referencia/empresas-cnpj.md): Obter o cadastro de uma empresa
- [GET /v1/oportunidades](https://fonteza.com.br/docs/referencia/oportunidades.md): Buscar oportunidades no PNCP
- [GET /v1/empresas/{cnpj}/despesas-federais](https://fonteza.com.br/docs/referencia/empresas-cnpj-despesas-federais.md): Listar despesas federais de uma empresa
- [GET /v1/meta](https://fonteza.com.br/docs/referencia/meta.md): Consultar publicações e cobertura
- [GET /healthz](https://fonteza.com.br/docs/referencia/healthz.md): Verificar o processo
- [GET /v1/cnaes](https://fonteza.com.br/docs/referencia/cnaes.md): Encontrar códigos CNAE por atividade

# API Fonteza

Empresas da Receita Federal, contratações do PNCP e despesas federais do Portal da Transparência em JSON. Somente leitura, com fonte e publicação em cada resposta. Para agentes de IA, as mesmas bases por [MCP](https://fonteza.com.br/docs/agentes.md).

curl:

```bash
curl -G https://api.fonteza.com.br/v1/buscar \
  -H "X-Api-Key: $FONTEZA_KEY" \
  --data-urlencode "q=transportadora" \
  --data-urlencode "uf=SP"
```

Python:

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/buscar",
    params={"q": "transportadora", "uf": "SP"},
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

JavaScript:

```javascript
const url = new URL("https://api.fonteza.com.br/v1/buscar");
url.search = new URLSearchParams({ q: "transportadora", uf: "SP" });
const resposta = await fetch(url, {
  headers: { "X-Api-Key": process.env.FONTEZA_KEY },
});
if (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);
const dados = await resposta.json();
```

Resposta 200, resumida:

```json
{
  "total": null,
  "limit": 20,
  "offset": 0,
  "items": [
    {
      "cnpj": "12345678000190",
      "razao_social": "TRANSPORTADORA EXEMPLO LTDA",
      "nome_fantasia": "EXEMPLO CARGAS",
      "municipio": "CAMPINAS",
      "uf": "SP",
      "cnae_principal": "4930202"
    }
  ],
  "has_more": false,
  "meta": {
    "fonte": [
      "RFB/DadosAbertos"
    ],
    "competencia": "2026-09",
    "snapshot_id": "rfb-2026-09-3f9c2a7d41b8e605",
    "total_calculado": false,
    "modo_busca": "texto",
    "plano": {
      "varredura_ampla": false,
      "custo_estimado": 1834.2,
      "custo_leitura_completa": 1834.2,
      "linhas_estimadas": 412,
      "limite_custo": 100000,
      "exige_confirmacao": true
    },
    "cnaes_sugeridos": [
      {
        "rotulo": "Caminhoneiros e transportadoras",
        "codigos": [
          "4930201",
          "4930202",
          "4930203"
        ],
        "descricoes": [
          "Transporte rodoviário de carga",
          "exceto produtos perigosos e mudanças",
          "municipal",
          "Transporte rodoviário de carga, exceto produtos perigosos e mudanças, intermunicipal, interestadual e internacional",
          "Transporte rodoviário de produtos perigosos"
        ],
        "origem": "categoria",
        "termo": "transportadora",
        "nota": "Maioria MEI: são sobretudo caminhoneiros autônomos."
      }
    ]
  }
}
```

Para trocar os filtros e copiar a requisição pronta, use [Montar uma busca](https://fonteza.com.br/docs/construtor.md).

| | |
| --- | --- |
| URL base | `https://api.fonteza.com.br` |
| Autenticação | `X-Api-Key: fk_...`. [Crie uma chave grátis](https://fonteza.com.br/app/login): 1.000 chamadas por dia. |
| Formato | Só `GET`, JSON em UTF-8. Sem SDK: qualquer cliente HTTP serve. |
| MCP | `https://fonteza.com.br/mcp` com `Authorization: Bearer fk_...`. [Instalar](https://fonteza.com.br/docs/agentes.md). |
| Contrato | [`/openapi.yaml`](https://fonteza.com.br/openapi.yaml), OpenAPI 3.1. Importe no Postman ou gere um cliente. |
| Para LLMs | [`/llms.txt`](https://fonteza.com.br/llms.txt), [`/llms-full.txt`](https://fonteza.com.br/llms-full.txt) e `.md` em qualquer página. |

## Rotas

| Rota | O que faz | Acesso |
| --- | --- | --- |
| [`GET /v1/buscar`](https://fonteza.com.br/docs/referencia/buscar.md) | Buscar empresas | chave |
| [`GET /v1/empresas/{cnpj}`](https://fonteza.com.br/docs/referencia/empresas-cnpj.md) | Obter o cadastro de uma empresa | chave |
| [`GET /v1/oportunidades`](https://fonteza.com.br/docs/referencia/oportunidades.md) | Buscar oportunidades no PNCP | chave |
| [`GET /v1/empresas/{cnpj}/despesas-federais`](https://fonteza.com.br/docs/referencia/empresas-cnpj-despesas-federais.md) | Listar despesas federais de uma empresa | chave |
| [`GET /v1/meta`](https://fonteza.com.br/docs/referencia/meta.md) | Consultar publicações e cobertura | pública |
| [`GET /healthz`](https://fonteza.com.br/docs/referencia/healthz.md) | Verificar o processo | pública |
| [`GET /v1/cnaes`](https://fonteza.com.br/docs/referencia/cnaes.md) | Encontrar códigos CNAE por atividade | pública |

## O que não é óbvio

- `/v1/buscar` só devolve estabelecimentos ativos (situação `02`). Para um CNPJ em qualquer situação, use [`/v1/empresas/{cnpj}`](https://fonteza.com.br/docs/referencia/empresas-cnpj.md).
- Nenhum resultado não quer dizer que não existe: a busca cobre só o período e o recorte publicados, informados em `meta`. Veja [Cobertura e procedência](https://fonteza.com.br/docs/cobertura.md).
- Buscas amplas respondem `409 busca_ampla_requer_confirmacao`. Refine os filtros ou repita com `confirmar_varredura=true`.
- Toda requisição com chave válida a uma rota de dados conta na cota, inclusive as que terminam em erro e as recusadas com `429`. Não há `Retry-After`: a cota renova na virada do dia. [Autenticação e cota](https://fonteza.com.br/docs/autenticacao.md).
- Oportunidades e despesas paginam por cursor preso à publicação. `409 geracao_*_expirada` significa recomeçar sem cursor. [Paginação](https://fonteza.com.br/docs/paginacao.md).
- Trate erros pelo `codigo`, não pela mensagem. Há três formatos de corpo. [Erros](https://fonteza.com.br/docs/erros.md).
- Campos da Receita mantêm o formato da fonte: datas `AAAAMMDD`, capital `150000,00`, códigos de porte e situação.
- Chame a partir do servidor. CORS bloqueia navegadores em outros domínios, e a chave não deve ir para o front-end.

---

# Montar uma busca

Escolha os filtros de [`/v1/buscar`](https://fonteza.com.br/docs/referencia/buscar.md) e copie a requisição pronta em curl, Python ou JavaScript. Campos vazios ficam fora da requisição, e valores iguais ao padrão da API também.

curl:

```bash
curl -G https://api.fonteza.com.br/v1/buscar \
  -H "X-Api-Key: $FONTEZA_KEY" \
  --data-urlencode "q=transportadora" \
  --data-urlencode "uf=SP"
```

Python:

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/buscar",
    params={"q": "transportadora", "uf": "SP"},
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

JavaScript:

```javascript
const url = new URL("https://api.fonteza.com.br/v1/buscar");
url.search = new URLSearchParams({ q: "transportadora", uf: "SP" });
const resposta = await fetch(url, {
  headers: { "X-Api-Key": process.env.FONTEZA_KEY },
});
if (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);
const dados = await resposta.json();
```

## Como ler a requisição

- `-G` com `--data-urlencode` envia cada filtro na query string já codificado: acentos e espaços, como em `municipio=São Paulo`, chegam corretos.
- Informe `q`, `cnae` ou os dois. Os demais filtros só refinam o resultado.
- No campo `cnae`, digite a atividade (restaurante, dentista) e escolha uma sugestão: o construtor consulta [`/v1/cnaes`](https://fonteza.com.br/docs/referencia/cnaes.md), rota pública que não usa chave nem cota, e preenche os códigos. Até 10 códigos, separados por vírgula. Filtrar por CNAE usa o índice e é mais rápido e preciso que procurar a palavra em `q`.
- A resposta traz `meta.plano`: `varredura_ampla` diz se a busca lê uma parte grande da base. Sem `cnae`, uma varredura ampla responde `409` com `cnaes_sugeridos`; repita com um desses códigos ou confirme com `confirmar_varredura=true`.
- `tem_telefone` e `tem_email` aceitam `true` (exige o contato) ou `false` (exige a ausência). Sem eles, tanto faz.
- Para o município, envie também `uf`. Um nome desconhecido ou fora da UF devolve `422` com sugestões.
- Cada execução é uma chamada e conta na cota da chave, mesmo quando termina em erro. Veja [Autenticação e cota](https://fonteza.com.br/docs/autenticacao.md).

## Executar no painel

No [painel](https://fonteza.com.br/app), o Console da API monta a mesma requisição e a executa com a chave do painel, sem copiar nenhuma chave. As execuções contam na cota diária dessa chave e aparecem em Uso.

---

# Agentes de IA

A Fonteza foi feita para ser usada por agentes. O mesmo dado chega por três portas, com a mesma chave e a mesma cota.

| Seu agente | Use |
| --- | --- |
| Consulta dados durante uma conversa ou tarefa | [MCP remoto](#mcp-remoto) |
| Escreve código que integra a API | [Documentação em Markdown](#documentacao-para-llms) e [instruções para agentes de código](#instrucoes-para-agentes-de-codigo) |
| Gera ferramentas de function calling a partir de um contrato | [`/openapi.yaml`](https://fonteza.com.br/openapi.yaml) |

## MCP remoto

| | |
| --- | --- |
| Endpoint | `https://fonteza.com.br/mcp` |
| Transporte | Streamable HTTP |
| Autenticação | `Authorization: Bearer fk_...`, com uma chave criada no [painel](https://fonteza.com.br/app/login). Sem OAuth. |
| Modelo | Por sua conta: a Fonteza fornece os dados, não o modelo. |

### Instalar

Claude Code:

```bash
claude mcp add --transport http fonteza https://fonteza.com.br/mcp \
  --header "Authorization: Bearer $FONTEZA_KEY"
```

Cursor · `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "fonteza": {
      "url": "https://fonteza.com.br/mcp",
      "headers": { "Authorization": "Bearer ${env:FONTEZA_KEY}" }
    }
  }
}
```

VS Code · `.vscode/mcp.json`:

```json
{
  "inputs": [
    { "type": "promptString", "id": "fonteza-key", "description": "Chave da Fonteza (fk_...)", "password": true }
  ],
  "servers": {
    "fonteza": {
      "type": "http",
      "url": "https://fonteza.com.br/mcp",
      "headers": { "Authorization": "Bearer ${input:fonteza-key}" }
    }
  }
}
```

Codex · `~/.codex/config.toml`:

```toml
[mcp_servers.fonteza]
url = "https://fonteza.com.br/mcp"
bearer_token_env_var = "FONTEZA_KEY"
```

Claude Desktop · `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "fonteza": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://fonteza.com.br/mcp", "--header", "Authorization:${FONTEZA_AUTH}"],
      "env": { "FONTEZA_AUTH": "Bearer fk_..." }
    }
  }
}
```

OpenAI Agents:

```json
{
  "type": "mcp",
  "server_label": "fonteza",
  "transport": {
    "type": "http",
    "server_url": "https://fonteza.com.br/mcp",
    "authorization": "Bearer <FONTEZA_API_KEY>"
  },
  "connection_origin": "service",
  "required": true
}
```

O Claude Desktop conecta por meio do proxy `mcp-remote`, que requer Node.js. Testados por nós em produção: Claude Code e OpenAI Agents. As demais configurações seguem a documentação de cada cliente; outros clientes de MCP remoto que enviem um header personalizado devem funcionar.

### Ferramentas

| Ferramenta | O que faz | Equivalente REST |
| --- | --- | --- |
| `descobrir_cobertura` | Fontes publicadas, período, cobertura, limitações e o aviso operacional vigente. Não consome cota. | [`/v1/meta`](https://fonteza.com.br/docs/referencia/meta.md) |
| `buscar_empresas` | Busca por nome, atividade ou só pelo código CNAE, com os filtros da API. Cada item traz telefone e e-mail cadastrais. Até 50 por chamada. | [`/v1/buscar`](https://fonteza.com.br/docs/referencia/buscar.md) |
| `obter_empresa` | Cadastro e sócios de um CNPJ. | [`/v1/empresas/{cnpj}`](https://fonteza.com.br/docs/referencia/empresas-cnpj.md) |
| `buscar_oportunidades` | Oportunidades do PNCP por palavras, UF, município, modalidade, status e prazo. Até 50, com cursor. | [`/v1/oportunidades`](https://fonteza.com.br/docs/referencia/oportunidades.md) |
| `despesas_federais_da_empresa` | Empenhos, liquidações e pagamentos de um CNPJ. Até 50, com cursor. | [`/v1/empresas/{cnpj}/despesas-federais`](https://fonteza.com.br/docs/referencia/empresas-cnpj-despesas-federais.md) |

Os argumentos de cada ferramenta chegam ao agente pela listagem do próprio MCP. Enquanto houver um aviso operacional, como manutenção ou carga em andamento, toda resposta o traz em `meta.aviso`.

### Cota no MCP

- Só chamadas bem-sucedidas a ferramentas de dados consomem cota, mesmo com resultado vazio.
- Listar ferramentas, `descobrir_cobertura`, argumentos inválidos, falhas, timeouts e cancelamentos não consomem.
- Após 20 falhas de autenticação em um minuto a partir do mesmo IP, o MCP responde `429` até a janela liberar.

## Documentação para LLMs

Toda esta documentação existe em Markdown, sem navegação nem HTML, para caber no contexto de um modelo.

| Endereço | Conteúdo |
| --- | --- |
| [`/llms.txt`](https://fonteza.com.br/llms.txt) | Índice no formato llms.txt: fatos essenciais e links para cada página. |
| [`/llms-full.txt`](https://fonteza.com.br/llms-full.txt) | Todos os guias e a referência de todas as rotas em um único arquivo. |
| `/docs/<página>.md` | Qualquer página em Markdown. Ex.: [`/docs/referencia/buscar.md`](https://fonteza.com.br/docs/referencia/buscar.md.md). |
| `Accept: text/markdown` | O endereço normal da página responde Markdown quando o cliente pede. |

```bash
curl https://fonteza.com.br/docs/paginacao.md
curl -H "Accept: text/markdown" https://fonteza.com.br/docs/erros
```

Em cada página, **Copiar Markdown** leva o conteúdo para a área de transferência, e **Abrir no Claude** ou **Abrir no ChatGPT** inicia uma conversa que já lê a página.

## Instruções para agentes de código

Cole no `AGENTS.md`, `CLAUDE.md` ou nas regras do Cursor do seu projeto:

AGENTS.md:

```markdown
## API Fonteza
- Documentação completa para LLMs: https://fonteza.com.br/llms-full.txt
- Contrato: https://fonteza.com.br/openapi.yaml
- URL base: https://api.fonteza.com.br. Só GET, JSON. Header X-Api-Key lido de FONTEZA_KEY; nunca escreva a chave no código, em logs ou no front-end.
- Trate erros pelo campo `codigo` (em `detail.codigo` ou na raiz do corpo), nunca pelo texto da mensagem.
- Toda chamada com chave válida conta na cota diária, inclusive erros. Em 429, pare: não há Retry-After e repetir também conta.
- /v1/buscar pagina por offset e só devolve CNPJs ativos. Oportunidades e despesas paginam por `next_cursor`; em 409 geracao_*_expirada, recomece sem cursor.
- Nenhum resultado não quer dizer que não existe: a busca cobre só o recorte publicado. Consulte /v1/meta (gratuito) para cobertura e guarde `meta.snapshot_id` junto com o dado.
```

## Segurança

- Uma chave por agente ou ambiente, guardada em um gerenciador de segredos. Revogar uma não derruba as outras.
- Nunca coloque a chave em prompts, definições de agente compartilhadas, arquivos versionados ou logs.
- Se a chave vazar, revogue no [painel](https://fonteza.com.br/app/login) e crie outra; a revogação vale na requisição seguinte.

---

# Autenticação e cota

Toda rota de dados exige o header `X-Api-Key`. A chave é gratuita e tem 1.000 chamadas por dia.

```bash
curl https://api.fonteza.com.br/v1/empresas/12345678000190 \
  -H "X-Api-Key: $FONTEZA_KEY"
```

Sem chave, ou com chave inválida ou revogada: `401 {"detail": "API key inválida"}`. Públicas, sem chave e sem cota: [`/v1/meta`](https://fonteza.com.br/docs/referencia/meta.md) e [`/healthz`](https://fonteza.com.br/docs/referencia/healthz.md).

## Chaves

- Crie no [painel](https://fonteza.com.br/app/login), em **Chaves de API**. O valor começa com `fk_` e aparece uma única vez.
- Para testar sem chave, use o Console da API do painel: ele executa buscas com a chave do painel, que tem cota diária própria. Veja [Montar uma busca](https://fonteza.com.br/docs/construtor.md).
- Uma chave por integração ou ambiente: você revoga uma sem derrubar as outras e vê o uso de cada uma.
- A revogação vale na requisição seguinte.
- A mesma chave autentica o [MCP](https://fonteza.com.br/docs/agentes.md), enviada como `Authorization: Bearer`.

## O que conta na cota

| Requisição | Conta? |
| --- | --- |
| Rota de dados com chave válida, resposta `200` | Sim |
| Rota de dados com chave válida, resposta `404`, `409`, `422` ou `503` | Sim |
| Recusada com `429` por cota esgotada | Sim |
| Rotas públicas `/v1/meta` e `/healthz` | Não |
| Chave inválida (`401`) | Não |
| MCP: só chamadas bem-sucedidas a ferramentas de dados | Sim |

Cota esgotada: `429 {"detail": "cota diária excedida"}`, sem `Retry-After`. A contagem recomeça na virada do dia do servidor. Não repita em laço: cada tentativa recusada também conta. O painel mostra o uso de hoje e dos últimos 14 dias por chave.

Para gastar menos: valide parâmetros antes de chamar, consulte `/v1/meta` (gratuito) antes de buscar em um conjunto indisponível e refine filtros em vez de paginar fundo.

## Segurança

- Guarde a chave em variável de ambiente ou gerenciador de segredos; nunca em código versionado, front-end, prompts ou logs.
- Se vazar, revogue no painel e crie outra.

---

# Paginação

`/v1/buscar` pagina por offset. Oportunidades e despesas federais paginam por cursor, preso à publicação consultada. Cada página é uma chamada e conta na cota.

## Offset: empresas

[`/v1/buscar`](https://fonteza.com.br/docs/referencia/buscar.md) recebe `limit` (1 a 100, padrão 20) e `offset` (padrão 0). `has_more` indica se há ao menos mais um resultado.

```bash
curl -G https://api.fonteza.com.br/v1/buscar \
  -H "X-Api-Key: $FONTEZA_KEY" \
  --data-urlencode "q=transportadora" \
  --data-urlencode "uf=SP" \
  --data-urlencode "limit=100" \
  --data-urlencode "offset=100"
```

- `total` vem `null`. Para o total exato, envie `incluir_total=true`; a contagem é mais lenta em buscas grandes.
- Páginas fundas são caras. Refine por UF, município, CNAE ou porte em vez de percorrer milhares de resultados.

## Cursor: oportunidades e despesas

[`/v1/oportunidades`](https://fonteza.com.br/docs/referencia/oportunidades.md) e [`/v1/empresas/{cnpj}/despesas-federais`](https://fonteza.com.br/docs/referencia/empresas-cnpj-despesas-federais.md) recebem `limit` (1 a 100, padrão 20) e `cursor`. Enquanto `has_more` for `true`, envie o `next_cursor` com os mesmos filtros.

- O cursor é opaco: não monte nem altere.
- Trocar filtros mantendo o cursor responde `422`.
- O cursor fica preso à publicação (`meta.geracao`). Se uma nova carga entrar no ar no meio da paginação, a resposta é `409 geracao_oportunidades_expirada` ou `409 geracao_despesas_expirada`: recomece sem cursor e descarte o que veio da publicação anterior.

Python:

```python
import os
import requests

URL = "https://api.fonteza.com.br/v1/oportunidades"
HEADERS = {"X-Api-Key": os.environ["FONTEZA_KEY"]}
FILTROS = {"q": "mobiliário", "status": "aberta", "limit": 100}


def todas_as_paginas():
    params = dict(FILTROS)
    while True:
        resposta = requests.get(URL, headers=HEADERS, params=params, timeout=30)
        if resposta.status_code == 409:
            params = dict(FILTROS)  # nova publicação: recomece do início
            continue
        resposta.raise_for_status()
        pagina = resposta.json()
        yield from pagina["items"]
        if not pagina["has_more"]:
            return
        params = {**FILTROS, "cursor": pagina["next_cursor"]}
```

JavaScript:

```javascript
const FILTROS = { q: "mobiliário", status: "aberta", limit: "100" };

async function* todasAsPaginas() {
  let params = { ...FILTROS };
  while (true) {
    const url = new URL("https://api.fonteza.com.br/v1/oportunidades");
    url.search = new URLSearchParams(params);
    const resposta = await fetch(url, { headers: { "X-Api-Key": process.env.FONTEZA_KEY } });
    if (resposta.status === 409) {
      params = { ...FILTROS }; // nova publicação: recomece do início
      continue;
    }
    if (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);
    const pagina = await resposta.json();
    yield* pagina.items;
    if (!pagina.has_more) return;
    params = { ...FILTROS, cursor: pagina.next_cursor };
  }
}
```

---

# Erros

Erros vêm em JSON com status HTTP. Quando há um código estável, ele está em `codigo`: trate pelo código, nunca pelo texto da mensagem.

## Status

| Status | Quando | O que fazer |
| --- | --- | --- |
| `401` | Chave ausente, inválida ou revogada. | Confira o header `X-Api-Key` e se a chave está ativa. |
| `404` | CNPJ não encontrado, rota inexistente ou método diferente de `GET`. | Confira CNPJ e rota. Um CNPJ fora da base não quer dizer que a empresa não existe. |
| `409` | Busca ampla que pede confirmação, ou cursor de uma publicação anterior. | Veja o `codigo`. |
| `422` | Parâmetro inválido ou recorte fora da cobertura publicada. | Corrija o parâmetro indicado. |
| `429` | Cota diária esgotada. | Pare até a virada do dia; não repita em laço. |
| `503` | Base em carga, conjunto sem publicação completa ou muitas consultas pesadas ao mesmo tempo. | Veja o `codigo`. Com `Retry-After`, espere esses segundos; nos demais, consulte `/v1/meta` e tente de novo com intervalo crescente. |
| `504` | Busca confirmada interrompida após 60 segundos. | Refine os filtros. |

Rotas inexistentes e métodos diferentes de `GET` respondem `404` em texto puro. Sem chave, uma rota inexistente responde `401`: a chave é verificada antes da rota.

## Formatos do corpo

Um cliente robusto trata os três:

Mensagem simples:

```json
{"detail": "limit deve estar entre 1 e 100"}
```

Com código:

```json
{
  "detail": {
    "codigo": "recorte_nao_coberto",
    "mensagem": "UF ou modalidade fora da janela PNCP publicada."
  }
}
```

Operacional:

```json
{
  "erro": true,
  "codigo": "base_indisponivel",
  "mensagem": "Base em carga inicial ou indisponível. Tente novamente em alguns minutos."
}
```

Python:

```python
def codigo_do_erro(corpo: dict) -> str | None:
    detail = corpo.get("detail")
    if isinstance(detail, dict):
        return detail.get("codigo")
    return corpo.get("codigo")
```

JavaScript:

```javascript
function codigoDoErro(corpo) {
  const detail = corpo?.detail;
  if (detail && typeof detail === "object") return detail.codigo ?? null;
  return corpo?.codigo ?? null;
}
```

## Códigos

| Código | Status | Significado e ação |
| --- | --- | --- |
| `busca_ampla_requer_confirmacao` | 409 | A busca varreria boa parte da base. Traz `custo_estimado` e `linhas_estimadas`. Refine ou repita com `confirmar_varredura=true`. |
| `busca_demorada` | 409, 504 | Passou do tempo limite (12 s, ou 60 s se confirmada). Com `pode_tentar_com_aprovacao: true`, repita com `confirmar_varredura=true`; senão, refine. |
| `municipio_uf_incompativel` | 422 | O município não existe na UF. Traz `ufs_validas` ou `sugestoes` de grafia. |
| `recorte_nao_coberto` | 422 | UF ou modalidade fora da janela PNCP publicada. Veja `meta.ufs` e `meta.modalidades`. |
| `geracao_oportunidades_expirada` | 409 | Nova publicação de oportunidades durante a paginação. Recomece sem cursor. |
| `geracao_despesas_expirada` | 409 | Nova publicação de despesas durante a paginação. Recomece sem cursor. |
| `oportunidades_indisponiveis` | 503 | Não há janela PNCP completa publicada. |
| `despesas_indisponiveis` | 503 | Não há janela de despesas completa publicada. |
| `api_sobrecarregada` | 503 | Há muitas consultas do mesmo tipo em andamento (buscas textuais, totais, páginas profundas, despesas). Vem com `Retry-After`: espere e repita; diminua o paralelismo do cliente. |
| `consulta_demorada` | 503 | A consulta passou do tempo limite e foi interrompida para proteger a base. Refine os filtros ou tente de novo. |
| `base_indisponivel` | 503 | Base em carga ou indisponível. Tente de novo em alguns minutos. |
| `base_carregando` | 503 | `/v1/meta`: o catálogo ainda está carregando. |

---

# Cobertura e procedência

Cada resposta diz qual publicação foi consultada e o que ela cobre. Assim você sabe se uma busca sem resultados quer dizer que algo não existe ou só que está fora do que foi publicado. Guarde `meta` junto com o dado.

## Campos de procedência

| Campo | Onde | Significado |
| --- | --- | --- |
| `fonte` | todas | Fonte oficial: `RFB/DadosAbertos`, `PNCP` ou `Portal da Transparência/Despesas Federais`. |
| `competencia` | empresas | Mês de referência da base da Receita (`AAAA-MM`). |
| `snapshot_id` | todas | Publicação consultada. Muda quando uma nova carga entra no ar. |
| `geracao` | oportunidades, despesas | Número da publicação atual; os cursores ficam presos a ela. |
| `cobertura`, `criterio`, `data_inicial`, `data_final` | oportunidades, despesas | A janela publicada: por qual data o recorte foi feito e quais dias inclui. |
| `ufs`, `modalidades` | oportunidades | UFs e modalidades na janela. Filtrar fora delas responde `422 recorte_nao_coberto`. |
| `datas_listadas`, `datas_coletadas` | despesas | Arquivos diários do Portal da Transparência que existiam e que foram coletados. |
| `evidencia`, `contrato_fonte` | oportunidades, despesas | Como o conjunto foi obtido e verificado. `fixture_sintetica` indica dados de teste. |

## Disponibilidade: /v1/meta

[`GET /v1/meta`](https://fonteza.com.br/docs/referencia/meta.md) é público e não consome cota. `data.fontes` tem uma entrada por conjunto: `rfb`, `pncp_oportunidades` e `transparencia_despesas`.

```bash
curl https://api.fonteza.com.br/v1/meta
```

| Campo | Valores |
| --- | --- |
| `estado` | `disponivel`; `desatualizado` (consultável, carga seguinte atrasada; motivo em `motivo_atraso`); `indisponivel`. |
| `cobertura` | `completa`, `parcial` ou `desconhecida`. |
| `disponivel` | `true` quando a publicação pode ser consultada. |
| `periodo`, `coletado_em`, `promovido_em` | Período coberto, data da coleta e data em que entrou no ar. |
| `registros` | Quantidade de registros publicados. |

`data.aviso` traz o aviso operacional vigente, como manutenção ou carga em andamento, e vem `null` quando não há nenhum.

Leia `/v1/meta` no início de cada rotina e guarde o `snapshot_id` de cada conjunto. Se não mudou, os resultados que você já tem continuam valendo.

## Quando a busca não encontra nada

- **Empresas.** `/v1/buscar` só devolve estabelecimentos ativos (situação `02`). Baixadas e inaptas aparecem em [`/v1/empresas/{cnpj}`](https://fonteza.com.br/docs/referencia/empresas-cnpj.md).
- **Oportunidades.** Só a janela, as UFs e as modalidades declaradas em `meta`. Nenhum resultado quer dizer só que nada foi encontrado nesse recorte.
- **Despesas federais.** Só os arquivos diários coletados. Nenhum resultado não quer dizer que a empresa nunca recebeu recursos federais.
- **Conjunto indisponível.** A rota responde `503` com código próprio, em vez de uma resposta sem resultados.

## Formatos da Receita Federal

Campos de empresa preservam a fonte: datas `AAAAMMDD`, capital social com vírgula decimal (`150000,00`), porte e situação cadastral em códigos da RFB. Os significados estão em [Obter o cadastro de uma empresa](https://fonteza.com.br/docs/referencia/empresas-cnpj.md).

---

# Buscar empresas

`GET /v1/buscar` · requer o header `X-Api-Key`

Busca estabelecimentos por nome, atividade, CNPJ ou CNAE, com filtros estruturados. Somente estabelecimentos com situação cadastral ativa (02) entram no resultado; para consultar um CNPJ em qualquer situação, use /v1/empresas/{cnpj}. Informe q, cnae ou os dois. Um CNPJ completo em q faz busca exata (modo cnpj); palavras usam busca textual em português sobre razão social, nome fantasia e descrição do CNAE (modo texto); quando nenhuma empresa corresponde ao texto, a busca tenta semelhança de grafia na razão social (modo fuzzy). meta.modo_busca informa o modo aplicado. O total exato é opcional (incluir_total) porque custa uma contagem completa. Cada item traz telefone e e-mail cadastrais; com tem_telefone e tem_email, uma página de até 100 contatos custa uma chamada. Toda busca é planejada antes de rodar, e meta.plano diz se ela é uma varredura ampla da base. Sem cnae, uma varredura ampla pede confirmação explícita (409 busca_ampla_requer_confirmacao). Com q e sem cnae, meta.cnaes_sugeridos traz os códigos CNAE das palavras de q (os mesmos de /v1/cnaes): filtrar por eles é a forma indexada, e rápida, de buscar uma atividade.

## Requisição

```bash
curl -G https://api.fonteza.com.br/v1/buscar \
  -H "X-Api-Key: $FONTEZA_KEY" \
  --data-urlencode "q=transportadora" \
  --data-urlencode "uf=SP"
```

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/buscar",
    params={"q": "transportadora", "uf": "SP"},
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

| Parâmetro | Tipo | Onde | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `q` | string | query | não | Nome, razão social, atividade ou CNPJ. Obrigatório se cnae não for informado. Ao menos 2 caracteres. |
| `uf` | string | query | não | Sigla da UF do estabelecimento. Até 2 caracteres. |
| `municipio` | string | query | não | Nome do município, sem diferenciar maiúsculas nem acentos (sao paulo equivale a São Paulo); hífens e apóstrofos contam. Envie também uf: sem ela, municípios homônimos de outras UFs entram no resultado. Nome desconhecido ou fora da UF informada devolve 422 com sugestões. |
| `cnae` | string | query | não | Código CNAE principal, ou até 10 códigos separados por vírgula (uma categoria de /v1/cnaes pode ter vários). Com sete dígitos busca o código exato; com menos, filtra pelo prefixo. Pontos, hífens e barras são ignorados (4721-1/02). |
| `somente_matriz` | boolean | query | não | Exclui filiais. Padrão: `false`. |
| `ordenar_por` | string | query | não | Valores: `relevancia`, `nome`, `municipio`, `uf`, `capital_social`, `data_inicio_atividade`, `cnae`. Padrão: `"relevancia"`. |
| `ordem` | string | query | não | Valores: `asc`, `desc`. Padrão: `"desc"`. |
| `porte` | string | query | não | Códigos de porte da RFB separados por vírgula: 00 não informado, 01 microempresa, 03 empresa de pequeno porte, 05 demais. |
| `capital_min` | number | query | não | Capital social mínimo em reais. Mínimo 0. |
| `capital_max` | number | query | não | Capital social máximo em reais; não pode ser menor que capital_min. Mínimo 0. |
| `data_inicio_de` | string (date) | query | não | Início de atividade a partir desta data (AAAA-MM-DD). |
| `data_inicio_ate` | string (date) | query | não | Início de atividade até esta data (AAAA-MM-DD). |
| `tem_email` | boolean | query | não | true exige e-mail cadastrado; false exige ausência. |
| `tem_telefone` | boolean | query | não | true exige telefone cadastrado; false exige ausência. |
| `simples` | string | query | não | Opção pelo Simples Nacional. Valores: `S`, `N`. |
| `mei` | string | query | não | Opção pelo MEI. Valores: `S`, `N`. |
| `incluir_total` | boolean | query | não | Calcula o total exato de resultados; sem ele, total vem null. Padrão: `false`. |
| `confirmar_varredura` | boolean | query | não | Confirma uma busca ampla recusada com 409 e amplia o tempo limite de 12 para 60 segundos. Padrão: `false`. |
| `limit` | integer | query | não | Entre 1 e 100. Padrão: `20`. |
| `offset` | integer | query | não | Deslocamento da página. Prefira refinar filtros a paginar fundo. Mínimo 0. Padrão: `0`. |

## Resposta 200

Página de resultados.

- `total` (integer | null): Total exato quando incluir_total=true; caso contrário null.
- `limit` (integer)
- `offset` (integer)
- `items` (array de objeto)
  - `cnpj` (string): CNPJ completo com 14 caracteres.
  - `razao_social` (string | null)
  - `nome_fantasia` (string | null, opcional)
  - `municipio` (string | null)
  - `uf` (string | null)
  - `cnae_principal` (string | null): Código CNAE de sete dígitos.
  - `cnae_principal_desc` (string | null, opcional)
  - `porte` (string | null): 00 não informado, 01 microempresa, 03 empresa de pequeno porte, 05 demais.
  - `situacao_cadastral` (string | null): Sempre 02 (ativa) nesta rota.
  - `opcao_simples` (string | null): S ou N.
  - `matriz_filial` (string | null): 1 matriz, 2 filial.
  - `telefone` (string | null): DDD e número do primeiro telefone cadastral, sem formatação; vazio quando ausente.
  - `email` (string | null): E-mail cadastral declarado à RFB.
  - `score` (number): Relevância relativa dentro desta busca; não compare entre buscas.
- `has_more` (boolean): Indica que há ao menos mais um resultado após esta página.
- `meta` (objeto)
  - `fonte` (array de string)
  - `competencia` (string | null, opcional)
  - `snapshot_id` (string | null, opcional)
  - `total_calculado` (boolean): Indica se total foi calculado (incluir_total=true).
  - `modo_busca` (string): Estratégia aplicada: CNPJ exato, texto, semelhança de grafia ou somente CNAE. Valores: `cnpj`, `texto`, `fuzzy`, `cnae`.
  - `plano` (objeto, opcional): Estimativa do PostgreSQL (EXPLAIN) para a busca, calculada em toda busca. varredura_ampla diz se a leitura mais cara de empresas_busca atinge limite_custo; sem cnae (exige_confirmacao), essa busca responde 409 até receber confirmar_varredura=true. O custo considera o limit da página quando a leitura para assim que a página enche (um índice percorrido em ordem); ordenações e contagens (incluir_total) leem tudo e contam inteiras.
    - `varredura_ampla` (boolean)
    - `custo_estimado` (number): Custo estimado da leitura mais cara de empresas_busca, como ela vai rodar.
    - `custo_leitura_completa` (number): Custo da mesma leitura lida até o fim, sem o limit da página; para comparação.
    - `linhas_estimadas` (integer): Linhas estimadas nessa leitura.
    - `limite_custo` (number)
    - `exige_confirmacao` (boolean): Se a regra de confirmação vale para esta busca (vale sem cnae).
  - `cnaes_sugeridos` (array de objeto, opcional): Com q e sem cnae: os códigos CNAE das palavras de q, como em /v1/cnaes. Vazio para um nome de empresa.
    - `rotulo` (string): Nome da categoria curada ou descrição oficial da subclasse.
    - `codigos` (array de string): Subclasses CNAE 2.3; envie-as em cnae separadas por vírgula.
    - `descricoes` (array de string): Descrição oficial de cada código, na mesma ordem.
    - `origem` (string): Valores: `categoria`, `ibge`.
    - `termo` (string, opcional): Sinônimo ou atividade do IBGE que correspondeu à consulta.
    - `nota` (string, opcional)
    - `aproximado` (boolean, opcional): Nenhuma categoria tem todas as palavras; esta tem a palavra da atividade.

Exemplo (Transportadoras em SP):

```json
{
  "total": null,
  "limit": 20,
  "offset": 0,
  "items": [
    {
      "cnpj": "12345678000190",
      "razao_social": "TRANSPORTADORA EXEMPLO LTDA",
      "nome_fantasia": "EXEMPLO CARGAS",
      "municipio": "CAMPINAS",
      "uf": "SP",
      "cnae_principal": "4930202",
      "cnae_principal_desc": "Transporte rodoviário de carga, exceto produtos perigosos e mudanças, intermunicipal, interestadual e internacional",
      "porte": "03",
      "situacao_cadastral": "02",
      "opcao_simples": "S",
      "matriz_filial": "1",
      "telefone": "1932345678",
      "email": "contato@transportadoraexemplo.com.br",
      "score": 0.83
    }
  ],
  "has_more": false,
  "meta": {
    "fonte": [
      "RFB/DadosAbertos"
    ],
    "competencia": "2026-09",
    "snapshot_id": "rfb-2026-09-3f9c2a7d41b8e605",
    "total_calculado": false,
    "modo_busca": "texto",
    "plano": {
      "varredura_ampla": false,
      "custo_estimado": 1834.2,
      "custo_leitura_completa": 1834.2,
      "linhas_estimadas": 412,
      "limite_custo": 100000,
      "exige_confirmacao": true
    },
    "cnaes_sugeridos": [
      {
        "rotulo": "Caminhoneiros e transportadoras",
        "codigos": [
          "4930201",
          "4930202",
          "4930203"
        ],
        "descricoes": [
          "Transporte rodoviário de carga",
          "exceto produtos perigosos e mudanças",
          "municipal",
          "Transporte rodoviário de carga, exceto produtos perigosos e mudanças, intermunicipal, interestadual e internacional",
          "Transporte rodoviário de produtos perigosos"
        ],
        "origem": "categoria",
        "termo": "transportadora",
        "nota": "Maioria MEI: são sobretudo caminhoneiros autônomos."
      }
    ]
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 401 | Chave ausente, inválida ou revogada. |  |
| 409 | Busca ampla demais para executar sem confirmação, ou interrompida após 12 segundos. Refine os filtros, de preferência com um dos cnaes_sugeridos, ou repita com confirmar_varredura=true. | `busca_ampla_requer_confirmacao` `busca_demorada` |
| 422 | Parâmetro inválido ou município incompatível com a UF. | `municipio_uf_incompativel` |
| 429 | Cota diária da chave atingida (1.000 chamadas por padrão). Toda requisição autenticada conta, inclusive as que terminam em erro e as recusadas por cota. A contagem renova na virada do dia da base. A resposta não traz Retry-After. |  |
| 503 | Autenticação indisponível, base indisponível, consulta interrompida pelo tempo limite (consulta_demorada) ou, nas buscas, consultas demais do mesmo tipo em andamento (api_sobrecarregada). | `base_indisponivel` `api_sobrecarregada` `consulta_demorada` |
| 504 | Busca confirmada interrompida após 60 segundos. | `busca_demorada` |

---

# Obter o cadastro de uma empresa

`GET /v1/empresas/{cnpj}` · requer o header `X-Api-Key`

Cadastro completo de um estabelecimento e o quadro de sócios da empresa (raiz do CNPJ), na competência RFB publicada. Inclui estabelecimentos em qualquer situação cadastral. Os campos preservam os códigos e formatos da Receita Federal: datas em AAAAMMDD e capital social com vírgula decimal.

## Requisição

```bash
curl https://api.fonteza.com.br/v1/empresas/12345678000190 \
  -H "X-Api-Key: $FONTEZA_KEY"
```

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/empresas/12345678000190",
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

| Parâmetro | Tipo | Onde | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `cnpj` | string | caminho | sim | CNPJ completo com 14 caracteres. Pontos e hífen são removidos e letras viram maiúsculas (CNPJ alfanumérico); a barra não é aceita no caminho e resulta em 404. Os dígitos verificadores não são conferidos. Ao menos 14 caracteres. |

## Resposta 200

Cadastro e sócios.

- `data` (objeto)
  - `cnpj` (string): CNPJ completo com 14 caracteres.
  - `cnpj_basico` (string): Raiz do CNPJ (8 primeiros caracteres), comum a matriz e filiais.
  - `razao_social` (string | null)
  - `nome_fantasia` (string | null)
  - `matriz_filial` (string | null): 1 matriz, 2 filial.
  - `situacao_cadastral` (string | null): Código RFB: 01 nula, 02 ativa, 03 suspensa, 04 inapta, 08 baixada.
  - `data_inicio_atividade` (string | null): Data no formato RFB AAAAMMDD.
  - `cnae_principal` (string | null): Código CNAE de sete dígitos.
  - `cnae_principal_desc` (string | null)
  - `municipio` (string | null)
  - `uf` (string | null)
  - `cep` (string | null): Oito dígitos, sem hífen.
  - `telefone` (string | null): DDD e número do primeiro telefone cadastral, sem formatação; vazio quando ausente.
  - `email` (string | null): E-mail cadastral declarado à RFB.
  - `natureza_juridica` (string | null): Descrição da natureza jurídica.
  - `porte` (string | null): 00 não informado, 01 microempresa, 03 empresa de pequeno porte, 05 demais.
  - `capital_social` (string | null): Valor em reais no formato RFB, com vírgula decimal (ex. 150000,00).
  - `opcao_simples` (string | null): S ou N.
  - `opcao_mei` (string | null): S ou N.
  - `qtde_socios` (integer | null)
  - `socios` (array de objeto)
    - `nome` (string | null)
    - `cpf_mascarado` (string | null): Documento do sócio como publicado pela RFB: CPF mascarado para pessoa física, CNPJ para pessoa jurídica.
    - `qualificacao` (string | null): Código RFB da qualificação do sócio (ex. 49 sócio-administrador).
    - `desde` (string | null): Data de entrada na sociedade, formato AAAAMMDD.
- `meta` (objeto)
  - `fonte` (array de string)
  - `competencia` (string | null, opcional)
  - `snapshot_id` (string | null, opcional)

Exemplo (Cadastro com sócios):

```json
{
  "data": {
    "cnpj": "12345678000190",
    "cnpj_basico": "12345678",
    "razao_social": "TRANSPORTADORA EXEMPLO LTDA",
    "nome_fantasia": "EXEMPLO CARGAS",
    "matriz_filial": "1",
    "situacao_cadastral": "02",
    "data_inicio_atividade": "20150312",
    "cnae_principal": "4930202",
    "cnae_principal_desc": "Transporte rodoviário de carga, exceto produtos perigosos e mudanças, intermunicipal, interestadual e internacional",
    "municipio": "CAMPINAS",
    "uf": "SP",
    "cep": "13050000",
    "telefone": "1932345678",
    "email": "contato@transportadoraexemplo.com.br",
    "natureza_juridica": "Sociedade Empresária Limitada",
    "porte": "03",
    "capital_social": "150000,00",
    "opcao_simples": "S",
    "opcao_mei": "N",
    "qtde_socios": 1,
    "socios": [
      {
        "nome": "FULANA DE TAL",
        "cpf_mascarado": "***123456**",
        "qualificacao": "49",
        "desde": "20150312"
      }
    ]
  },
  "meta": {
    "fonte": [
      "RFB/DadosAbertos"
    ],
    "competencia": "2026-09",
    "snapshot_id": "rfb-2026-09-3f9c2a7d41b8e605"
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 401 | Chave ausente, inválida ou revogada. |  |
| 404 | Registro não encontrado. |  |
| 422 | Parâmetro inválido. |  |
| 429 | Cota diária da chave atingida (1.000 chamadas por padrão). Toda requisição autenticada conta, inclusive as que terminam em erro e as recusadas por cota. A contagem renova na virada do dia da base. A resposta não traz Retry-After. |  |
| 503 | Autenticação indisponível, base indisponível, consulta interrompida pelo tempo limite (consulta_demorada) ou, nas buscas, consultas demais do mesmo tipo em andamento (api_sobrecarregada). | `base_indisponivel` `api_sobrecarregada` `consulta_demorada` |

---

# Buscar oportunidades no PNCP

`GET /v1/oportunidades` · requer o header `X-Api-Key`

Descoberta por palavras e filtros explícitos na janela PNCP publicada. O status deriva do código de situação da fonte e dos prazos de proposta avaliados no instante fixado em meta. Fornecedores aparecem somente quando um resultado coletado identifica uma PJ por CNPJ exato; a publicação do edital não identifica vencedor. Nenhum resultado quer dizer só que nada corresponde ao recorte publicado, não que a oportunidade não existe. O exemplo é uma resposta oficial observada em um recorte limitado (um dia, DF, uma modalidade).

## Requisição

```bash
curl -G https://api.fonteza.com.br/v1/oportunidades \
  -H "X-Api-Key: $FONTEZA_KEY" \
  --data-urlencode "q=mobiliário"
```

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/oportunidades",
    params={"q": "mobiliário"},
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

| Parâmetro | Tipo | Onde | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `q` | string | query | não | Palavras no objeto e nas descrições dos itens; não aplica CNAE. De 2 a 120 caracteres. |
| `uf` | string | query | não | UF explícita; deve pertencer ao manifesto da janela publicada. Formato: `^[A-Z]{2}$`. |
| `municipio_ibge` | string | query | não | Código IBGE de sete dígitos informado pelo PNCP. Formato: `^[0-9]{7}$`. |
| `modalidade` | integer | query | não | Código PNCP explícito; deve pertencer ao manifesto da janela publicada. Mínimo 1. |
| `status` | string | query | não | Valores: `futura`, `aberta`, `prazo_encerrado`, `prazo_indeterminado`, `revogada`, `anulada`, `suspensa`, `desconhecida`. |
| `prazo_de` | string (date) | query | não | Primeiro dia inclusivo do encerramento no calendário de Brasília. |
| `prazo_ate` | string (date) | query | não | Último dia inclusivo do encerramento no calendário de Brasília. |
| `limit` | integer | query | não | Entre 1 e 100. Padrão: `20`. |
| `cursor` | string | query | não | Cursor opaco vinculado à geração, aos filtros e a avaliado_em. |

## Resposta 200

Página vinculada à geração, aos filtros e ao instante de avaliação.

- `items` (array de objeto)
  - `numero_controle_pncp` (string)
  - `numero_compra` (string | null)
  - `ano_compra` (integer)
  - `objeto` (string)
  - `itens_resumo` (array de objeto)
    - `numero_item` (integer)
    - `descricao` (string)
    - `situacao_fonte` (string | null)
  - `itens_total` (integer)
  - `contratante` (objeto)
    - `cnpj` (string)
    - `nome` (string | null)
  - `localidade` (objeto)
    - `uf` (string)
    - `municipio_ibge` (string | null)
    - `municipio` (string | null)
  - `modalidade` (objeto)
    - `codigo` (integer)
    - `nome` (string | null)
  - `situacao_fonte` (objeto)
    - `codigo` (integer | null)
    - `nome` (string | null)
  - `status` (string): Valores: `futura`, `aberta`, `prazo_encerrado`, `prazo_indeterminado`, `revogada`, `anulada`, `suspensa`, `desconhecida`.
  - `data_abertura_proposta` (string | null (date-time))
  - `data_encerramento_proposta` (string | null (date-time))
  - `data_publicacao_pncp` (string (date-time))
  - `data_atualizacao` (string | null (date-time))
  - `valor_total_estimado` (string | null)
  - `url_fonte` (string (uri))
  - `fornecedores_identificados` (array de objeto)
    - `cnpj` (string)
    - `nome` (string | null)
    - `numero_item` (integer)
    - `situacao_resultado` (objeto)
      - `codigo` (integer | null)
      - `nome` (string | null)
    - `data_resultado` (string | null (date-time))
    - `data_cancelamento` (string | null (date-time))
    - `motivo_cancelamento` (string | null)
    - `url_fonte` (string (uri))
  - `fornecedores_total` (integer)
- `has_more` (boolean)
- `next_cursor` (string | null)
- `meta` (objeto)
  - `fonte` ("PNCP")
  - `conjunto` ("pncp-oportunidades")
  - `snapshot_id` (string)
  - `geracao` (integer)
  - `cobertura` ("janela_completa_recortada")
  - `criterio` ("data_publicacao_pncp")
  - `data_inicial` (string (date))
  - `data_final` (string (date))
  - `ufs` (array de string)
  - `modalidades` (array de integer)
  - `consultado_em` (string (date-time))
  - `avaliado_em` (string (date-time))
  - `evidencia` (string): Valores: `fixture_sintetica`, `live_nao_verificada`, `pncp_live_contrato_2026-09-25`.
  - `contrato_fonte` (string): Valores: `documentado_nao_verificado_live`, `openapi_oficial_capturado`.

Exemplo (Resposta oficial observada em 2026-09-25 para 2026-09-24, DF e modalidade 1 somente):

```json
{
  "items": [
    {
      "numero_controle_pncp": "00489828000902-1-000019/2026",
      "numero_compra": "SPU/DF 020",
      "ano_compra": 2026,
      "objeto": "Alienação, mediante venda, de imóvel(eis) sob gestão da Secretaria do Patrimônio da União",
      "itens_resumo": [
        {
          "numero_item": 1,
          "descricao": "Apartamento - AOS 4 BLOCO D, 4 - Apartamento 313 da AOS 04 Bloco D - AREA OCTOGONAL - CEP: 70660-044 - Brasília - DF",
          "situacao_fonte": "Em andamento"
        }
      ],
      "itens_total": 1,
      "contratante": {
        "cnpj": "00489828000902",
        "nome": "MINISTERIO DA GESTAO E DA INOVACAO EM SERVICOS PUBLICOS"
      },
      "localidade": {
        "uf": "DF",
        "municipio_ibge": "5300108",
        "municipio": "Brasília"
      },
      "modalidade": {
        "codigo": 1,
        "nome": "Leilão - Eletrônico"
      },
      "situacao_fonte": {
        "codigo": 1,
        "nome": "Divulgada no PNCP"
      },
      "status": "aberta",
      "data_abertura_proposta": "2026-09-24T20:00:00+02:00",
      "data_encerramento_proposta": "2026-11-05T13:59:00+01:00",
      "data_publicacao_pncp": "2026-09-24T16:50:31+02:00",
      "data_atualizacao": "2026-09-24T16:50:31+02:00",
      "valor_total_estimado": "633434.97",
      "url_fonte": "https://pncp.gov.br/api/consulta/v1/orgaos/00489828000902/compras/2026/19",
      "fornecedores_identificados": [],
      "fornecedores_total": 0
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "meta": {
    "fonte": "PNCP",
    "conjunto": "pncp-oportunidades",
    "snapshot_id": "pncpo-20260924-20260924-529544838db8de16",
    "geracao": 1,
    "cobertura": "janela_completa_recortada",
    "criterio": "data_publicacao_pncp",
    "data_inicial": "2026-09-24",
    "data_final": "2026-09-24",
    "ufs": [
      "DF"
    ],
    "modalidades": [
      1
    ],
    "consultado_em": "2026-09-25T11:53:26.251698Z",
    "avaliado_em": "2026-09-25T12:06:33.524502419Z",
    "evidencia": "pncp_live_contrato_2026-09-25",
    "contrato_fonte": "openapi_oficial_capturado"
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 401 | Chave ausente, inválida ou revogada. |  |
| 409 | A geração do cursor deixou de ser a publicação atual. | `geracao_oportunidades_expirada` |
| 422 | Parâmetro ou cursor inválido, ou recorte fora da cobertura publicada. | `recorte_nao_coberto` |
| 429 | Cota diária da chave atingida (1.000 chamadas por padrão). Toda requisição autenticada conta, inclusive as que terminam em erro e as recusadas por cota. A contagem renova na virada do dia da base. A resposta não traz Retry-After. |  |
| 503 | Oportunidades sem janela completa publicada, autenticação indisponível, base indisponível, consulta interrompida pelo tempo limite ou consultas demais em andamento. | `oportunidades_indisponiveis` `base_indisponivel` `api_sobrecarregada` `consulta_demorada` |

---

# Listar despesas federais de uma empresa

`GET /v1/empresas/{cnpj}/despesas-federais` · requer o header `X-Api-Key`

Documentos de empenho, liquidação e pagamento nos arquivos diários publicados do Portal da Transparência. O escopo raiz inclui estabelecimentos com a mesma raiz e o escopo estabelecimento exige CNPJ exato; cada aparição conserva o CNPJ e o papel do favorecido. Fases e medidas monetárias permanecem separadas. Nenhum resultado quer dizer só que nada apareceu na janela de arquivos declarada em meta, não que a empresa nunca recebeu recursos federais. Não há chave PNCP oficial verificada neste contrato batch e nenhum vínculo é inferido. Exemplos ilustrativos; não representam conciliação oficial nem cobertura histórica completa.

## Requisição

```bash
curl https://api.fonteza.com.br/v1/empresas/11222333000181/despesas-federais \
  -H "X-Api-Key: $FONTEZA_KEY"
```

```python
import os
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/empresas/11222333000181/despesas-federais",
    headers={"X-Api-Key": os.environ["FONTEZA_KEY"]},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

| Parâmetro | Tipo | Onde | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `cnpj` | string | caminho | sim | CNPJ normalizado do favorecido; 12 caracteres alfanuméricos e dois dígitos verificadores. Formato: `^[0-9A-Z]{12}[0-9]{2}$`. |
| `escopo` | string | query | não | Raiz consulta CNPJs com os mesmos oito primeiros caracteres; estabelecimento exige CNPJ exato. Valores: `raiz`, `estabelecimento`. Padrão: `"raiz"`. |
| `fase` | string | query | não | Fase documental oficial; fases não são somadas entre si. Valores: `empenho`, `liquidacao`, `pagamento`. |
| `data_de` | string (date) | query | não | Primeira data de emissão, inclusive; não amplia a janela de arquivos publicada. |
| `data_ate` | string (date) | query | não | Última data de emissão, inclusive; não amplia a janela de arquivos publicada. |
| `limit` | integer | query | não | Entre 1 e 100. Padrão: `20`. |
| `cursor` | string | query | não | Cursor opaco vinculado à geração, ao CNPJ, ao escopo e aos filtros. |

## Resposta 200

Página vinculada a uma única geração publicada e aos filtros da consulta.

- `items` (array de objeto)
  - `fase` (string): Valores: `empenho`, `liquidacao`, `pagamento`.
  - `codigo_documento` (string): Código SIAFI completo de 23 caracteres (UG, gestão e número do documento).
  - `id_empenho` (string | null)
  - `exercicio` (integer)
  - `data_emissao` (string (date))
  - `tipo_documento` (objeto)
    - `codigo` (string | null)
    - `nome` (string | null)
  - `favorecido` (objeto)
    - `cnpj` (string)
    - `nome` (string | null)
    - `papel` (string): Valores: `direto`, `final`.
  - `orgao` (objeto)
    - `codigo` (string | null)
    - `nome` (string | null)
  - `unidade_gestora` (objeto)
    - `codigo` (string | null)
    - `nome` (string | null)
    - `codigo_gestao` (string | null)
    - `gestao` (string | null)
  - `processo` (string | null)
  - `observacao` (string | null)
  - `valores` (objeto): Medidas distintas preservadas por fase; não devem ser somadas entre si.
    - `documento_brl` (string | null)
    - `empenhos_impactados_brl` (string | null)
    - `favorecido_final_brl` (string | null)
  - `url_fonte` (string (uri))
  - `vinculo_pncp` (objeto): O contrato batch verificado não contém chave PNCP; nenhuma ligação é inferida.
    - `situacao` ("sem_chave_oficial")
    - `numero_controle_pncp` (null)
  - `arquivo_data` (string (date))
- `has_more` (boolean)
- `next_cursor` (string | null)
- `meta` (objeto)
  - `fonte` ("Portal da Transparência/Despesas Federais")
  - `conjunto` ("transparencia-despesas")
  - `snapshot_id` (string)
  - `geracao` (integer)
  - `cobertura` ("janela_arquivos_completa")
  - `criterio` ("data_arquivo")
  - `data_inicial` (string (date))
  - `data_final` (string (date))
  - `datas_listadas` (array de string (date))
  - `datas_coletadas` (array de string (date))
  - `manifesto_sha256` (string)
  - `coletado_em` (string (date-time))
  - `evidencia` (string): Valores: `fixture_sintetica`, `arquivo_oficial_nao_conciliado`.
  - `contrato_fonte` ("dicionario_lote_documentado")
  - `vinculos_pncp` (objeto)
    - `com_chave_oficial` (0)
    - `sem_chave_oficial` (integer)

Exemplo (Pagamento sem vínculo PNCP inferido):

```json
{
  "items": [
    {
      "fase": "pagamento",
      "codigo_documento": "150002000012026OB000123",
      "id_empenho": null,
      "exercicio": 2026,
      "data_emissao": "2026-09-22",
      "tipo_documento": {
        "codigo": "OB",
        "nome": "Ordem Bancária"
      },
      "favorecido": {
        "cnpj": "11222333000181",
        "nome": "FORNECEDOR EXEMPLO LTDA",
        "papel": "direto"
      },
      "orgao": {
        "codigo": "26000",
        "nome": "Ministério da Educação"
      },
      "unidade_gestora": {
        "codigo": "150002",
        "nome": "UNIDADE GESTORA EXEMPLO/MEC",
        "codigo_gestao": "00001",
        "gestao": "TESOURO NACIONAL"
      },
      "processo": "23000.012345/2026-11",
      "observacao": "Pagamento referente à nota fiscal 1234, serviços de transporte de agosto/2026",
      "valores": {
        "documento_brl": "120.00",
        "empenhos_impactados_brl": "120.00",
        "favorecido_final_brl": null
      },
      "url_fonte": "https://portaldatransparencia.gov.br/despesas/pagamento/150002000012026OB000123",
      "vinculo_pncp": {
        "situacao": "sem_chave_oficial",
        "numero_controle_pncp": null
      },
      "arquivo_data": "2026-09-22"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "meta": {
    "fonte": "Portal da Transparência/Despesas Federais",
    "conjunto": "transparencia-despesas",
    "snapshot_id": "desp-20260922-20260922-8d41f07a2c96b3e5",
    "geracao": 1,
    "cobertura": "janela_arquivos_completa",
    "criterio": "data_arquivo",
    "data_inicial": "2026-09-22",
    "data_final": "2026-09-22",
    "datas_listadas": [
      "2026-09-22"
    ],
    "datas_coletadas": [
      "2026-09-22"
    ],
    "manifesto_sha256": "8d41f07a2c96b3e5d2a0c7f91e4b6a38f5c09d27b1e83a4c6f72d05e9ab14c63",
    "coletado_em": "2026-09-23T12:00:00Z",
    "evidencia": "arquivo_oficial_nao_conciliado",
    "contrato_fonte": "dicionario_lote_documentado",
    "vinculos_pncp": {
      "com_chave_oficial": 0,
      "sem_chave_oficial": 1
    }
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 401 | Chave ausente, inválida ou revogada. |  |
| 409 | A geração do cursor deixou de ser a publicação atual. | `geracao_despesas_expirada` |
| 422 | CNPJ, filtro ou cursor inválido. |  |
| 429 | Cota diária da chave atingida (1.000 chamadas por padrão). Toda requisição autenticada conta, inclusive as que terminam em erro e as recusadas por cota. A contagem renova na virada do dia da base. A resposta não traz Retry-After. |  |
| 503 | Despesas sem janela completa publicada, contrato inconsistente, base indisponível, consulta interrompida pelo tempo limite ou consultas demais em andamento. | `despesas_indisponiveis` `base_indisponivel` `api_sobrecarregada` `consulta_demorada` |

---

# Consultar publicações e cobertura

`GET /v1/meta` · pública, sem chave

Devolve a competência da base RFB e, em data.fontes, o estado de cada conjunto (disponivel, desatualizado ou indisponivel), a cobertura, o período e as datas de coleta e promoção. Consulte antes de interpretar uma busca sem resultados: com o conjunto indisponível ou parcial, nenhum resultado não quer dizer que o fato não existe. data.aviso traz o aviso operacional vigente (manutenção, carga em andamento) ou null.

## Requisição

```bash
curl https://api.fonteza.com.br/v1/meta
```

```python
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/meta",
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

Esta rota não recebe parâmetros.

## Resposta 200

Competência e disponibilidade das fontes.

- `data` (objeto)
  - `competencia` (string | null)
  - `aviso` (string | null): Aviso operacional vigente, como manutenção ou carga em andamento; null quando não há.
  - `total_linhas` (integer | null)
  - `promovido_em` (string | null (date-time))
  - `snapshot_id` (string | null)
  - `fonte` (array de string)
  - `fontes` (objeto)
    - `rfb` (objeto)
      - `estado` (string): Valores: `disponivel`, `desatualizado`, `indisponivel`.
      - `cobertura` (string): Valores: `completa`, `parcial`, `desconhecida`.
      - `disponivel` (boolean): Verdadeiro para publicação completa e consultável; RFB legada permanece consultável com cobertura desconhecida.
      - `snapshot_id` (string | null)
      - `periodo` (string | null)
      - `coletado_em` (string | null (date-time))
      - `promovido_em` (string | null (date-time))
      - `ultima_tentativa` (string | null (date-time))
      - `ultima_carga_valida` (string | null (date-time))
      - `motivo_atraso` (string | null): Valores: `cadencia_excedida`.
      - `registros` (integer)
      - `registros_visiveis` (integer | null, opcional)
      - `evidencia` (string | null, opcional): Valores: `fixture_sintetica`, `live_nao_verificada`, `pncp_live_contrato_2026-09-25`, `arquivo_oficial_nao_conciliado`.
      - `contrato_fonte` (string | null, opcional): Valores: `documentado_nao_verificado_live`, `openapi_oficial_capturado`, `dicionario_lote_documentado`.
    - `pncp_oportunidades` (objeto): Disponível somente quando evidência e contrato PNCP formam um par aceito; recorte continua limitado ao snapshot publicado.
      - `estado` (string): Valores: `disponivel`, `desatualizado`, `indisponivel`.
      - `cobertura` (string): Valores: `completa`, `parcial`, `desconhecida`.
      - `disponivel` (boolean): Verdadeiro para publicação completa e consultável; RFB legada permanece consultável com cobertura desconhecida.
      - `snapshot_id` (string | null)
      - `periodo` (string | null)
      - `coletado_em` (string | null (date-time))
      - `promovido_em` (string | null (date-time))
      - `ultima_tentativa` (string | null (date-time))
      - `ultima_carga_valida` (string | null (date-time))
      - `motivo_atraso` (string | null): Valores: `cadencia_excedida`.
      - `registros` (integer)
      - `registros_visiveis` (integer | null)
      - `evidencia` (string | null): Valores: `fixture_sintetica`, `live_nao_verificada`, `pncp_live_contrato_2026-09-25`, `arquivo_oficial_nao_conciliado`.
      - `contrato_fonte` (string | null): Valores: `documentado_nao_verificado_live`, `openapi_oficial_capturado`, `dicionario_lote_documentado`.
    - `transparencia_despesas` (objeto)
      - `estado` (string): Valores: `disponivel`, `desatualizado`, `indisponivel`.
      - `cobertura` (string): Valores: `completa`, `parcial`, `desconhecida`.
      - `disponivel` (boolean): Verdadeiro para publicação completa e consultável; RFB legada permanece consultável com cobertura desconhecida.
      - `snapshot_id` (string | null)
      - `periodo` (string | null)
      - `coletado_em` (string | null (date-time))
      - `promovido_em` (string | null (date-time))
      - `ultima_tentativa` (string | null (date-time))
      - `ultima_carga_valida` (string | null (date-time))
      - `motivo_atraso` (string | null): Valores: `cadencia_excedida`.
      - `registros` (integer)
      - `registros_visiveis` (integer | null, opcional)
      - `evidencia` (string | null): Valores: `fixture_sintetica`, `live_nao_verificada`, `pncp_live_contrato_2026-09-25`, `arquivo_oficial_nao_conciliado`.
      - `contrato_fonte` (string | null): Valores: `documentado_nao_verificado_live`, `openapi_oficial_capturado`, `dicionario_lote_documentado`.
- `meta` (objeto)
  - `fonte` (array de string)
  - `competencia` (string | null, opcional)
  - `snapshot_id` (string | null, opcional)

Exemplo (RFB validada; demais fontes ainda sem carga publicada):

```json
{
  "data": {
    "competencia": "2026-09",
    "total_linhas": 61234567,
    "promovido_em": "2026-10-06T10:00:00Z",
    "snapshot_id": "rfb-2026-09-3f9c2a7d41b8e605",
    "aviso": null,
    "fonte": [
      "RFB/DadosAbertos"
    ],
    "fontes": {
      "rfb": {
        "estado": "disponivel",
        "cobertura": "completa",
        "disponivel": true,
        "snapshot_id": "rfb-2026-09-3f9c2a7d41b8e605",
        "periodo": "2026-09",
        "coletado_em": "2026-10-06T09:00:00Z",
        "promovido_em": "2026-10-06T10:00:00Z",
        "ultima_tentativa": "2026-10-06T08:30:00Z",
        "ultima_carga_valida": "2026-10-06T09:00:00Z",
        "motivo_atraso": null,
        "registros": 61234567
      },
      "pncp_oportunidades": {
        "estado": "indisponivel",
        "cobertura": "desconhecida",
        "disponivel": false,
        "snapshot_id": null,
        "periodo": null,
        "coletado_em": null,
        "promovido_em": null,
        "ultima_tentativa": null,
        "ultima_carga_valida": null,
        "motivo_atraso": null,
        "registros": 0,
        "registros_visiveis": null,
        "evidencia": null,
        "contrato_fonte": null
      },
      "transparencia_despesas": {
        "estado": "indisponivel",
        "cobertura": "desconhecida",
        "disponivel": false,
        "snapshot_id": null,
        "periodo": null,
        "coletado_em": null,
        "promovido_em": null,
        "ultima_tentativa": null,
        "ultima_carga_valida": null,
        "motivo_atraso": null,
        "registros": 0,
        "evidencia": null,
        "contrato_fonte": null
      }
    }
  },
  "meta": {
    "fonte": [
      "RFB/DadosAbertos"
    ]
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 503 | Base ainda não disponível. | `base_indisponivel` |

---

# Verificar o processo

`GET /healthz` · pública, sem chave

Confirma que o processo da API está de pé, sem consultar a base. Para saber se os dados estão disponíveis, use /v1/meta.

## Requisição

```bash
curl https://api.fonteza.com.br/healthz
```

```python
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/healthz",
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

Esta rota não recebe parâmetros.

## Resposta 200

Processo disponível.

- `status` ("ok")
- `base` (string)

---

# Encontrar códigos CNAE por atividade

`GET /v1/cnaes` · pública, sem chave

Traduz as palavras de uma atividade ("restaurante", "dentista", "pet shop") em subclasses da CNAE 2.3, para usar no filtro cnae de /v1/buscar. Procura primeiro em categorias curadas com sinônimos do dia a dia (origem categoria, que pode reunir vários códigos) e depois no índice oficial de atividades do IBGE (origem ibge, um código). Dígitos buscam pelo código ("5611", "5611-2/01"). Quando nenhuma categoria tem todas as palavras, devolve as que têm a palavra da atividade e marca aproximado; um nome de empresa não devolve nada. Rota pública: não usa chave nem cota, e não consulta a base.

## Requisição

```bash
curl -G https://api.fonteza.com.br/v1/cnaes \
  --data-urlencode "q=restaurante"
```

```python
import requests

resposta = requests.get(
    "https://api.fonteza.com.br/v1/cnaes",
    params={"q": "restaurante"},
    timeout=30,
)
resposta.raise_for_status()
dados = resposta.json()
```

## Parâmetros

| Parâmetro | Tipo | Onde | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `q` | string | query | sim | Atividade, profissão ou código CNAE. De 1 a 100 caracteres. |
| `limit` | integer | query | não | Entre 1 e 10. Padrão: `10`. |
| `completar` | boolean | query | não | Para digitação: a última palavra pode estar incompleta (restau). Padrão: `false`. |

## Resposta 200

Sugestões, a melhor primeiro; lista vazia quando nada corresponde.

- `items` (array de objeto)
  - `rotulo` (string): Nome da categoria curada ou descrição oficial da subclasse.
  - `codigos` (array de string): Subclasses CNAE 2.3; envie-as em cnae separadas por vírgula.
  - `descricoes` (array de string): Descrição oficial de cada código, na mesma ordem.
  - `origem` (string): Valores: `categoria`, `ibge`.
  - `termo` (string, opcional): Sinônimo ou atividade do IBGE que correspondeu à consulta.
  - `nota` (string, opcional)
  - `aproximado` (boolean, opcional): Nenhuma categoria tem todas as palavras; esta tem a palavra da atividade.
- `meta` (objeto)
  - `fonte` (array de string)
  - `classificacao` (string)

Exemplo (Uma categoria curada e uma subclasse do IBGE):

```json
{
  "items": [
    {
      "rotulo": "Restaurantes",
      "codigos": [
        "5611201"
      ],
      "descricoes": [
        "Restaurantes e similares"
      ],
      "origem": "categoria",
      "termo": "restaurante",
      "nota": "Pizzarias, churrascarias e japoneses não têm código próprio: todos são restaurantes."
    },
    {
      "rotulo": "Hotéis",
      "codigos": [
        "5510801"
      ],
      "descricoes": [
        "Hotéis"
      ],
      "origem": "ibge",
      "termo": "hotel com ou sem serviço de restaurante"
    }
  ],
  "meta": {
    "fonte": [
      "IBGE/CNAE 2.3",
      "Fonteza/categorias"
    ],
    "classificacao": "CNAE 2.3 subclasses"
  }
}
```

## Erros

| Status | Quando | Código |
| --- | --- | --- |
| 422 | Parâmetro inválido. |  |
