API publique

Les points d'accès JSON que ce site utilise pour ses propres recherches, documentés tels qu'ils se comportent aujourd'hui. Lecture seule, sans inscription.

Pas de clé d'API, pas de SLA, peut changerIl n'y a ni clé d'API, ni offre payante, ni engagement de disponibilité ou de latence. L'API n'est pas versionnée : des champs peuvent être ajoutés, renommés ou supprimés sans préavis, et un point d'accès peut être limité davantage ou retiré s'il fait l'objet d'abus. Ne construisez rien qui casse gravement lorsqu'une requête échoue.

Conventions

Tous les points d'accès répondent aux requêtes GET par du JSON (UTF-8). Le domaine est un segment du chemin ; les noms internationalisés peuvent être envoyés en Unicode ou sous forme d'A-labels (xn--…). Les dates sont au format ISO 8601, en UTC.

Chaque observation est faite depuis un seul emplacement de serveur, au moment de la requête, et indique d'où elle vient : chaque section porte status, source et observedAt. Une section au statut "error" ou "unsupported" n'a pas pu être vérifiée. Ce n'est jamais la preuve qu'un enregistrement est absent, et un état d'enregistrement "not-found" ne garantit jamais qu'un nom peut être enregistré.

Les noms de documentation réservés (example.com, example.net, example.org et tout ce qui se trouve sous .example, .test, .invalid ou .localhost) ne sont jamais recherchés en direct : ils répondent 422. Les exemples de réponse de cette page utilisent example.com et des plages IP de documentation à titre purement illustratif ; pour essayer l'API, utilisez un domaine que vous exploitez.

Rapport de domaine

GET /api/lookup/{domain}

Un rapport comptant jusqu'à cinq sections indépendantes : registration (RDAP, avec WHOIS sur le port 43 pour certains registres sans RDAP), dns, mail (MX, SPF, DMARC), hosting (de l'IP à l'ASN et au réseau) et web (une requête HTTPS et le certificat TLS avec lequel elle a été servie). Une section qui échoue ne fait pas échouer le rapport.

Un "www." initial est retiré. Pour un sous-domaine, les données d'enregistrement sont recherchées pour le domaine enregistrable, et les autres sections pour le nom d'hôte que vous avez envoyé.

Paramètres

NomDansDescription
domaincheminDomaine ou nom d'hôte, 253 caractères au maximum.
sectionsrequêteSous-ensemble, séparé par des virgules, de registration, dns, mail, hosting, web. Les noms inconnus sont ignorés ; si aucun nom n'est valide, la réponse est 400. Les sections que vous n'avez pas demandées reviennent avec le statut "skipped". Par défaut : les cinq.

Codes de statut

StatutSignification
200{ "report": … }. Envoyé avec Cache-Control: public, max-age=60, s-maxage=300.
400{ "error": "invalid-domain", "reason": … } lorsque le nom échoue à la validation, ou { "error": "invalid-sections", "allowed": […] }.
422{ "error": "reserved-name" } pour les noms de documentation et de test, ou { "error": "blocked-target" } lorsque le nom ne pointe que vers des adresses privées ou réservées, auxquelles le service ne se connecte pas.
429{ "error": "rate-limited", "retryAfterSeconds": n } avec un en-tête Retry-After.
Exemple de requête
curl -s "https://www.orbitprobe.com/api/lookup/yourdomain.com?sections=registration,dns"
Exemple de réponse (valeurs illustratives, abrégé)
{
  "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
      }
    }
  }
}

Propagation DNS

GET /api/propagation/{domain}

Pose la même question à chaque résolveur d'une liste fixe de résolveurs récursifs publics et regroupe les réponses identiques. Lisez le résultat comme « 9 résolveurs sur 10 ont renvoyé cette réponse » : il ne dit rien des résolveurs qui ne figurent pas dans la liste, et un résolveur anycast peut répondre différemment dans une autre région.

Ici, "www." est conservé, car la question porte exactement sur le nom que vous envoyez.

Paramètres

NomDansDescription
domaincheminNom d'hôte à interroger, 253 caractères au maximum.
typerequêteA, AAAA, NS, MX, TXT ou CNAME (insensible à la casse). Par défaut : A.

Codes de statut

StatutSignification
200{ "report": … } avec une entrée par résolveur (statut answer, no-records, nxdomain, timeout ou error), les groupes de réponses distinctes et les compteurs total, answered et failed. Cache-Control: public, max-age=30.
400{ "error": "invalid-domain" } ou { "error": "invalid-type" }.
422{ "error": "reserved-name" }.
429{ "error": "rate-limited" } avec un en-tête Retry-After.
Exemple de requête
curl -s "https://www.orbitprobe.com/api/propagation/www.yourdomain.com?type=A"
Exemple de réponse (valeurs illustratives, abrégé)
{
  "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.

Paramètres

NomDansDescription
toolcheminOne of the eight tool keys above. Anything else answers 404.
domaincheminDomain or hostname, at most 253 characters; for reverse-dns also an IP address (percent-encode the colons of an IPv6 address).
selectorrequêtedkim-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".

Codes de statut

StatutSignification
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.
Exemple de requête
curl -s "https://www.orbitprobe.com/api/tools/tls-rpt-checker/yourdomain.com"
Exemple de réponse (valeurs illustratives, abrégé)
{
  "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 } }]
  }
}

Compteurs d'activité

GET /api/stats/activity

Les nombres derrière le globe de la page d'accueil : combien de recherches ont été lancées au cours des dernières 24 heures, par pays du visiteur et par pays d'enregistrement du bloc d'adresses recherché. Il n'existe que des codes de pays et des compteurs ; aucune adresse IP et aucun nom de domaine n'est stocké. Les compteurs vivent dans la mémoire d'un seul processus serveur : ils sont donc remis à zéro au redémarrage et diffèrent d'une instance à l'autre. Traitez-les comme un signal approximatif, pas comme des statistiques.

Codes de statut

StatutSignification
200L'instantané. Cache-Control: public, max-age=30. Ce point d'accès n'est pas limité en débit.
Exemple de requête
curl -s "https://www.orbitprobe.com/api/stats/activity"
Exemple de réponse (valeurs illustratives, abrégé)
{
  "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"
}

Limites de débit

Les limites sont comptées par adresse IP cliente, séparément pour chaque famille de points d'accès. Un résultat servi depuis le cache du serveur n'est pas décompté de la limite. Lorsqu'une limite est atteinte, la réponse est 429 avec un en-tête Retry-After ; attendez ce délai au lieu de réessayer en boucle.

Le badge et les outils MCP passent par les mêmes moteurs ; ils partagent donc ces limites.

Point d'accèsLimite par adresse IPCache du serveur
/api/lookup/{domain}30 par minute · 300 par heure300 s (30 s lorsqu'une section a échoué)
/api/propagation/{domain}12 par minute · 120 par heure60 s
/api/tools/{tool}/{domain}10 par minute · 80 par heure (per tool; DKIM 10 par minute · 80 par heure, subdomain finder 5 par minute · 40 par heure)300 s / 1800 s
/api/stats/activityaucunaucun

Cache

Les rapports sont mis en cache sur le serveur par nom d'hôte et par liste de sections ; redemander les mêmes sections pendant la durée du cache renvoie donc la même observation, avec "cached": true et ses heures observedAt d'origine. Les en-têtes Cache-Control indiqués ci-dessus permettent aussi aux navigateurs et au CDN de réutiliser une réponse pendant un court moment. Aucun paramètre ne permet de forcer une nouvelle recherche.

CORS

Les points d'accès de cette page envoient Access-Control-Allow-Origin: * et répondent aux requêtes préliminaires OPTIONS, pour GET uniquement. Vous pouvez les appeler depuis une page d'une autre origine. Ils ne lisent jamais de cookies ; aucun identifiant n'entre donc en jeu.

Usage raisonnable

Chaque requête déclenche de vraies requêtes vers des registres, des résolveurs DNS et le site recherché lui-même. Utilisez l'API pour des outils interactifs, des tableaux de bord et des contrôles occasionnels de domaines que vous avez une raison d'examiner. Ne parcourez pas des listes de domaines, ne faites pas tourner des adresses pour contourner les limites et ne revendez pas les résultats. La politique d'utilisation acceptable s'applique au trafic de l'API exactement comme au site.

Politique d'utilisation acceptable

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

NomDescription
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)

NomDescription
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.

Description OpenAPI

Un document OpenAPI 3.1 lisible par machine, décrivant ces points d'accès, est généré à partir du même code : /api/openapi.json

Questions fréquentes

Ai-je besoin d'une clé d'API ?

Non. L'API n'a ni clés ni comptes. Les requêtes sont limitées par adresse IP.

Puis-je compter sur l'API en production ?

Uniquement pour ce qui a le droit d'échouer. Il n'y a ni SLA ni versionnage ; les points d'accès existent parce que le site en a besoin, et ils sont documentés pour que vous puissiez les utiliser aussi. Mettez les résultats en cache de votre côté et gérez les réponses 429 et 5xx.

Pourquoi example.com répond-il 422 ?

Les noms réservés de documentation et de test sont traités par des jeux de données internes à l'application et ne sont jamais recherchés en direct. Utilisez un vrai domaine pour essayer l'API.

Une section en échec signifie-t-elle que l'enregistrement n'existe pas ?

Non. "error" et "unsupported" signifient que la source n'a pas pu être lue depuis notre serveur à ce moment-là. Seule une section au statut "ok" ou "partial" vous dit ce qui a été trouvé, et seulement pour les types qui ne figurent pas dans failedTypes.