Documentação da API de CNPJ

Referência completa da API do apipj. Base: https://apipj.com.br/v1. Todas as respostas são JSON em UTF-8. Esta página também existe em Markdown e há um OpenAPI 3.1 gerado a partir das rotas. Para testar sem escrever código, abra o sandbox.

Visão geral

  • Fonte dos dados: arquivos de dados abertos da Receita Federal do Brasil, carregados em lote uma vez por mês. Cada documento traz o campo atualizado_em com o mês do lote (AAAA-MM). Não há consulta em tempo real.
  • Autenticação: obrigatória em GET /v1/cnpj (chave de API; sem chave a resposta é 401 unauthorized). A conta é grátis e não pede cartão: 10 por minuto e 50.000 por mês. /v1/valida e /v1/status são abertos.
  • Formatos: completo (padrão: objetos aninhados, datas ISO AAAA-MM-DD e códigos da Receita ao lado das descrições) e simplificado (formato plano com campos já formatados: datas DD/MM/AAAA, CNPJ e CEP com pontuação), para integrações que preferem receber os valores prontos para exibir.
  • CNPJ alfanumérico: aceito em todos os endpoints desde o primeiro dia. Entrada com ou sem pontuação, maiúsculas ou minúsculas.
  • CORS: liberado para GET /v1/cnpj/*, /v1/valida/* e /v1/status; você pode chamar direto do navegador.
  • Compressão: gzip e brotli, negociadas pelo header Accept-Encoding.

Autenticação

Crie uma conta em /criar-conta: a primeira chave é gerada na hora e exibida uma única vez. Você pode ter até 5 chaves ativas por conta e revogar qualquer uma no painel; a revogação é imediata.

Envie a chave de uma destas formas, em ordem de preferência:

FormaExemploObservação
Header AuthorizationAuthorization: Bearer apipj_live_...Recomendado
Header X-API-KeyX-API-Key: apipj_live_...Alternativa para clientes que não montam Authorization
Query string ?token=/v1/cnpj/...?token=apipj_live_...Só para clientes que não conseguem enviar cabeçalhos. Não recomendado: a chave vai parar em logs e históricos

No próprio site (sandbox e consulta da página inicial), quem está logado consulta pela sessão (cookie, mesma origem), sem chave; o uso conta na mesma cota mensal da conta. Sem chave e sem sessão, GET /v1/cnpj responde 401 com {"status":"ERROR","message":"Crie uma conta grátis para consultar","code":"unauthorized"}.

Chaves têm o prefixo apipj_live_. Guardamos apenas um hash; se você perder a chave, revogue e crie outra. Nunca exponha a chave em código público, em apps distribuídos ou no front-end de sites abertos: use um backend intermediário.

Limites e cotas

Dois limites agem ao mesmo tempo: um por minuto (token bucket; zera a cada 60 segundos) e uma cota mensal por conta (somando todas as chaves e o uso pela sessão do site). Os valores vêm de /precos:

PlanoPor minutoCota
Gratuito1050.000 por mês
Dev (R$ 12,90)30200.000 por mês
Pro (R$ 59,90)200600.000 por mês

Toda resposta de /v1/cnpj traz os cabeçalhos abaixo, inclusive em erro:

CabeçalhoSignificado
X-RateLimit-LimitConsultas permitidas por minuto (por chave; no site, por conta)
X-RateLimit-RemainingQuantas ainda cabem neste minuto
X-RateLimit-ResetSegundos até o limite por minuto zerar
X-Quota-LimitCota mensal da conta
X-Quota-RemainingQuanto resta da cota
X-Quota-ResetSegundos até a cota zerar
Retry-AfterSó em 429: segundos a esperar antes de tentar de novo

A cota mensal reinicia no primeiro dia de cada mês (horário de Brasília) e não acumula. /v1/valida e /v1/status não consomem cota.

GET /v1/cnpj/{cnpj}

Devolve o documento cadastral de um CNPJ. Parâmetro de query formato: completo (padrão, pode ser omitido) ou simplificado. Qualquer outro valor responde 400 validation_error.

curl -s "https://apipj.com.br/v1/cnpj/11378117000120" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer apipj_live_SUA_CHAVE"
const resp = await fetch("https://apipj.com.br/v1/cnpj/11378117000120", {
  headers: {
    Accept: "application/json",
    Authorization: "Bearer apipj_live_SUA_CHAVE",
  },
});
if (!resp.ok) {
  const erro = await resp.json().catch(() => ({}));
  throw new Error(`HTTP ${resp.status}: ${erro.message ?? "erro"}`);
}
const dados = await resp.json();
console.log(dados.razao_social, dados.situacao_cadastral.descricao);
console.log("restam no minuto:", resp.headers.get("X-RateLimit-Remaining"));
import requests

r = requests.get(
    "https://apipj.com.br/v1/cnpj/11378117000120",
    headers={
        "Accept": "application/json",
        "Authorization": "Bearer apipj_live_SUA_CHAVE",
    },
    timeout=10,
)
if r.status_code == 429:
    raise SystemExit(f"limite: tente em {r.headers.get('Retry-After')} s")
r.raise_for_status()
dados = r.json()
print(dados["razao_social"], dados["situacao_cadastral"]["descricao"])
<?php
$ch = curl_init("https://apipj.com.br/v1/cnpj/11378117000120");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        "Accept: application/json",
        "Authorization: Bearer apipj_live_SUA_CHAVE",
    ],
]);
$corpo  = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("HTTP $status: $corpo");
}
$dados = json_decode($corpo, true, 512, JSON_THROW_ON_ERROR);
echo $dados["razao_social"], " - ", $dados["situacao_cadastral"]["descricao"], PHP_EOL;
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "apipj_live_SUA_CHAVE");

using var resp = await http.GetAsync("https://apipj.com.br/v1/cnpj/11378117000120");
if (!resp.IsSuccessStatusCode)
    throw new HttpRequestException($"HTTP {(int)resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}");

using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
var raiz = doc.RootElement;
Console.WriteLine(raiz.GetProperty("razao_social"));

Resposta 200 (formato completo)

{
  "cnpj": "11378117000120",
  "cnpj_formatado": "11.378.117/0001-20",
  "razao_social": "LEADS2B S/A",
  "nome_fantasia": "LEADS2B.COM",
  "matriz_filial": "MATRIZ",
  "situacao_cadastral": {
    "codigo": "02",
    "descricao": "ATIVA",
    "data": "2009-11-24",
    "motivo": {
      "codigo": "00",
      "descricao": "SEM MOTIVO"
    }
  },
  "situacao_especial": {
    "descricao": null,
    "data": null
  },
  "data_inicio_atividade": "2009-11-24",
  "natureza_juridica": {
    "codigo": "2054",
    "descricao": "Sociedade Anônima Fechada"
  },
  "porte": {
    "codigo": "05",
    "descricao": "DEMAIS"
  },
  "capital_social": 218878,
  "cnae_principal": {
    "codigo": "6311900",
    "descricao": "Tratamento de dados, provedores de serviços de aplicação e serviços de hospedagem na internet"
  },
  "cnaes_secundarios": [
    {
      "codigo": "8599604",
      "descricao": "Treinamento em desenvolvimento profissional e gerencial"
    }
  ],
  "endereco": {
    "tipo_logradouro": "RUA",
    "logradouro": "PADRE ANCHIETA",
    "numero": "2285",
    "complemento": "CONJ 1603 ANDAR 16 COND BUSINESS CENTER",
    "bairro": "BIGORRILHO",
    "cep": "80730001",
    "municipio": {
      "codigo_ibge": "4106902",
      "nome": "CURITIBA"
    },
    "uf": "PR",
    "pais": null
  },
  "telefones": [
    {
      "ddd": "41",
      "numero": "30287828"
    }
  ],
  "fax": null,
  "email": "contabilidade@leads2b.com",
  "ente_federativo_responsavel": null,
  "simples": {
    "optante": false,
    "data_opcao": "2009-11-24",
    "data_exclusao": "2019-12-31"
  },
  "mei": {
    "optante": false,
    "data_opcao": null,
    "data_exclusao": null
  },
  "socios": [
    {
      "nome": "FULANO DE TAL",
      "tipo": "PESSOA FISICA",
      "cpf_cnpj": "***123456**",
      "qualificacao": {
        "codigo": "10",
        "descricao": "Diretor"
      },
      "data_entrada": "2009-11-24",
      "faixa_etaria": "41 a 50 anos",
      "pais": null,
      "representante_legal": null
    }
  ],
  "atualizado_em": "2026-09"
}

Regras do documento:

  • Datas sempre em ISO AAAA-MM-DD ou null. Nunca inventamos campos: o que a Receita não traz vem como null.
  • cnpj tem 14 caracteres sem pontuação, em maiúsculas; cnpj_formatado tem a máscara 00.000.000/0000-00.
  • situacao_cadastral.descricao é uma de ATIVA, BAIXADA, INAPTA, SUSPENSA, NULA.
  • capital_social é número (reais, duas casas).
  • socios[].cpf_cnpj vem exatamente como a Receita publica: CPF mascarado (***123456**) para pessoa física. Esses são dados pessoais de acesso público; veja a seção sobre LGPD na política de privacidade.
  • atualizado_em é o mês do lote de dados abertos em uso.

Erros

HTTPcodeQuandoCorpo
400invalid_cnpjCNPJ com tamanho ou dígito verificador inválido{"status":"ERROR","message":"CNPJ inválido: dígito verificador não confere","code":"invalid_cnpj"}
400validation_errorParâmetro fora do esperado (ex.: formato diferente de completo ou simplificado){"status":"ERROR","message":"...","code":"validation_error"}
401unauthorizedSem chave (e sem sessão do site), ou chave inválida ou revogada{"status":"ERROR","message":"Crie uma conta grátis para consultar","code":"unauthorized"}
404not_foundCNPJ válido, mas ausente no lote atual{"status":"ERROR","message":"CNPJ não encontrado","code":"not_found"}
429rate_limitedLimite por minuto excedido{"status":"ERROR","message":"Limite de consultas por minuto excedido","code":"rate_limited","retry_after":37} + header Retry-After
429quota_exceededCota mensal esgotada{"status":"ERROR","message":"Cota mensal esgotada","code":"quota_exceeded","upgrade_url":"https://apipj.com.br/precos"}
5xxinternal_errorFalha do nosso lado{"status":"ERROR","message":"Erro interno","code":"internal_error"}; confira /status

Todo erro tem a forma {status:"ERROR", message, code}; code é estável e em snake_case, message é texto em português para humanos.

Formato simplificado

Acrescente ?formato=simplificado para receber um formato plano, com os campos já formatados para exibir: abertura (DD/MM/AAAA), situacao, tipo, nome, fantasia, porte, natureza_juridica ("205-4 - Sociedade Anônima Fechada"), atividade_principal ([{code, text}]), atividades_secundarias, logradouro, numero, complemento, municipio, bairro, uf, cep ("80.730-001"), email, telefone ("(41) 3028-7828"), data_situacao, cnpj (com máscara), ultima_atualizacao, status, efr, motivo_situacao, situacao_especial, data_situacao_especial, capital_social (string), simples, simei, qsa (sócios), extra e billing.

Diferenças em relação ao completo: textos vazios vêm como "" em vez de null, códigos e descrições aparecem juntos em um só campo (natureza_juridica, qsa[].qual) e endereço e telefones vêm como texto em campos planos (o completo os detalha em objetos). billing.database vem sempre true, porque toda resposta sai da base mensal. Os limites, a cota e os erros são os mesmos nos dois formatos.

curl -s "https://apipj.com.br/v1/cnpj/11378117000120?formato=simplificado" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer apipj_live_SUA_CHAVE"
const resp = await fetch("https://apipj.com.br/v1/cnpj/11378117000120?formato=simplificado", {
  headers: {
    Accept: "application/json",
    Authorization: "Bearer apipj_live_SUA_CHAVE",
  },
});
if (!resp.ok) {
  const erro = await resp.json().catch(() => ({}));
  throw new Error(`HTTP ${resp.status}: ${erro.message ?? "erro"}`);
}
const dados = await resp.json();
console.log(dados.nome, dados.situacao);
console.log("restam no minuto:", resp.headers.get("X-RateLimit-Remaining"));
import requests

r = requests.get(
    "https://apipj.com.br/v1/cnpj/11378117000120?formato=simplificado",
    headers={
        "Accept": "application/json",
        "Authorization": "Bearer apipj_live_SUA_CHAVE",
    },
    timeout=10,
)
if r.status_code == 429:
    raise SystemExit(f"limite: tente em {r.headers.get('Retry-After')} s")
r.raise_for_status()
dados = r.json()
print(dados["nome"], dados["situacao"])
<?php
$ch = curl_init("https://apipj.com.br/v1/cnpj/11378117000120?formato=simplificado");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        "Accept: application/json",
        "Authorization: Bearer apipj_live_SUA_CHAVE",
    ],
]);
$corpo  = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("HTTP $status: $corpo");
}
$dados = json_decode($corpo, true, 512, JSON_THROW_ON_ERROR);
echo $dados["nome"], " - ", $dados["situacao"], PHP_EOL;
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "apipj_live_SUA_CHAVE");

using var resp = await http.GetAsync("https://apipj.com.br/v1/cnpj/11378117000120?formato=simplificado");
if (!resp.IsSuccessStatusCode)
    throw new HttpRequestException($"HTTP {(int)resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}");

using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
var raiz = doc.RootElement;
Console.WriteLine(raiz.GetProperty("nome"));

Resposta 200 (formato simplificado)

{
  "abertura": "24/11/2009",
  "situacao": "ATIVA",
  "tipo": "MATRIZ",
  "nome": "LEADS2B S/A",
  "fantasia": "LEADS2B.COM",
  "porte": "DEMAIS",
  "natureza_juridica": "205-4 - Sociedade Anônima Fechada",
  "atividade_principal": [
    {
      "code": "63.11-9-00",
      "text": "Tratamento de dados, provedores de serviços de aplicação e serviços de hospedagem na internet"
    }
  ],
  "atividades_secundarias": [
    {
      "code": "85.99-6-04",
      "text": "Treinamento em desenvolvimento profissional e gerencial"
    }
  ],
  "logradouro": "RUA PADRE ANCHIETA",
  "numero": "2285",
  "complemento": "CONJ 1603 ANDAR 16 COND BUSINESS CENTER",
  "municipio": "CURITIBA",
  "bairro": "BIGORRILHO",
  "uf": "PR",
  "cep": "80.730-001",
  "email": "contabilidade@leads2b.com",
  "telefone": "(41) 3028-7828",
  "data_situacao": "24/11/2009",
  "cnpj": "11.378.117/0001-20",
  "ultima_atualizacao": "2026-09-14T00:00:00.000Z",
  "status": "OK",
  "efr": "",
  "motivo_situacao": "",
  "situacao_especial": "",
  "data_situacao_especial": "",
  "capital_social": "218878.00",
  "simples": {
    "optante": false,
    "data_opcao": "24/11/2009",
    "data_exclusao": "31/12/2019",
    "ultima_atualizacao": "2026-09-14T00:00:00.000Z"
  },
  "simei": {
    "optante": false,
    "data_opcao": null,
    "data_exclusao": null,
    "ultima_atualizacao": "2026-09-14T00:00:00.000Z"
  },
  "qsa": [
    {
      "nome": "FULANO DE TAL",
      "qual": "10-Diretor",
      "pais_origem": "",
      "nome_rep_legal": "",
      "qual_rep_legal": ""
    }
  ],
  "extra": {},
  "billing": {
    "free": true,
    "database": true
  }
}

GET /v1/valida/{cnpj}

Valida formato e dígito verificador, inclusive de CNPJ alfanumérico. Não exige chave, não consome cota e não consulta a base (um CNPJ pode ser válido e não existir).

curl -s "https://apipj.com.br/v1/valida/12ABC34501DE35" \
  -H "Accept: application/json"
# sem chave e sem cota (rota aberta)
const resp = await fetch("https://apipj.com.br/v1/valida/12ABC34501DE35", {
  headers: {
    Accept: "application/json",
    // Authorization: "Bearer apipj_live_...", // opcional nesta rota
  },
});
if (!resp.ok) {
  const erro = await resp.json().catch(() => ({}));
  throw new Error(`HTTP ${resp.status}: ${erro.message ?? "erro"}`);
}
const dados = await resp.json();
console.log(dados.valido, dados.formatado);
console.log("restam no minuto:", resp.headers.get("X-RateLimit-Remaining"));
import requests

r = requests.get(
    "https://apipj.com.br/v1/valida/12ABC34501DE35",
    headers={
        "Accept": "application/json",
        # "Authorization": "Bearer apipj_live_...",  # opcional nesta rota
    },
    timeout=10,
)
if r.status_code == 429:
    raise SystemExit(f"limite: tente em {r.headers.get('Retry-After')} s")
r.raise_for_status()
dados = r.json()
print(dados["valido"], dados["formatado"])
<?php
$ch = curl_init("https://apipj.com.br/v1/valida/12ABC34501DE35");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        "Accept: application/json",
        // "Authorization: Bearer apipj_live_...", // opcional
    ],
]);
$corpo  = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("HTTP $status: $corpo");
}
$dados = json_decode($corpo, true, 512, JSON_THROW_ON_ERROR);
echo var_export($dados["valido"], true), " - ", $dados["formatado"], PHP_EOL;
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "apipj_live_..."); // opcional

using var resp = await http.GetAsync("https://apipj.com.br/v1/valida/12ABC34501DE35");
if (!resp.IsSuccessStatusCode)
    throw new HttpRequestException($"HTTP {(int)resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}");

using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
var raiz = doc.RootElement;
Console.WriteLine(raiz.GetProperty("valido"));

Resposta 200

{
  "cnpj": "12ABC34501DE35",
  "valido": true,
  "formatado": "12.ABC.345/01DE-35",
  "alfanumerico": true,
  "erro": null
}

Com DV errado a resposta continua 200, com valido: false e erro explicando ("Dígito verificador não confere"). Só devolvemos 400 quando o parâmetro está vazio ou passa de 18 caracteres.

GET /v1/status

Estado da API e do dataset. Sem chave, sem cota. Use para monitorar e para saber o mês dos dados antes de uma carga em lote.

curl -s "https://apipj.com.br/v1/status" \
  -H "Accept: application/json"
# sem chave e sem cota (rota aberta)
const resp = await fetch("https://apipj.com.br/v1/status", {
  headers: {
    Accept: "application/json",
    // Authorization: "Bearer apipj_live_...", // opcional nesta rota
  },
});
if (!resp.ok) {
  const erro = await resp.json().catch(() => ({}));
  throw new Error(`HTTP ${resp.status}: ${erro.message ?? "erro"}`);
}
const dados = await resp.json();
console.log(dados.status, dados.dataset_month);
console.log("restam no minuto:", resp.headers.get("X-RateLimit-Remaining"));
import requests

r = requests.get(
    "https://apipj.com.br/v1/status",
    headers={
        "Accept": "application/json",
        # "Authorization": "Bearer apipj_live_...",  # opcional nesta rota
    },
    timeout=10,
)
if r.status_code == 429:
    raise SystemExit(f"limite: tente em {r.headers.get('Retry-After')} s")
r.raise_for_status()
dados = r.json()
print(dados["status"], dados["dataset_month"])
<?php
$ch = curl_init("https://apipj.com.br/v1/status");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        "Accept: application/json",
        // "Authorization: Bearer apipj_live_...", // opcional
    ],
]);
$corpo  = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("HTTP $status: $corpo");
}
$dados = json_decode($corpo, true, 512, JSON_THROW_ON_ERROR);
echo $dados["status"], " - ", $dados["dataset_month"], PHP_EOL;
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "apipj_live_..."); // opcional

using var resp = await http.GetAsync("https://apipj.com.br/v1/status");
if (!resp.IsSuccessStatusCode)
    throw new HttpRequestException($"HTTP {(int)resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}");

using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
var raiz = doc.RootElement;
Console.WriteLine(raiz.GetProperty("status"));

Resposta 200

{
  "status": "OK",
  "dataset_month": "2026-09",
  "rows": 65000000,
  "version": "1.0.0",
  "time": "2026-10-09T12:00:00.000Z"
}

CNPJ alfanumérico

Desde 31/07/2026 a Receita Federal emite CNPJs com letras nas 12 primeiras posições (raiz de 8 e ordem de 4); os dois dígitos verificadores continuam numéricos. O formato é [A-Z0-9]{12}[0-9]{2}. O cálculo do DV usa módulo 11 com os pesos de sempre, mas o valor de cada caractere é o seu código ASCII menos 48 (0→0 … 9→9, A→17 … Z→42).

Exemplo válido para testes: 12.ABC.345/01DE-35. A API aceita letras minúsculas e qualquer pontuação na entrada e normaliza antes de validar. Nos nossos documentos o campo cnpj vem sempre em maiúsculas sem pontuação, e cnpj_formatado com a máscara tradicional.

Se o seu sistema valida CNPJ localmente, troque a rotina pelo novo algoritmo ou use /v1/valida (grátis, sem cota) enquanto atualiza o seu sistema.

Boas práticas

  • Cache por CNPJ. A base muda uma vez por mês; guarde a resposta por até 30 dias (ou até atualizado_em mudar em /v1/status). Isso reduz seu consumo de cota e a latência para zero na maioria das consultas repetidas.
  • Respeite Retry-After. Em 429, espere o número de segundos indicado antes de tentar de novo; não faça retry imediato. Para 5xx, use backoff exponencial com jitter (1 s, 2 s, 4 s) e no máximo 3 tentativas.
  • Leia X-RateLimit-Remaining e X-Quota-Remaining. Em cargas em lote, controle o ritmo pelo X-RateLimit-Limit em vez de disparar tudo e esperar o 429.
  • Valide antes de consultar. /v1/valida (ou o algoritmo local) evita gastar cota com CNPJs digitados errado.
  • Trate null. Muitos campos (e-mail, telefones, nome fantasia, situação especial) vêm null legitimamente.
  • Timeout de 10 s no cliente e Accept: application/json.
  • Não dependa de ordem de chaves no JSON nem de campos fora desta documentação; novos campos podem ser acrescentados sem aviso (nunca removidos sem mudança de versão).

Conta, chaves e cobrança (rotas do painel)

Estas rotas existem para o painel e usam sessão por cookie, não chave de API. Estão aqui por transparência; para integrar, use as rotas públicas acima.

RotaDescrição
POST /v1/auth/signup{email, password, name, cpf_cnpj?, accept_tos: true} cria a conta, a sessão e a primeira chave
POST /v1/auth/login / POST /v1/auth/logoutSessão web
GET /v1/auth/providersProvedores de login disponíveis (`{google: truefalse}`)
GET /v1/auth/google/start, GET /v1/auth/google/callbackEntrar, criar conta ou vincular a conta Google (OpenID Connect com PKCE); POST /v1/auth/google/unlink desvincula
GET /v1/meUsuário, plano, validade e uso do mês
GET /v1/keys, POST /v1/keys, DELETE /v1/keys/{id}Lista, cria (máximo 5 ativas) e revoga chaves
GET /v1/usageUso do período, limites e reset (aceita sessão ou chave)
POST /v1/billing/checkout{plan} devolve {url} da página de pagamento do Asaas (cartão de crédito)
POST /v1/billing/cancelCancela a assinatura; o plano continua até o fim do período pago

Rotas mutáveis exigem o header X-Requested-With: apipj e Origin da mesma origem. Nenhum dado de cartão passa pelos nossos servidores.

Changelog

  • 09/10/2026: versão inicial da documentação (v1).