For AI agents: a documentation index is available at /llms.txt. Every guide and example is also available as markdown by adding .md to its URL.
Error Handling
The API uses standard HTTP status codes. A 2xx status means success; 4xx means the
request was rejected; 5xx means something went wrong on our side.
Error shape
Errors return a JSON body with a machine-readable code, the HTTP status, and a data
object carrying the human-readable detail:
{ "defined": false, "code": "BAD_REQUEST", "status": 400, "message": "Bad Request", "data": { "message": "instagram requires at least 1 media file." } }
Read the reason from data.message. The top-level message is just the generic status
text.
When the failure comes from a known service error (for example a platform rejecting a
request), data also carries:
| Field | Meaning |
|---|---|
data.userMessage | A short, end-user-friendly explanation you can show in your own UI |
data.errorTag | A stable identifier for the error type, safe to branch on |
data.isRetryable | true if the same request may succeed later; false if it never will |
{ "defined": false, "code": "INTERNAL_SERVER_ERROR", "status": 500, "message": "Internal Server Error", "data": { "message": "…", "userMessage": "…", "errorTag": "…", "isRetryable": true } }
These fields are omitted when they don't apply. For 5xx errors, internal details are
never returned — data.message is a generic message (or the userMessage, if there is one).
Validation errors (422)
Requests that fail schema validation return INPUT_VALIDATION_FAILED with field-level detail
under data.fieldErrors:
{ "defined": false, "code": "INPUT_VALIDATION_FAILED", "status": 422, "message": "INPUT_VALIDATION_FAILED", "data": { "formErrors": [], "fieldErrors": { "caption": ["Invalid input: expected string, received undefined"] } } }
The same 422 is used for business rules checked against your request — for example a
scheduledAt in the past when creating or scheduling a post, action: "schedule"
without a scheduledAt, or a status you're not allowed to set. Field-specific problems are
listed under data.fieldErrors; otherwise read data.message.
Payment required (402)
Returned when creating, updating, or publishing a post needs an active plan your workspace
doesn't have. Don't retry; upgrade first. The reason is in data.message.
Generating an AI image (POST /v0/studio/generateImage) without enough AI credits returns
403 with "Insufficient AI credits" in data.message — top up before retrying.
Not found (404)
Returned for a resource that doesn't exist in your workspace (or belongs to another
workspace), and for an unknown path (for example a typo in the URL). Both use the same JSON
envelope, so you can always parse the body. The message names the resource consistently —
"Post not found", "Media not found", "Tag not found", "Social account not found", and
so on:
{ "code": "NOT_FOUND", "status": 404, "message": "Not Found", "data": { "message": "Post not found" } }
Read code and data.message. Resource 404s also include "defined": false, like the other
errors on this page.
Conflicts (409)
Returned when the request clashes with existing state — for example creating a tag whose name already exists, deleting media that is still attached to a post, or updating a post that has already been published:
{ "defined": false, "code": "CONFLICT", "status": 409, "message": "Conflict", "data": { "message": "A tag named \"Launch\" already exists." } }
Rate limits (429)
There are two 429 shapes depending on which limit you hit — see
Rate Limits. Both carry a Retry-After header (seconds). Note that the
first shape has no top-level code/status; check for error.code as well as code when
detecting a rate limit.
The per-method limit (writes 60/min, reads 300/min):
{ "error": { "message": "Rate limit exceeded. Retry after 12s.", "code": "RATE_LIMITED" } }
The overall per-credential limit:
{ "defined": false, "code": "TOO_MANY_REQUESTS", "status": 429, "message": "Too Many Requests", "data": { "message": "You are being rate limited. Please try again later." } }
Common status codes
| Status | Meaning |
|---|---|
400 | Bad request — the request is well-formed but can't be applied (e.g. a platform rule) |
401 | Unauthorized — missing, invalid, or revoked API key or OAuth token |
402 | Payment required — the plan doesn't include this (post writes) |
403 | Forbidden — the credential can't access this resource, an OAuth token lacks the required scope, or not enough AI credits |
404 | Not found — the resource doesn't exist in this workspace |
409 | Conflict — the request clashes with existing state |
422 | Validation error — the body or query failed schema validation, or a business rule (e.g. a past scheduledAt) |
429 | Too many requests — you hit a rate limit |
5xx | Server error — safe to retry with backoff |
Retrying
The official SDKs already retry 429 and 5xx responses automatically with backoff;
configure this with the client's maxRetries / max_retries option.
Timeouts are client-side and never reach the API: set them with timeoutInSeconds /
timeout. In Python a timed-out request raises httpx.TimeoutException (not an API error),
so catch it separately if you retry on timeouts.