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.

StatuscodeMeaning
400validation_errorThe request body failed schema validation (missing field, wrong type, invalid enum value).
401missing_api_keyNo Authorization header was sent.
401invalid_api_keyThe key is malformed, revoked, or unrecognized.
403sandbox_key_not_allowedA sandbox key attempted an action explicitly marked as live (e.g. "sandbox": false in the request body).
403plan_limit_exceededThe workspace's plan limit was hit — e.g. monthly post cap, connected account cap, or API key cap. See Rate Limits.
404not_foundThe requested post, account, or webhook ID doesn't exist in this workspace.
409duplicate_requestThe same Idempotency-Key header was reused with a different request body within 24 hours.
422unsupported_media_for_platformThe media type, format, duration, or size isn't accepted by one of the requested platforms. See the platform guides.
422account_not_connectedNo connected account exists for a requested platform (or the accounts.<platform> id given doesn't exist), and none could be inferred.
429rate_limitedWorkspace request-rate limit exceeded. Retry-After header indicates the wait in seconds.
500internal_errorUnexpected 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_codeMeaning
platform_auth_expiredThe connected account's token could not be refreshed. The account's status is now expired — see Connecting Accounts.
platform_rate_limitedThe 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_mediaThe platform rejected the media after upload — wrong codec, unsupported aspect ratio, file too large, etc.
container_expiredInstagram/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_failedOmniPost could not fetch the media URL you provided (timeout, 4xx/5xx response, or an unreachable host).
url_ownership_unverifiedTikTok only: PULL_FROM_URL was used with a media host that isn't a verified domain on the connected TikTok app.
caption_too_longThe caption exceeds the target platform's character limit (e.g. 500 for Threads).
unknown_platform_errorThe 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.