Skip to main content
A rede societária brasileira é um grafo: empresas se conectam a pessoas (e a outras empresas) pelo quadro de sócios. A partir de um único CNPJ ou CPF você consegue mapear toda a vizinhança — sócios diretos, as outras empresas desses sócios, os sócios dessas empresas, e assim por diante. Cada salto nesse caminho é um grau de conexão.

O modelo

Pense em dois tipos de nó que se alternam: empresas (CNPJ) e pessoas (CPF). Uma aresta liga uma empresa a quem é sócio dela.
  • Grau 0 — a empresa de onde você parte.
  • Grau 1 — as outras empresas dos sócios diretos.
  • Grau 2 — as empresas dos sócios dessas empresas.
  • …e assim sucessivamente.
Partir de uma pessoa (CPF) é simétrico: o grau 0 são as empresas onde ela é sócia, e a partir daí o caminho é o mesmo.

Duas rotas, ida e volta

A travessia usa apenas dois endpoints, que são o inverso um do outro:

Empresa → sócios

GET /empresas/cnpj/{cnpj} retorna a empresa já com o array socios. Cada sócio traz documento (CPF/CNPJ) e tipo.

Pessoa → empresas

GET /empresas/cpf/{cpf} faz a busca reversa: todas as empresas onde o CPF é sócio — cada uma também já com seu socios.
Como o /empresas/cpf já devolve os sócios de cada empresa, ao expandir a rede a partir de uma pessoa você ganha o próximo grau na mesma resposta — sem precisar voltar no /empresas/cnpj para cada empresa descoberta. Menos chamadas, menos latência.

Como o retorno encadeia

Formato da resposta

GET /empresas/cnpj/{cnpj} (grau 0 a partir de uma empresa):
{
  "documento": "33.000.167/0001-01",
  "razao_social": "Petroleo Brasileiro S A Petrobras",
  "situacao": "Ativa",
  "socios": [
    {
      "nome": "Claudio Romeo Schlosser",
      "documento": "406.077.120-15",
      "tipo_documento": "CPF",
      "tipo": "Pessoa Física",
      "qualificacao": "Diretor",
      "data_entrada": "2023-04-17"
    }
  ]
}
GET /empresas/cpf/{cpf} (grau 1 — as outras empresas do sócio, com sócios aninhados):
{
  "empresas": [
    {
      "documento": "11.111.111/0001-11",
      "razao_social": "Empresa B Participações Ltda",
      "situacao": "Ativa",
      "socios": [
        { "nome": "Claudio Romeo Schlosser", "documento": "406.077.120-15", "tipo": "Pessoa Física", "qualificacao": "Sócio-Administrador" },
        { "nome": "Maria Souza",             "documento": "123.456.789-09", "tipo": "Pessoa Física", "qualificacao": "Sócio" }
      ]
    }
  ]
}
Repare que cada empresa do array já carrega seus próprios sóciosMaria Souza é o nó de grau 2 pronto para ser expandido.

Percorrendo a rede

A travessia é uma busca em largura (BFS): você processa um grau inteiro antes de descer para o próximo. Três controles são essenciais:
1

Profundidade máxima (max_depth)

Quantos graus descer. O número de nós cresce exponencialmente — raramente faz sentido passar de 2 ou 3.
2

Amplitude por nó (fan-out)

Quantos sócios/empresas expandir por nó. Sócios de uma holding ou empresas de um sócio profissional podem chegar a centenas — limite para não explodir.
3

Deduplicação (visited)

A rede tem ciclos (A é sócio de B, B é sócio de A). Sem um conjunto de “já visitados”, o percurso entra em loop.

Código — travessia a partir de uma empresa

import requests

BASE = "https://221b-api.sherlocker.com.br/api/v1"
TOKEN = "SEU_TOKEN"

def get(endpoint, **params):
    params["token"] = TOKEN
    return requests.get(f"{BASE}{endpoint}", params=params).json()

# Grau 0 — a empresa e seu quadro societário
empresa = get("/empresas/cnpj/33000167000101")

# Grau 1 — loop pelos sócios
for socio in empresa.get("socios", []):
    print(f"{empresa['razao_social']} —> {socio['nome']} ({socio['qualificacao']})")

    # Grau 2 — outras empresas de cada sócio pessoa física
    doc = socio.get("documento", "")
    if socio.get("tipo") == "Pessoa Física" and doc:
        cpf = "".join(c for c in doc if c.isdigit())
        for emp in get(f"/empresas/cpf/{cpf}").get("empresas", []):
            print(f"   ↳ também sócio de: {emp['razao_social']}")

Otimização — aproveitando os sócios aninhados

Quando você expande uma pessoa, o /empresas/cpf já devolve os sócios de cada empresa. Dá para descer um grau extra sem nenhuma chamada nova:
def expandir_pessoa(cpf, fan_out=10):
    """Um único GET entrega grau 1 (empresas) e grau 2 (sócios dessas empresas)."""
    resp = get(f"/empresas/cpf/{so_digitos(cpf)}", limit=fan_out)
    grau1, grau2 = [], []
    for emp in resp.get("empresas", []):
        grau1.append(emp["documento"])                         # empresa do sócio (grau 1)
        for s in emp.get("socios", [])[:fan_out]:
            grau2.append((emp["documento"], s["documento"]))   # co-sócio (grau 2) — de graça
    return grau1, grau2

Calculando o tamanho máximo da rede

O número de nós cresce de forma exponencial com a profundidade. Se cada nó tem em média f conexões (fan-out) e você desce d graus:
nós no grau k   = f^k
total de nós    = 1 + f + f² + ... + f^d = (f^(d+1) − 1) / (f − 1)
E como cada nó expandido custa aproximadamente uma chamada, o número de chamadas à API segue a mesma ordem de grandeza (antes da deduplicação, que reduz o real).
def estimar_rede(fan_out, max_depth):
    if fan_out == 1:
        return max_depth + 1
    total = (fan_out ** (max_depth + 1) - 1) // (fan_out - 1)
    por_grau = [fan_out ** k for k in range(max_depth + 1)]
    return total, por_grau

total, por_grau = estimar_rede(fan_out=10, max_depth=3)
print(por_grau)  # [1, 10, 100, 1000]
print(total)     # 1111 nós no pior caso
fan-outgrau 1grau 2grau 3total (pior caso)
5525125156
10101001.0001.111
20204008.0008.421
A tabela é o teto teórico. Na prática, a deduplicação (visited) corta muito, porque redes reais são densamente interligadas — os mesmos sócios e empresas reaparecem. Ainda assim, trate fan_out e max_depth como orçamento de chamadas: 2 graus com fan-out 10 já é o ponto de equilíbrio para a maioria das investigações.

Limitando o orçamento na prática

MAX_CHAMADAS = 200

def graus_com_orcamento(cnpj_raiz, max_depth=3, fan_out=10, max_chamadas=MAX_CHAMADAS):
    chamadas = 0
    # ... mesma BFS, mas antes de cada get():
    #   if chamadas >= max_chamadas: break
    #   chamadas += 1
    # Garante custo previsível independente do tamanho real da rede.

Boas práticas

1

Comece raso

max_depth=1 ou 2 resolve a maioria dos casos. Cada grau a mais multiplica o custo.
2

Sempre deduplique

Mantenha visited para CPFs e CNPJs. Redes societárias têm ciclos.
3

Aproveite os sócios aninhados

Ao expandir uma pessoa, leia os socios que já vêm no /empresas/cpf antes de fazer chamadas novas.
4

Defina um teto de chamadas

Um limite global de requisições protege contra holdings com centenas de participações.
5

Filtre por relevância

Ignore sócios com data_saida antiga ou empresas Baixada se só interessa o vínculo ativo.

APIs utilizadas

Perfil da empresa

GET /empresas/cnpj/{cnpj} — dados cadastrais + quadro societário (socios).

Vínculos societários

GET /empresas/cpf/{cpf} — empresas de uma pessoa, cada uma com seus sócios.