Skip to main content

Understand and handle API errors

The CaptainDNS public API returns every error in a standardized JSON envelope. This page lists the canonical codes, what they mean and the recommended corrective action.

Standard envelope

Every error response follows this format:

{
  "code": "QUOTA_EXCEEDED",
  "message": "Monthly credits quota exceeded for plan 'starter'.",
  "details": {
    "tier": "starter",
    "credits_used": 50000,
    "credits_limit": 50000
  },
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}
  • code: stable string documented here. Use it to branch your error handling logic.
  • message: human-readable description in English. Do not parse it; it may evolve.
  • details: optional object with context specific to the code. Its schema varies by code. Most codes do not expose details: the information is then carried by message.
  • request_id: CaptainDNS request identifier, useful when opening a support ticket. Present only if the X-Request-Id header was propagated.
  • documentation_url: link to this page.

The HTTP status always accompanies the code. A robust client branches on the (status, code) pair and uses code as the primary key.

Authentication codes (401)

INVALID_API_KEY

Status: 401.

Cause: missing Authorization header, missing prefix, malformed key, tampered secret, or key not found on the CaptainDNS side. An HMAC mismatch and an unknown key return the same code so the attacker cannot distinguish between the two.

Action: verify the header is Authorization: Bearer cdns_live_.... A stray whitespace, a newline, or a truncated key are the most common culprits.

{
  "code": "INVALID_API_KEY",
  "message": "Invalid API key.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field; the information is only in message.

EXPIRED_API_KEY

Status: 401.

Cause: the key has passed its expires_at date.

{
  "code": "EXPIRED_API_KEY",
  "message": "This API key has expired.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field.

Action: create a new key with a later expiration or no expiration. Keys without expires_at are valid until revoked.

REVOKED_API_KEY

Status: 401.

Cause: the key was revoked manually or automatically (rotation grace period end).

{
  "code": "REVOKED_API_KEY",
  "message": "This API key has been revoked.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field.

Action: use the active key. If you do not have one, create one from the dashboard.

Authorization codes (403)

INSUFFICIENT_SCOPE

Status: 403.

Cause: the key lacks the scope required by the endpoint. The missing scope is concatenated into message.

{
  "code": "INSUFFICIENT_SCOPE",
  "message": "This API key does not have the required scope: web:read",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field; the required scope is read from message.

Action: create a new key with the missing scope or use a key that carries it. Scopes are not editable after creation.

IP_NOT_ALLOWED

Status: 403.

Cause: the key has an IP allowlist and the request's source IP is not in it. The client IP is derived from r.RemoteAddr on the backend (never from a client-supplied X-Forwarded-For), after potential rewriting by the TrustedProxyRealIP layer when the immediate hop is a trusted proxy configured on the CaptainDNS side.

{
  "code": "IP_NOT_ALLOWED",
  "message": "Your IP is not in this key's allowlist.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field; the detected IP and the CIDR list are not returned to the client (they are logged server-side).

Action: add your IP to the key allowlist, or route your traffic through an authorized egress (proxy, NAT).

QUOTA_EXCEEDED

Status: 403.

Cause: the monthly credits quota is reached and the plan does not allow overage (Free plan, hard cap).

{
  "code": "QUOTA_EXCEEDED",
  "message": "Monthly credits quota exceeded for plan 'free'.",
  "details": {
    "tier": "free",
    "credits_used": 500,
    "credits_limit": 500
  },
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

Action: wait until the next period or upgrade to a higher plan. The CaptainDNS dashboard lets you switch plans in one click.

OVERAGE_BUDGET_EXCEEDED

Status: 403.

Cause: the profile has enabled overage but has reached the monthly budget cap configured from the dashboard. Subsequent calls are refused until the period closes or the cap is raised.

{
  "code": "OVERAGE_BUDGET_EXCEEDED",
  "message": "Overage budget cap reached for plan 'starter'.",
  "details": {
    "tier": "starter",
    "credits_used": 68000,
    "credits_limit": 50000
  },
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

Action: raise the cap from Account > API usage, upgrade to a plan with a larger envelope, or wait for the next monthly period.

Request codes (400)

INVALID_REQUEST

Status: 400.

Cause: a component of the request cannot be processed. The public API emits two cases:

  • Malformed Idempotency-Key header (non-ASCII characters, whitespace, out-of-range length, empty value after trim).
  • Unreadable request body or body exceeding the idempotency size limit (11 MiB).
{
  "code": "INVALID_REQUEST",
  "message": "Invalid Idempotency-Key header.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field; the precise reason is in message.

Action: fix the header or the body, then retry. For endpoint-specific validation errors (invalid domain, unknown selector, etc.), see the Validation codes (400) section.

Conflict codes (409)

IDEMPOTENCY_CONFLICT

Status: 409.

Cause: an Idempotency-Key is reused with a body different from the original request. The comparison is based on the SHA-256 hash of the raw body.

{
  "code": "IDEMPOTENCY_CONFLICT",
  "message": "Idempotency-Key reused with a different request body.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field; the conflicting key is the one you just sent.

Action: generate a new key for the new request, or fix the client that mutates the body between retries.

Rate limit codes (429)

RATE_LIMITED

Status: 429.

Cause: the per-key token bucket is empty or the per-IP rate limit is exceeded. Two message variants depending on which layer was hit:

  • Pre-auth IP bucket: "Too many requests from this IP. Slow down.".
  • Per-key bucket: "Rate limit exceeded for this API key.".
{
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded for this API key.",
  "request_id": "req_a1b2c3d4",
  "documentation_url": "https://www.captaindns.com/en/docs/api/errors"
}

No details field. The Retry-After header always accompanies this code, expressed in seconds. Wait at least this duration before retrying. See the rate limiting guide for backoff strategies.

Server codes (5xx)

INTERNAL_ERROR

Status: 500 or 503.

Cause: unexpected error on the CaptainDNS side. Two variants:

  • 500: transient application error (handler panic, DB dependency unavailable, tier lookup error, etc.). message specifies the subsystem.
  • 503: the public API is not configured or the /public/v1/* group is explicitly disabled on the platform side (missing pepper, PUBLICAPI_ENABLED=false flag). There is no separate SERVICE_UNAVAILABLE code: unavailability shares the INTERNAL_ERROR code with a 503 status.
{
  "code": "INTERNAL_ERROR",
  "message": "public api is not configured",
  "request_id": "req_a1b2c3d4"
}

No details field.

Action: keep the request_id and open a support ticket. 503s are usually tied to planned maintenance or an incident: check the official status channel before retrying. Retry after a few minutes for transient 500s; if the rate exceeds 1 % over a five-minute window, alert the CaptainDNS team.

Validation codes (400)

The API also returns 400 BAD_REQUEST for validation errors specific to each endpoint. These errors are not covered by the standardized envelope above because their structure varies per endpoint.

Example for a DNS resolve with an invalid domain:

{
  "error": "invalid domain: must be a valid FQDN",
  "field": "domain"
}

For endpoints that return validation errors, see the OpenAPI reference which documents the exact schemas.

Synthetic table

StatusCodeDescriptionAction
400VariesEndpoint validation errorFix the request body
400INVALID_REQUESTUnreadable Idempotency-Key or bodyFix the header or the body
401INVALID_API_KEYMissing, malformed or unknown keyCheck the Authorization header
401EXPIRED_API_KEYExpired keyCreate a new key
401REVOKED_API_KEYRevoked keyUse an active key
403INSUFFICIENT_SCOPEMissing scope on keyCreate a key with the right scope
403IP_NOT_ALLOWEDIP not in allowlistAdd IP or use an authorized egress
403QUOTA_EXCEEDEDMonthly quota reached (hard cap)Wait or upgrade plan
403OVERAGE_BUDGET_EXCEEDEDOverage cap reachedRaise the cap or upgrade
409IDEMPOTENCY_CONFLICTSame key with different bodyGenerate a new key
429RATE_LIMITEDPer-IP or per-key rate limit exceededWait Retry-After
500INTERNAL_ERRORTransient server errorRetry and open a ticket if persistent
503INTERNAL_ERRORPublic API not configuredCheck status, retry

Error handling best practices

  • Branch on the (status, code) pair. Status alone is not enough: two 403s can come from INSUFFICIENT_SCOPE or IP_NOT_ALLOWED, each deserving a different action.
  • Log the request_id. It is the unique identifier on the CaptainDNS side and is required for any investigation with support.
  • Do not retry blindly. 4xx codes other than 429 indicate a client-side problem: retrying without change will not succeed.
  • Respect Retry-After. 429 and 503 responses always include it. Do not bypass.
  • Alert on error rates. 1 % of 5xx over a 5-minute window is an incident signal. Surface it in your observability stack.

Jump to the OpenAPI reference to explore schemas endpoint by endpoint, or go back to the quickstart to restart from the beginning.