---
title: "Busca de empresas paginada por cursor"
description: "Busca de empresas brasileiras por UF, município, CNAE, natureza jurídica e situação na API do cnpj.ia.br, percorrendo as páginas pelo meta.next_cursor em Python e curl."
canonical: "https://cnpj.ia.br/exemplos/busca-paginada-por-cursor"
date_modified: 2026-09-23
source: "cnpj.ia.br"
---

Exemplo

# Busca de empresas paginada por cursor

Busca de empresas brasileiras por UF, município, CNAE, natureza jurídica e situação na API do cnpj.ia.br, percorrendo as páginas pelo meta.next_cursor em Python e curl.

Python curl [Código no GitHub](https://github.com/Atmosphere-Technologies/cnpj-ia-exemplos/blob/main/python/busca_paginada.py)

A busca é um `GET /v1/cnpjs` com filtros; cada resposta traz até 20 empresas e um `meta.next_cursor`. A página seguinte é a mesma chamada, com os mesmos filtros, mais `cursor`. O exemplo percorre as páginas até `next_cursor` vir nulo ou até um teto de páginas, e soma o que cada página cobrou.

## O problema

Montar uma lista de empresas por filtro (as ativas de um CNAE num município, os órgãos federais de uma UF) passa de uma página. Paginação por número de página falha quando a base muda no meio; o cursor não. O erro comum é trocar um filtro entre uma página e outra, o que invalida o cursor, ou seguir paginando sem teto e pagar por empresas que ninguém vai usar.

## O fluxo

1. Chama `GET /v1/cnpjs` com os filtros. O padrão do exemplo é `uf=DF&natureza=1015&situacao=ATIVA`: órgãos do Poder Executivo Federal ativos no DF.
2. Processa `data` (até 20 empresas no perfil `basic`) e guarda `meta.next_cursor`.
3. Repete com os **mesmos** filtros mais `cursor`, até `next_cursor` vir nulo ou até `--max-paginas`.
4. A contagem do filtro vem na primeira página, em `meta.total_count_capped`, com teto de exibição.

A busca exige plano pago: no Free, responde `insufficient_plan`. Os filtros aceitos estão em [Busca de empresas](https://cnpj.ia.br/docs/busca-empresas).

## O código

Python, com `requests` (`python busca_paginada.py --filtro uf=SC --filtro situacao=ATIVA --max-paginas 3`):

```python
"""Busca empresas por filtros e percorre as páginas pelo cursor.

    CNPJIA_KEY=... python busca_paginada.py
    CNPJIA_KEY=... python busca_paginada.py --filtro uf=SC --filtro situacao=ATIVA --max-paginas 3

Cada página traz até 20 empresas no perfil basic. A próxima página é a mesma
chamada, com os MESMOS filtros, mais cursor=<meta.next_cursor>; mudar um filtro
invalida o cursor. meta.next_cursor null = última página. A busca exige plano
pago (no Free responde insufficient_plan) e cobra por empresa retornada: veja
https://cnpj.ia.br/precos.
"""

import argparse
import os
import sys
import time

import requests

API = "https://api.cnpj.ia.br"
ESPERA_MAXIMA = 60

# Órgãos do Poder Executivo Federal ativos no DF (natureza jurídica 1015).
FILTROS_PADRAO = {"uf": "DF", "natureza": "1015", "situacao": "ATIVA"}

def get(sessao, url, params):
    """GET com timeout; em 429/503 espera o Retry-After e repete uma vez."""
    for tentativa in (1, 2):
        resp = sessao.get(url, params=params, timeout=(5, 30))
        if tentativa == 1 and resp.status_code in (429, 503):
            espera = resp.headers.get("Retry-After", "")
            if espera.isdigit() and int(espera) <= ESPERA_MAXIMA:
                print(f"HTTP {resp.status_code}: aguardando {espera}s (Retry-After)", file=sys.stderr)
                time.sleep(int(espera))
                continue
        return resp

def paginas(sessao, filtros, max_paginas):
    """Gera uma página (o JSON inteiro) por chamada, seguindo meta.next_cursor."""
    cursor = None
    for _ in range(max_paginas):
        params = dict(filtros, cursor=cursor) if cursor else filtros
        resp = get(sessao, f"{API}/v1/cnpjs", params)
        corpo = resp.json()
        if not resp.ok:
            erro = corpo.get("error", {})
            sys.exit(
                f"HTTP {resp.status_code}: {erro.get('code')}: {erro.get('message')} "
                f"(request_id {erro.get('request_id')})"
            )
        yield corpo
        cursor = corpo["meta"].get("next_cursor")
        if not cursor:
            return

def main():
    parser = argparse.ArgumentParser(description="Busca paginada na API do cnpj.ia.br.")
    parser.add_argument("--filtro", action="append", metavar="CAMPO=VALOR", help="repita para vários filtros")
    parser.add_argument("--max-paginas", type=int, default=2)
    args = parser.parse_args()

    chave = os.environ.get("CNPJIA_KEY")
    if not chave:
        print("Defina CNPJIA_KEY.", file=sys.stderr)
        sys.exit(2)

    filtros = dict(f.split("=", 1) for f in args.filtro) if args.filtro else FILTROS_PADRAO

    sessao = requests.Session()
    sessao.headers["Authorization"] = f"Bearer {chave}"

    n_paginas = n_empresas = creditos = 0
    total = "?"
    for pagina in paginas(sessao, filtros, args.max_paginas):
        n_paginas += 1
        for empresa in pagina["data"]:
            n_empresas += 1
            print(f"{empresa['cnpj']};{empresa['razao_social']}")
        creditos += pagina["meta"]["credits_charged"]
        if n_paginas == 1:  # a contagem (com teto de exibição) vem na primeira página
            total = pagina["meta"].get("total_count_capped") or "?"

    print(
        f"# páginas: {n_paginas}, empresas: {n_empresas}, total do filtro: {total}, créditos cobrados: {creditos}",
        file=sys.stderr,
    )

if __name__ == "__main__":
    main()
```

A versão em curl, com `jq` lendo o cursor, está em [`curl/busca-paginada.sh`](https://github.com/Atmosphere-Technologies/cnpj-ia-exemplos/blob/main/curl/busca-paginada.sh). A busca cobra por empresa retornada, e página vazia não cobra; a tabela abaixo conta mil empresas retornadas, o equivalente a 50 páginas cheias. O valor vigente está em [Preços](https://cnpj.ia.br/precos).

## Custo em créditos

| Operação | Quantidade por mil registros | Peso em créditos | Créditos |
| --- | --- | --- | --- |
| Busca, empresas retornadas | 1000 | 1 | 1000 |
| Total por mil registros |  |  | 1000 |
