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:

verdictMeaningcandidates
exactOne legal unit carries this name key in the register (within your filters)exactly one element
ambiguousSeveral legal units carry it - all of them are served, up to 25, with total exacttwo or more elements
noneNo 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:

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

ParameterTypeRequiredDescription
qstringyesThe name as you hold it: legal name or acronym. Must contain at least one letter or digit
departmentstringno2 or 3 digits, matched as a prefix of the head-office postal code (13, 971). Exclusive with postal_code
postal_codestringno5 digits, the exact head-office postal code. Exclusive with department
statusstringnoactive or ceased - the administrative status of the legal unit, the one /company/resolve serves
legal_formstringno1 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:

  • department filters on the postal code, not on the administrative department code. So department=20 returns both Corsican departments together (their postal codes are 20xxx; 2A and 2B are a 400), and department=97 returns the five overseas departments at once.
  • department and postal_code are 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 a 400.
  • status is 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. exact means one legal unit carries the key within your filters. les jardins de provence alone is ambiguous (38 legal units nationwide); with postal_code=13330 it is exact.

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:

  1. case, accents and punctuation are dropped (&, ', -, . become separators);
  2. dotted abbreviations are glued back (S.A.R.L. → sarl), so step 3 can recognise them;
  3. 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;
  4. the remaining words are glued together.

What that folds, measured on the 2026-09-30 register:

You sendKeyMatches names published as
L'OréallorealL'OREAL, LOREAL, L OREAL S A (3 legal units)
S.A.R.L. Boulangerie AureliaboulangerieaureliaBOULANGERIE AURELIA
Boulangerie & AureliaboulangerieaureliaBOULANGERIE AURELIA
Les Jardins de ProvencejardinsprovenceJARDINS DE PROVENCE, AUX JARDINS DE PROVENCE, LES JARDINS DE PROVENCE, SCI LES JARDINS DE PROVENCE

And what it deliberately does not fold:

You sendKeyResult
BNP Paribasbnpparibasambiguous (3) - BNP PARIBAS FACTOR is not among them: an added word is another name
BNP Paribas Factorbnpparibasfactorexact - SIREN 775675069
TotalEnergiestotalenergiesnone - the register publishes TOTALENERGIES SE, and se is not in the folded list
TotalEnergies SEtotalenergiesseambiguous (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_of is 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

FieldTypeDescription
querystringThe text you sent, echoed as received
verdictstringexact, ambiguous or none
candidatesobject[]The legal units carrying the key, ordered by SIREN - a reading order, not a relevance ranking. At most 25
totalnumberExact number of matching legal units, before truncation
truncatedbooltrue when total exceeds the 25 candidates served - narrow with a filter
normalizedstringThe search key your text was folded to
filtersobjectThe filters applied, echoed ({} when none)
limitsstringThe fixed sentence quoted above

Each candidate:

FieldTypeDescription
idstringThe SIREN - pass it as is to any other company/* route
id_kindstringAlways siren
labelstringThe name as the register publishes it - not the key
matched_onstringdenomination or acronym - which published label matched
statusstringactive or ceased (legal unit)
legal_formobject{ code, label } - INSEE legal category; label: null for a code outside the embedded nomenclature
headquartersobject{ 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.

StatuscodeCase
400MISSING_PARAMETERq absent or empty
400INVALID_INPUTq 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
400INVALID_INPUTUnknown query parameter name (name= instead of q=) - the message lists what the route accepts
503DATA_UNAVAILABLEThe name index has not been built yet - there is no last good index to serve
500INTERNALInternal 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 same lookup contract for vehicles: a designation in, a make/model/year key out - where ambiguous is 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 /catalog and how settlement works.