registercheckby openlaw group
Guides

Follow register changes

Poll the register-event feed on created_at and never miss a change, across every company.

The shape of it

every few minutes  →  GET /events?created_at[gte]=<T − 10 min>  →  page to the end  →  dedupe by id  →  T = newest created_at

GET /events lists what the register courts entered for any company: a new managing director, a moved registered office, a changed purpose, a capital increase, an insolvency, a deletion. Each item is the same company_event that GET /companies/{id}/events lists for one company, with the company named in company, so you need no extra call to know whose it is.

A company's first entry, its registration, is not in this list. New companies are GET /formations.

GET /company-events is the previous name of this list. It keeps answering exactly as GET /events does, at the same price, and is deprecated: its answers carry a Deprecation header, and a new integration should call GET /events.

Events, not entries

One register entry can carry several events. An entry that removes one managing director and appoints another is two company.officer.* events with the same register_entry_number, and officer_role tells you the role (managing_director, authorized_signatory for a Prokura, liquidator, …). Group by company.id and register_entry_number if you want one row per entry.

The type says what the entry did, read from its text: a deletion is company.deregistered, a change to the articles of association is company.articles.changed, insolvency proceedings are company.insolvency.recorded. An entry that only repeats the registered office or the name already on file is not published.

Why not poll on effective_date

The list is ordered by the register day (effective_date), newest first. That is the order you want to read, and the wrong one to poll on:

  • Late arrivals. About 7 % of entries reach Registercheck one or more days after their register day. They sort into the middle of the list, below rows you already read, so paging from the top stops before it reaches them.
  • The register records a day, not a time. Two polls on the same effective_date cannot tell what is new.

created_at is the moment an event became available through the API. Every event you have not seen yet has a created_at later than your last poll, whatever its register day. It moves only when an event is served again: the register rewrote the entry (its type, day, entry number or the entry before it), and the event comes back under the same id with the new values. An event that no longer stands for a change (it now restates an earlier entry, or its day left the 180-day window) is no longer served; GET /events/{id} answers 404 for it.

Poll

from datetime import datetime, timedelta, timezone

OVERLAP = timedelta(minutes=10)

def poll(since: datetime, seen: dict[str, str]) -> datetime:
    newest = since
    params = {"created_at[gte]": (since - OVERLAP).strftime("%Y-%m-%dT%H:%M:%SZ"), "limit": 100}
    while True:
        r = session.get(f"{BASE}/events", params=params)
        r.raise_for_status()
        page = r.json()
        for event in page["data"]:
            if seen.get(event["id"]) == event["created_at"]:
                continue                           # the overlap re-reads a few; that is its job
            seen[event["id"]] = event["created_at"]
            handle(event)                          # new, or an update of an event you have
            created = datetime.fromisoformat(event["created_at"].replace("Z", "+00:00"))
            newest = max(newest, created)
        if not page["has_more"]:
            return newest                          # store it; it is the next `since`
        params["starting_after"] = page["data"][-1]["id"]

With curl, the brackets need -g so they are not read as a glob:

curl -G -g https://api.registercheck.de/v1/events \
  -H "Authorization: Bearer $REGISTERCHECK_API_KEY" \
  -d "created_at[gte]=2026-09-30T14:00:00Z" \
  -d "type=company.officer.appointed" \
  -d "officer_role=managing_director" \
  -d "state=DE-BY" \
  -d "limit=100"

Three rules:

  1. Overlap by ten minutes and deduplicate by id and created_at. Delivery is at-least-once: the overlap re-reads events you already have, which is what the deduplication is for. An id you have with a newer created_at is an update: replace what you stored. Keep the last hour or so of pairs; that is enough.
  2. Page forward with starting_after until has_more is false. The order is stable (effective_date, then created_at, then id, all descending), so an event that arrives while you page is never served twice in one walk and is caught by the next poll.
  3. Every page costs 10 credits. Poll every few minutes, not every few seconds, and keep limit=100: a quiet interval is one page. Retrieving one event by id costs 1 credit.

Narrow it

Filters combine with the poll (AND across parameters, OR within a repeated one):

You wantAdd
New managing directorstype=company.officer.appointed&officer_role=managing_director
Insolvencies and deletionstype=company.insolvency.recorded&type=company.deregistered
GmbHs and UGs in Bavarialegal_form=gmbh&legal_form=ug&state=DE-BY
One courtregister_court=D2601, or its name (Amtsgericht München) or seat (München)
Companies with at least 25,000 EUR capitalcapital[gte]=25000
Words in the name, the seat, the court or the entryq=holding augsburg

q does not search people's names.

When a cursor stops working

An event whose company or person has been restricted since you listed it is no longer served, and starting_after naming it answers 400 validation_failed. Do not retry the cursor: run the poll again from your stored since. That is the same recipe, and it picks up exactly where you were.

What to know

  • The window. The list covers the last 180 days of register days. Without any effective_date[*] or created_at[*] bound it shows the last 30. An effective_date[gte] older than 180 days answers 400 validation_failed naming the parameter.
  • Completeness. Register days before 2026-09-10 are incomplete.
  • Where it comes from. Registercheck reads each court's register continuously; most entries are here on their own register day, from about 07:00.
  • Right after a restart the list can answer 503 with Retry-After. Wait that many seconds and poll again with the same since; nothing was charged.
  • One company, pushed to you. To be told about one company's changes without polling, use a monitor and a webhook.

On this page