API documentation
One route returns every known subdomain of a domain, as JSON or plain text. Base URL: https://api.assetdiscovery.io
Quickstart
- Create a free account and verify your email.
- Create an API key under API keys. The full key is shown once, so store it right away.
- Export it as
ASSETDISCOVERY_API_KEYand send your first request:
curl "https://api.assetdiscovery.io/api/domain/example.com" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY"Authentication
Send your key in the Api-Key header on every request. Keys start with ad_. Keep keys on the server; never ship them in browser or mobile code. Revoking a key in the app stops it immediately.
Api-Key: ad_8f3c…Endpoints
All formats share one route, GET /api/domain/{domain}. The format query parameter is optional: json (default), txt or full. The total field is always the real number of known subdomains, even when your plan returns fewer.
Subdomains
GET/api/domain/{domain}
Returns hostnames as a JSON array. This is the default and works on every plan, up to your plan's results-per-query limit. Results are plain hostnames: wildcard certificate names are folded into their base name unless you pass wildcards=true.
- domainpath
- example.comRegistrable domain or subdomain, without scheme or path.
- wildcardsquery
- trueOptional. Return wildcard names such as *.cloud.example.com as stored. By default they are folded into cloud.example.com.
- Api-Keyheader
- ad_…Your API key. Required on every request.
curl "https://api.assetdiscovery.io/api/domain/example.com" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY"{
"domain": "example.com",
"total": 4521,
"subdomains": [
"api.example.com",
"auth.example.com",
"cdn.example.com",
"dev.api.example.com",
"mail.example.com"
]
}Plain text
GET/api/domain/{domain}?format=txt
Returns one hostname per line, ready to pipe into other tools. Needs a plan that includes export; other plans get plan_required. Errors are still returned as JSON.
- domainpath
- example.comRegistrable domain or subdomain, without scheme or path.
- formatquery
- txtOptional. Return text/plain instead of JSON.
- Api-Keyheader
- ad_…Your API key. Required on every request.
curl "https://api.assetdiscovery.io/api/domain/example.com?format=txt" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY" \
-o example.com.txtapi.example.com
auth.example.com
cdn.example.com
dev.api.example.com
mail.example.comFull records
GET/api/domain/{domain}?format=full
Returns each host with first_seen and last_seen, plus total and a meta block with your plan and quota.
- domainpath
- example.comRegistrable domain or subdomain, without scheme or path.
- formatquery
- fullOptional. Return records with timestamps and meta.
- Api-Keyheader
- ad_…Your API key. Required on every request.
curl "https://api.assetdiscovery.io/api/domain/example.com?format=full" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY"{
"domain": "example.com",
"total": 4521,
"returned": 100,
"truncated": true,
"next_cursor": null,
"subdomains": [
{ "host": "api.example.com", "first_seen": "2023-11-04T00:00:00Z", "last_seen": "2026-10-17T00:00:00Z" }
],
"meta": {
"query_ms": 38,
"plan": "free",
"results_limit": 100,
"quota": { "used": 2, "limit": 10, "period": "day", "resets_at": "2026-10-21T00:00:00Z" },
"upgrade_url": "https://assetdiscovery.io/pricing"
}
}Large results
No pagination is needed. One call is one query, and every format returns all the subdomains your plan allows in a single response; format only changes the shape. When truncated is true, the rest is beyond your plan's results-per-query limit and meta.upgrade_url points to the plans that return it.
total_estimated is true for very large domains; total is then an upper-bound estimate. It is omitted otherwise.
next_cursor is null on every response except on Enterprise, for domains with more than 1,000,000 subdomains. Pass it as ?cursor= to continue the same query at no extra query cost. A cursor only works for the same domain and API key and expires after 1 hour; after that, a request without a cursor starts a new, charged query.
Past your plan's limit (Pro and Team). When a domain has more subdomains than your plan returns per query, the response also carries continuation_cursor (for format=txt, theX-Continuation-Cursor header). Pass it as ?cursor= to get the next block of up to the same number of results, continuing alphabetically where the last one ended, with nothing skipped or repeated. Each block is a new query and uses one from your quota; requesting the same block again the same day is free. It is never in next_cursor, so a loop that follows next_cursor never spends queries you did not ask for. It expires after 24 hours.
# Pass next_cursor from the previous response while it is not null
curl "https://api.assetdiscovery.io/api/domain/example.com?format=full&cursor=$NEXT_CURSOR" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY"Data
We collect names from certificate transparency logs, passive DNS and our own passive discovery. first_seen and last_seen are when we first and last observed a name, not whether it resolves today.
GET /v1/stats/top-domains?limit=100 lists the domains with the most known subdomains. It needs no key and uses no searches; limit is 1 to 1,000 and the list is rebuilt weekly. Counts are approximate, within about 1%, and domains under a shared cloud or hosting suffix are counted with the domain they belong to (registrable_domains says how many were merged).
Quota headers
Every response, including errors, carries your current quota so you can pace requests without an extra call. A 429 also includes Retry-After in seconds.
A search uses one from your quota the first time your workspace queries a domain on a given day (UTC). Querying the same domain again that day, and following its next_cursor, is free. So is a search that finds nothing, and a domain that is refused as invalid.
| Header | Example | Description |
|---|---|---|
| X-Quota-Limit | 5 | Queries allowed in the current period, or "unlimited" on plans without a cap. |
| X-Quota-Remaining | 3 | Queries left in the current period, after this request. |
| X-Quota-Reset | 2026-10-21T00:00:00Z | When the period resets, as an ISO 8601 UTC timestamp. |
curl -s -D - -o /dev/null "https://api.assetdiscovery.io/api/domain/example.com" \
-H "Api-Key: $ASSETDISCOVERY_API_KEY" | grep -i '^x-quota'HTTP/2 200
content-type: application/json
x-quota-limit: 10
x-quota-remaining: 8
x-quota-reset: 2026-10-21T00:00:00ZErrors
Errors return a JSON body with a stable code to branch on. The message is for people and may change.
Every response carries an X-Request-ID header, and error bodies repeat it as error.request_id (req_…). Include it when you contact support and we can find exactly what happened.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and retry shortly.",
"retry_after_seconds": 42,
"docs_url": "https://assetdiscovery.io/docs#errors",
"request_id": "req_8f3a1c9e2b7d4f60"
}
}| Code | HTTP | Meaning | What to do |
|---|---|---|---|
| unauthorized | 401 | The Api-Key header is missing, malformed or the key was revoked. | Check the header name and value. Create a new key under API keys if this one was revoked. |
| email_not_verified | 403 | The account email is not verified, so its keys are inactive. | Open the verification email and confirm. You can resend it from the app. |
| quota_exceeded | 429 | The daily or monthly query allowance for your plan is used up. On Free, also when the free searches of every account on your network are used up for the day. | Wait until resets_at or X-Quota-Reset, or upgrade your plan. |
| rate_limited | 429 | Requests per second for this key went over the plan's burst limit. | Retry after retry_after_seconds (also sent as Retry-After). Back off instead of retrying in a loop. |
| plan_required | 402 | The request uses a feature your plan does not include, such as format=txt. | Remove the parameter, or compare plans to find the one that includes it. |
| invalid_domain | 422 | The domain is not a valid hostname, its top-level domain does not exist, or it is a subdomain. | Send a registrable domain such as example.com or example.co.uk, without https:// or a path. For www.example.com, search example.com: its results include every subdomain. |
| invalid_request | 422 | A parameter the endpoint doesn't accept, such as format=csv. | Check the parameter against this page: format is json, full or txt. |
| not_found | 404 | There is no such endpoint. | Check the path, for example /api/domain/example.com. |
| service_unavailable | 500 / 502 / 503 | A temporary problem on our side. | Retry with exponential backoff. Failed requests do not count against your quota. If it persists, contact support with the request_id. |
| role_required | 403 | The caller's role in the workspace can't do this (billing needs owner or admin). | Ask an owner or admin of the workspace. API keys never manage billing. |
| two_factor_enrollment_required | 403 | The workspace requires two-factor authentication and this member has not turned it on. | Turn on two-factor in Settings. Dashboard requests only: API keys keep working. |
| turnstile_failed | 400 | The captcha token is missing, expired or already used. | The public search on the home page only. Reload the page and search again. |
| impersonation_read_only | 403 | Staff are viewing the account as its user, which is read-only: no searches or changes. | Only seen by staff. Stop viewing as the user to act as yourself. |
Rate limits
Burst limits and query allowances apply per workspace, so all your keys and the dashboard share them. Both come from your plan. The quota headers and meta.quota show where you stand.