Privacidade e Cookies

Utilizamos cookies para melhorar sua experiência e analisar o tráfego do site. Ao continuar navegando, você concorda com nossa Política de Privacidade.

EcoBigData
Voltar ao blog
Pipelines Python e Node conectados a uma consulta CNPJ
API CNPJ Python Node.js

Como integrar consulta de CNPJ no seu sistema (Python e Node)

Tutorial de integração da API CNPJ EcoBigData com Python e Node, incluindo validação, cache, timeout e retry com backoff.

Publicado em Atualizado em 13 min

Integrar uma consulta de CNPJ não deveria significar apenas montar uma URL e executar fetch. Em produção, a aplicação precisa validar o número antes da chamada, proteger o token, tratar limites, definir timeout e evitar pagar repetidamente pelo mesmo dado. Este tutorial implementa esse fluxo em Python e Node.js usando endpoints reais da EcoBigData.

A referência atualizada de autenticação, campos e exemplos fica na página da API de dados. Os exemplos abaixo usam:

  • GET /api/company/details?cnpj={cnpj} para os dados da empresa;
  • GET /api/company/socios?cnpj={cnpj} para o quadro societário;
  • cabeçalho Authorization: Bearer SEU_TOKEN.

Use CNPJs de teste autorizados e nunca grave o token no repositório.

Arquitetura recomendada

Um cliente de API CNPJ robusto pode ser dividido em cinco etapas:

  1. remover pontuação e validar os dígitos verificadores;
  2. procurar uma resposta recente no cache;
  3. chamar a API com timeout;
  4. classificar erros entre definitivos e transitórios;
  5. armazenar a resposta por um período compatível com o processo.

Essa separação evita chamadas desnecessárias e facilita trocar de provedor. Se você ainda está avaliando opções, veja o comparativo de APIs CNPJ.

Variáveis de ambiente

Defina a URL base e o token fora do código:

export ECOBIGDATA_API_URL="https://ecobigdata.com.br"
export ECOBIGDATA_TOKEN="seu_token_aqui"

Em produção, use o gerenciador de segredos da nuvem ou da plataforma de deploy. Arquivos .env são convenientes no desenvolvimento, mas não devem ser enviados ao Git.

Validar o CNPJ antes da chamada

O CNPJ tradicional possui 14 dígitos. Os dois últimos são verificadores calculados por módulo 11. Validar localmente reduz respostas 400, consumo desnecessário e ruído nos logs.

Função pronta em Python

import re

def normalize_cnpj(value: str) -> str:
    return re.sub(r"\D", "", value or "")

def is_valid_cnpj(value: str) -> bool:
    cnpj = normalize_cnpj(value)
    if len(cnpj) != 14 or len(set(cnpj)) == 1:
        return False

    def digit(base: str, weights: list[int]) -> str:
        total = sum(int(number) * weight for number, weight in zip(base, weights))
        remainder = total % 11
        return "0" if remainder < 2 else str(11 - remainder)

    first = digit(cnpj[:12], [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2])
    second = digit(cnpj[:12] + first, [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2])
    return cnpj[-2:] == first + second

Função pronta em Node.js

export function normalizeCnpj(value) {
  return String(value ?? "").replace(/\D/g, "");
}

export function isValidCnpj(value) {
  const cnpj = normalizeCnpj(value);
  if (cnpj.length !== 14 || /^(\d)\1{13}$/.test(cnpj)) return false;

  const calculate = (base, weights) => {
    const total = [...base].reduce(
      (sum, number, index) => sum + Number(number) * weights[index],
      0,
    );
    const remainder = total % 11;
    return remainder < 2 ? "0" : String(11 - remainder);
  };

  const first = calculate(cnpj.slice(0, 12), [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]);
  const second = calculate(cnpj.slice(0, 12) + first, [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]);
  return cnpj.endsWith(first + second);
}

O algoritmo evita a armadilha de aceitar sequências repetidas, como 00000000000000, que podem satisfazer implementações incompletas.

Cliente completo em Python

O exemplo usa requests, timeout explícito, cache em memória e retry exponencial para 429 e falhas transitórias. Em múltiplos processos, substitua o dicionário por Redis, banco ou cache compartilhado.

import os
import random
import time
from dataclasses import dataclass
from typing import Any

import requests

BASE_URL = os.environ.get("ECOBIGDATA_API_URL", "https://ecobigdata.com.br")
TOKEN = os.environ["ECOBIGDATA_TOKEN"]

@dataclass
class CacheEntry:
    expires_at: float
    value: dict[str, Any]

CACHE: dict[str, CacheEntry] = {}
CACHE_TTL_SECONDS = 6 * 60 * 60

class ApiError(RuntimeError):
    pass

def cached_get(key: str):
    entry = CACHE.get(key)
    if not entry:
        return None
    if entry.expires_at <= time.time():
        CACHE.pop(key, None)
        return None
    return entry.value

def cached_put(key: str, value: dict):
    CACHE[key] = CacheEntry(time.time() + CACHE_TTL_SECONDS, value)

def request_company(path: str, cnpj_input: str, attempts: int = 4) -> dict:
    cnpj = normalize_cnpj(cnpj_input)
    if not is_valid_cnpj(cnpj):
        raise ValueError("CNPJ inválido")

    cache_key = f"{path}:{cnpj}"
    if cached := cached_get(cache_key):
        return cached

    url = f"{BASE_URL}{path}"
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Accept": "application/json",
    }

    for attempt in range(attempts):
        try:
            response = requests.get(
                url,
                params={"cnpj": cnpj},
                headers=headers,
                timeout=(3.05, 20),
            )
        except (requests.Timeout, requests.ConnectionError) as error:
            if attempt == attempts - 1:
                raise ApiError(f"Falha de rede após {attempts} tentativas") from error
            delay = (2 ** attempt) + random.random()
            time.sleep(delay)
            continue

        if response.status_code == 200:
            value = response.json()
            cached_put(cache_key, value)
            return value

        if response.status_code == 401:
            raise ApiError("Token ausente, expirado ou inválido")
        if response.status_code == 403:
            raise ApiError("Token sem permissão ou plano sem acesso ao recurso")
        if response.status_code == 404:
            raise ApiError("CNPJ não encontrado")

        retryable = response.status_code == 429 or response.status_code >= 500
        if not retryable or attempt == attempts - 1:
            raise ApiError(
                f"API retornou HTTP {response.status_code}: {response.text[:200]}"
            )

        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt
        time.sleep(delay + random.random())

    raise ApiError("Tentativas esgotadas")

def company_details(cnpj: str) -> dict:
    return request_company("/api/company/details", cnpj)

def company_partners(cnpj: str) -> dict:
    return request_company("/api/company/socios", cnpj)

Uso:

details = company_details("00.000.000/0001-91")
partners = company_partners("00.000.000/0001-91")

print(details)
print(partners)

O CNPJ do exemplo deve ser substituído por um número válido disponível para seu ambiente. Não use dados reais em testes automatizados sem controlar finalidade, logs e retenção.

Cliente completo em Node.js

O Node 18 ou superior oferece fetch nativo. O exemplo usa AbortSignal.timeout, cache em Map e respeita Retry-After quando o servidor envia o cabeçalho.

const BASE_URL = process.env.ECOBIGDATA_API_URL ?? "https://ecobigdata.com.br";
const TOKEN = process.env.ECOBIGDATA_TOKEN;
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
const cache = new Map();

if (!TOKEN) throw new Error("ECOBIGDATA_TOKEN não definido");

const sleep = (milliseconds) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

function getCached(key) {
  const entry = cache.get(key);
  if (!entry) return null;
  if (entry.expiresAt <= Date.now()) {
    cache.delete(key);
    return null;
  }
  return entry.value;
}

function setCached(key, value) {
  cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
}

async function requestCompany(path, cnpjInput, attempts = 4) {
  const cnpj = normalizeCnpj(cnpjInput);
  if (!isValidCnpj(cnpj)) throw new TypeError("CNPJ inválido");

  const cacheKey = `${path}:${cnpj}`;
  const cached = getCached(cacheKey);
  if (cached) return cached;

  const url = new URL(path, BASE_URL);
  url.searchParams.set("cnpj", cnpj);

  for (let attempt = 0; attempt < attempts; attempt += 1) {
    let response;
    try {
      response = await fetch(url, {
        headers: {
          Authorization: `Bearer ${TOKEN}`,
          Accept: "application/json",
        },
        signal: AbortSignal.timeout(20_000),
      });
    } catch (error) {
      if (attempt === attempts - 1) {
        throw new Error(`Falha de rede após ${attempts} tentativas`, { cause: error });
      }
      await sleep((2 ** attempt) * 1000 + Math.random() * 500);
      continue;
    }

    if (response.ok) {
      const value = await response.json();
      setCached(cacheKey, value);
      return value;
    }

    if (response.status === 401) throw new Error("Token inválido ou expirado");
    if (response.status === 403) throw new Error("Acesso não permitido para este token");
    if (response.status === 404) throw new Error("CNPJ não encontrado");

    const retryable = response.status === 429 || response.status >= 500;
    if (!retryable || attempt === attempts - 1) {
      const body = (await response.text()).slice(0, 200);
      throw new Error(`API retornou HTTP ${response.status}: ${body}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delay = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : (2 ** attempt) * 1000;
    await sleep(delay + Math.random() * 500);
  }

  throw new Error("Tentativas esgotadas");
}

export const companyDetails = (cnpj) =>
  requestCompany("/api/company/details", cnpj);

export const companyPartners = (cnpj) =>
  requestCompany("/api/company/socios", cnpj);

Uso:

const details = await companyDetails("00.000.000/0001-91");
const partners = await companyPartners("00.000.000/0001-91");

console.log({ details, partners });

Como tratar 401, 403 e 429

Esses códigos não devem receber o mesmo tratamento.

HTTP 401

Indica credencial ausente, inválida ou expirada. Repetir imediatamente com o mesmo token apenas aumenta tráfego. Atualize a credencial ou execute o fluxo de renovação antes de tentar novamente.

HTTP 403

O servidor reconheceu a credencial, mas negou o recurso. Verifique permissões, plano, escopo e situação da conta. Também não é um erro para retry automático.

HTTP 429

O limite foi atingido. Respeite Retry-After quando presente. Sem esse cabeçalho, aplique backoff exponencial com pequena aleatoriedade, chamada de jitter. A aleatoriedade impede que várias instâncias voltem ao mesmo tempo.

Erros 500, 502, 503, 504, timeout e falha de conexão podem ser transitórios. Restrinja o número de tentativas e registre a causa final. Retry infinito converte uma indisponibilidade parcial em sobrecarga.

Estratégia de cache por CNPJ

O TTL depende do uso:

  • preenchimento de formulário: algumas horas podem ser suficientes;
  • análise cadastral: registre quando o dado foi consultado;
  • monitoramento: mantenha o snapshot e programe nova consulta;
  • situação crítica: use a política de atualização contratada.

A chave deve incluir endpoint, CNPJ e, se aplicável, versão ou conjunto de campos. Não misture a resposta de details com a de socios. Para evitar stampede, um cache distribuído pode aplicar lock curto enquanto uma instância atualiza o item.

Dados cadastrais também têm implicações de privacidade e governança. Defina finalidade, usuários autorizados, prazo de retenção e informações que podem aparecer em logs.

Checklist para produção

  • token somente no servidor;
  • HTTPS obrigatório;
  • validação do CNPJ antes da rede;
  • timeout de conexão e resposta;
  • retry apenas para erros transitórios;
  • respeito ao Retry-After;
  • cache com TTL e chave por endpoint;
  • logs sem token e sem resposta completa desnecessária;
  • métricas de latência, 429 e falhas;
  • teste de contrato para detectar mudança de esquema.

Faça primeiro uma consulta CNPJ manual para conhecer os dados e só depois automatize os campos realmente necessários.

Perguntas frequentes

Posso chamar a API diretamente do navegador?

Não é recomendado quando a chamada exige um Bearer token secreto. O navegador expõe credenciais ao usuário e a extensões. Crie um endpoint no seu backend, aplique autorização e faça a chamada à EcoBigData no servidor.

Devo repetir uma chamada que retornou 401?

Não com a mesma credencial. Corrija ou renove o token. Retry automático é apropriado para 429, falhas de rede e alguns erros 5xx, sempre com limite.

Por quanto tempo devo manter o CNPJ em cache?

Não existe TTL universal. Considere a criticidade e a frequência de mudança do campo. Registre a data da consulta e use um TTL menor quando situação cadastral ou QSA precisarem de atualização frequente.

Como validar CNPJ alfanumérico?

Os exemplos implementam o formato numérico tradicional de 14 dígitos. A Receita Federal prevê CNPJ alfanumérico para novas inscrições a partir de 31 de julho de 2026. CNPJs atuais continuam válidos, mas a normalização e o cálculo devem seguir a nova especificação oficial; não force letras no algoritmo numérico antigo.

O endpoint de sócios substitui uma análise de KYB?

Não. Ele fornece dados para apoiar o processo. Beneficiário final, documentos, listas restritivas, contexto da operação e revisão humana podem continuar necessários conforme risco e obrigação aplicável.