https://<api-host>/v1

API reference

One apex domain in, an owned-and-enriched subdomain dataset out — certificate transparency and passive DNS from several providers, merged, de-duplicated, resolved, scored and persisted, so repeat lookups get richer rather than merely faster.

Coming soon. The public API is not open yet — this reference documents the contract it will ship with, so the shapes and parameters below are stable to build against, but the endpoints do not answer external calls today. Everything described here already backs the UI, so use the site in the meantime.

Every endpoint lives under /v1 and answers JSON. Reads are three-tier — Redis, then Postgres, then a live provider fan-out — and the X-Cache response header reports which tier served the request: HIT, DB, DB-STALE or MISS. The same value appears in the body as served_from.

The dataset records observations, not liveness. A returned hostname was seen on a certificate or in a passive-DNS record at some point; whether it resolves today is a separate question, answered by the dns field and the resolve endpoint.

Reads — exports included — need no credentials, and nothing here is paid for. Signing in or presenting an API key raises the rate limit and unlocks what spends work on your say-so: forcing a re-scan, on-demand DNS resolution and security audits.

Dates. Timestamps render as DD-MM-YYYY for date-granular values and DD-MM-YYYY HH:MM:SS (UTC) for instants. These are deliberately not RFC 3339 — Date.parse will not read them. Request parameters run the other way: since and until accept RFC 3339 or YYYY-MM-DD.

timestamps as returned
"first_seen_at": "12-05-2026 10:05:50"
"not_before": "09-08-2026"

Errors. Every failure is the same envelope — a stable code to switch on, a generic message safe to show a user, and a request_id that matches the server log line. Driver text, SQL and stack traces never appear.

response · 400
{
"code": "invalid_apex",
"message": "apex is not a valid domain name",
"request_id": "abc/def-000001"
}

Provider disclosure. Non-admin callers do not see raw provider names: a sources array becomes a source_count, and per-source payloads are withheld. Build against source_count and treat sources as a bonus.

Pagination. List endpoints take page (1-based) and page_size and echo both back with a total. Filters narrow the total as well as the page.

GET/v1/domains/{apex}no authComing soon

The one call behind the dashboard: counts, a first page of hostnames, certificates to the inline cap, complete stats and tree, data_freshness, and the hygiene summary.

ParameterRequiredDescription
apexyesPath segment. Validated, IDNA-normalised, then collapsed to the registrable domain — api.example.co.uk resolves to example.co.uk. A bare public suffix is rejected with 400 invalid_apex.
refreshnoSet to true to skip the cache and DB tiers and re-aggregate from the providers. Authenticated callers only, and honoured at most once per apex per force floor — otherwise the response carries X-Refresh: throttled or auth-required.

Inline arrays are capped. hostnames and certificates are truncated for a large apex — check hostnames_truncated / certificates_truncated and page the rest. The counts, stats and tree are always the true totals.

A cold apex that takes longer than the inline budget answers 202 with a retry_after_seconds while the first fan-out runs in the background. That is a normal path, not an error — poll it.

GET/v1/domains/{apex}/hostnamesno authComing soon

The paginated, filterable, sortable subdomain list. Filters combine with AND and narrow the reported total as well as the page.

ParameterRequiredDescription
page · page_sizeno1-based paging. page_size caps at 500, defaults to 100.
qnoCase-insensitive substring on the hostname, up to 128 characters. Matched as a bound parameter, never interpolated into SQL.
branchnoExact first-label branch — api for api.*.domainlens.devbit.in.
dnsnoLast-known resolution state: up, down or unknown.
sortnoname, first_seen, last_seen or depth, with a - prefix for descending. Defaults to -first_seen. Applied server-side, so a csv/txt export matches the page.
formatnojson (default), csv, or txt (one hostname per line). csv and txt stream the full filtered set as a download, pagination ignored.
GET/v1/hostnames/{hostname}no authComing soon

Everything known about a single name: identity, the sources that reported it with a corroboration confidence, the current DNS snapshot and full resolution timeline, the certificates it has appeared on, and its slice of the changefeed and the hygiene score.

POST/v1/domains/{apex}/hostnames/{hostname}/resolveauth requiredComing soon

Resolves A / AAAA / CNAME for one hostname synchronously. Expensive, so it is gated — anonymous callers get 401.

GET/v1/domains/{apex}/certificatesno authComing soon

Distinct certificates, newest expiry first, with a normalised issuer, the lifecycle flags is_expired / expires_soon, days_to_expiry (negative once expired), validity_days for the full lifetime, and the SAN group — every name that shared the certificate, which is usually one deployment.

GET/v1/domains/{apex}/graphno authComing soon

The same relationship as a bipartite graph of hostnames linked by shared certificates. Clusters reveal infrastructure groupings.

GET/v1/domains/{apex}/securityno authComing soon

A 0–100 score and letter grade computed from data already held — dangling CNAMEs at takeover-prone providers, sensitive-looking subdomains that resolve, expired or fragmented certificate operations, attack-surface shape, and single-source corroboration. It is recomputed on every refresh and stored, so ?history=N returns a series.

This is a passive heuristic, not a penetration test. Nothing is probed beyond DNS resolution; the response carries the same disclaimer inside factors.

POST/v1/domains/{apex}/auditauth requiredComing soon

The active counterpart to the score above: a scoped audit of the hostnames you name, running a deep TLS check, HTTP security headers, mail and DNS-auth posture, DNSSEC / CAA / AXFR checks, Shodan InternetDB, an Internet Archive sweep for forgotten endpoints and a passive technology fingerprint against each one — plus nuclei templates where the deployment enables them. The body must carry authorized: true — a claim that you may test these hosts, stored on the audit row and logged against your account.

GET/v1/domains/{apex}/audit/{id}auth requiredComing soon

The poll target. While the run is queued or in flight it answers 202 with a live progress block naming the host and tool in hand, and 200once it is done — findings, a per-severity summary, and the raw tool output per target. Someone else's audit is a 404, not a 403.

GET/v1/domains/{apex}/auditsauth requiredComing soon

Your runs against this apex, with the limits block that governs the next one: hostnames per audit, audits per rolling 24 hours, and the tools this deployment permits.

Point-in-time OSINT plus light-active probing — not a penetration test. Nothing is exploited, and an anonymous caller can never start a run. Use it against hosts you are authorised to assess.

GET/v1/domains/{apex}/changesno authComing soon

What the dataset gained or lost on each refresh — added, dns_up, dns_down, cert_new, security_changed — newest first, filterable by since and type.

GET/v1/domains/{apex}/timelineno authComing soon

Cumulative distinct hostnames over time, bucketed by day, week or month, with per-bucket DNS and certificate event counts. The changefeed is only populated from the second refresh onward — the first has nothing to diff against.

GET/v1/domains/{apex}/refreshesno authComing soon

Recent refresh-log entries: what each pass discovered and how many providers succeeded.

GET/v1/search?q=<substring>no authComing soon

Substring match across every indexed hostname, any apex. Wildcard metacharacters are escaped and matched literally. total is capped at 10 000 (it reports 10001 when there are more) so a huge match set never forces an unbounded scan — show 10000+.

GET/v1/search/breakdown?q=<substring>no authComing soon

The same term seen across organisations: per-apex match counts plus the earliest first-seen date for each.

GET/v1/top?n=<number>no authComing soon

Apexes ranked by indexed hostname count, largest first.

GET/v1/stream/pulseno authComing soon

A public text/event-stream: dataset totals every few seconds, and apex names as they land in the seed queue. Open it with EventSource — the handler ends itself after about 40 seconds and the browser reconnects with Last-Event-ID, so the feed carries on without a gap. It is the counter on the front page.

Stand-alone lookups that touch no stored data. Resolver and provider hosts are constants, and a caller-named host is dialled through a guard that rejects non-public addresses and re-checks every redirect — so there is no SSRF surface. Every one of these has a form on the utilities page, and each is linkable with its input prefilled, e.g. /utils?tool=tls&host=domainlens.devbit.in.

EndpointWhat it does
/v1/utils/digdig over DNS-over-HTTPS, any record type
/v1/utils/propagationthe same query at every resolver, with an in_sync verdict
/v1/utils/rdapWHOIS-over-RDAP registration data
/v1/utils/tlslive chain, negotiated parameters and a per-version support matrix
/v1/utils/httpfetch, trace redirects, grade security headers
/v1/utils/mailSPF / DMARC / DKIM analysis and a rough grade
/v1/utils/cronexplain a cron expression and its next runs
/v1/utils/convertYAML to JSON and back (POST)
POST/v1/auth/signup · /v1/auth/loginno authComing soon

Email and password, setting an HttpOnly dl_session cookie that lasts 30 days from sign-in and is never renewed — when it runs out, sign in again. The login error is deliberately vague — no user enumeration.

GET/v1/auth/oauth/{provider}no authComing soon

Google or GitHub sign-in, wherever the deployment has them set up — GET /v1/auth/providers lists which. Navigate to it rather than fetching it: it redirects to the provider, and the callback sets the same session cookie. A verified email that already has an account signs into that account.

POST/v1/session/keysauth requiredComing soon

Mints an API key. The raw dlk_… secret is returned once and never again; only its hash is stored. Three live keys per account — revoke one to mint another — and no more than five minted in any 24 hours, revoked ones included. Send it as Authorization: Bearer dlk_….

authenticated request
curl -s -H "Authorization: Bearer dlk_..." \
"https://<api-host>/v1/domains/domainlens.devbit.in"

Reading and exporting need neither. An account — free, like everything else here — raises the rate limit and unlocks ?refresh=true, the on-demand resolve and security audits, plus the keys themselves.

~1/s

anonymous

burst 10

~3/s

signed in

burst 30

~4/s

API key

burst 40

~6/s

per account

burst 60

Rate limiting is a token bucket per caller, not per route — one bucket covers the whole /v1 surface. A signed-in request must also fit its accountbucket, which the session and every one of that user's keys share, so a second key adds no allowance. Every non-admin response carries X-RateLimit-Limit, -Remaining and -Reset so a client can self-regulate before it trips a 429, which adds Retry-After — between 1 and 60 seconds. /v1/health, /v1/openapi.yaml and /v1/auth/* are exempt. The figures above are server defaults; read the headers rather than hardcoding them.

This app serves the API from its own origin, on the same /v1/*path. That keeps the session cookie first-party, lets Google and GitHub sign-in land back here, and passes each visitor's own address through, so nobody spends a stranger's allowance.

PurposeEndpointCopy
Full enriched dataset/v1/domains/domainlens.devbit.in
Force a re-scan/v1/domains/domainlens.devbit.in?refresh=true
Paginated hostnames/v1/domains/domainlens.devbit.in/hostnames?page_size=100
Filtered + sorted/v1/domains/domainlens.devbit.in/hostnames?q=api&dns=up&sort=-last_seen
Export every name/v1/domains/domainlens.devbit.in/hostnames?format=txt
Statistics/v1/domains/domainlens.devbit.in/stats
Hierarchy/v1/domains/domainlens.devbit.in/tree?min_count=2
Certificates/v1/domains/domainlens.devbit.in/certificates
Hygiene score/v1/domains/domainlens.devbit.in/security
Changefeed/v1/domains/domainlens.devbit.in/changes
Growth over time/v1/domains/domainlens.devbit.in/timeline
Source corroboration/v1/domains/domainlens.devbit.in/sources/breakdown
One hostname/v1/hostnames/www.domainlens.devbit.in
Cross-apex search/v1/search?q=staging
Ranked apexes/v1/top?n=100
DNS lookup/v1/utils/dig?name=domainlens.devbit.in&type=MX
TLS inspection/v1/utils/tls?host=domainlens.devbit.in
Mail authentication/v1/utils/mail?domain=domainlens.devbit.in
Liveness/v1/health
This contract/v1/openapi.yaml
domainlens — bash
Headline numbers
curl -s "https://<api-host>/v1/domains/stripe.com" \
| jq '{apex, hostname_count, certificate_count, served_from}'
Every hostname, one per line
curl -s "https://<api-host>/v1/domains/stripe.com/hostnames?format=txt" \
-o stripe-subdomains.txt
Only what resolves
curl -s "https://<api-host>/v1/domains/stripe.com/hostnames?dns=up&page_size=500" \
| jq -r '.hostnames[].name'
Hygiene score and findings
curl -s "https://<api-host>/v1/domains/stripe.com/security" \
| jq '{score, grade, findings: .factors.findings}'
What changed this week
curl -s "https://<api-host>/v1/domains/stripe.com/changes?type=added" \
| jq -r '.changes[] | "\(.detected_at) \(.hostname)"'
Authenticated — with an API key
curl -s -H "Authorization: Bearer dlk_..." \
"https://<api-host>/v1/domains/stripe.com?refresh=true"
Is this domain spoofable?
curl -s "https://<api-host>/v1/utils/mail?domain=stripe.com" \
| jq '{grade, spf: .spf.all_qualifier, dmarc: .dmarc.policy}'
Which TLS versions does it still accept?
curl -s "https://<api-host>/v1/utils/tls?host=stripe.com" \
| jq '{grade, tls_version, protocols}'