code and human-readable message.
Error response format
{
"error": {
"code": "validation_error",
"message": "keyword: Required",
"details": { ... },
"request_id": "req_abc123"
}
}
| Field | Description |
|---|---|
code | Machine-readable error code (see table below) |
message | Human-readable explanation |
details | Additional context (optional) |
request_id | Unique request identifier for support (optional) |
Always include the
request_id when contacting support about a failed request. It allows the team to trace the exact request through the system.Error Handling Decision Tree
Use this diagram to determine the correct recovery strategy for each error class:Do not retry 4xx errors (except
429). These indicate a problem with the request itself — retrying the same request will always fail. Fix the issue first.HTTP status codes
| Status | Meaning |
|---|---|
200 | Request succeeded |
201 | Resource created |
202 | Async operation accepted and started |
204 | Deleted (no response body) |
400 | Invalid request parameters |
401 | Authentication failed |
403 | Authenticated but lacking permissions |
404 | Resource not found or not accessible |
409 | Idempotency conflict |
429 | Rate limit exceeded |
500 | Server error |
Error codes
| Code | Status | Description |
|---|---|---|
validation_error | 400 | Request body or parameters failed validation |
unauthorized | 401 | Missing or invalid API key |
key_expired | 401 | API key has expired |
key_revoked | 401 | API key has been revoked |
forbidden | 403 | Key lacks the required scope |
not_found | 404 | Resource does not exist or is not accessible to this key |
idempotency_conflict | 409 | Same idempotency key used with different parameters |
rate_limit_exceeded | 429 | Too many requests |
internal_error | 500 | Unexpected server error |
For
429 errors, see the Rate Limiting guide for retry logic examples with exponential backoff.Handling errors
const response = await fetch(url, { headers });
if (!response.ok) {
const { error } = await response.json();
switch (error.code) {
case "validation_error":
console.error("Bad request:", error.message);
break;
case "rate_limit_exceeded": {
const retryAfter = response.headers.get("Retry-After");
await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
break;
}
case "unauthorized":
case "key_expired":
case "key_revoked":
console.error("Auth issue:", error.message);
break;
default:
console.error("API error:", error.code, error.message);
}
}
import time
import requests
response = requests.get(url, headers=headers)
if not response.ok:
error = response.json()["error"]
if error["code"] == "validation_error":
print(f"Bad request: {error['message']}")
elif error["code"] == "rate_limit_exceeded":
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after)
# retry the request
elif error["code"] in ("unauthorized", "key_expired", "key_revoked"):
print(f"Auth issue: {error['message']}")
else:
print(f"API error: {error['code']} {error['message']}")
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["X-API-Key: $apiKey"],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$body = json_decode(substr($response, $headerSize), true);
if ($httpCode >= 400) {
$error = $body["error"];
switch ($error["code"]) {
case "validation_error":
echo "Bad request: " . $error["message"];
break;
case "rate_limit_exceeded":
$retryAfter = (int) ($error["details"]["retry_after"] ?? 5);
sleep($retryAfter);
// retry the request
break;
case "unauthorized":
case "key_expired":
case "key_revoked":
echo "Auth issue: " . $error["message"];
break;
default:
echo "API error: " . $error["code"] . " " . $error["message"];
}
}
