Error reference — every status code the DomainScan API can return, and the right client-side reaction.
DomainScan errors pair standard HTTP status codes with stable, machine-readable codes inside a consistent error envelope. This page lists every code we can return, the conditions that produce it and the recommended retry/escalation behavior. Match on `error.code` in your client — the human-readable `error.message` is for logs and dashboards, not control flow.
Error envelope shape
Every non-2xx response has the same shape. `success` is always present; `error` is always present on failure; HTTP status code is meaningful.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "`domain` must be a valid hostname.",
"details": {
"field": "domain",
"requestId": "req_01HZBN4F8K3MN7"
}
}
}- code Stable, snake-cased identifier. Safe to match in client code. Never changes for a given failure mode.
- message Human-readable explanation. May be reworded between revisions — don't match against it.
- details Per-error-type metadata: bad fields for VALIDATION_ERROR, retry hints for RATE_LIMITED, request ID for everything.
Client errors — your call is wrong
4xx means you sent something that can't be processed as-is. Fix the client. Retrying without changes will produce the same error.
- 400 VALIDATION_ERROR Required parameter missing or malformed. Inspect error.details.field for the offending param. Most common: bad domain format, IP version mismatch, missing required query string.
- 401 UNAUTHORIZED Missing or invalid bearer token. Re-authenticate. Note: anonymous calls do NOT return 401 — they fall back to per-IP rate limits, not a 401.
- 403 FORBIDDEN Authenticated but lacks permission. Examples: free-plan key trying to access a Pro-only endpoint, key revoked but still cached locally.
- 404 NOT_FOUND Resource doesn't exist. Domain unregistered, IP not allocated, MAC OUI not in registry. Treat as authoritative — don't retry.
- 409 CONFLICT State conflict (e.g. attempting to create a duplicate resource). Rare on a read-only API.
- 422 UNPROCESSABLE_ENTITY Parameters parsed but semantically wrong (e.g. start_date after end_date).
- 429 RATE_LIMITED Per-minute, per-day or burst cap exceeded. Inspect Retry-After header. See /docs/rate-limits for backoff strategy.
Server errors — something on our side
5xx means something failed server-side. Retry with backoff. If persistent, file a ticket with the request ID.
- 500 INTERNAL_ERROR Unhandled bug on our side. Retry once or twice; if persistent, report via /contact with error.details.requestId.
- 502 BAD_GATEWAY Edge can't reach origin. Usually transient (deploy windows, fleet rollout). Retry with backoff.
- 503 UPSTREAM_UNAVAILABLE An external service we depend on is down — DNS registry, blacklist DB, certificate authority, etc. error.details.upstream identifies which one. Retry with longer backoff (5–30s).
- 504 GATEWAY_TIMEOUT Upstream timed out. Common on slow snapshots; the snapshot endpoint has an `async=true` mode that avoids this entirely.
Domain-specific error codes
Codes you'll see only on certain endpoints. The standard 4xx/5xx codes above still apply on top of these:
- DOMAIN_NOT_FOUND WHOIS / RDAP returned no record. The domain is unregistered or recently dropped.
- DOMAIN_NOT_RESOLVABLE Domain exists but DNS returns no answer for the requested record type. Common: querying AAAA on an IPv4-only domain.
- PORT_FILTERED Port scan timed out — usually means a firewall is dropping packets (not strictly an error). Often combined with 200 OK and status=filtered in the body.
- IP_PRIVATE Lookup attempted on a private IP range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16). Not routable, not geolocatable.
- TLD_NOT_SUPPORTED Some ccTLDs are not supported for WHOIS/RDAP. The list is published in /docs and updated as registries enable RDAP.
- CERT_UNREACHABLE SSL endpoint refused connection or didn't complete TLS handshake. Often means HTTPS isn't actually configured.
Recommended client-side handling pattern
Four-tier handler that covers 95% of integrations:
async function callApi(url: string, opts: RequestInit) {
const res = await fetch(url, opts);
if (res.ok) return await res.json();
const err = await res.json().catch(() => null);
const code = err?.error?.code || `HTTP_${res.status}`;
switch (true) {
case res.status === 429: // retry with backoff
throw new RetryableError(code, res);
case res.status >= 500: // retry with backoff
throw new RetryableError(code, res);
case res.status === 401 || res.status === 403:
throw new AuthError(code); // re-issue token / surface
case res.status >= 400: // fix client; don't retry
throw new ClientError(code, err?.error?.message);
default:
throw new Error(code);
}
}