Skip to content

Search by Name, Then Look Up by ID

Most real-world workflows start with a name, not a registry number. The two-step search → lookup pattern is the cheapest, most accurate way to resolve a name to a full company record.

1. SEARCH  →  GET /v1/companies/search?q=...&country=FR    (free)
2. LOOKUP  →  GET /v1/companies/FR/{registry_id}           (1 result)

Search is free and fuzzy. Lookup is exact and consumes one billable result. Together they let you spend nothing on the disambiguation step and pay only for the records you actually keep.

Why two steps?

Search is optimised for recall — it returns ranked candidates so you (or your model) can pick the right one. Lookup is optimised for completeness — it returns the canonical record with every field the registry publishes. Trying to get both in one call would either bill for every search hit or strip detail from the result.

Pass a name fragment in q and always include the country you expect. Country-scoped search is fast and costs one rate-limit unit; cross-country search scans all 19 partitions and costs 10×.

curl -G "https://api.firmadb.com/v1/companies/search" \
  -H "Authorization: Bearer $FIRMADB_API_KEY" \
  --data-urlencode "q=Société Générale" \
  --data-urlencode "country=FR" \
  --data-urlencode "limit=5"
import httpx, os

resp = httpx.get(
    "https://api.firmadb.com/v1/companies/search",
    params={"q": "Société Générale", "country": "FR", "limit": 5},
    headers={"Authorization": f"Bearer {os.environ['FIRMADB_API_KEY']}"},
)
candidates = resp.json()["results"]
const url = new URL("https://api.firmadb.com/v1/companies/search");
url.searchParams.set("q", "Société Générale");
url.searchParams.set("country", "FR");
url.searchParams.set("limit", "5");

const resp = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.FIRMADB_API_KEY}` },
});
const { results } = await resp.json();

What you get back

Search returns a ranked list of candidates. Each candidate carries a registry_id, a score between 0 and 1, and the matched_fields that produced the hit.

{
  "results": [
    {
      "country": "FR",
      "registry_id": "914002639",
      "name": "L'OREAL",
      "status": "active",
      "score": 0.97,
      "matched_fields": ["name"],
      "address": { "city": "PARIS", "postal_code": "75009" }
    },
    {
      "country": "FR",
      "registry_id": "552120222",
      "name": "L'OREAL PRODUITS DE LUXE FRANCE",
      "status": "active",
      "score": 0.84,
      "matched_fields": ["name"]
    }
  ],
  "total_count": 47,
  "total_count_relation": "approximate",
  "has_more": true,
  "next_cursor": "eyJrIjoiMC44NCIsImlkIjoiNTUyMTIwMjIyIn0"
}

Approximate counts

total_count_relation: "approximate" means the value can be off by up to ±20%. Never use an approximate count for billing math — call firmadb_estimate_cost instead.

Picking the right candidate

For distinctive names, the top result is almost always correct. For common names ("ABC Trading"), inspect address, status, and score before committing to a lookup:

  • score >= 0.9 and a single result → safe to look up directly.
  • Multiple high-scoring results → disambiguate by city, postcode, or NACE.
  • All scores below 0.5 → try a query variant (drop the legal suffix, add a city).

Query tips

  • Strip legal suffixes. Search "Acme Holdings", not "Acme Holdings Limited". Suffixes like SA, Ltd, GmbH, BV, OY add noise.
  • Use ASCII fallbacks. "Société Générale" matches "Société Générale". Search is diacritic-insensitive.
  • Minimum 3 characters. Shorter queries return 400 query_too_short.
  • Filter aggressively. Add nace= or status=active when you know them — fewer candidates means less to disambiguate.

Step 2 — Lookup

Once you have a registry_id, switch to the exact lookup endpoint. This is the canonical record: every field the registry publishes, plus FirmaDB metadata (source_url, data_freshness, request_id).

curl "https://api.firmadb.com/v1/companies/FR/552120222" \
  -H "Authorization: Bearer $FIRMADB_API_KEY"
import httpx, os

resp = httpx.get(
    "https://api.firmadb.com/v1/companies/FR/552120222",
    headers={"Authorization": f"Bearer {os.environ['FIRMADB_API_KEY']}"},
)
company = resp.json()
const resp = await fetch(
  "https://api.firmadb.com/v1/companies/FR/552120222",
  { headers: { Authorization: `Bearer ${process.env.FIRMADB_API_KEY}` } }
);
const company = await resp.json();

A successful response (abbreviated):

{
  "country": "FR",
  "registry_id": "552120222",
  "name": "SOCIETE GENERALE",
  "status": "active",
  "status_active": true,
  "address": {
    "street": "29 BOULEVARD HAUSSMANN",
    "postal_code": "75009",
    "city": "PARIS",
    "country": "FR"
  },
  "nace_code": "64.19Z",
  "website": null,
  "source_url": "https://annuaire-entreprises.data.gouv.fr/etablissement/55212022200013",
  "data_freshness": {
    "last_confirmed_at": "2026-05-04T03:14:22Z",
    "freshness_class": "fresh"
  },
  "request_id": "req_01HZAB7MJX9V8GG6P3K5R2QC4P"
}

Always preserve source_url and data_freshness

These two fields are your provenance chain. End-users — especially compliance reviewers — need a way to verify the data against the original government registry. Strip them at your peril.

End-to-end example

import httpx, os

API = "https://api.firmadb.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FIRMADB_API_KEY']}"}

def resolve(name: str, country: str) -> dict | None:
    search = httpx.get(
        f"{API}/companies/search",
        params={"q": name, "country": country, "limit": 5},
        headers=HEADERS,
    ).json()

    if not search["results"]:
        return None

    best = search["results"][0]
    if best["score"] < 0.5:
        return None  # too noisy — ask the user to clarify

    return httpx.get(
        f"{API}/companies/{best['country']}/{best['registry_id']}",
        headers=HEADERS,
    ).json()

print(resolve("Societe Generale", "FR")["name"])
# SOCIETE GENERALE

Handling not-found

If the registry ID looks valid but no record exists, you'll get a 404 company_not_found. The error includes a correction and often a suggested_request — agents should retry with the suggestion before giving up.

{
  "type": "https://errors.firmadb.com/company-not-found",
  "title": "Company not found",
  "status": 404,
  "code": "company_not_found",
  "detail": "No company with registry_id '12345' exists in FR.",
  "correction": "Try searching by name with /v1/companies/search?country=FR&q=...",
  "suggested_request": null
}

See errors for the full catalog and recovery patterns.

What's next