Error Handling
The Schedulala API uses conventional HTTP status codes and returns structured error responses to help you handle failures gracefully.
Error response format
All error responses share a consistent JSON structure. The success field is always false, and the error object contains a machine-readable code, a human-readable message, and optional details:
{"success": false,"error": {"code": "invalid_request","message": "The 'content' field is required","details": {"field": "content","reason": "required"}}}
| Field | Type | Description |
|---|---|---|
success | boolean | Always false for errors |
error.code | string | Machine-readable error code (use this for programmatic handling) |
error.message | string | Human-readable description of the error |
error.details | object | null | Additional context (e.g., which field failed validation). Omitted when there is none. |
Error codes
Use the error.code field to programmatically handle different error types:
| Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Malformed request (missing fields, invalid JSON, etc.) |
validation_error | 400 | Request validation failed (e.g., content too long for platform) |
url_not_allowed | 400 | URL rejected (media-from-URL): must be a publicly reachable http(s) URL; private and internal addresses are blocked |
sandbox_unsupported | 400 | Operation not available with test (sk_test_) keys; use a live key |
unauthorized | 401 | Invalid or missing API key |
forbidden | 403 | API key doesn't have permission for this operation |
plan_limit_exceeded | 403 | Plan limit reached (upgrade to increase limits) |
not_found | 404 | Resource not found |
idempotency_conflict | 409 | Idempotency key reused with a different request body |
media_too_large | 413 | Uploaded file exceeds the size limit |
unsupported_media | 415 | Unsupported media type for the target platform |
download_failed | 422 | Media could not be downloaded from the provided URL (media-from-URL) |
rate_limit_exceeded | 429 | Too many requests — wait and retry with backoff |
internal_error | 500 | Internal server error — retry with backoff |
platform_error | 502 | Error from the social media platform — retry with backoff |
Error handling examples
Always check the success field first, then handle specific error codes:
async function createPost(payload) {const response = await fetch('https://schedulala.com/api/v1/posts', {method: 'POST',headers: {'Authorization': `Bearer ${API_KEY}`,'Content-Type': 'application/json',},body: JSON.stringify(payload),});const data = await response.json();if (!data.success) {switch (data.error.code) {case 'rate_limit_exceeded': {// Wait until the rate limit window resets (ms timestamp)const resetMs = response.headers.get('X-RateLimit-Reset');const waitMs = resetMs? Math.max(0, parseInt(resetMs, 10) - Date.now()): 60000;await sleep(waitMs);return createPost(payload);}case 'platform_error':// Social platform issue — retry with exponential backoffconsole.error('Platform error:', data.error.message);throw new RetryableError(data.error.message);case 'validation_error':// Fix the request — don't retryconsole.error('Validation failed:', data.error.details);throw new ValidationError(data.error.message, data.error.details);case 'unauthorized':// Check your API keythrow new AuthError('Invalid API key');default:throw new Error(data.error.message);}}return data;}
Best practices
Check the success field first
Every API response includes a success field. Check it before accessing data fields to avoid unexpected runtime errors in your application.
Handle specific error codes
Use error.code (not the HTTP status) for programmatic error handling. This gives you more granular control — for example, both forbidden and plan_limit_exceeded return HTTP 403, but require different handling.
Retry with exponential backoff
Implement retry logic with exponential backoff for these retryable errors:
| Error Code | Retry? | Strategy |
|---|---|---|
rate_limit_exceeded | Yes | Wait until the X-RateLimit-Reset timestamp (OAuth and device-flow 429s also send Retry-After) |
platform_error | Yes | Exponential backoff (1s, 2s, 4s, ...) |
internal_error | Yes | Exponential backoff (1s, 2s, 4s, ...) |
| All other 4xx errors | No | Fix the request before retrying |
Don't retry 4xx errors (except 429). Client errors like validation_error, unauthorized, and forbidden will return the same error every time. Fix the underlying issue before retrying.
Use idempotency keys for safe retries
When retrying POST requests, always include an Idempotency-Key header to prevent duplicate operations. See the Idempotency guide for details.
Tip: Log the full error response (including error.code, error.message, and error.details) in your application. This makes debugging much easier when issues arise.