registercheckby openlaw group
Guides

Find a company

Turning a name your user typed into a company id you can rely on.

Almost every integration starts here: you have a string, and you need the right company. There are three ways in, and they are not interchangeable.

If you have a register number

This is the only exact route. Give the number and the court's code (D2505 is Amtsgericht Memmingen):

curl -G "https://api.registercheck.de/v1/search/companies" \
  --data-urlencode "register_number=13557" \
  --data-urlencode "register_court=D2505" \
  -H "Authorization: Bearer $REGISTERCHECK_API_KEY"

Register numbers repeat across courts — HRB 670 exists at most of them — so a number without a court will match many unrelated companies.

If a human is typing

Use /search/suggestions. It is built for autocomplete: one call per keystroke-ish, cheap, and ranked.

const res = await fetch(
  `${BASE}/search/suggestions?name=${encodeURIComponent(term)}&limit=8`,
  { headers: { Authorization: `Bearer ${key}` } },
);
const { data } = await res.json();

Each hit is a summary, and its type says whether it is a company or a person. For a company, show register.formatted and status next to the name. Two companies in the same town with nearly the same name are common, and the register line is how your user tells them apart.

Debounce to ~250 ms, and start at the second character: a term shorter than 2 characters (or longer than 200) is a 400 and costs nothing. At 1 credit a call, a busy search box is the one place this API can quietly get expensive.

If you are matching a list

Use /search/companies with as many fields as you have. They combine, and name or register_number is required:

ParameterMatching
namethe name or part of it, by words (see below)
register_number, register_court (the court's code), register_typeexact
cityexact, the city of the registered address
statusany of the statuses given

Unknown parameters are rejected

A parameter the endpoint does not read answers 400 validation_failed, naming it in errors[].name, and costs nothing. ?name=Siemens&register_court_code=D2601 (the name before 2026-10-02) is refused rather than answered with Siemens companies at every court.

How name matches

name matches by words, ignoring case, on all three searches:

  • Every word must appear, in any order: name=Enduro Reise finds Enduro Reise GmbH and Reise Enduro UG.
  • The last word also matches as the start of a word: name=Beisp finds Beispiel GmbH.
  • Small typos are tolerated: name=Siemsn finds Siemens.
  • A fragment from the middle of a word does not match: name=analytics does not find Dataanalytics GmbH.
  • When no name contains every word, names that contain fewer of them are returned instead, so check the hits rather than assuming each one carries every word.

Company search ranks active companies first, then the best match; person search and suggestions rank by match.

`q` is the old name of `name`

Until 2026-12-31 the searches still accept q and treat it exactly like name. Every response to a request that uses it carries Deprecation (RFC 9745) and Sunset: Thu, 31 Dec 2026 23:59:59 GMT (RFC 8594) headers, and a Link with rel="deprecation" to this section. After that date q is removed, and a request that uses it is refused like any unknown parameter. Sending both name and q is a 400.

Matching well

  • Strip the legal form before comparing. legal_form tells you which one the name carries.
  • Use the city. name plus city resolves most ambiguity in one call.
  • Expect branches. A name that matches several entries at different courts is usually one business with registered branches, not a bad match.
  • Score, do not guess. total_count of 1 is a match; total_count of 40 means you need another field.

Find a person's companies

The routes above start from a company's name or number, and /search/companies takes no person. When you start from a person, use POST /search/companies/filter, at 1 credit per 10 returned results, with a condition on the company's roles.

curl -X POST "https://api.registercheck.de/v1/search/companies/filter" \
  -H "Authorization: Bearer $REGISTERCHECK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "roles": {
      "conditions": [{ "person_id": "per_3v3w754f3t9w79tajtdgf8pkcg" }]
    },
    "company": { "status": ["active"] },
    "limit": 20
  }'

The person_id is the id of a hit from GET /search/persons, at 1 credit a call. A condition can also ask by name, as a substring: {"name": {"contains": "Hans Müller"}} matches a person's first, last or full name as well as the name of a company that holds a role, and first_name and last_name match one of a person's names. Only contains is applied to these three (equals and starts_with answer 400), and a substring also matches longer names (Müller finds Müllermann), so the id is the precise way when you have one.

The answer is a page of summary hits, as from /search/companies: here, the active companies where that person holds or held a role, as a managing director, a board member, a liquidator and the like. Page through it with cursor: send the same filter again, with cursor set to the page's next_cursor. A cursor holds its filter: sent with a changed filter, it answers 400 validation_failed naming /cursor. These limits apply today:

  • One person_id per condition, and one roles condition per filter. A second condition, or min_matching / max_matching even with one condition, answers 400: the data store does not combine conditions yet. One human who appears under two person ids takes two filters, each priced by its limit.
  • role_type narrows to Prokura only. Prokura is held apart from the other roles: without role_type the condition reads a person's other roles, and "role_type": ["authorized_signatory"] reads the companies where they hold Prokura instead. Any other role type answers 400: the register data records no role type for the other roles. Prokura holders are found by person_id, status and the date fields, not by name: name, first_name or last_name together with "role_type": ["authorized_signatory"] answers 400.

If one person's roles are all you need, GET /persons/{person_id}/roles lists them for 10 credits. Filtering has the other conditions, and Persons explains why one human can appear under several ids.

Then store the id

{ "id": "cmp_63b6n0c6p09pnsrr6nrtd0dzsc" }

Store that id. Names change, companies are renamed and re-registered, and register numbers are reused across courts. The id is the only stable handle.

On this page