signals
Endpoints

Signals

20 Credits pro erfolgreichem Aufruf · Katalog kostenlos • 60/min

Ein Signal ist eine normalisierte Änderung aus dem Handelsregister. Du kannst Signals unternehmensübergreifend durchsuchen, nach Themen filtern oder ein einzelnes Signal per ID abrufen.

Zwei Dinge, über die viele stolpern: Lässt Du topics weg, bekommst Du nicht alle Themen — sondern still nur die aus Deinem Tarif. Und ein unbekannter Query-Parameter wird nicht ignoriert: Er lässt die ganze Anfrage mit 422 scheitern.

Endpunkte

Method Path Descrição
GET /v1/signals Signals auflisten und filtern. Bis zu 20 pro Seite, Blättern per Cursor.
GET /v1/signals/catalog Alle öffentlichen Themen-Codes mit Bezeichnungen, Beschreibungen und Stufen. Kostenlos.
GET /v1/signals/{signal_id} Ein einzelnes Signal per ID abrufen.

Eine Signal-ID ist 1 bis 255 Zeichen lang, besteht aus A-Z, a-z, 0-9, Punkt, Unterstrich, Tilde und Bindestrich und beginnt mit einem Buchstaben oder einer Ziffer. Alles andere liefert ein schlichtes JSON-404 ohne Credit- und Request-ID-Header. Andere Methoden als GET und HEAD liefern 405.

Themen

Du filterst Signals über Themen. Es gibt sieben Codes, und sie sind case-sensitiv: NEW_REGISTRATIONS funktioniert, new_registrations nicht.

Code Nome Descrição
NEW_REGISTRATIONS Neueintragungen Neu ins Handelsregister eingetragene Unternehmen.
MASTER_DATA_CHANGES Stammdatenänderungen Änderungen von Firmenname, Sitz, Geschäftsanschrift oder Registerzuständigkeit.
CLOSURES Löschungen & Auflösungen Auflösung, Liquidation, Löschung oder Erlöschen von Unternehmen.
ROLE_HOLDER_CHANGES Führungswechsel Eintritte, Austritte und Wechsel von Geschäftsführung, Vorstand und Prokura.
CAPITAL_CHANGES Kapitaländerungen Änderungen am Stamm-, Grund-, Haft- oder genehmigten Kapital.
INSOLVENCIES Insolvenzen Pro Eröffnungen, Sicherungsmaßnahmen und Beendigungen von Insolvenzverfahren.
TRANSFORMATIONS Umwandlungen & M&A Max Verschmelzungen, Spaltungen, Formwechsel, Vermögensübertragungen, Unternehmensverträge und Squeeze-outs.

Themen und Dein Tarif

Fünf Themen stehen jedem Account offen, auch ohne Abo und mit Plus. INSOLVENCIES braucht Pro oder Max, TRANSFORMATIONS braucht Max. Fragst Du ein Thema oberhalb Deines Tarifs an, bekommst Du 403 PLAN_REQUIRED bei 0 Credits — im Listen- wie im Detailendpunkt.

Ohne topics bekommst Du nur die Themen aus Deinem Tarif: fünf ohne Plan und mit Plus, sechs mit Pro, sieben mit Max. Die Antwort sagt davon nichts: filters.topics kommt so oder so als leere Liste zurück. Schick topics also mit, sobald der genaue Umfang zählt.

Das Thema OTHER

event.topic ist einer der sieben Codes oben oder OTHER. OTHER steht für jedes Registerereignis außerhalb der sieben, etwa Zweckänderungen, Zweigniederlassungen und Berichtigungen. Der Detailendpunkt liefert es regelmäßig, im Listenendpunkt ist es selten. OTHER ist nicht tarifgebunden, jeder Account kann es lesen. Filtern kannst Du damit nicht: topics=OTHER liefert 422.

Authentifizierung

Alle drei Endpunkte brauchen einen authentifizierten Account. Anonymen Zugriff gibt es nicht, eine bestimmte Token-Ability ist nicht nötig.

Zugang Hinweise
x-api-key: YOUR_API_KEY Ein aktiver API-Key aus Deinem Dashboard. Nimm diesen Weg — so landet das Geheimnis nicht in URLs und Logs.
?api_key=YOUR_API_KEY Bei Signals erlaubt. Der Wert wird aus Query-String und Logs entfernt. Schickst Du beides, gewinnt der Header.
Authorization: Bearer YOUR_TOKEN Jedes gültige, nicht abgelaufene Personal Access Token.

Fehlt der Zugang oder ist er ungültig, bekommst Du 401 UNAUTHORIZED. Eine nicht bestätigte E-Mail-Adresse und ein gesperrter Account liefern beide 403 UNAUTHORIZED mit demselben detail: „Signals access is not available for this account.“ Die beiden Fälle sind bewusst nicht unterscheidbar.

Parameter

Listenparameter

Nome Tipo Descrição
cursor string Der next_cursor aus Deiner vorherigen Antwort. Schick ihn unverändert zurück und zerlege ihn nicht. Höchstens 8192 Zeichen.
topics string list Ein oder mehrere Codes aus der Tabelle oben. topics=A&topics=B, topics[]=A&topics[]=B und topics=A,B bedeuten dasselbe. Werte werden dedupliziert und sortiert. Die Codes sind case-sensitiv.
organization_ids string list Entity-IDs der Unternehmen, auf die sich die Signals beziehen, in denselben drei Listenformen. Höchstens 5 pro Anfrage, jede 1 bis 128 Zeichen aus A-Z, a-z, 0-9, Punkt, Unterstrich, Tilde und Bindestrich.
from / to date Anfang und Ende des Zeitraums. Nutze YYYY-MM-DD oder ein zeitzonenloses YYYY-MM-DDTHH:MM:SS mit optional 1 bis 6 Nachkommastellen. Ein angehängtes Z oder ein Offset wie +02:00 liefert 422. to muss gleich from oder später sein; ein einzelner Tag ist gültig.

Unbekannte Parameter werden abgewiesen

Der Listenendpunkt akzeptiert cursor, topics, organization_ids, from und to. Jeder andere Query-Parameter liefert 422 mit der Meldung „Unknown Signals parameter.“ Gemeldet wird nur der erste unbekannte Parameter, korrigiere sie also einzeln. Seitengröße, Sortierung und Feldauswahl kannst Du nicht ändern.

GET /api/v1/signals?topics=NEW_REGISTRATIONS&limit=5

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
X-Credits-Charged: 0
X-Request-Id: req_01k1c2...

{
  "type": "https://handelsregister.ai/problems/invalid-request",
  "title": "Invalid request",
  "status": 422,
  "code": "INVALID_REQUEST",
  "detail": "One or more Signals parameters are invalid.",
  "invalid_params": {
    "limit": ["Unknown Signals parameter."]
  },
  "meta": {
    "request_id": "req_01k1c2...",
    "request_credit_cost": 0,
    "credits_remaining": 95
  }
}

Der Detailendpunkt nimmt keine Query-Parameter. Die ID im Pfad genügt, alles Zusätzliche liefert 422.

Der Katalog liefert dieselben sieben Themen maschinenlesbar: code, deutsche und englische Bezeichnung und Beschreibung, die nötige Stufe (ALL, PRO oder MAX) und ein observed-Flag für Themen, die schon Daten führen. Alle sieben erscheinen unabhängig von Deinem Tarif. Er ist kostenlos und nimmt keine Query-Parameter — jeder Parameter liefert 422.

Validierungsmeldungen

Ein Validierungsfehler liefert 422 mit einem invalid_params-Objekt, dessen Schlüssel die Parameternamen sind. Diese Meldungen kannst Du bekommen:

Meldung Wann
Unknown Signals parameter. Ein Query-Parameter außerhalb der erlaubten Menge. Gemeldet wird nur der erste.
Unknown topics value. Ein Themen-Code, der keiner der sieben öffentlichen Codes ist. Kleinschreibung gilt als unbekannt.
organization_ids accepts at most 50 values. Mehr als 5 organization_ids in einer Anfrage.
organization_ids contains an invalid identifier. Eine ID mit einem nicht unterstützten Zeichen oder mit mehr als 128 Zeichen.
from must use YYYY-MM-DD or a timezone-naive ISO datetime with seconds. from oder to ist weder YYYY-MM-DD noch ein zeitzonenloser Zeitstempel mit Sekunden. Ein angehängtes Z oder ein Offset landet ebenfalls hier.
to must be the same as or later than from. to liegt vor from.
cursor is invalid. cursor ist leer, länger als 8192 Zeichen oder enthält Steuerzeichen.
cursor does not match these filters. Der Cursor wurde nicht für diese Filter ausgegeben. Starte ohne Cursor neu.
signal_id is invalid. Die Signal-ID im Pfad ist leer oder länger als 255 Zeichen.

Beispiel

Beispiel für eine Listenanfrage

NEW_REGISTRATIONS und CAPITAL_CHANGES funktionieren in jedem Tarif. Tauschst Du sie gegen INSOLVENCIES (Pro oder Max) oder TRANSFORMATIONS (Max), liefern niedrigere Tarife 403 PLAN_REQUIRED.

# NEW_REGISTRATIONS and CAPITAL_CHANGES are open to every plan.
# INSOLVENCIES needs Pro or Max, TRANSFORMATIONS needs Max.
curl --get 'https://handelsregister.ai/api/v1/signals' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Accept: application/json' \
  --data-urlencode 'topics=NEW_REGISTRATIONS,CAPITAL_CHANGES' \
  --data-urlencode 'from=2026-07-01'

Beispielantwort

{
  "signals": [
    {
      "event": {
        "id": "0123456789abcdef0123456789abcdef",
        "topic": "CAPITAL_CHANGES",
        "topic_name": {
          "de": "Kapitaländerungen",
          "en": "Capital changes"
        },
        "occurred_on": "2026-07-25",
        "announced_on": "2026-07-25",
        "date_basis": "REGISTER_ENTRY"
      },
      "organization": {
        "type": "ORGANIZATION",
        "entity_id": "0123456789abcdef0123456789abcdef",
        "current_profile": {
          "name": "Beispiel Handel GmbH",
          "status": {
            "code": "ACTIVE"
          }
        }
      },
      "register_entry": {
        "entry_number": "7",
        "entry_date": "2026-07-25",
        "phase": "INCREASED",
        "description_text": "Das Stammkapital wurde erhöht.",
        "context": "PRIMARY",
        "flags": []
      },
      "source": {
        "kind": "COMMERCIAL_REGISTER"
      },
      "details": {
        "schema": "CAPITAL_CHANGE",
        "capital_kind": "SHARE_CAPITAL",
        "resulting_amount": {
          "amount": "50000.00",
          "currency": "EUR",
          "source_text": "50.000,00 EUR"
        }
      }
    }
  ],
  "pagination": {
    "mode": "CURSOR",
    "limit": 20,
    "returned": 1,
    "has_more": true,
    "next_cursor": "sgc1.eyJiIjoiYTNmMmVkZWJlNWU5Iiw..."
  },
  "filters": {
    "topics": ["CAPITAL_CHANGES", "NEW_REGISTRATIONS"],
    "organization_ids": [],
    "from": "2026-07-01"
  },
  "warnings": [],
  "meta": {
    "schema_version": "1.0",
    "request_id": "req_01k1c2...",
    "request_credit_cost": 20,
    "credits_remaining": 95
  }
}

In filters steht normalisiert, was Du gesendet hast: topics kommt dedupliziert und sortiert zurück, und from erscheint nur, weil es mitgeschickt wurde.

Aufbau der Antwort

Eine Listenantwort hat immer diese fünf Member. Enum-Werte kommen in GROSSBUCHSTABEN. Felder ohne Wert fehlen, statt als null zu erscheinen, und ein Objekt, das dadurch alle Member verliert, fehlt ebenfalls. Leere Listen bleiben, eine leere signals-Liste ist also ein echter Wert.

Feld Inhalt
signals Bis zu 20 Einträge, neuestes Veröffentlichungsdatum zuerst. Eine leere Liste ist eine gültige Seite.
pagination mode ist immer CURSOR. limit ist immer 20, und 20 ist das Maximum, keine Zusage. returned zählt diese Seite, has_more sagt Dir, ob Du weiterblättern musst, und next_cursor fehlt auf der letzten Seite.
filters Deine Filter, normalisiert. topics und organization_ids sind immer da, ohne Angabe als leere Listen; from, to und cursor erscheinen nur, wenn Du sie gesendet hast.
warnings Leer, oder ein Eintrag mit dem Code SOURCE_CATALOG_WARNING, wenn ein Filter im aktuellen Quellkatalog fehlt.
meta schema_version (1.0), request_id (nenne sie in Support-Anfragen), request_credit_cost, credits_remaining nach der Buchung sowie generated_at und profiles_resolved_at, sofern verfügbar.

Im Inneren eines Signals

Jeder Eintrag hat event, organization und source. parties, register_entry und details erscheinen nur, wenn die Quelle etwas dazu hergibt.

Feld Inhalt
event.id Reich sie an den Detailendpunkt weiter und überspringe damit Duplikate.
event.occurred_on, event.announced_on Reine YYYY-MM-DD-Daten. Beide können fehlen, verlass Dich also nie darauf, dass sie da sind.
event.date_basis Welche Art Datum die Quelle verwendet hat: REGISTER_ENTRY, SOURCE_EVENT, ROLE_HOLDER_EVENT, COURT_PUBLICATION, PUBLICATION oder OTHER.
organization, parties Beide tragen ein current_profile mit name, registration, legal_form, seat und status.
details Zusatzfelder, die vom Ereignistyp abhängen, gekennzeichnet über details.schema. Geldbeträge nutzen amount, currency und source_text.

Der Detailendpunkt liefert denselben Aufbau unter dem Schlüssel signal. Er kann auch ein Ereignis ausliefern, das im Listenendpunkt nicht aufgetaucht wäre.

Ereignistypen: event.type

Manche Ereignisse tragen in event.type einen feineren Typ, mit deutscher und englischer Bezeichnung in event.type_name. Heute haben nur Funktionsträger-Ereignisse einen, den meisten Signals fehlen also beide Felder. Wo kein Typ freigegeben ist, fehlen beide Felder ganz — null kommt nie. Prüfe deshalb, ob event.type da ist, bevor Du es ausliest.

"event": {
  "id": "0123456789abcdef0123456789abcdef",
  "topic": "ROLE_HOLDER_CHANGES",
  "topic_name": {
    "de": "Führungswechsel",
    "en": "Management changes"
  },
  "type": "ROLE_HOLDER_ENTRY",
  "type_name": {
    "de": "Funktionsträger eingetreten",
    "en": "Role holder joined"
  },
  "occurred_on": "2026-07-25",
  "announced_on": "2026-07-25",
  "date_basis": "ROLE_HOLDER_EVENT"
}

Diese Werte werden heute ausgeliefert. Alle gehören zu ROLE_HOLDER_CHANGES, das jeder Account lesen kann:

event.type Bedeutung
ROLE_HOLDER_ENTRY Ein Funktionsträger ist hinzugekommen: neue Bestellung, zusätzliche Position oder Rückkehr. type_name: „Funktionsträger eingetreten“.
ROLE_HOLDER_EXIT Ein Funktionsträger hat eine Position verlassen, auch ohne genannten Grund. type_name: „Funktionsträger ausgeschieden“.
ROLE_HOLDER_TRANSITION Ein Funktionsträger hat einen anderen im selben Registereintrag abgelöst. type_name: „Funktionsträgerwechsel“.
ROLE_HOLDER_CHANGE Die Angaben zu einem bestehenden Funktionsträger haben sich geändert, ohne Eintritt oder Austritt. type_name: „Funktionsträger geändert“.
event.type ist ein offenes Enum. Weitere Werte kommen mit der Zeit dazu, ohne Versionssprung und ohne Vorwarnung. Verzweige deshalb nie erschöpfend über event.type und verwirf nie ein Signal, nur weil sein Typ unbekannt ist. Ordne zu, was Du kennst, und falle sonst auf event.topic zurück — eine feste Liste, die sich nicht ändert. AUTHORITY_CHANGE ist definiert, wird heute aber nicht ausgeliefert.
  • event.type_name erscheint nur zusammen mit event.type. Es ist ein Bezeichnungsobjekt mit einem de- und einem en-Member — zeig es Menschen, verzweige aber über event.type.
  • event.type ist kein Filter. Einen Parameter type gibt es nicht, und schickst Du einen mit, kommt 422. Filtere über topics und grenze in Deinem eigenen Code weiter ein.
  • Ein Signal ohne event.type kann später eines bekommen, ohne dass sich id, topic oder sonst etwas ändert.

Was nie zurückkommt

  • null-Werte und jedes Objekt, das dadurch leer bleibt. Leere Listen bleiben erhalten.
  • Quellbelege: evidence_excerpt, evidence_text, evidence_redactions und evidence_text_status werden immer entfernt.
  • Interne Details: der feingliedrige Quellcode hinter einem Thema, first_seen_at, detail_completeness und observed_identity.
  • details-Felder außerhalb der für ihr schema definierten Menge sowie Signals zu gesperrten Unternehmen. Das passiert nach dem Blättern, eine Seite kann deshalb kurz sein, während has_more true bleibt.

Paginierung

Geblättert wird nur per Cursor, neuestes Veröffentlichungsdatum zuerst. Zum Weiterblättern schickst Du pagination.next_cursor unverändert und mit exakt denselben Filtern zurück.

  • 20 ist die maximale Seitengröße, keine Garantie. Kurze und sogar leere Seiten sind normal — nicht aufgelöste Profile und gesperrte Entitäten fallen aus einer Seite heraus.
  • Blättere weiter, solange pagination.has_more true ist, auch direkt nach einer leeren Seite. Hör auf, sobald es false ist; next_cursor fehlt dann, statt null zu sein.
  • Die Zustellung ist at-least-once. Dasselbe Signal kann also später erneut auftauchen — überspringe Duplikate über event.id.
  • Jede Seite ist ein eigener erfolgreicher Aufruf zu 20 Credits, leere eingeschlossen. Eine Gesamtzahl gibt es nicht und keinen Sprung zu Seite N.

Cursor gehören zu ihrer Abfrage

Behandle einen Cursor als undurchsichtig: nicht selbst bauen, nicht zerlegen, unter 8192 Zeichen halten. Einen Filter mitten im Blättern zu ändern, wird nicht unterstützt. Meist bekommst Du 422 INVALID_CURSOR bei 0 Credits, manche Wechsel werden aber angenommen und liefern dann eine falsch versetzte Seite, die trotzdem 20 Credits kostet. Willst Du die Filter ändern, starte eine neue Abfrage ohne Cursor.

Sicher blättern

import requests

URL = "https://handelsregister.ai/api/v1/signals"
HEADERS = {"x-api-key": "YOUR_API_KEY", "Accept": "application/json"}
FILTERS = {"topics": "NEW_REGISTRATIONS,CAPITAL_CHANGES"}

seen = set()
cursor = None

while True:
    params = dict(FILTERS)           # identical filters on every page
    if cursor:
        params["cursor"] = cursor

    response = requests.get(URL, headers=HEADERS, params=params)
    response.raise_for_status()      # 20 credits per successful page
    page = response.json()

    for signal in page["signals"]:   # 0 to 20 items, short pages are normal
        event_id = signal["event"]["id"]
        if event_id in seen:
            continue                 # at-least-once: repeats are expected
        seen.add(event_id)
        handle(signal)

    if not page["pagination"]["has_more"]:
        break                        # last page: next_cursor is absent

    cursor = page["pagination"]["next_cursor"]

Credits

  • Eine Listenseite kostet 20 Credits, ein Detailaufruf ebenfalls. Der Katalog ist kostenlos und prüft nicht einmal Dein Guthaben.
  • Abgebucht wird kurz, bevor die Antwort rausgeht. meta.credits_remaining und X-Credits-Charged beschreiben deshalb, was tatsächlich passiert ist.
  • Unter 20 Credits bekommst Du 402 INSUFFICIENT_CREDITS, bevor überhaupt etwas passiert. Scheitert die Abbuchung, bekommst Du 402 BILLING_FAILED, es wird nichts berechnet und die Daten werden verworfen — wiederhole den Aufruf.
  • Fehlgeschlagene Anfragen werden nie berechnet: 401, 402, 403, 404, 422, 429 und 503 kosten alle 0 Credits.
  • Im Ledger stehen topics, organization_ids, from, to und ob ein Cursor verwendet wurde. Detailaufrufe protokollieren nichts davon.

Fehler

Signals-Fehler kommen als JSON mit einem maschinenlesbaren code, auf den Du verzweigen kannst. Dazu gibt es type, title, status, detail und meta. invalid_params gibt es nur bei Validierungsfehlern. Die einzige Ausnahme ist der 429, siehe unten.

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
X-Credits-Charged: 0
X-Request-Id: req_01k1c2...

{
  "type": "https://handelsregister.ai/problems/plan-required",
  "title": "Plan upgrade required",
  "status": 403,
  "code": "PLAN_REQUIRED",
  "detail": "Your current plan does not include the requested topic(s): INSOLVENCIES requires the Pro plan.",
  "meta": {
    "request_id": "req_01k1c2...",
    "request_credit_cost": 0,
    "credits_remaining": 95
  }
}
Status code Wann
401 UNAUTHORIZED Kein Zugang oder ein ungültiger. credits_remaining ist null; ein X-Request-Id-Header fehlt.
402 INSUFFICIENT_CREDITS Dein Guthaben liegt unter 20 Credits. Es wurde nichts abgerufen. Der Katalog liefert das nie.
402 BILLING_FAILED Die Daten wurden geholt, die Abbuchung ist gescheitert. Es wurde nichts berechnet und es kommen keine Daten zurück — wiederhole den Aufruf.
403 UNAUTHORIZED Nicht bestätigte E-Mail-Adresse oder gesperrter Account, von außen nicht unterscheidbar. credits_remaining ist null; ein X-Request-Id-Header fehlt.
403 PLAN_REQUIRED Du hast ein Thema angefragt, das Dein Tarif nicht enthält; detail nennt jedes gesperrte Thema und den nötigen Tarif. Im Detailendpunkt gilt dasselbe für das Thema des abgerufenen Signals.
403 EVIDENCE_NOT_AVAILABLE Für Anfragen nach Quellbelegen reserviert. Über öffentliche Parameter nicht auslösbar.
404 NOT_FOUND Signals ist für diesen Account gar nicht verfügbar: Alle drei Endpunkte antworten wie eine unbekannte Route.
404 SIGNAL_NOT_FOUND Nur im Detailendpunkt: kein Signal mit dieser ID, oder sein Subjekt wird nicht ausgeliefert.
422 INVALID_REQUEST Die Validierung ist fehlgeschlagen und invalid_params nennt den Parameter, der Katalog wurde mit einem Query-Parameter aufgerufen, oder die Quelle hat die Filterkombination abgelehnt.
422 INVALID_CURSOR Der Cursor ist für diese Anfrage nicht verwendbar. Starte ohne Cursor neu.
429 Du hast das gemeinsame Limit von 60 Anfragen pro Minute erreicht. Das ist die einzige Signals-Antwort mit einem anderen Aufbau — siehe Rate-Limits unten.
503 SIGNALS_UNAVAILABLE Die Signals-Quelle ist vorübergehend nicht verfügbar, oder die Cursor-Paginierung ist es. Ein erneuter Versuch ist unbedenklich; Retry-After kann dabei sein.

Response-Header

  • X-Credits-Charged: 5 oder 0 — die verbindliche Auskunft darüber, was der Aufruf gekostet hat.
  • X-Request-Id: req_… passt zu meta.request_id. Nenne sie in Support-Anfragen. Bei den oben genannten 401 und 403 fehlt der Header, beim 429 ebenfalls.
  • Retry-After: nur bei 503, nur wenn die Quelle einen Wert geliefert hat, und auf höchstens 5 Sekunden begrenzt.
  • Cache-Control: private, no-store, max-age=0, dazu Pragma: no-cache und Vary: x-api-key. Jede Signals-Antwort ist privat — leg sie nie in einen gemeinsamen Cache. Die Ausnahme ist der 429, er kommt mit no-cache, private.

Rate-Limits

Authentifizierte Anfragen teilen sich ein Kontingent von 60 pro Minute mit /v1/get-organization, /v1/search-organizations und /v1/fetch-document. Schöpfst Du es auf einem davon aus, sind die anderen ebenfalls ausgebremst, auch der kostenlose Katalog.

Das Rate-Limit greift, bevor Deine Anfrage bei Signals ankommt. Ein 429 sieht deshalb anders aus als jeder andere Fehler: kein code, kein detail, kein meta, und der Body hängt vom gesendeten Accept-Header ab.

So sieht eine ausgebremste Anfrage aus

GET /api/v1/signals?topics=NEW_REGISTRATIONS
Accept: application/json

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Cache-Control: no-cache, private

{
  "message": "Too Many Attempts."
}
  • Mit Accept: application/json bekommst Du JSON mit einer kurzen message. Verzweige über den Statuscode 429 und parse den Body nicht.
  • Ohne diesen Header bekommst Du stattdessen eine HTML-Fehlerseite. Sende deshalb bei jeder Signals-Anfrage Accept: application/json, damit auch eine ausgebremste maschinenlesbar bleibt.
  • Verlass Dich nicht darauf, dass Retry-After oder die X-RateLimit-*-Header ankommen. Warte nach Deinem eigenen Zeitplan; ein sofortiger neuer Versuch verbrennt nur das nächste Zeitfenster.
  • Weder X-Credits-Charged noch X-Request-Id ist vorhanden. Ein 429 kostet trotzdem 0 Credits, aber nichts in der Antwort sagt das.