Errors
OmniPost uses standard HTTP status codes. Every non-2xx response body is a JSON error object with a stable machine-readable code.
The error object
{
"error": {
"code": "unsupported_media_for_platform",
"message": "TikTok video duration exceeds this creator's max_video_post_duration_sec."
}
}code is stable and intended for programmatic branching. message is human-readable, may change wording over time, and should not be pattern-matched.
Request-level errors
These are returned directly by the API call itself — the request as a whole was rejected before (or instead of) creating or updating anything.
| Status | code | Meaning |
|---|---|---|
| 400 | validation_error | The request body failed schema validation (missing field, wrong type, invalid enum value). |
| 401 | missing_api_key | No Authorization header was sent. |
| 401 | invalid_api_key | The key is malformed, revoked, or unrecognized. |
| 403 | sandbox_key_not_allowed | A sandbox key attempted an action explicitly marked as live (e.g. "sandbox": false in the request body). |
| 403 | plan_limit_exceeded | The workspace's plan limit was hit — e.g. monthly post cap, connected account cap, or API key cap. See Rate Limits. |
| 404 | not_found | The requested post, account, or webhook ID doesn't exist in this workspace. |
| 409 | duplicate_request | The same Idempotency-Key header was reused with a different request body within 24 hours. |
| 422 | unsupported_media_for_platform | The media type, format, duration, or size isn't accepted by one of the requested platforms. See the platform guides. |
| 422 | account_not_connected | No connected account exists for a requested platform (or the accounts.<platform> id given doesn't exist), and none could be inferred. |
| 429 | rate_limited | Workspace request-rate limit exceeded. Retry-After header indicates the wait in seconds. |
| 500 | internal_error | Unexpected server-side failure. Safe to retry with backoff. |
Target-level errors
POST /v1/upload can return 201 and still have one or more targets fail asynchronously — for example, if Instagram rejects the media after OmniPost already accepted the request. These errors appear as error_code / error_message on the individual target in GET /v1/posts/:id and in the post.failed webhook payload — never as the top-level HTTP error of the create call.
| error_code | Meaning |
|---|---|
| platform_auth_expired | The connected account's token could not be refreshed. The account's status is now expired — see Connecting Accounts. |
| platform_rate_limited | The platform itself rejected the publish call due to its own rate limit (e.g. TikTok's spam_risk_too_many_posts, Instagram's 50/24h cap). |
| platform_rejected_media | The platform rejected the media after upload — wrong codec, unsupported aspect ratio, file too large, etc. |
| container_expired | Instagram/Threads only: the upload container wasn't published within the platform's 24-hour (Instagram) or ~5-minute processing window (Threads) and expired. |
| media_download_failed | OmniPost could not fetch the media URL you provided (timeout, 4xx/5xx response, or an unreachable host). |
| url_ownership_unverified | TikTok only: PULL_FROM_URL was used with a media host that isn't a verified domain on the connected TikTok app. |
| caption_too_long | The caption exceeds the target platform's character limit (e.g. 500 for Threads). |
| unknown_platform_error | The platform returned an error OmniPost couldn't map to a known code. error_message includes the raw platform response. |
You can force any of these on demand in sandbox mode using the simulate_error field, to test your handling before you ever see it live.
Idempotent retries
POST /v1/upload accepts an optional Idempotency-Key header. Reusing the same key with an identical body within 24 hours returns the original post instead of creating a duplicate; reusing it with a different body returns 409 duplicate_request. This is the safe way to retry a request after a timeout without risking a double-post.
curl -X POST https://api.omnipost.dev/v1/upload \
-H "Authorization: Bearer $OMNIPOST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2c9b1a7e-4f3d-4b6a-9c1e-8f2a7d9b3e10" \
-d '{ "caption": "...", "media": [...], "platforms": ["x"] }'For 429 rate_limited responses, always read the Retry-After header rather than retrying immediately — see Rate Limits for handling guidance.