The API
One metered door, the same for everyone: /v1. It starts keyless, a DRM3 account raises it, and every response is built to be verified, not just believed. The interactive reference with your own key and live meter is the API console; the OpenAPI 3.1 spec is at /v1/openapi.json.
Keyless: the first look
curl https://domaindrift.io/v1/domains/example.com
No key, no account: one domain at a time, the latest observation, as a reduced preview - the derived facts (who runs the nameservers, mail, and CDN; certificate issuer and expiry; reachability; scan time; snapshot id) plus per-type record counts. One request every 15 seconds per IP and a daily cap per IP, under a shared daily ceiling. Every lane's limits: Rate limits by lane.
The preview deliberately carries no signature. A signature commits to the exact bytes of the full record, so one attached to a reduced subset could never verify - shipping it would hand you a check guaranteed to fail. Instead the preview carries the receipt pointer: the receipt id, the full record's output hash, the signing key, and a public /receipts/<id> URL you can resolve right now, keyless.
Authentication
# either header works curl -H "X-DomainDrift-Key: dd_..." https://domaindrift.io/v1/domains/example.com curl -H "Authorization: Bearer dd_..." https://domaindrift.io/v1/domains/example.com
Mint a key from the API console with a DRM3 account. A keyed call returns the complete record and opens every read endpoint below.
The read surface
| Endpoint | What it returns |
|---|---|
GET /v1/domains | The list, with filters (category, tag, search); ?format=csv for a download. |
GET /v1/domains/:domain | The latest signed record (keyless: reduced preview; keyed: full). |
GET /v1/whois/:domain | WHOIS for any registrable domain, tracked or not. Read live from the registry and the registrar; cached up to 1 hour. |
GET /v1/domains/:domain/history | The snapshot time series for one domain. |
GET /v1/domains/:domain/provenance | The Ed25519 receipt chain for one domain. |
GET /v1/domains/:domain/planes | The latest observation per data plane, with completeness. |
GET /v1/changes | The global change feed as structured deltas, with sync cursors. |
GET /v1/stream | Live Server-Sent Events: scans, changes, receipts. |
GET /v1/stream/json | The polling fallback when you cannot hold an SSE open. |
GET /v1/groups | Your bring-your-own-domain monitoring groups. |
GET /v1/alerts | Your alert subscriptions (POST to create, DELETE to remove). |
GET /v1/usage | Your metered usage for the period. |
GET /v1/evidence/:domain | A printable, signed evidence report for auditors. |
GET /v1/domains/:domain/bundle | A signed, offline-verifiable export bundle (paid, rate-capped). |
Fields returned by the lookup
GET /v1/domains/:domain returns the latest observation, whatever its age. scanned_at is when the record last changed; last_checked_at is when DomainDrift last looked. The catalog sweep re-checks paid-plan priority domains every 6 hours, watched and curated domains every 24 hours, and the rest of the catalog every 30 days. A domain under watch is also re-checked on the watch lane, as often as every 5 minutes. DomainDrift reads domain infrastructure: DNS, mail, hosting and CDN operators, certificates, email authentication, reachability and registration. It does not detect website technologies (CMS, analytics, frameworks). The full schemas, with examples, are DomainRecord and TeaserRecord in /v1/openapi.json.
| Field | What it is | No key | API key or x402 |
|---|---|---|---|
providers.dns | DNS host operator | providers.dns | signal.dns.ns |
providers.email | Mail provider and SPF-authorized senders | providers.email | signal.dns.mx, signal.dns.txt |
providers.hosting | Hosting operator and web server | providers.hosting | signal.dns.a, signal.dns.cname, signal.deep.http_server |
providers.cdn | CDN in front of the site | providers.cdn | signal.dns.cname, signal.deep |
providers.verification | Services the domain has verified with (TXT tokens) | providers.verification | signal.dns.txt |
providers.security | Security records (DMARC) | providers.security | signal.dns.txt |
providers.registrar | Registrar | providers.registrar | whois.registrar |
certificate | Issuers, earliest expiry, days left, status, certificates seen | certificate | posture.cert, signal.deep.certificates |
email_auth | SPF, DMARC and policy, DKIM, MTA-STS | email_auth | posture.email |
reachability | Probe verdict, HTTP status, DNS status, response time | reachability | signal.deep (probe_verdict, http_status, dns_status, response_time_ms) |
scanned_at | When the record last changed | scanned_at | observed_at, signal.scanned_at |
last_checked_at | When DomainDrift last looked, changed or not | last_checked_at | last_checked_at |
whois | Registrar, IANA id, created, updated and expiry dates, name servers, statuses, DNSSEC, registrant organization, name and country, abuse contact, source, observed_at. Read live from the registry and the registrar; cached up to 1 hour (source rdap) | Withheld | whois |
A domain DomainDrift holds no observation of still answers with a key, when the registry has it: catalog_record is false, signal is null, and whois carries the registration. A name the registry does not hold is a 404, not charged.
WHOIS for any domain
curl -H "X-DomainDrift-Key: dd_..." https://domaindrift.io/v1/whois/example.com
GET /v1/whois/:domain answers for any registrable domain, in the catalog or not: registrar and IANA id, created, updated and expiry dates, name servers, statuses, DNSSEC, registrant organization, name and country, and the abuse contact. Read live from the registry and the registrar; cached up to 1 hour. The answer is {domain, whois, source, observed_at, last_checked_at}; WhoisAnswer in /v1/openapi.json is the schema. An API key or an x402 payment; metered like the lookup. On the WHOIS plan: any number of lookups up to 50,000 a day, not charged per lookup. Lookups that need the registry count against the key's requests a minute and that daily cap (reset at 00:00 UTC); an answer held from the last hour counts against neither, and past either limit the answer is 429 with Retry-After, not charged. A name the registry does not hold is a 404 and a registry that does not answer in time is a 503; neither is charged.
Discovery, for humans and agents
| Endpoint | What it is |
|---|---|
GET /v1/openapi.json | OpenAPI 3.1 - every endpoint and schema. |
GET /.well-known/domaindrift-keys.json | The Ed25519 public keys that sign every observation. |
GET /receipts/:id | Resolve any receipt by id - public, keyless. |
GET /llms.txt | The plain-text site and API map for language models (/llms-full.txt for the long form). |
GET /badge/:domain.svg | An embeddable signed status badge for any domain. |
GET /v1/tape | The 24-hour rollup as JSON. CORS-open, safe to embed. |
POST /mcp | A read-only MCP server (streamable HTTP, stateless): lookup_domain, whois_lookup, whats_changed_today, resolve_receipt, internet_tape, signing_keys. Descriptor at /.well-known/mcp.json. |
The MCP tools are names for the same HTTP routes and pass through the same access lanes: keyless calls get the reduced preview under the same throttle; send your key as Authorization: Bearer to open the complete record. If you are pointing an AI agent at DomainDrift, start at the machine front door - it is the same surface, arranged for agents.
The meter, in the open
Every metered response tells you where you stand: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset ride on the response, and a 429 carries Retry-After plus a body that names the limit and where to raise it. API use is metered in DomainDrift credits - an API request is 0.05 credits against your daily allowance, and past it, requests meter from your DRM3 credit balance. The full model: Plans & limits.
Try it without writing code
The playground runs real read-only calls from the browser, and the API console is the full interactive reference with your key, your usage, and copyable requests for every endpoint.