Öffentliche API

Die JSON-Endpunkte, die diese Website für ihre eigenen Abfragen nutzt, dokumentiert nach ihrem heutigen Verhalten. Nur lesend, ohne Anmeldung.

Keine API-Keys, kein SLA, Änderungen möglichEs gibt keine API-Keys, keine kostenpflichtigen Stufen und keine Zusage zu Verfügbarkeit oder Latenz. Die API ist nicht versioniert: Felder können ohne Ankündigung hinzukommen, umbenannt oder entfernt werden, und ein Endpunkt kann bei Missbrauch weiter eingeschränkt oder abgeschaltet werden. Bauen Sie nichts, das schwer ausfällt, wenn eine Anfrage fehlschlägt.

Konventionen

Alle Endpunkte beantworten GET-Anfragen mit JSON (UTF-8). Die Domain ist ein Pfadsegment; internationalisierte Namen können in Unicode oder als A-Labels (xn--…) gesendet werden. Datumsangaben sind ISO 8601 in UTC.

Jede Beobachtung wird zum Zeitpunkt der Anfrage von einem einzigen Serverstandort aus gemacht und nennt ihre Herkunft: Jeder Abschnitt trägt status, source und observedAt. Ein Abschnitt mit dem Status "error" oder "unsupported" konnte nicht geprüft werden. Das ist nie ein Beleg dafür, dass ein Record fehlt, und der Registrierungsstatus "not-found" ist nie eine Garantie dafür, dass sich ein Name registrieren lässt.

Reservierte Dokumentationsnamen (example.com, example.net, example.org und alles unter .example, .test, .invalid oder .localhost) werden nie live abgefragt: Sie liefern 422. Die Antwortbeispiele auf dieser Seite verwenden example.com und Dokumentations-IP-Bereiche nur zur Veranschaulichung; zum Ausprobieren der API nehmen Sie eine Domain, die Sie selbst betreiben.

Domain-Bericht

GET /api/lookup/{domain}

Ein Bericht mit bis zu fünf unabhängigen Abschnitten: registration (RDAP, bei einigen Registries ohne RDAP stattdessen WHOIS über Port 43), dns, mail (MX, SPF, DMARC), hosting (IP zu ASN und Netz) und web (eine HTTPS-Anfrage und das TLS-Zertifikat, mit dem sie beantwortet wurde). Schlägt ein Abschnitt fehl, schlägt deshalb nicht der ganze Bericht fehl.

Ein führendes „www.“ wird entfernt. Bei einer Subdomain werden die Registrierungsdaten für die registrierbare Domain abgefragt, die übrigen Abschnitte für den Hostnamen, den Sie gesendet haben.

Parameter

NameOrtBeschreibung
domainPfadDomain oder Hostname, höchstens 253 Zeichen.
sectionsQueryKommagetrennte Auswahl aus registration, dns, mail, hosting, web. Unbekannte Namen werden ignoriert; ist kein Name gültig, lautet die Antwort 400. Nicht angeforderte Abschnitte kommen mit dem Status "skipped" zurück. Standard: alle fünf.

Statuscodes

StatusBedeutung
200{ "report": … }. Gesendet mit Cache-Control: public, max-age=60, s-maxage=300.
400{ "error": "invalid-domain", "reason": … }, wenn der Name die Validierung nicht besteht, oder { "error": "invalid-sections", "allowed": […] }.
422{ "error": "reserved-name" } bei Dokumentations- und Testnamen oder { "error": "blocked-target" }, wenn der Name nur auf private oder reservierte Adressen auflöst, zu denen der Dienst keine Verbindung aufbaut.
429{ "error": "rate-limited", "retryAfterSeconds": n } mit einem Retry-After-Header.
Beispielanfrage
curl -s "https://www.orbitprobe.com/api/lookup/yourdomain.com?sections=registration,dns"
Beispielantwort (Werte zur Veranschaulichung, gekürzt)
{
  "report": {
    "input": "example.com",
    "ascii": "example.com",
    "unicode": "example.com",
    "registrableDomain": "example.com",
    "isSubdomain": false,
    "tld": "com",
    "generatedAt": "2026-01-01T12:00:00.000Z",
    "dataMode": "live",
    "cached": false,
    "registration": {
      "status": "ok",
      "source": "RDAP · rdap.registry.example",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 412,
      "state": "registered",
      "registrableDomain": "example.com",
      "registrar": "Example Registrar, Inc.",
      "registrarIanaId": "9999",
      "registrarUrl": null,
      "abuseEmail": "abuse@registrar.example",
      "createdAt": "2015-03-01T00:00:00Z",
      "updatedAt": "2025-02-10T00:00:00Z",
      "expiresAt": "2027-03-01T00:00:00Z",
      "statusCodes": ["client transfer prohibited"],
      "nameservers": ["ns1.example.net", "ns2.example.net"],
      "dnssec": false,
      "registrant": { "organization": null, "country": null, "redacted": true },
      "rdapUrl": "https://rdap.registry.example/domain/example.com"
    },
    "dns": {
      "status": "ok",
      "source": "DNS · public recursive resolvers",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 95,
      "resolver": "1.1.1.1, 8.8.8.8",
      "records": [
        { "type": "A", "name": "example.com", "value": "203.0.113.10", "ttl": 300 },
        { "type": "AAAA", "name": "example.com", "value": "2001:db8::10", "ttl": 300 },
        { "type": "MX", "name": "example.com", "value": "mail.example.com", "priority": 10 },
        { "type": "TXT", "name": "example.com", "value": "v=spf1 mx -all" }
      ],
      "failedTypes": [],
      "nxdomain": false
    },
    "mail": {
      "status": "ok",
      "source": "DNS",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 140,
      "mx": [{ "priority": 10, "host": "mail.example.com", "addresses": ["203.0.113.25"], "provider": null }],
      "spf": "v=spf1 mx -all",
      "dmarc": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com",
      "dkim": "selector-required",
      "findings": [{ "level": "ok", "code": "…", "title": "…", "detail": "…" }]
    },
    "hosting": {
      "status": "ok",
      "source": "IP lookups",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 310,
      "addresses": [
        { "address": "203.0.113.10", "family": 4, "reverse": null, "asn": 64500, "asName": "EXAMPLE-NET", "prefix": "203.0.113.0/24", "country": null, "networkName": null, "networkOrg": null, "edge": null }
      ],
      "summary": "EXAMPLE-NET (AS64500)"
    },
    "web": {
      "status": "ok",
      "source": "HTTPS request",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 520,
      "finalUrl": "https://example.com/",
      "httpStatus": 200,
      "redirects": [],
      "serverHeader": null,
      "poweredBy": null,
      "hsts": true,
      "responseMs": 180,
      "page": { "title": "Example Domain", "description": null, "keywords": null },
      "headers": [{ "name": "content-type", "value": "text/html; charset=UTF-8" }],
      "tls": {
        "protocol": "TLSv1.3",
        "issuer": "Example CA",
        "subject": "example.com",
        "validFrom": "2025-12-01T00:00:00.000Z",
        "validTo": "2026-03-01T00:00:00.000Z",
        "altNames": ["example.com", "www.example.com"],
        "authorized": true,
        "authorizationError": null
      }
    }
  }
}

DNS-Propagation

GET /api/propagation/{domain}

Stellt jedem Resolver einer festen Liste öffentlicher rekursiver Resolver dieselbe Frage und fasst identische Antworten zu Gruppen zusammen. Lesen Sie das Ergebnis als „9 von 10 Resolvern haben diese Antwort geliefert“: Es sagt nichts über Resolver aus, die nicht auf der Liste stehen, und ein Anycast-Resolver kann in einer anderen Region anders antworten.

Hier bleibt „www.“ erhalten, denn gefragt wird nach genau dem Namen, den Sie senden.

Parameter

NameOrtBeschreibung
domainPfadHostname, nach dem gefragt wird, höchstens 253 Zeichen.
typeQueryA, AAAA, NS, MX, TXT oder CNAME (Groß- und Kleinschreibung egal). Standard: A.

Statuscodes

StatusBedeutung
200{ "report": … } mit einem Eintrag pro Resolver (Status answer, no-records, nxdomain, timeout oder error), den unterschiedlichen Antwortgruppen und den Zählern total, answered und failed. Cache-Control: public, max-age=30.
400{ "error": "invalid-domain" } oder { "error": "invalid-type" }.
422{ "error": "reserved-name" }.
429{ "error": "rate-limited" } mit einem Retry-After-Header.
Beispielanfrage
curl -s "https://www.orbitprobe.com/api/propagation/www.yourdomain.com?type=A"
Beispielantwort (Werte zur Veranschaulichung, gekürzt)
{
  "report": {
    "ascii": "www.example.com",
    "unicode": "www.example.com",
    "type": "A",
    "generatedAt": "2026-01-01T12:00:00.000Z",
    "cached": false,
    "answers": [
      {
        "resolver": { "id": "cloudflare", "name": "Cloudflare", "address": "1.1.1.1", "operatorCountry": "US" },
        "status": "answer",
        "values": ["203.0.113.10"],
        "ttl": 300,
        "durationMs": 21,
        "group": 0
      },
      {
        "resolver": { "id": "quad9", "name": "Quad9", "address": "9.9.9.9", "operatorCountry": "CH" },
        "status": "timeout",
        "values": [],
        "ttl": null,
        "durationMs": 2800,
        "group": null
      }
    ],
    "groups": [{ "values": ["203.0.113.10"], "count": 9 }],
    "total": 10,
    "answered": 9,
    "failed": 1,
    "consistent": false
  }
}

Single-purpose checks

GET /api/tools/{tool}/{domain}

The engines behind the tool result pages, one request per check: dkim-checker, subdomain-finder, mta-sts-checker, tls-rpt-checker, bimi-checker, dnssec-checker, reverse-dns and security-txt-checker (the same keys as the site's tool URLs). The answer carries ok, tool, source, observedAt and cached, and the engine's report unchanged.

A query that failed stays visible inside the report as a state such as "failed", "error" or "unknown", or as a finding: it is never turned into "no record". A leading "www." is removed, except for reverse-dns, which keeps it and also accepts an IPv4 or IPv6 address.

Parameter

NameOrtBeschreibung
toolPfadOne of the eight tool keys above. Anything else answers 404.
domainPfadDomain or hostname, at most 253 characters; for reverse-dns also an IP address (percent-encode the colons of an IPv6 address).
selectorQuerydkim-checker and bimi-checker only. DKIM: the s= selector of a real message; leave it out to try a fixed list of common provider selectors. BIMI: default "default".

Statuscodes

StatusBedeutung
200{ "ok": true, "tool": …, "source": …, "observedAt": …, "cached": …, "report": … }. Sent with Cache-Control: public, max-age=60, s-maxage=300.
400{ "ok": false, "code": "invalid-domain", "reason": … } or { "ok": false, "code": "invalid-selector" }.
404{ "ok": false, "code": "unknown-tool", "tools": […] }.
422{ "ok": false, "code": "reserved-name" } for documentation and test names.
429{ "ok": false, "code": "rate-limited", "retryAfterSeconds": n } with a Retry-After header.
Beispielanfrage
curl -s "https://www.orbitprobe.com/api/tools/tls-rpt-checker/yourdomain.com"
Beispielantwort (Werte zur Veranschaulichung, gekürzt)
{
  "ok": true,
  "tool": "tls-rpt-checker",
  "source": "Public DNS (1.1.1.1, 8.8.8.8)",
  "observedAt": "2026-01-01T12:00:00.000Z",
  "cached": false,
  "report": {
    "ascii": "example.com",
    "unicode": "example.com",
    "observedAt": "2026-01-01T12:00:00.000Z",
    "durationMs": 38,
    "cached": false,
    "host": "_smtp._tls.example.com",
    "state": "ok",
    "records": ["v=TLSRPTv1; rua=mailto:tls-reports@example.com"],
    "parsed": { "version": "TLSRPTv1", "valid": true, "rua": [{ "raw": "mailto:tls-reports@example.com", "scheme": "mailto", "valid": true, "target": "example.com" }] },
    "findings": [{ "level": "ok", "code": "RPT_OK", "params": { "n": 1 } }]
  }
}

Aktivitätszähler

GET /api/stats/activity

Die Zahlen hinter dem Globus auf der Startseite: wie viele Abfragen in den letzten 24 Stunden liefen, nach Land der Besucher und nach dem registrierten Land des abgefragten Adressblocks. Es gibt nur Ländercodes und Zähler; weder eine IP-Adresse noch ein Domainname wird gespeichert. Die Zähler liegen im Arbeitsspeicher eines einzelnen Serverprozesses, werden also bei einem Neustart zurückgesetzt und unterscheiden sich zwischen Instanzen: Betrachten Sie sie als grobes Signal, nicht als Statistik.

Statuscodes

StatusBedeutung
200Der Snapshot. Cache-Control: public, max-age=30. Für diesen Endpunkt gilt kein Rate-Limit.
Beispielanfrage
curl -s "https://www.orbitprobe.com/api/stats/activity"
Beispielantwort (Werte zur Veranschaulichung, gekürzt)
{
  "windowHours": 24,
  "total": 3,
  "visitors": [{ "country": "TR", "count": 2 }, { "country": "DE", "count": 1 }],
  "hosting": [{ "country": "US", "count": 3 }],
  "generatedAt": "2026-01-01T12:00:00.000Z"
}

Rate-Limits

Limits werden pro Client-IP-Adresse gezählt, getrennt für jede Endpunkt-Familie. Ein Ergebnis aus dem Server-Cache zählt nicht gegen das Limit. Ist ein Limit erreicht, lautet die Antwort 429 mit einem Retry-After-Header; warten Sie so lange, statt die Anfrage in einer Schleife zu wiederholen.

Das Badge und die MCP-Tools laufen über dieselben Engines und teilen sich daher diese Limits.

EndpunktLimit pro IP-AdresseServer-Cache
/api/lookup/{domain}30 pro Minute · 300 pro Stunde300 s (30 s, wenn ein Abschnitt fehlgeschlagen ist)
/api/propagation/{domain}12 pro Minute · 120 pro Stunde60 s
/api/tools/{tool}/{domain}10 pro Minute · 80 pro Stunde (per tool; DKIM 10 pro Minute · 80 pro Stunde, subdomain finder 5 pro Minute · 40 pro Stunde)300 s / 1800 s
/api/stats/activitykeinerkeiner

Caching

Berichte werden auf dem Server nach Hostname und Abschnittsliste zwischengespeichert. Wer dieselben Abschnitte innerhalb der Cache-Zeit erneut anfragt, erhält dieselbe Beobachtung mit "cached": true und den ursprünglichen observedAt-Zeiten. Die oben genannten Cache-Control-Header erlauben außerdem Browsern und dem CDN, eine Antwort kurze Zeit wiederzuverwenden. Einen Parameter, der eine frische Abfrage erzwingt, gibt es nicht.

CORS

Die Endpunkte auf dieser Seite senden Access-Control-Allow-Origin: * und beantworten OPTIONS-Preflight-Anfragen, nur für GET. Sie können sie von einer Seite mit anderem Origin aus aufrufen. Cookies werden nie gelesen, es sind also keine Anmeldedaten im Spiel.

Faire Nutzung

Jede Anfrage löst echte Abfragen bei Registries, DNS-Resolvern und der abgefragten Website selbst aus. Nutzen Sie die API für interaktive Tools, Dashboards und gelegentliche Prüfungen von Domains, für die Sie einen Anlass haben. Arbeiten Sie keine Domainlisten ab, wechseln Sie keine Adressen, um die Limits zu umgehen, und verkaufen Sie die Ausgabe nicht weiter. Die Richtlinie zur zulässigen Nutzung gilt für API-Verkehr genauso wie für die Website.

Richtlinie zur zulässigen Nutzung

Outbound webhooks

Signed-in members can have watch alerts, expiry reminders and marketplace notices posted to a URL they own: a Slack incoming webhook or a generic https endpoint (workspace → Settings → Notification channels). This section documents what the receiver gets. It is outbound only: OrbitProbe never accepts requests on these URLs, and there is no inbound API.

Receivers must be https on a public host (default port, no credentials; private, local and reserved hosts are refused, and the host is resolved and checked again at send time). Redirects are not followed. A delivery counts as done when the receiver answers 2xx; after five failed deliveries in a row the webhook is switched off and the owner sees a notice in the workspace.

Request

NameBeschreibung
POSTJSON body, Content-Type: application/json; charset=utf-8, User-Agent: OrbitProbe-Webhook/1.0
X-OrbitProbe-EventThe kind (see below).
X-OrbitProbe-DeliveryThe delivery id (ntf_<number>). Repeated on a retry, so you can de-duplicate.
X-OrbitProbe-SignatureGeneric endpoints only: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> with the signing secret shown in Settings.

Body (generic endpoints)

NameBeschreibung
idDelivery id, the same as X-OrbitProbe-Delivery.
kindOne of the kinds below.
categorywatch, expiry, market or test.
title, textThe notification in the account’s language: the same wording the push notification carries. Counts are "N of M resolvers", never percentages.
urlWhere to look in the workspace.
createdAtWhen the event was queued (ISO 8601, UTC).
accountThe recipient’s account id, so one receiver can serve several accounts.
dataThe raw event as stored: domain, counts, listing id, amount in integer minor units and currency, and so on. Fields vary by kind; anything missing was not measured, never "no".
Body (generic endpoints)
{
  "id": "ntf_1042",
  "kind": "watch-changed",
  "category": "watch",
  "title": "Watch: unexpected change",
  "text": "example.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.",
  "url": "https://www.orbitprobe.com/app/watch/00000000-0000-4000-8000-000000000000",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "account": "00000000-0000-4000-8000-0000000000aa",
  "data": {
    "domain": "example.com",
    "kind": "dns-change",
    "record_type": "TXT",
    "name": "example.com",
    "matched": 3,
    "total": 12,
    "failed": 0,
    "differing": 9,
    "watch_id": "00000000-0000-4000-8000-000000000000",
    "event_id": 77
  }
}

Body (Slack)

Slack incoming webhooks receive {"text": "…"} only: title, text and URL on three lines, no Block Kit and no attachments, so the message renders in every Slack client and in Slack-compatible receivers such as Mattermost or Rocket.Chat.

Body (Slack)
{
  "text": "*Watch: unexpected change*\nexample.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.\nhttps://www.orbitprobe.com/app/watch/…"
}

Verifying the signature

Recompute the HMAC over "<t>.<raw body>" with your secret, compare in constant time, and reject timestamps older than five minutes. Do not parse and re-serialise the JSON before hashing: sign the bytes as received.

Verifying the signature
// Node.js receiver: verify X-OrbitProbe-Signature before trusting the body
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, rawBody, header, nowSeconds = Math.floor(Date.now() / 1000)) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
  if (!m || Math.abs(nowSeconds - Number(m[1])) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(m[2], 'hex'));
}

Kinds

watch-changed, watch-target, watch-failed, watch-recovered, watch-partial · expiry · offer-received, offer-countered, offer-accepted, offer-rejected, offer-withdrawn, offer-expired, listing-submitted, listing-approved, listing-paused, listing-removed, agreement-cancelled · test (the "Send test" button).

Limits

At most 5 webhooks per account and 20 posts per account per hour; beyond that, events still reach the in-app inbox and e-mail but are not posted. Deliveries are retried up to five times over later runs of the five-minute job. An "offer-accepted" event records an agreement only: no payment, escrow or transfer is implied.

OpenAPI-Beschreibung

Ein maschinenlesbares OpenAPI-3.1-Dokument dieser Endpunkte wird aus demselben Code erzeugt: /api/openapi.json

Häufige Fragen

Brauche ich einen API-Key?

Nein. Für die API gibt es weder Keys noch Konten. Stattdessen sind die Anfragen pro IP-Adresse begrenzt.

Kann ich mich im Produktivbetrieb auf die API verlassen?

Nur bei Dingen, die ausfallen dürfen. Es gibt kein SLA und keine Versionierung; die Endpunkte existieren, weil die Website sie braucht, und sie sind dokumentiert, damit auch Sie sie nutzen können. Speichern Sie Ergebnisse auf Ihrer Seite zwischen und behandeln Sie 429- und 5xx-Antworten.

Warum antwortet example.com mit 422?

Reservierte Dokumentations- und Testnamen werden von Fixtures innerhalb der Anwendung bedient und nie live abgefragt. Nehmen Sie zum Ausprobieren der API eine echte Domain.

Bedeutet ein fehlgeschlagener Abschnitt, dass der Record nicht existiert?

Nein. "error" und "unsupported" bedeuten, dass die Quelle in diesem Moment von unserem Server aus nicht gelesen werden konnte. Nur ein Abschnitt mit dem Status "ok" oder "partial" sagt Ihnen, was gefunden wurde, und auch das nur für die Typen, die nicht in failedTypes aufgeführt sind.