Skip to main content

Error Handling

The API uses conventional HTTP status codes and returns a consistent JSON error envelope so you can handle failures programmatically.

Error Response Format

Every error response follows the same structure:

{
"error": {
"code": "content_too_long",
"message": "Text exceeds the 280-character limit for X/Twitter.",
"status": 400
}
}
FieldTypeDescription
error.codestringMachine-readable error code (see table below)
error.messagestringHuman-readable explanation of what went wrong
error.statusnumberHTTP status code

Error Codes by HTTP Status

400 Bad Request

Validation or input errors. Do not retry these -- fix the request first.

CodeDescription
validation_errorRequest body failed schema validation. Check required fields and types.
content_too_longPost text exceeds the platform's character limit.
too_many_accountsThe accountIds array exceeds the maximum number of accounts per request.
invalid_timezoneThe timezone value is not a valid IANA timezone string.
invalid_accountsOne or more account IDs do not exist or do not belong to your organization.
invalid_datetimeThe scheduledAt value is not a valid ISO 8601 datetime string.
past_schedule_timeThe scheduledAt time is in the past. Schedule at least 5 minutes ahead.
invalid_post_statusThe post is in a status that does not allow the requested action.
no_failed_destinationsRetry was called on a post with no failed platform destinations.
unsupported_media_typeThe uploaded file MIME type is not supported. See Media Requirements.
file_too_largeThe uploaded file exceeds the size limit (20 MB images, 500 MB videos).
upload_failedThe file upload could not be processed. Try uploading again.
file_not_uploadedThe media ID references a file that has not finished uploading or does not exist.
file_type_mismatchThe uploaded file's actual type does not match the declared MIME type.

401 Unauthorized

Authentication failures. Check your API key.

CodeDescription
invalid_api_keyThe API key in the Authorization header is not valid.
api_key_revokedThe API key has been revoked. Generate a new key in Settings > Developers.
api_key_expiredThe API key has expired. Generate a new key in Settings > Developers.

402 Payment Required

Billing or entitlement issues.

CodeDescription
insufficient_creditsYour plan's AI credits have been exhausted for this billing period.
upgrade_requiredYour current plan does not include this feature. Upgrade your plan.

403 Forbidden

Permission or scope errors.

CodeDescription
insufficient_scopeYour API key does not have the required scope for this endpoint. See Scopes.
prompt_rejectedThe AI content generation request was rejected by the safety filter.

404 Not Found

CodeDescription
not_foundThe requested resource does not exist or does not belong to your organization.

429 Too Many Requests

CodeDescription
rate_limit_exceededYou have exceeded the per-minute or per-hour rate limit. See Rate Limits.

500 Internal Server Error

CodeDescription
generation_failedAn AI content generation request failed internally. Safe to retry.
service_unavailableA downstream service (platform API, media processing) is temporarily unavailable. Safe to retry.
internal_errorAn unexpected server error occurred. Safe to retry with backoff.

Which Errors to Retry

StatusRetry?Reason
400NoFix the request. The same payload will always fail.
401NoFix your API key or generate a new one.
402NoUpgrade your plan or wait for credits to reset.
403NoCheck your API key scopes or modify the request.
404NoThe resource does not exist.
429YesWait for the X-RateLimit-Reset timestamp, then retry.
500YesRetry with exponential backoff (see below).

Retry Strategy

For retryable errors (429 and 5xx), use exponential backoff with jitter:

async function callWithRetry(fn, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fn();

if (response.ok) {
return response.json();
}

const status = response.status;

// Don't retry client errors (except 429)
if (status >= 400 && status < 500 && status !== 429) {
const body = await response.json();
throw new Error(`${body.error.code}: ${body.error.message}`);
}

if (attempt === maxRetries) {
throw new Error(`Request failed after ${maxRetries + 1} attempts`);
}

// For 429, respect the Retry-After header if present
const retryAfter = response.headers.get("Retry-After");
if (retryAfter) {
await sleep(parseInt(retryAfter, 10) * 1000);
continue;
}

// Exponential backoff: 1s, 2s, 4s, 8s, 16s + random jitter
const baseDelay = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
await sleep(baseDelay + jitter);
}
}

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
# Example: a 429 response with rate-limit headers
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711929660
Retry-After: 30

{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 30 seconds.",
"status": 429
}
}

Best Practices

  1. Always check the error.code field -- not just the HTTP status. Multiple error codes can share the same status.
  2. Log the full error response for debugging. The message field provides useful context.
  3. Set a retry budget. Cap retries at 5 attempts to avoid infinite loops.
  4. Handle 429 specifically. Respect the Retry-After header when present instead of using your own backoff timer.
  5. Distinguish transient from permanent errors. Only retry 429 and 5xx codes.