Error Handling
Understand API error responses and implement robust error handling in your applications.
Error Response Format
All API errors follow a consistent JSON structure. Every error response includes a success field set to false, an error object with a machine-readable code and human-readable message, and a meta object with request metadata.
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
},
"meta": {
"request_id": "req_abc123def456",
"timestamp": "2026-02-01T12:00:00Z"
}
}The request_id is useful for debugging — include it when contacting support.
Error Codes Reference
The following table lists all possible error codes, their HTTP status codes, and common causes:
| Status | Code | Description | Common Cause |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request body or parameters | Missing required fields, wrong data types, values out of range |
| 401 | UNAUTHORIZED | Missing or invalid authentication | No Bearer token, expired key, malformed key |
| 402 | INSUFFICIENT_CREDITS | Not enough credits for the operation | Account balance too low for the requested operation |
| 403 | FORBIDDEN | API key lacks the required permission or scope | Key missing write permission for POST endpoints, wrong scope |
| 404 | NOT_FOUND | Resource does not exist or is not owned by the user | Invalid ID, deleted resource, accessing another user's data |
| 409 | CONFLICT | Operation conflicts with current resource state | Publishing already-published content, duplicate operations |
| 412 | PRECONDITION_FAILED | Required precondition not met | Platform not connected for social publishing |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests | Exceeded per-minute or daily rate limit |
| 500 | INTERNAL_ERROR | Unexpected server error | Server-side issue — retry with backoff |
Error Response Examples
400 Validation Error
400VALIDATION_ERROR
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "topic is required and must be 3-500 characters"
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-02-01T12:00:00Z"
}
}401 Unauthorized
401UNAUTHORIZED
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired API key"
},
"meta": {
"request_id": "req_def456",
"timestamp": "2026-02-01T12:00:00Z"
}
}402 Insufficient Credits
402INSUFFICIENT_CREDITS
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "This operation requires 40 credits but your balance is 12"
},
"meta": {
"request_id": "req_ghi789",
"timestamp": "2026-02-01T12:00:00Z"
}
}403 Forbidden
403FORBIDDEN
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "API key lacks 'write' permission required for this endpoint"
},
"meta": {
"request_id": "req_jkl012",
"timestamp": "2026-02-01T12:00:00Z"
}
}429 Rate Limit Exceeded
429RATE_LIMIT_EXCEEDED
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 45 seconds."
},
"meta": {
"request_id": "req_mno345",
"timestamp": "2026-02-01T12:00:00Z",
"retry_after": 45
}
}Retry Strategies
Not all errors should be retried. Use the following guidelines to determine when and how to retry failed requests:
- 429 Rate Limit: Always retry with exponential backoff using the
Retry-Afterheader - 500 Internal Error: Retry up to 3 times with exponential backoff
- 400 / 401 / 403 / 404: Do NOT retry — fix the request first
async function apiRequest(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.ok) return res.json();
const body = await res.json();
// Don't retry client errors (except rate limits)
if (res.status < 500 && res.status !== 429) {
throw new Error(`API Error: ${body.error.code} — ${body.error.message}`);
}
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries + 1} attempts: ${body.error.code}`);
}
// Exponential backoff: 1s, 2s, 4s
const retryAfter = res.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}Best Practices
- Always check the
successfield before accessingdata - Log
request_idfrom error responses for debugging - Implement exponential backoff for 429 and 500 errors
- Check credit balance before expensive operations to avoid 402 errors
- Validate input client-side before sending to reduce 400 errors
- Use appropriate permissions and scopes to prevent 403 errors