# handelsregister.ai > REST API for official German company data (Handelsregister): company master data, Signals (normalized commercial-register changes searchable across companies), financials, management, shareholders, UBOs, shareholdings, M&A transactions, insolvencies, news, and register documents. Credit-based billing (1 credit = EUR 0.01 base price); features are only charged when they return data. Base URL: `https://handelsregister.ai/api` Machine-readable spec: https://handelsregister.ai/openapi.json Human documentation: https://handelsregister.ai/en/documentation (German: https://handelsregister.ai/de/documentation) ## Authentication - API key header (recommended): `x-api-key: YOUR_API_KEY` - API key query parameter (legacy): `?api_key=YOUR_API_KEY` - Bearer token: `Authorization: Bearer ` (create via `POST /api/v1/auth/tokens/create`) - Get an API key: sign up at https://handelsregister.ai and find it in the dashboard. ## Endpoints - `GET /api/v1/search-organizations` - search companies by `q` and/or JSON-encoded `filters` (legal form, location incl. geo-radius, industry codes - `industry_code` takes one WZ2025 code or an array matching any of them - employee counts, balance-sheet and P&L ranges nested under `financial_filters`). `skip`/`limit` pagination (max 30). 1 credit. Returns `entity_id` per result. - `GET /api/v1/fetch-organization` - full company profile. `q` accepts a company name, register number, or (most reliable) an `entity_id` from search. Optional repeatable `feature` parameter selects paid data features (see below). `ai_search=on-default` (+20 credits) enables AI enrichment/fuzzy resolution; `realtime_mode=handelsregister-default` (+10 credits, charged only on success) re-fetches live register data. Base cost 5 credits. The base payload always includes `capital` - registered capital as `{current, history}`, where `current` carries `amount`, `currency` and `kind` (STAMMKAPITAL, GRUNDKAPITAL, HAFTSUMME, GENEHMIGTES_KAPITAL, BEDINGTES_KAPITAL, VARIABLES_KAPITAL; open enum) and each `history` entry carries `value`, `effective_from` and `effective_to` (null while in force). A history entry's `change_amount` is the amount named in the register entry: it is unsigned and does not always match the difference between two consecutive amounts, so derive increases and decreases from the amounts themselves. Entities without registered capital, such as partnerships, return `current: null` and an empty `history`. - `GET /api/v1/fetch-person` - person profile merging register data with AI web evidence. Requires `person_q` and `organization_q` (min 2 chars each) and an active Plus/Pro/Max subscription (otherwise 403 `subscription_required`). 15 credits base (AI included); optional `features=shareholdings` +5 credits when data is returned. - `GET /api/v1/fetch-document` - official register documents by `company_id` (entity_id) and `document_type`: `AD` (current excerpt, PDF), `CD` (chronological excerpt, PDF), `shareholders_list` (PDF), `articles_of_association` (PDF), `SI` (structured content, XML). 15 credits, 5/min. - `GET /api/v1/signals` - list normalized company changes. Accepted parameters, exhaustively: `topics`, `organization_ids`, `from`, `to`, `cursor` - anything else (including `limit`, `order_by`, `projection`) is rejected, not ignored: 422 `INVALID_REQUEST` with `invalid_params: {"": ["Unknown Signals parameter."]}` (only the first unknown key is reported). `topics` takes the case-sensitive UPPER_SNAKE codes from the catalog, repeatable as `topics=A&topics=B`, `topics[]=`, or comma-separated; omitting it returns only the plan-accessible topics, never all of them - the five base topics on any plan (no subscription and Plus included), `INSOLVENCIES` additionally on Pro, `TRANSFORMATIONS` additionally on Max - silently, with no warning and `filters.topics: []` either way. Naming a topic above your plan returns 403 `PLAN_REQUIRED` (0 credits, no upstream call). `organization_ids`: at most 5 values, each matching `[A-Za-z0-9._~-]{1,128}`. `from`/`to`: `YYYY-MM-DD` or a timezone-naive `YYYY-MM-DDTHH:MM:SS[.ffffff]` - a trailing `Z` or a numeric offset such as `+02:00` is rejected with 422 - and `to` must be the same as or later than `from`. Ordering is fixed (newest announcement date first); evidence fields are always stripped. Pagination is cursor-only and 20 is a maximum, not a guarantee: short and even empty pages with `pagination.has_more: true` are normal, so keep paging while `has_more` is true and stop only when `next_cursor` is absent. Delivery is at-least-once - de-duplicate by `signals[].event.id`. Send `next_cursor` back unchanged with a byte-identical filter set (`cursor` is opaque, max 8192 characters); changing any filter mid-pagination, `organization_ids`/`from`/`to` included, is unsupported: it usually fails with 422 `INVALID_CURSOR` but can also silently return a wrongly-offset page, so start a new query without a cursor instead. `event.topic` is one of the seven filterable codes or `OTHER` (`OTHER` cannot be used as a filter value and is not plan-gated). `event.type` is a finer-grained type released per signal and is an OPEN ENUM: currently only role-holder values (`ROLE_HOLDER_ENTRY`, `ROLE_HOLDER_EXIT`, `ROLE_HOLDER_TRANSITION`, `ROLE_HOLDER_CHANGE`; `AUTHORITY_CHANGE` is defined in the spec but not emitted today), more will be released over time, so treat unknown values as valid and fall back to `event.topic`. It is absent - omitted, never null - on every signal without a released type, which is most of them today, and `event.type_name` (localized de/en label object) appears only alongside `event.type`. Neither is a filter: filtering is by `topics` only. 20 credits per successful page, an empty page included. - `GET /api/v1/signals/catalog` - discover the stable public Signals vocabulary and supported options. Always lists all seven topics with their `tier` (`ALL`, `PRO` or `MAX` - never null) regardless of the caller's plan, plus `capabilities.page_size` 20 and `capabilities.pagination: ["CURSOR"]`. It never exposes or proxies source-system classifications. Accepts no query parameter whatsoever: any one is rejected with 422 `INVALID_REQUEST`, detail `The catalog endpoint does not accept query parameters.` and no `invalid_params` member. Free (0 credits), never 402, and served even at a zero credit balance. - `GET /api/v1/signals/{signalId}` - retrieve one Signal by opaque ID; returns `{"signal": {...}, "meta": {...}}`. Query parameters are not merely unnecessary, they are rejected: any one yields 422 `INVALID_REQUEST` / `Unknown Signals parameter.`. `signalId` must match `[A-Za-z0-9][A-Za-z0-9._~-]{0,254}`; a value that misses the pattern never reaches the API and returns a plain JSON 404 instead of `application/problem+json`, while an unknown or blocked ID returns 404 `SIGNAL_NOT_FOUND`. The plan gate is applied after the lookup, so a Pro- or Max-only Signal returns 403 `PLAN_REQUIRED` (0 credits); events outside the seven topics resolve to `event.topic: "OTHER"` and are readable on any plan. 20 credits on success. - Token management: `POST /api/v1/auth/tokens/create`, `GET /api/v1/auth/tokens`, `DELETE /api/v1/auth/tokens/{id}`, `DELETE /api/v1/auth/tokens` (API-key auth, 10/min). ## Account endpoints (free, 0 credits, 30/min) Read-only self-service endpoints for building your own dashboards and admin tooling. Header or Bearer auth only - the `?api_key=` query parameter is rejected with 400 on these routes. - `GET /api/v1/account` - profile summary of the authenticated account (name, email, language, plan). - `GET /api/v1/account/credits` - credit balance split into subscription/purchased, next expiring grant, per-grant breakdown. - `GET /api/v1/account/usage` - aggregated usage for a bounded range (`from`/`to`, default current month, max 366 days; `group_by=day|month`): totals, per-endpoint breakdown, zero-filled time series. - `GET /api/v1/account/usage/transactions` - cursor-paginated ledger of billable requests, newest first (`endpoint` filter, `per_page` max 100, `cursor` for pagination). - `GET /api/v1/account/subscription` - current subscription state incl. plan, status, period bounds, and included features (`null` without one). - `GET /api/v1/account/api-keys` - active API keys, masked (last 8 chars), with last-use timestamp. - `POST /api/v1/account/api-keys` / `DELETE /api/v1/account/api-keys/{id}` - create or immediately revoke an API key (10/min). Requires a Bearer token with the `account:keys` ability, which can only be created on the profile page - an API key can never mint or destroy credentials. ## fetch-organization features (cost in credits; charged only when data is returned) - `financial_kpi` (1) - yearly financial metrics; base fields on every plan: year, employees, revenue, net_income, active_total, material_expenses, personnel_expenses. Higher plans can receive additional balance-sheet, income-statement and ratio fields in the same rows at the same 1-credit cost. - `publications` (1) - organization-profile publication history returned under `history`; distinct from the cross-company `/signals` feed - `related_persons` (2) - current & past directors/officers with roles and tenures - `balance_sheet_accounts` (3) - hierarchical balance sheets per year - `profit_and_loss_account` (3) - hierarchical P&L statements per year - `insolvency_publications` (5) - insolvency court publications - `annual_financial_statements` (5) - full annual reports as Markdown - `annual_financial_statements__html` (5) - full annual reports as HTML - `shareholders` (5) - shareholder list with contributions and ownership ratios - `shareholdings` (5) - the company's holdings in other companies - `ubos` (10) - ultimate beneficial owners incl. ownership paths and unresolved owners - `news` (10) - recent news articles with source and URL - `mergers_and_acquisitions` (20) - M&A transactions (mergers, splits, enterprise agreements), control relationships, and summary - `network` (25) - relationship graph of the entity: nodes (ORGANIZATION|PERSON, with depth and is_root) and connections (GROUP|ROLE|OWNERSHIP, localized role labels, start/end dates, is_current). Also available on fetch-person. Requires a Pro or Max plan (the key is ignored on other plans). Charged only when at least one connection is returned. - `website_content` (0, requires AI mode) - company website as LLM-optimized Markdown ## Billing semantics - Every fetch-organization request: 5 credits base + per-feature costs, but a feature that returns no data costs nothing. The response `meta.request_credit_cost` always shows the real charge; `meta.credits_remaining` the balance. - Every successful Signals list, detail, or pagination page costs exactly 20 credits - an empty page included - and every page is a separate charge; the Signals catalog is always free. No non-2xx response is ever charged: validation (422), authentication (401/403), plan-required (403), insufficient credits (402), not-found (404), rate limit (429), and upstream failures (503) all cost 0. Credits are deducted only after the upstream fetch and transform succeed; if the deduction itself fails you get 402 `BILLING_FAILED` and no data. Signals responses from the API carry `Cache-Control: private, no-store`, `X-Credits-Charged` and `X-Request-Id` - a route-pattern 404 is a generic Laravel JSON error without those headers and without a problem body - and raw source taxonomy is never returned. A throttled 429 is raised by the rate limiter above the Signals stack and is therefore never `application/problem+json`: always send `Accept: application/json` so you get `{"message": "Too Many Attempts."}` instead of an HTML error page, and handle it as a different shape from every other Signals error. It carries `Cache-Control: no-cache, private` - note `no-cache`, not the `no-store` of every other Signals response - but neither `X-Credits-Charged` nor `X-Request-Id`; do not depend on `Retry-After` or the `X-RateLimit-*` headers reaching the client, and back off on your own schedule instead. - Failed requests charge 0 credits - except the +20 AI surcharge, which applies even on failure. - Errors: 401 (auth), 402 (insufficient credits, body: `{"meta": {...}}`), 403 (subscription/verification), 404 (company not found, no charge), 429 (rate limit; most endpoints 60/min, documents 5/min). ## AI agents & MCP - MCP server (Model Context Protocol): `https://mcp.handelsregister.ai/mcp` - one tool per data product (`search_organizations`, `get_company_overview`, `get_financials`, `get_balance_sheet`, `get_profit_and_loss`, `get_annual_financial_statements[_html]`, `get_related_persons`, `get_shareholders`, `get_ubos`, `get_shareholdings`, `get_mergers_and_acquisitions`, `get_publications`, `get_insolvency_publications`, `get_news`, `get_website_content`, `fetch_person`, `fetch_document`). Same credits as the REST API. Auth: `X-API-Key` header, or OAuth 2.1 for interactive MCP clients (Claude, ChatGPT). - OAuth 2.1 authorization server with dynamic client registration (RFC 7591) and PKCE: discovery at `https://handelsregister.ai/.well-known/oauth-authorization-server`. Scopes: `mcp`, `api`, `offline_access`. Issued access tokens also work on the REST API as Bearer tokens. ## SDKs - Python: `pip install handelsregister` - https://github.com/Handelsregister-AI/handelsregister - Node.js: `npm install handelsregister` - https://github.com/Handelsregister-AI/handelsregister-js ## More - Changelog: https://handelsregister.ai/de/changelog - FAQ: https://handelsregister.ai/de/faq - n8n integration: https://n8n.io/integrations/handelsregisterai/ - Pricing/plans: https://handelsregister.ai (Plus EUR 69, Pro EUR 129, Max EUR 499 per month) ## Organization Monitoring and outbound webhooks Monitoring watches companies selected by the account and sends new normalized commercial-register changes through signed webhooks. It is an independent product from the cross-company Signals search API, although both intentionally use the same topic vocabulary, `organization.signal.detected`, `data.signal`, and the Signal-detail link. The Monitoring management API is under `/api/v1/account`. Account routes accept only `x-api-key` or `Authorization: Bearer ...`; `?api_key=` is rejected. Reads are free and require `account:read` on Bearer tokens. Monitor mutations and endpoint verification/test/delivery retry accept API keys; Bearer tokens need `account:read` plus the literal `monitoring:manage` ability. Endpoint create, secret rotation, enable/disable, and archive require a Bearer token with `account:read` plus literal `account:keys`; released/public API keys cannot perform them. After Monitoring is released, every mutation below requires `Idempotency-Key: 1-128 ASCII [A-Za-z0-9._:-]`; the durable ledger is database-backed, not Redis. The first non-5xx result has `Idempotency-Status: created`; an exact retry within 24 hours replays the same status/body (including a one-time secret) with `replayed`. Reusing a key for different parameters is 409; missing one is 428. Database-only monitor/endpoint/retry mutations commit the effect and replay atomically, so a stale atomic claim can be safely re-driven with the same key. Endpoint verify/test perform network I/O: a 409 `idempotency_in_progress`/processing ambiguity means the API cannot prove whether the receiver saw the request; stop automatic retries and contact support, do not retry later or invent a new key. A pre-operation kill-switch 503 does not claim the key. ### Monitor endpoints - `GET /api/v1/account/monitoring/pricing?poll_interval_days=1..30` - free current policy, topic entitlement, and fresh-cycle estimate; default interval 30. - `GET /api/v1/account/monitors` - newest 100 non-archived monitors; no cursor. - `POST /api/v1/account/monitors` - body: `entity_id`, optional `label`, integer `poll_interval_days`, non-empty distinct `endpoint_ids`. The cycle is priced at the rate in force when it opens; the `pricing_policy_version` that applied is returned on the monitor and its billing cycle, and is not an input. Exact endpoints must be owned, active, verified and unarchived; the account needs at least one accessible Monitoring topic. Returns 202 `initializing`, queues a free baseline, and charges 0 in the request. - `GET /api/v1/account/monitors/{mon_id}` - monitor, active billing-cycle summary or null, newest 20 runs. Known archived history remains readable. - `PATCH /api/v1/account/monitors/{mon_id}` - required interval and current policy; prospective schedule change only. It cannot change label/destinations and never refunds or rewrites settled charges. - `POST /api/v1/account/monitors/{mon_id}/pause` - only active transitions to `paused_user`; every other state is a 200 no-op. In particular, pausing `initializing` does not stop its baseline, activation, or its possible cycle charge; archive it to stop before activation. No refund. - `POST /api/v1/account/monitors/{mon_id}/resume` - optional policy (defaults stored). Requeues an incomplete baseline for 0. A completed baseline rechecks policy, endpoint, and at least one accessible topic before funding; it reuses a live funded cycle for 0 or can charge a new cycle price. Inspect `meta.activated`, `baseline_queued`, and `billing`. - `DELETE /api/v1/account/monitors/{mon_id}` - archive, never hard-delete. Monitor states: `initializing`, `active`, `paused_user`, `paused_configuration`, `paused_entitlement`, `paused_billing`, `error`, `archived`. A monitor has no per-topic filter. Every account can receive `NEW_REGISTRATIONS`, `MASTER_DATA_CHANGES`, `CLOSURES`, `ROLE_HOLDER_CHANGES`, and `CAPITAL_CHANGES`; Pro and Max can also receive `INSOLVENCIES`; only Max can receive `TRANSFORMATIONS` (Transformations & M&A). Missing or unknown topics fail closed and cannot be published. Future catalog additions require an explicit tier assignment. The scanner still observes all seven topics for durable baseline/deduplication, but only topics accessible under the account's current plan may be released. An inaccessible new observation is terminal `suppressed_plan`. Upgrading affects only observations first seen after the upgrade: already baselined or plan-suppressed history is not replayed. Downgrading leaves the monitor active and continues accessible topics; already released messages and eligible retries are immutable. `paused_entitlement` remains a defensive state for an account with no accessible topics. ### Billing and polling Per monitor, the asynchronous all-topic baseline is free and suppresses historical observations. Only after a complete baseline, including one completed with non-fatal warnings, does activation attempt the first cycle charge. A prospective rolling 30-day cycle is a set price per cadence: daily 50, every 3 days 40, weekly 20, monthly 10 credits. An interval with no listed price is billed at the next more frequent cadence, so every 4 days costs the same as every 3. The price covers the checks that cadence forecasts (`ceil(30 / interval)`); each further complete successful check costs 2. A complete successful check costs the same with zero or many eligible/new signals. Failed, partial, superseded, or unfunded checks add no run charge; the cadence price is not refunded. Extra-run affordability is checked before scanning and the debit settles exactly once before notifications are released. A funded cycle keeps its frozen policy; current policy consent is checked on create/update, activation/resume, and each new-cycle renewal, not at every mid-cycle settlement. Renewal while active is prospective. Pause/archive gives no refund; resume within an unexpired funded cycle does not charge the cadence price again. Missed time is coalesced into one overlap scan billed like any other complete successful check, not back-billed per missed slot. For a succeeded run, `events_new` is the distinct count of plan-eligible new notifications released by that run; multiple endpoints do not multiply it. A scheduled continuation may show accumulated progress. An unfunded failed run can show candidates that remained undelivered, so do not treat `events_new` as an unconditional delivery count. ### Webhook management and history - `GET /api/v1/account/webhook-endpoints` - all non-archived endpoints (maximum 10); URLs and custom headers are masked. - `POST /api/v1/account/webhook-endpoints` - `name`, public HTTPS/443 `url`, optional write-only `headers`; returns 201 pending endpoint plus one-time `whsec_` signing secret. - `POST /api/v1/account/webhook-endpoints/{wep_id}/verify` - only `pending_verification` sends a synchronous signed challenge; the receiver must return 2xx and echo `data.challenge` in `webhook-verification`. Failure returns 422 with `verified:false`. An already verified or disabled endpoint returns its stored verification state without network I/O and does not become enabled; call `enable` separately. - `POST /api/v1/account/webhook-endpoints/{wep_id}/rotate-secret` - returns a new one-time secret. Normal deliveries, tests, and samples carry signatures for current and predecessor secrets for seven days; verification challenges use only the current secret. - `POST /api/v1/account/webhook-endpoints/{wep_id}/test` - synchronous signed test attempt. HTTP 200 means the attempt was recorded, not necessarily delivered; inspect `delivery.status`. - `POST /api/v1/account/webhook-endpoints/{wep_id}/enable` / `disable` - enable requires verification; disable parks monitors that lose their last endpoint. - `DELETE /api/v1/account/webhook-endpoints/{wep_id}` - archive endpoint and subscriptions. - `GET /api/v1/account/webhook-deliveries?endpoint={wep_id}` - newest 50 delivery summaries; no cursor/detail. - `POST /api/v1/account/webhook-deliveries/{del_id}/retry` - retry an eligible retained failed delivery with the same message id/body. The public `delivery.attempts` schedule counter resets to 0; append-only audit attempt numbers remain monotonic. - `GET /api/v1/account/webhook-events` - newest 50 summaries; no body/detail/replay endpoint. Production endpoint URLs must use HTTPS port 443, contain no userinfo, fragment, or IP-literal host, resolve only to public addresses, and pass DNS pinning. Redirects are never followed. Custom header values and full URLs are encrypted/write-only; transport, forwarding, and `webhook-*` names are reserved. ### Receiver contract Delivery is at-least-once with no cross-event ordering guarantee. The JSON envelope is `{id,event_id,type,timestamp,schema_version:1,data}`; verification omits `event_id`. Live type `organization.signal.detected` carries `data.monitor`, a normalized `data.signal`, and `data.links.signal`; it never carries `data.sample`. Heavy `details` and `register_entry` fields may be removed with `data.truncated=true`. `data.links.signal` points to the independent Signals detail API. The webhook itself does not call or charge that API. Following the link is a separate authenticated request, costs 20 credits only on success, and can return 401, 402, 403 `PLAN_REQUIRED`, 404, or upstream errors. The link remains present even when the public Signals catalog/list product is hidden. Test and UI sample types are `endpoint.test` and `organization.signal.sample`. A sample always sets `data.sample=true`; a detected event never does. Sample discovery is a bounded scan of at most the last 48 months, returns at most 10 current-plan items, and can be partial rather than exhaustive. Sending a sample is free and rechecks the current plan. Verify the exact raw request bytes before parsing. Headers are `webhook-id`, Unix-seconds `webhook-timestamp`, and `webhook-signature`. For each space-separated `v1,` candidate, compute base64(HMAC-SHA256(`webhook-id.webhook-timestamp.raw_body`, base64-decode(the `whsec_` suffix))). For normal deliveries, tests, and samples, accept either signature during the seven-day rotation grace; verification challenges use the current secret only. Deduplicate `webhook-id`. Any 2xx succeeds. 3xx redirects, ordinary 4xx, 5xx, timeouts and connection failures retry; 410 immediately exhausts the delivery and disables the endpoint. Automatic delivery has ten total attempts spread over roughly three days; configured individual retry gaps range from about five seconds to one day, with jitter. Valid `Retry-After` is honored only for 429/502/503/504 and capped at one day. Five consecutive exhausted deliveries disable an endpoint. Request timeout is 15 seconds (5-second connect); receiver responses are capped at 64 KiB. Delivery states: `withheld`, `pending`, `in_flight`, `retry_wait`, `succeeded`, `blocked_endpoint`, `exhausted`, `cancelled`. Event/message data is retained encrypted for 30 days then tombstoned; attempt audit rows are retained 90 days. Common errors: 401 authentication; 403 ability/plan/account; tenant-scoped 404; 409 stale pricing/duplicate/idempotency conflict or ambiguity; 422 Laravel/domain validation (verification failure is a normal `verified:false` body); 428 missing idempotency key; 429 rate limit; 503 execution kill switch. Webhook policy/business failures generally use 400 `invalid_request`.