GET /company/lookup
Turns the name an agent holds into the SIREN that
/company/resolve,
/company/events,
/company/vat and
/company/report require. The agent sends the
legal name or acronym as it has it - from an email signature, a contract
header, a web page - and gets back a closed verdict and the companies of
the official INSEE Sirene register that carry that name. The answer is
served from a local index built at ingestion, so a lookup resolves in
milliseconds with no network call and no secret at request time.
This route sells a search, in a catalog that otherwise sells sourced
facts. That is why the next two sections come before any example: what a
resolution does not attest, and what is not indexed at all. See the live
/catalog for the authoritative listing and price.
The verdict - closed, and never a pick
data.verdict is one of exactly three values:
verdict | Meaning | candidates |
|---|---|---|
exact | One legal unit carries this name key in the register (within your filters) | exactly one element |
ambiguous | Several legal units carry it - all of them are served, up to 25, with total exact | two or more elements |
none | No legal unit carries it | [] |
candidates is always present - empty on none, a one-element list on
exact - so a consumer never has to guess the shape of the answer from the
verdict. The service never chooses for you: when several companies share
a name, the list is the answer, and each candidate carries what tells them
apart (status, legal form, head-office postal code and town).
Ambiguity is real but it is the minority case here: measured on the full
index of the August 2026 register (13.7M rows, 9.2M distinct keys), 89.74 %
of name keys resolve exact. It concentrates where you would expect it - common trade words,
place names, family names used as company names.
none is a paid answer
A none is served as a 200 and billed. It is not an error and never a
404: “no company in the register carries this name” is the result of the
search, and it is often exactly what the agent needed to know before trusting
an invoice header. Only requests the service cannot answer - a missing or
empty q, a malformed filter - leave the 200 range, and those are not
billed. Same doctrine as the unresolved entry of
/certify/batch.
What a resolution does not attest
Every answer carries the same fixed sentence in data.limits, served
verbatim:
A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. Verify a candidate against /company/resolve before acting on it. Only the legal names and acronyms of legal persons are indexed, as the Sirene register publishes them — sole traders (personnes physiques) are not searchable by name here, and neither are trade names, brands, addresses or officers. Candidates are ordered by SIREN, which is a reading order, not a relevance ranking.
Read the first half literally. An exact verdict means “one legal unit
carries this name in the register” - never “this is the company you are
dealing with”. A supplier called BOULANGERIE AURELIA on an invoice may be
the one in Fréjus that the register knows, or a business that never
registered under that name. The lookup gives you an identifier to check, not
an identity:
GET /company/resolvefor the identity card behind the SIREN (status, legal form, head office);GET /preflight/supplierfor the counterparty check before paying it.
What is not indexed
The index holds the legal names (dénomination) and acronyms (sigle) of legal persons, as Sirene publishes them. It does not hold:
- sole traders (personnes physiques, entrepreneurs individuels) - 56.5 % of the legal units in the register. Sirene publishes their last and first name, not a company name, so there is nothing to index;
- trade names and shop signs (nom commercial, enseigne);
- brands;
- addresses;
- officers and shareholders.
So a none on the name of a craftsman trading under their own name does
not mean “absent from the register” - it means “outside what is indexed”.
If the agent holds a SIREN or a SIRET instead, it should skip this route and
call /company/resolve directly.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | yes | The name as you hold it: legal name or acronym. Must contain at least one letter or digit |
department | string | no | 2 or 3 digits, matched as a prefix of the head-office postal code (13, 971). Exclusive with postal_code |
postal_code | string | no | 5 digits, the exact head-office postal code. Exclusive with department |
status | string | no | active or ceased - the administrative status of the legal unit, the one /company/resolve serves |
legal_form | string | no | 1 to 4 digits, matched as a prefix of the INSEE legal-category code (5 = every commercial company, 5710 = SAS only) |
GET /company/lookup?q=Les%20Jardins%20de%20Provence&department=13&status=active
Four things worth knowing about the filters before reading a result:
departmentfilters on the postal code, not on the administrative department code. Sodepartment=20returns both Corsican departments together (their postal codes are20xxx;2Aand2Bare a400), anddepartment=97returns the five overseas departments at once.departmentandpostal_codeare exclusive: they filter the same column, and accepting both would mean deciding which one wins - answering a different question than the one asked. Sending both is a400.statusis the legal unit’s, never an establishment’s. Leaving it out returns both active and ceased companies, which is usually what you want: a ceased company is a useful answer, often the one that explains why an invoice went unpaid.- A filter narrows the verdict.
exactmeans one legal unit carries the key within your filters.les jardins de provencealone isambiguous(38 legal units nationwide); withpostal_code=13330it isexact.
Every filter accepted is echoed in data.filters, so the verdict can always
be read against the question that produced it.
How the name is folded
The text you send and every name in the register go through the same
function, and matching is an equality on its output - the search key,
echoed in data.normalized. There is no similarity score, no edit distance,
no typo tolerance. Four steps:
- case, accents and punctuation are dropped (
&,',-,.become separators); - dotted abbreviations are glued back (
S.A.R.L.→sarl), so step 3 can recognise them; - a legal form at the start or the end is dropped -
sa,sas,sasu,sarl,eurl,sci,snc,scs,sca,scm,scp,selarl,selas,gie,eirl,sem,scop,scic- and linking words anywhere -le,la,les,de,du,des,au,aux,et; - the remaining words are glued together.
What that folds, measured on the 2026-09-30 register:
| You send | Key | Matches names published as |
|---|---|---|
L'Oréal | loreal | L'OREAL, LOREAL, L OREAL S A (3 legal units) |
S.A.R.L. Boulangerie Aurelia | boulangerieaurelia | BOULANGERIE AURELIA |
Boulangerie & Aurelia | boulangerieaurelia | BOULANGERIE AURELIA |
Les Jardins de Provence | jardinsprovence | JARDINS DE PROVENCE, AUX JARDINS DE PROVENCE, LES JARDINS DE PROVENCE, SCI LES JARDINS DE PROVENCE |
And what it deliberately does not fold:
| You send | Key | Result |
|---|---|---|
BNP Paribas | bnpparibas | ambiguous (3) - BNP PARIBAS FACTOR is not among them: an added word is another name |
BNP Paribas Factor | bnpparibasfactor | exact - SIREN 775675069 |
TotalEnergies | totalenergies | none - the register publishes TOTALENERGIES SE, and se is not in the folded list |
TotalEnergies SE | totalenergiesse | ambiguous (2) |
The TotalEnergies line is the one to remember: a legal form outside the
list above is a word of the name, and a missing word is a different key. When
a well-known company comes back none, retry with the form the register
publishes before concluding anything.
One known limit of the glued key: it cannot tell a removed separator from
a merged word. MAISON NEUVE and MAISONNEUVE share the key maisonneuve
(189 legal units, served as ambiguous with truncated: true). That is a
deliberate trade: on a search key, widening costs you an ambiguous you can
read, while narrowing would cost you a none you cannot see. Every candidate
carries its name as the register publishes it (label), so the
difference stays visible.
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "insee-sirene",
"fetched_at": "2026-10-15T15:25:47.617824945Z",
"freshness": { "kind": "snapshot", "as_of": "2026-09-30T00:00:00Z" }
}
}
provenance.source:insee-sirene, the served Sirene store.freshness.kind:snapshot;as_ofis the end-of-month date of the Sirene stock image the index was built from. A resolution is dated like everything else.
Fields of data
| Field | Type | Description |
|---|---|---|
query | string | The text you sent, echoed as received |
verdict | string | exact, ambiguous or none |
candidates | object[] | The legal units carrying the key, ordered by SIREN - a reading order, not a relevance ranking. At most 25 |
total | number | Exact number of matching legal units, before truncation |
truncated | bool | true when total exceeds the 25 candidates served - narrow with a filter |
normalized | string | The search key your text was folded to |
filters | object | The filters applied, echoed ({} when none) |
limits | string | The fixed sentence quoted above |
Each candidate:
| Field | Type | Description |
|---|---|---|
id | string | The SIREN - pass it as is to any other company/* route |
id_kind | string | Always siren |
label | string | The name as the register publishes it - not the key |
matched_on | string | denomination or acronym - which published label matched |
status | string | active or ceased (legal unit) |
legal_form | object | { code, label } - INSEE legal category; label: null for a code outside the embedded nomenclature |
headquarters | object | { postal_code, town } of the head office - the usual way to tell homonyms apart |
matched_on matters on short inputs. SNCF comes back ambiguous (7):
the national railway company is matched on its acronym - its legal name
is SOCIETE NATIONALE SNCF - next to four legal units whose legal name
folds to sncf (SNCF, S.N.C.F., SCI SN&CF…) and two unrelated
companies that also file SNCF as their acronym. Without the field, a
candidate whose label reads NOUVELLE DE CONSTRUCTION ET DE FABRICATION
would be inexplicable.
When more than 25 legal units match, the first 25 by SIREN are served with
truncated: true and the exact total - 0.28 % of keys were in that case
at the same August 2026 measurement.
The answer never pretends to be complete when it is not; a department,
postal_code or legal_form filter is the way to narrow it.
Example - exact
GET /company/lookup?q=Boulangerie%20Aurelia, against the 2026-09-30 Sirene
snapshot:
{
"data": {
"query": "Boulangerie Aurelia",
"verdict": "exact",
"candidates": [
{
"id": "394406938",
"id_kind": "siren",
"label": "BOULANGERIE AURELIA",
"matched_on": "denomination",
"status": "active",
"legal_form": {
"code": "5710",
"label": "SAS, société par actions simplifiée"
},
"headquarters": { "postal_code": "83600", "town": "FREJUS" }
}
],
"total": 1,
"truncated": false,
"normalized": "boulangerieaurelia",
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. Verify a candidate against /company/resolve before acting on it. Only the legal names and acronyms of legal persons are indexed, as the Sirene register publishes them — sole traders (personnes physiques) are not searchable by name here, and neither are trade names, brands, addresses or officers. Candidates are ordered by SIREN, which is a reading order, not a relevance ranking."
},
"provenance": {
"source": "insee-sirene",
"fetched_at": "2026-10-15T15:25:47.617824945Z",
"freshness": { "kind": "snapshot", "as_of": "2026-09-30T00:00:00Z" }
}
}
The id goes straight into
/company/resolve?siren=394406938, which serves
the identity card of the same company.
Example - ambiguous
GET /company/lookup?q=BNP%20Paribas, same snapshot (limits and
provenance as above):
{
"data": {
"query": "BNP Paribas",
"verdict": "ambiguous",
"candidates": [
{
"id": "442245510",
"id_kind": "siren",
"label": "BNP- PARIBAS",
"matched_on": "denomination",
"status": "active",
"legal_form": {
"code": "8311",
"label": "Comité social économique d'établissement"
},
"headquarters": { "postal_code": "59140", "town": "DUNKERQUE" }
},
{
"id": "500569983",
"id_kind": "siren",
"label": "BNP PARIBAS",
"matched_on": "denomination",
"status": "active",
"legal_form": {
"code": "3220",
"label": "Société étrangère non immatriculée au RCS"
},
"headquarters": { "postal_code": "06400", "town": "CANNES" }
},
{
"id": "662042449",
"id_kind": "siren",
"label": "BNP PARIBAS",
"matched_on": "denomination",
"status": "active",
"legal_form": {
"code": "5599",
"label": "SA à conseil d'administration (s.a.i.)"
},
"headquarters": { "postal_code": "75009", "town": "PARIS" }
}
],
"total": 3,
"truncated": false,
"normalized": "bnpparibas",
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, …"
},
"provenance": { "…": "…" }
}
This is why the service never picks. A works council registered under the
bank’s name and a foreign entity filed in Cannes carry the same key as the
bank itself; the register says so, and so does the answer. Which one the agent
meant is a question only the agent’s context can settle - here the legal form
and the head-office town make it a one-line decision, and a legal_form=5
filter would have made it for you.
Example - none
GET /company/lookup?q=Boulangerie%20Aurelia%20Frejus, same snapshot:
{
"data": {
"query": "Boulangerie Aurelia Frejus",
"verdict": "none",
"candidates": [],
"total": 0,
"truncated": false,
"normalized": "boulangerieaureliafrejus",
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, …"
},
"provenance": {
"source": "insee-sirene",
"fetched_at": "2026-10-15T15:25:47.649994740Z",
"freshness": { "kind": "snapshot", "as_of": "2026-09-30T00:00:00Z" }
}
}
A 200, billed. The town was typed into the name, which makes it another
key - an added word is never folded. The normalized echo shows exactly what
was searched, which is what makes a none explainable rather than mute: the
right call here is q=Boulangerie Aurelia&postal_code=83600.
Freshness
The index is rebuilt from each monthly Sirene stock;
freshness.as_of carries its end-of-month date. A company registered after
that date is not yet in the index - a none is an answer as of as_of.
If a monthly refresh is missed, the service keeps serving the last good index,
honestly dated, and never turns staleness into a 5xx.
Errors
Only requests the service cannot answer leave the 200 range - none of them
are billed. There is no 404 on this route: “this name is not in the
register” is a none, not an error.
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | q absent or empty |
| 400 | INVALID_INPUT | q with neither letter nor digit; a non-numeric or wrong-length filter (department=2A); a status other than active/ceased; both department and postal_code |
| 400 | INVALID_INPUT | Unknown query parameter name (name= instead of q=) - the message lists what the route accepts |
| 503 | DATA_UNAVAILABLE | The name index has not been built yet - there is no last good index to serve |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "code": "MISSING_PARAMETER", "error": "missing or empty required parameter: q" }
{ "code": "INVALID_INPUT", "error": "provide at most one of 'department' or 'postal_code': both filter the registered office postcode" }
{ "code": "INVALID_INPUT", "error": "unknown query parameter 'name'; expected one of: q, department, postal_code, status, legal_form" }
Attribution
Company names and identifiers are derived from the Sirene register, published by INSEE (the French national institute of statistics) under the Licence Ouverte / Open Licence (Etalab).
See also
GET /company/resolve- the identity card behind a SIREN: the call to make on any candidate before acting on it.GET /preflight/supplier- the whole counterparty check before paying, once the SIREN is known.GET /company/report- identity, legal events, VAT, Peppol and GLEIF blocks in one call, per SIREN.GET /company/lei- the same question outside France: legal identity from an exact name plus country, from the GLEIF reference.GET /vehicle/lookup- the samelookupcontract for vehicles: a designation in, a make/model/year key out - whereambiguousis the usual answer, not the exception.GET /medication/lookup- the same contract for medications: a brand or substance name in, a CIS, an NDC or a substance identifier out.GET /label/lookup- the same contract for EU labelling: an ingredient name in, a CAS, EC, INCI or E number out.- For agents - discovery surfaces, the live
/catalogand how settlement works.