senapati
Documentation index

Errors & the response envelope

Every route answers with the same two shapes. Successes carry a data object (or array); failures carry an errors list. Both carry a request_id you can quote to support.

Success

200 OKAPISingleResponse
{
"request_id": "req_5d1e9b3c",
"timestamp": "2026-10-11T16:20:03.118402+00:00",
"data": {
"plan": "Starter",
"billing_status": "active",
"credits_remaining": 61218,
"credits_per_month": 80000,
"credits_reset_at": "2026-11-04T09:12:40+00:00",
"current_period_end": "2026-11-05T09:10:17+00:00",
"cancel_at_period_end": false,
"rate_limit_per_second": 30,
"max_api_keys": 5
}
}

A bulk lookup uses the same envelope, with its per-item results as an array inside data. The HTTP status is always 200 on success.

Failure

422 Unprocessable Entityvalidation
{
"errors": [
{
"code": "INVALID_FORMAT",
"message": "Invalid IP address",
"field": "ip",
"details": null
}
],
"request_id": "req_8f2c1a7e"
}
401 Unauthorizedauthentication
{
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Authentication required. Pass a Bearer token or API key (X-API-Key header or ?key= query param).",
"field": null,
"details": null
}
],
"request_id": "req_8f2c1a7e"
}
402 Payment Requiredcredits
{
"errors": [
{
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits. This request costs 1 credit(s).",
"field": null,
"details": {
"cost": 1
}
}
],
"request_id": "req_8f2c1a7e"
}

errors is a list because validation can fail on several fields at once. Each entry has a stable machine-readable code, a human message, the offending field when there is one, and optional details. Branch on code, show message.

Codes by status

StatusCodesMeaning
400 / 422VALIDATION_ERROR · INVALID_FORMAT · INVALID_FIELD · MISSING_FIELD · INVALID_EMAIL · VALUE_OUT_OF_RANGE · VALUE_TOO_LONG · VALUE_TOO_SHORTThe request is malformed. Fix the field named in the error; never retry as-is.
401UNAUTHORIZED · INVALID_TOKENNo key, or an invalid or revoked one.
402INSUFFICIENT_CREDITSBalance too low for this call. Top up or wait for the monthly reset.
403FORBIDDEN · INSUFFICIENT_PERMISSIONSAuthenticated but not allowed (e.g. a deactivated account).
404NOT_FOUNDUnknown route; check the path and the method.
429RATE_LIMIT_EXCEEDEDOver the plan's request rate or concurrency limit. Wait for Retry-After, then retry.
500INTERNAL_ERROR · UNKNOWN_ERROR · DATABASE_*Our fault. Safe to retry with backoff; include the request_id if it persists.
503EXTERNAL_SERVICE_ERROR · EXTERNAL_SERVICE_TIMEOUT · EXTERNAL_SERVICE_UNAVAILABLEAn upstream (DNS resolver, WHOIS server) failed. Retry with backoff.

Handling errors well

  • Check res.ok first, then read errors[0].code. Don't parse messages; they are for humans and may change.
  • Retry only 429 and 5xx, with exponential backoff. 4xx responses will keep failing until you change the request, the key, or the balance.
  • Log the request_id with every failure; it links the call to our request logs.
  • An engine that is unavailable during a lookup does not fail the request; the remaining engines still answer.