How to Verify a French Company (SIREN/SIRET) via API in 2026

A step-by-step tutorial: check a SIREN's digits, look a French company up by SIREN, SIRET or VAT number, search by name, read the source and handle every error.

· By the Fuentio team · 7 min read

Share image: "How to Verify a French Company (SIREN/SIRET) via API in 2026" on Fuentio's paper background, with the Tutorial label.

You have a French company's number, from a form, an invoice or a contract, and you want to know: is it real, what's its legal name, is it still active? This tutorial does that through the Fuentio API, step by step, with cURL, Python and JavaScript, and shows what to do when something goes wrong.

What you'll learn

  • How to check a SIREN's digits before you call anything
  • How to look a company up by SIREN, SIRET or VAT number
  • How to search by name when you have no number
  • How to read the answer, its source and its date
  • How to handle each error, and what each call costs

Step 1: Get an API key

Create a free key with one request (100 units a month on the Free plan):

curl -X POST https://api.fuentio.com/v1/keys

Store it in an environment variable, never in your code:

export FUENTIO_API_KEY="the key you received"

You can also try a few calls without a key: the keyless trial allows 20 units a day per address.

Step 2: Check the number before you call

A French company is identified by its SIREN, nine digits. According to INSEE, the ninth digit is a check digit, so most typos can be caught locally, for free. The check is the Luhn algorithm:

def siren_ok(siren: str) -> bool:
    digits = siren.replace(" ", "")
    if len(digits) != 9 or not digits.isdigit():
        return False
    total = 0
    for i, ch in enumerate(reversed(digits)):
        n = int(ch) * (2 if i % 2 else 1)
        total += n - 9 if n > 9 else n
    return total % 10 == 0

print(siren_ok("441639465"))  # Renault: True
print(siren_ok("441639465"[:-1] + "6"))  # last digit changed: False
function sirenOk(siren) {
  const d = siren.replace(/\s/g, "");
  if (!/^\d{9}$/.test(d)) return false;
  let total = 0;
  [...d].reverse().forEach((ch, i) => {
    let n = Number(ch) * (i % 2 ? 2 : 1);
    total += n > 9 ? n - 9 : n;
  });
  return total % 10 === 0;
}

If you skip this step, the API does the same check and tells you, at no cost (see step 6).

A SIREN lookup step by step: the nine-digit number, the check digit (a wrong one returns invalid_identifier and costs nothing), the lookup GET /v1/companies/FR/441639465, and the answer with source, licence and check date
From a number to a sourced answer.

Step 3: Look the company up by SIREN

One request, one unit:

curl https://api.fuentio.com/v1/companies/FR/441639465 -H "Authorization: Bearer $FUENTIO_API_KEY"

With Python (httpx):

import os, httpx

r = httpx.get("https://api.fuentio.com/v1/companies/FR/441639465",
              headers={"Authorization": f"Bearer {os.environ['FUENTIO_API_KEY']}"})
body = r.json()
company = body["data"]
print(company["name"]["original"], company["status"]["value"], company["provenance"]["source"])

With JavaScript (fetch):

const res = await fetch("https://api.fuentio.com/v1/companies/FR/441639465", {
  headers: { Authorization: `Bearer ${process.env.FUENTIO_API_KEY}` },
});
const { data, meta, error } = await res.json();
console.log(data.name.original, data.status.value, data.provenance.source);

Spaces, dots and dashes inside the number are ignored: 441 639 465 works too.

Step 4: Read the answer

Every answer has the same three parts: data, meta and error. Here is the real answer for Renault, shortened:

{
  "data": {
    "id": "FR-SIREN-441639465",
    "identifiers": [
      {"scheme": "FR_SIREN", "value": "441639465", "primary": true},
      {"scheme": "FR_SIRET", "value": "44163946500091", "primary": false},
      {"scheme": "EU_VAT", "value": "FR63441639465", "primary": false}
    ],
    "name": {"original": "RENAULT"},
    "legal_form": {"original_code": "5510", "original_label": "SA nationale à conseil d'administration", "elf_code": "1NF1"},
    "status": {"value": "active", "original_label": "Active", "as_of": "2025-12-06"},
    "incorporation_date": "2002-04-01",
    "registered_address": {"street": "122 B AVENUE DU GENERAL LECLERC", "postal_code": "92100", "locality": "BOULOGNE-BILLANCOURT", "country": "FR"},
    "activity": {"original_code": "64.20Z", "nace_rev2": "64.20", "label_en": "Activities of holding companies"},
    "provenance": {
      "source": "fr_recherche_entreprises",
      "source_url": "https://recherche-entreprises.api.gouv.fr/search?q=441639465&per_page=1",
      "license": "Licence Ouverte / Etalab 2.0 (Annuaire des Entreprises, INSEE Sirene); officers: INPI RNE reuse licence",
      "last_verified_at": "2026-10-05T14:02:11Z",
      "source_updated_at": "2026-10-05",
      "coverage_level": "full"
    }
  },
  "meta": {"source": "fr_recherche_entreprises", "last_verified_at": "2026-10-05T14:02:11+00:00", "stale": false},
  "error": null
}

The fields to read first:

  • status.value (active, or a closed state) and status.original_label, the register's own word. In France a closed company is "ceased", with the date the register gives.
  • name.original, the legal name as registered.
  • provenance: the source, a link to it, its licence, and when it was checked. Keep it with your record.
  • meta.stale: true means the official source wasn't answering and you're getting the last copy we checked, with its date. Decide whether that's good enough for your case.

Step 5: Look up by SIRET or VAT number, or search by name

A SIRET points to an establishment; the API resolves it to the company, since the legal facts belong to the company:

curl https://api.fuentio.com/v1/companies/FR/44163946500091 -H "Authorization: Bearer $FUENTIO_API_KEY"
curl https://api.fuentio.com/v1/companies/FR/FR63441639465 -H "Authorization: Bearer $FUENTIO_API_KEY"

With no number, search by name (two units). Filter by postal code, town or status to narrow it down, and page through results (page_size up to 25):

curl "https://api.fuentio.com/v1/companies/search?country=FR&q=renault&postal_code=92100" -H "Authorization: Bearer $FUENTIO_API_KEY"

The search answer's meta tells you total, page, page_size and next_page. When several companies share a name, show the candidates to a person, or ask your user for the SIREN: a name is a label, a number is an identity.

Step 6: Handle every error

Errors use the same envelope, with an error.code from a closed list and a hint for the next step. The ones you'll meet with French numbers:

  • invalid_identifier (400): the number has the format of a SIREN but its check digits are wrong, for example Renault's SIREN with its last digit changed. Free.
  • not_found (404): a well-formed number with no public record. In France, the company may have opted out of public diffusion. This one costs one unit, because it's a real answer.
  • rate_limited (429): too many requests per second for your plan. Wait the seconds given in the Retry-After header.
  • quota_exceeded (402): your units are used up; error.details says your limit and when it resets.
  • upstream_unavailable (503, or 504 when a request runs longer than 30 seconds): the official source isn't answering and there's no earlier copy to serve. Retry later.
body = r.json()
if body["error"]:
    print(body["error"]["code"], "-", body["error"]["hint"])

Except not_found on a well-formed number, errors cost nothing.

Step 7: Put it together in your form

In a real product, these steps usually sit behind a sign-up or supplier form. A simple order works well:

  1. Check the digits in the browser or on your server as the user types. Tell them at once if the number can't be right; it's the cheapest error to fix.
  2. When the form is sent, look the company up by the number.
  3. Compare the answer with what the user typed: the legal name after normalising case, accents and punctuation, and the postal code of the registered office.
  4. Accept a clear match, and send the rest to a person with the source link: a different name, a ceased company, or a stale answer when your use needs today's record.

Don't refuse automatically on a small difference in the name. Companies often write their own name in a shorter or older form than the register. A person looking at both, with the source in hand, decides faster than a rule tuned to every case.

If the user types an establishment's SIRET, you get the company's record. Keep the SIRET too: it tells you which site of the company you deal with, which matters for deliveries and invoices.

Step 8: Keep the answer with your record

When someone asks later how you knew a company was active, you want to show the answer as it was on the day you checked. Store, next to your customer or supplier record:

  • The number you looked up, and the record's id.
  • The legal name and the status, with the status date the register gave.
  • The provenance block: source, source link, licence, last_verified_at and source_updated_at.

That's enough to show where each fact came from and when, without storing more personal data than you need. Check again from time to time: companies close, move and change their name, and a check from last year says nothing about today.

What does each step cost?

  • Lookup by SIREN, SIRET or VAT number: 1 unit.
  • Search by name: 2 units.
  • The coverage list: free.

See the pricing page for the plans, and what we cover in France and Spain.

For the background on the French numbers, read French company registers explained; for where this check fits in onboarding, read what KYB is.

Frequently asked questions

Do I need to check the digits myself?

No, the API checks them and returns invalid_identifier for free. Checking locally saves a round trip and catches typos in your own forms.

Why did a well-formed SIREN return not_found?

The company may have opted out of public diffusion, so the official source doesn't publish it. That's a real answer, so it costs one unit.

Can I look up by SIRET?

Yes. The API resolves a SIRET to its company, and returns the company's record with the SIRET among its identifiers.

How fresh is the answer?

Each record carries the time we checked the source (last_verified_at) and the source's own update date (source_updated_at).

Sources

← All articles · RSS feed