Start now →

How to Look Up Any French Company by SIRET in Seconds

By Antoine · Published April 9, 2026 · 6 min read · Source: Fintech Tag
Regulation

How to Look Up Any French Company by SIRET in Seconds

AntoineAntoine5 min read·Just now

--

France has a public registry of every business and establishment in the country — over 40 million records. If you’re doing KYC checks, CRM enrichment, invoice validation, or just building a form where users enter a company number, you’ll need to query this data. The catch is that the official API is designed for bulk data consumers, not app developers who just want to look up a single company. Here’s a faster path.

SIREN vs SIRET: the basics

Every French business has a SIREN number: 9 digits that identify the legal entity. Think of it like a company ID.

Each SIREN can have one or more SIRET numbers. A SIRET is 14 digits: the 9-digit SIREN plus a 5-digit NIC (Numero Interne de Classement) that identifies a specific establishment (office, warehouse, store). A company with 3 offices has 1 SIREN and 3 SIRETs.

SIRET: 802 954 785 00028
|_________| |___|
SIREN NIC

Both numbers use a Luhn checksum, just like credit card numbers. Here’s a quick validator:

def luhn_check(number: str) -> bool:
"""Validate a SIREN or SIRET using the Luhn algorithm."""
digits = [int(d) for d in number]
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
total = sum(odd_digits) + sum(sum(divmod(d * 2, 10)) for d in even_digits)
return total % 10 == 0


# Validate a SIRET
siret = "80295478500028"
assert len(siret) == 14 and siret.isdigit()
print(luhn_check(siret)) # True or False

There’s one famous exception: La Poste (the French postal service, SIREN 356000000) fails the Luhn check but is perfectly valid. Edge cases are fun.

The official INSEE API

The French statistics institute (INSEE) maintains the SIRENE database and offers an API at `api.insee.fr`. It works, but:

- You need to register for OAuth2 credentials and manage token refresh

- Rate limits are strict (30 requests/minute for free accounts)

- The response format is deeply nested JSON with French field names

- Search is limited and doesn’t support fuzzy matching well

- Bulk data is distributed as massive CSV files (14+ GB)

For a production app that needs fast lookups, the overhead of managing INSEE credentials and dealing with their rate limits adds friction you don’t need.

Looking up a company by SIRET

The European Business API indexes the full SIRENE database and exposes it through simple REST endpoints. Here’s a direct SIRET lookup:

import httpx

API_KEY = "your-api-key"
BASE_URL = "https://frenchbusinessapi.com"

def lookup_siret(siret: str) -> dict:
"""Look up a French establishment by SIRET number."""
resp = httpx.get(
f"{BASE_URL}/sirene/siret/{siret}",
headers={"X-API-Key": API_KEY},
)
resp.raise_for_status()
return resp.json()


company = lookup_siret("80295478500028")
print(company)

The response gives you everything about that establishment:

{
"siret": "80295478500028",
"siren": "802954785",
"nic": "00028",
"denomination": "ALAN",
"denomination_usuelle": null,
"enseigne": null,
"code_naf": "65.12Z",
"est_siege": true,
"etat_administratif": "A",
"date_creation": "2016-01-01",
"tranche_effectifs": "42",
"tranche_effectifs_libelle": "1 000-1 999 salaries",
"adresse": "40 RUE DE RICHELIEU",
"geo_adresse": "40 Rue de Richelieu 75001 Paris",
"code_postal": "75001",
"commune": "PARIS 1ER ARRONDISSEMENT",
"code_commune": "75101",
"longitude": 2.337,
"latitude": 48.866
}

A few useful fields to note:

- `etat_administratif`: `”A”` means active, `”F”` means closed. Essential for KYC.

- `est_siege`: `true` if this is the company headquarters.

- `tranche_effectifs`: Workforce size range, with a human-readable label.

- `longitude`/`latitude`: GPS coordinates of the establishment.

- `code_naf`: The French activity classification code (like SIC codes in the US).

Finding all establishments for a company

A SIREN lookup returns every establishment (office, branch, store) for a company:

def lookup_siren(siren: str, actif_only: bool = True) -> dict:
"""Get all establishments for a SIREN."""
resp = httpx.get(
f"{BASE_URL}/sirene/siren/{siren}",
params={"actif_only": actif_only},
headers={"X-API-Key": API_KEY},
)
resp.raise_for_status()
return resp.json()


result = lookup_siren("802954785")
print(f"Found {result['count']} establishments")
for etab in result["results"]:
hq = " (HQ)" if etab["est_siege"] else ""
print(f" {etab['siret']} - {etab['commune']}{hq}")

Set `actif_only=False` to include closed establishments — useful for audit trails or historical analysis.

Searching by name

When you don’t have a SIRET but know the company name, you can search:

def search_companies(
name: str = None,
postal_code: str = None,
city: str = None,
naf_code: str = None,
hq_only: bool = False,
limit: int = 25,
) -> dict:
"""Search French companies by name, location, or activity."""
params = {"limit": limit, "actif_only": True}
if name:
params["q"] = name
if postal_code:
params["code_postal"] = postal_code
if city:
params["commune"] = city
if naf_code:
params["code_naf"] = naf_code
if hq_only:
params["siege_only"] = True

resp = httpx.get(
f"{BASE_URL}/sirene/search",
params=params,
headers={"X-API-Key": API_KEY},
)
resp.raise_for_status()
return resp.json()


# Find all bakeries in Lyon
bakeries = search_companies(city="LYON", naf_code="10.71C", hq_only=True, limit=10)
for b in bakeries["results"]:
print(f"{b['denomination']} - {b['adresse']} {b['code_postal']}")

You can combine filters: name + postal code, NAF code + city, etc. The search is case-insensitive.

Real-world use case: auto-fill from SIRET

Here’s a practical pattern for a B2B form where a user enters their SIRET and you auto-fill the rest:

def autofill_company_form(siret: str) -> dict:
"""
Given a SIRET, return pre-filled form data.
Returns an error dict if the SIRET is invalid or the company is closed.
"""
# Quick local validation first
clean = siret.replace(" ", "").replace("-", "")
if not clean.isdigit() or len(clean) != 14:
return {"error": "SIRET must be exactly 14 digits"}

try:
company = lookup_siret(clean)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return {"error": "SIRET not found in the registry"}
if e.response.status_code == 400:
return {"error": "Invalid SIRET format"}
raise

if company["etat_administratif"] == "F":
return {
"error": "This establishment is closed (ferme)",
"closed_since": company.get("date_creation"),
}

return {
"company_name": company["denomination"],
"trade_name": company["enseigne"] or company["denomination_usuelle"],
"address": company["geo_adresse"],
"postal_code": company["code_postal"],
"city": company["commune"],
"is_headquarters": company["est_siege"],
"activity_code": company["code_naf"],
"workforce": company["tranche_effectifs_libelle"],
"siren": company["siren"],
"siret": company["siret"],
}


# User enters their SIRET in a registration form
form = autofill_company_form("802 954 785 00028")
if "error" not in form:
print(f"Welcome, {form['company_name']}!")
print(f"Address: {form['address']}")
print(f"Workforce: {form['workforce']}")

This saves the user from manually typing their company name, address, and other details. It also lets you verify that the SIRET they entered actually exists and corresponds to an active business — a basic but important KYC step.

Tips for working with SIRENE data

Whether you’re building an onboarding flow, a CRM integration, or a compliance check, the SIRENE database is the authoritative source for French company data. How you access it is up to you.

I’m building European Business API — a toolkit of 7 APIs for European B2B operations. Free tier available.

This article was originally published on Fintech Tag and is republished here under RSS syndication for informational purposes. All rights and intellectual property remain with the original author. If you are the author and wish to have this article removed, please contact us at [email protected].

NexaPay — Accept Card Payments, Receive Crypto

No KYC · Instant Settlement · Visa, Mastercard, Apple Pay, Google Pay

Get Started →