Error Codes
All API errors follow a consistent format:
{
"error": {
"type": "validation_error",
"message": "description is required",
"request_id": "req_01JQ...",
"details": [
{ "field": "description", "message": "required" }
]
}
}Error Types
| Type | HTTP Status | Description |
|---|---|---|
validation_error | 400 | Request body or parameters are invalid |
authentication_error | 401 | Missing or invalid API key |
authorization_error | 403 | Key lacks required permissions |
not_found | 404 | Resource does not exist |
conflict | 409 | Resource state conflict (e.g., cancelling a completed job) |
idempotency_error | 409 | Idempotency key reused with different parameters |
rate_limit_exceeded | 429 | Too many requests — check Retry-After header |
insufficient_credits | 402 | Not enough credits to post the job |
internal_error | 500 | Unexpected server error |
Handling Errors
Retryable Errors
These errors are safe to retry with exponential backoff:
429 rate_limit_exceeded— Wait forRetry-Afterheader duration500 internal_error— Retry up to 3 times502,503,504— Retry up to 3 times
Non-Retryable Errors
These require fixing the request:
400 validation_error— Fix request body401 authentication_error— Check API key402 insufficient_credits— Purchase more credits404 not_found— Check resource ID409 conflict— Check resource state
SDK Error Handling
Both SDKs automatically retry retryable errors with exponential backoff.
import { FirstHandError } from '@firsthand/sdk';
try {
await client.createJob({...});
} catch (e) {
if (e instanceof FirstHandError) {
console.log(e.type); // "validation_error"
console.log(e.status); // 400
console.log(e.requestId); // "req_01JQ..."
console.log(e.retryable); // false
}
}from firsthand import FirstHandError
try:
client.create_job({...})
except FirstHandError as e:
print(e.type) # "validation_error"
print(e.status) # 400
print(e.request_id) # "req_01JQ..."
print(e.retryable) # False