This guide takes you from nothing to a working integration with the Fuentio REST API: a first lookup of a French company, the shape of every answer, how units are counted, and how to handle errors without hammering the API. Every example uses a real company and a real answer recorded from the API.
You need nothing to start: the keyless trial gives 20 units a day. When you want more, a free key gives 100 units a month.
What you'll learn
- How to start without a key, and how to create a free key
- Your first lookup in curl, Python and JavaScript
- The three parts of every answer:
data,meta,error - How units are counted, and where to read what you have left
- Which errors to fix, which to retry, and how long to wait
Step 1: start keyless, or create a free key
Every endpoint that reads company data works without a key, on the keyless trial: 20 units a day per connection. That is enough to try a few lookups.
For your application, create a free key. One call, with your email (one free key per person; the key is shown once, so store it straight away):
curl -X POST https://api.fuentio.com/v1/keys -H "Content-Type: application/json" -d '{"email": "YOUR_EMAIL"}'
You can also get it from the pricing page. Send the key in the X-API-Key header on every call (or as Authorization: Bearer YOUR_API_KEY). Never put it in front-end code: call the API from your server.
Step 2: your first lookup
Look up RENAULT by its SIREN, the 9-digit number every French company has:
curl https://api.fuentio.com/v1/companies/FR/441639465 -H "X-API-Key: YOUR_API_KEY"
The same call in Python, with httpx:
import httpx
r = httpx.get("https://api.fuentio.com/v1/companies/FR/441639465",
headers={"X-API-Key": "YOUR_API_KEY"}, timeout=30)
body = r.json()
if body["error"]:
print(body["error"]["code"], body["error"]["message"], body["error"]["hint"])
else:
company = body["data"]
print(company["name"]["original"], company["status"]["value"], body["meta"]["source"])
And in JavaScript (Node 18+ or any runtime with fetch):
const res = await fetch("https://api.fuentio.com/v1/companies/FR/441639465", {
headers: { "X-API-Key": process.env.FUENTIO_API_KEY },
});
const body = await res.json();
if (body.error) console.log(body.error.code, body.error.message);
else console.log(body.data.name.original, body.data.status.value, body.meta.source);
For France, the identifier can be a SIREN, a SIRET (14 digits), a French VAT number or an LEI; spaces, dots and dashes are ignored. For Spain, it is the Registro Mercantil sheet (for example V-5581) or an LEI. Spain has no lookup by tax ID: search by name first (see how to find a Spanish company by name).
Step 3: read the answer
Every answer has the same three parts.
data: the company record (or a list, for a search). Values keep the register's own wording, with normalised codes and English labels next to them.meta: where the answer comes from and what it cost:source,fetched_at(when we read the source),last_verified_at(when we last checked it),stale(trueif the source was down and you got our last copy), the units charged and arequest_id.error:null, or an object with acode, amessageand ahintthat tells you the next step.
Here is part of the real answer for RENAULT, recorded from the API on 12 October 2026:
{
"data": {
"id": "FR-SIREN-441639465",
"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": { "original": "122-122 122 B AVENUE DU GENERAL LECLERC 92100 BOULOGNE-BILLANCOURT", "postal_code": "92100" },
"activity": { "original_code": "64.20Z", "original_label": "Activités des sociétés holding", "label_en": "Activities of holding companies" }
},
"meta": {
"source": "fr_recherche_entreprises",
"stale": false,
"cost": { "units": 1 },
"request_id": "req_3geNJDKcO50j4-ke"
},
"error": null
}
Two habits pay off later:
- Store
meta.sourceandmeta.last_verified_atwith every value you keep. When someone asks "where does this come from?", you have the answer. - Keep the
request_id. If an answer looks wrong, quote it when you write to us, or report it withPOST /v1/issues.
Step 4: understand units
Each call costs a fixed number of units. The common ones:
| Call | Units |
|---|---|
GET /v1/companies/{country}/{identifier} (lookup) | 1 |
GET /v1/companies/search?country=&q= | 2 a page |
GET /v1/companies/{country}/{identifier}/history | 1 |
GET …/insolvency, …/address, …/vat | 1 each |
POST /v1/verifications | 5 |
GET /v1/countries, GET /v1/sources, GET /v1/usage | free |
Rules worth knowing:
- Errors cost nothing, except
not_foundon a well-formed ID (1 unit): it is a real answer from the register. - Units reset each month (the keyless trial each day). Over quota you get
quota_exceeded, never an overage charge. - Every answer carries
meta.cost: the units this call cost (units) and the units you have left (remaining). TheX-Request-IDresponse header repeats therequest_id. GET /v1/usage(free) returns your plan, the units used this period and the units left. The account page shows the same thing in a browser.
Step 5: handle errors, and retry only what is worth retrying
The list of error codes is closed, so you can handle every case. Group them by what your code should do:
| Do this | Codes |
|---|---|
| Fix the request, don't retry | invalid_request, invalid_identifier, ambiguous_identifier (pass id_scheme), id_scheme_not_supported (e.g. a Spanish NIF: search by name), country_not_supported, coverage_unavailable, payload_too_large |
| Treat as an answer, don't retry | not_found (a valid ID with no public record) |
| Fix the key or plan | unauthorized (unknown or revoked key), quota_exceeded, already_exists (you already have a free key) |
| Wait, then retry | rate_limited (429: wait for the Retry-After header), upstream_unavailable (503: the official source isn't answering), internal_error (500: once, then quote the request_id) |
A small retry wrapper in Python that follows those rules:
import time
import httpx
RETRY = {"rate_limited", "upstream_unavailable", "internal_error"}
def fuentio_get(path, key, attempts=4):
for attempt in range(attempts):
r = httpx.get("https://api.fuentio.com" + path, headers={"X-API-Key": key}, timeout=30)
body = r.json()
error = body.get("error")
if not error or error["code"] not in RETRY or attempt == attempts - 1:
return body
wait = int(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
It retries only the three temporary errors, honours Retry-After when the API sends it, and backs off otherwise. Everything else comes straight back to your code, where the hint tells you what to do.
Step 6: what to build next
- A decision, not just data:
POST /v1/verificationsreturns an outcome with reasons and a proof code anyone can check. See how to verify a company and get a proof code via API. - Insolvency and VAT: see insolvency and VAT checks via API.
- A list of companies: see how to check a list of SIREN numbers in bulk, or try the free list check first.
- An AI agent: the same data and units are available over MCP: connect your agent.
The full reference is in the docs, with the OpenAPI file and the interactive docs. See what we cover country by country.
Paging through search results
A search returns one page at a time and tells you where you are. The meta of the recorded search for MERCADONA shows the fields to read: page, page_size (10 by default), total (how many companies match), and next_page, which is null on the last page. Each page costs 2 units, so ask only for the pages you need: if the first result has a high match.score and the right town, stop there. When you do need every match, loop while next_page is set, and keep a ceiling on the number of pages so a very common name can't eat your monthly units.
Two more habits help with searches. Narrow them with postal_code or locality whenever you have one: fewer pages, better ranking. And never treat the first result as the company you meant: a search ranks names, and its meta.uncertainty says so. When the decision matters, confirm with an identifier, or call POST /v1/resolve, which explains why a candidate is linked, possible or rejected.
Limits. France: every company in the public Sirene directory, read from the State's Annuaire des Entreprises; insolvency notices from BODACC since 2008. Spain: companies with an act published in the BORME since 2 January 2009, events only, with a status that is unknown unless an act says otherwise; there is no lookup by tax ID. No person's name is ever returned. The example answer was recorded on 12 October 2026; live answers change when the registers do.
Frequently asked questions
Do I need an API key to try the Fuentio API?
No. Every endpoint that reads company data works on the keyless trial, 20 units a day. A free key gives 100 units a month.
How do I send my API key?
In the X-API-Key header, or as Authorization: Bearer YOUR_API_KEY. Call the API from your server, never from front-end code.
Which errors should I retry?
Only rate_limited (after Retry-After), upstream_unavailable and internal_error. The others need a fix in the request, the key or the plan, or are a real answer (not_found).
How do I know how many units I have left?
Read meta.cost.remaining on any answer, or call GET /v1/usage, which is free.
Sources
- Fuentio API reference: docs, the OpenAPI file and the interactive docs
- The example answer: RENAULT, recorded from the API on 12 October 2026 (
site/data/api-examplesin the website repository) - Official sources behind the data: sources
