Works with all AI agents

Connect your AI agent

Choose your AI agent and connect in seconds.

Add to Claude

Works on every Claude plan, including free. Full Claude guide

  1. 1

    Add to Claude

    One click from Claude’s connector directory.

  2. 2

    Sign up or sign in

    Takes seconds, then you’re sent straight back.

  3. 3

    Ask Claude

    “Plan and schedule my posts for next week.”

PostEverywhere Error Handling Reference

Complete reference of PostEverywhere API error codes, HTTP status codes, and retry strategies. Handle errors gracefully in your integration.

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

The shape of every error response

Every endpoint error follows the same structure. The one exception is the authentication layer: 401 codes (invalid_api_key, api_key_revoked, api_key_expired) and 402 subscription_required omit retryable and meta.request_id, and a request with no Authorization header at all returns a plain-text 401 Unauthorized.

{
  "data": null,
  "error": {
    "message": "Free X accounts are limited to 280 characters per post (currently 282).",
    "code": "post_creation_failed",
    "retryable": false,
    "details": {
      "validation_errors": [...]
    }
  },
  "meta": {
    "request_id": "a1b2c3d4",
    "timestamp": "2026-04-12T14:30:00Z"
  }
}
Field Type Description
data null Always null on error responses.
error.message string Human-readable explanation of what went wrong.
error.code string Machine-readable error code (see table below).
error.retryable boolean Whether retrying this exact request might succeed. true for transient failures (5xx, 429); false for permanent failures (most 4xx). Use this instead of inferring from the status code.
error.details object Optional. Additional context about the error (e.g. failed validation fields, circuit breaker info).
meta.request_id string Unique ID for this request, also sent as the X-Request-Id header. Search for it in Logs, and include it when contacting support.
meta.timestamp string ISO 8601 timestamp of when the response was generated.

The HTTP status code is returned in the response status line; there is no error.status field in the body (removed 2026-04-12).

Always check error.retryable

The simplest robust retry logic: retry if error.retryable === true, abort otherwise. This is more reliable than inferring from the status code and protects you from edge cases like our circuit breaker (see 422 below).

Every error code, grouped by HTTP status

400 Bad Request

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

Code Description
validation_error Request body failed schema validation. Check required fields and types.
invalid_json The request body is not valid JSON. Common cause: unescaped newlines/tabs inside a string value, use \n / \t, not raw control characters.
content_too_long Post text (or a title or description field) is over a platform's real limit, so the post is refused on create or edit instead of failing when it publishes. details.platform_errors[] gives each platform with its current_length and max_length. See the limits below.
x_poll_invalid An X poll breaks X's rules: it needs 2 to 4 options of up to 25 characters and a duration from 5 minutes to 7 days, with no images, video or quoted post. Refused when the post is created or edited.
unsupported_schedule_at schedule_at was sent to the REST API. It is an MCP option. Read GET /v1/best-times and send the time as scheduled_for.
request_approval_needs_draft request_approval: true was sent without draft: true. Nothing was created.
too_many_accounts The account_ids array exceeds the maximum number of accounts per request.
invalid_timezone The timezone value is not a valid IANA timezone string.
invalid_accounts One or more account IDs do not exist or do not belong to your organization.
invalid_datetime The scheduled_for value is not a valid ISO 8601 datetime string.
past_schedule_time The scheduled_for time is in the past. Any future instant is accepted (POST /v1/posts/{id}/schedule requires at least 1 minute ahead).
invalid_post_status The post is in a status that does not allow the requested action.
no_failed_destinations Retry was called on a post with no failed platform destinations.
unsupported_media_type The uploaded file MIME type is not supported. See Media Requirements.
file_too_large The uploaded file exceeds the size limit (20 MB images, 4 GB videos).
upload_failed The file upload could not be processed. Try uploading again.
file_not_uploaded The media ID references a file that has not finished uploading or does not exist.
file_type_mismatch The uploaded file's actual type does not match the declared MIME type.
bulk_limit_exceeded POST /v1/posts/bulk received more than 50 posts in one call. Split into multiple calls. Also fires for posts/retry-failed with post_ids > 200.
no_filter POST /v1/posts/retry-failed was called with no filter. At least one of post_ids, account_id, platform, failed_after, failed_before is required.
invalid_period GET /v1/analytics/summary received an invalid period value. Must be today, week, month, all, or custom.
invalid_range GET /v1/analytics/summary?period=custom is missing from/to or they're not valid ISO timestamps.
invalid_event_type POST /v1/webhooks received an unknown event name. The response details.valid_events lists every accepted event.
limit_reached The org has reached the per-org cap (e.g. 25 webhooks). Contact support to raise.
prompt_rejected The AI content generation request was rejected by the safety filter.
wordpress_post_invalid The WordPress settings in platform_content.wordpress failed validation; details.errors[] lists every problem. See the WordPress guide.
wordpress_title_required A WordPress post has no settings.title and an empty body, so no title can be derived. Any body text avoids it, because the first line becomes the title.
x_article_invalid An X Article request failed validation. details.errors[] lists every problem: non-X accounts in account_ids (articles publish to X only), a missing title or a title over 100 characters, an empty body, or a body over 25,000 characters in platform_content.x.content. See Publishing an X Article.

401 Unauthorized

Authentication failures. Check your API key.

Code Description
invalid_api_key The API key in the Authorization header is not valid.
api_key_revoked The API key has been revoked. Create a new key on the Developers page.
api_key_expired The API key has expired. Create a new key on the Developers page.
api_key_required No API key was supplied on an endpoint that requires one (social-connect flows).
auth_failed The platform rejected the supplied credentials during a social-connect flow.

402 Payment Required

Billing or entitlement issues.

Code Description
insufficient_credits Your plan's AI credits have been exhausted for this billing period.
upgrade_required Your current plan does not include this feature. Upgrade your plan.
subscription_required The organization has no active subscription or trial; the body includes subscription_status.

403 Forbidden

Permission or scope errors.

Code Description
insufficient_scope Your API key does not have the required scope for this endpoint. See Scopes.

404 Not Found

Code Description
not_found The requested resource does not exist or does not belong to your organization. Also returned, as JSON, for a path that isn't an API endpoint (for example a typo in the URL).

422 Unprocessable Entity

Code Description
idempotency_key_reused The Idempotency-Key was already used with a different request body. Use a new key for a new request. See Make retries safe.
permanent_failure_circuit_breaker This exact request body has failed 5+ times in the last 6 hours and the circuit breaker is now open for it. Modify any field of the request body (the content_hash in details will change), or wait 6 hours, to retry. Triggered when an integration ignores 4xx responses and retries the same payload in a loop.

When the circuit breaker is open, the response includes:

{
  "data": null,
  "error": {
    "message": "This exact request has failed 5 times in the last 6 hours...",
    "code": "permanent_failure_circuit_breaker",
    "retryable": false,
    "details": {
      "circuit_breaker_open": true,
      "content_hash": "a1b2c3d4e5f6g7h8",
      "fail_count": 5,
      "threshold": 5,
      "ttl_seconds": 21600,
      "hint": "The previous failures should have included `validation_errors` explaining what to fix..."
    }
  },
  "meta": { "request_id": "a1b2c3d4", "timestamp": "..." }
}

To clear the breaker before its 6-hour TTL, change any field of the request body: even adding an idempotency_key you weren't using before will produce a new content hash and reset the counter to 0. The breaker exists specifically to protect badly-written retry loops from burning rate-limit budget on requests that will never succeed. If you're hitting it, inspect the validation_errors from the previous 4xx responses: those tell you exactly what to fix.

409 Conflict

Code Description
idempotency_request_in_progress A request with the same Idempotency-Key is still running. Wait for the Retry-After header (2 seconds) and retry with the same key.
media_in_use DELETE /v1/media/{id} on a file that a post not yet published still uses. Nothing is deleted. Retry with ?force=true only if those posts should be cancelled: they are marked failed and never publish.

429 Too Many Requests

Code Description
rate_limit_exceeded You have exceeded the per-minute or per-hour rate limit. See Rate Limits.
post_limit_reached The organization's post allowance is exhausted (POST /v1/posts/{id}/schedule).
daily_limit_reached The daily posting cap is exhausted (POST /v1/posts/{id}/schedule).

500 Internal Server Error

Code Description
generation_failed An AI content generation request failed internally. Safe to retry.
service_unavailable A downstream service (platform API, media processing) is temporarily unavailable. Safe to retry.
internal_error An unexpected server error occurred. Safe to retry with backoff.

Failures reported on the post, not the request

Some problems can only be known when a post publishes, so they appear on the failed destination (and in post.failed webhooks) rather than as an HTTP error on create. For X Articles:

Failure What it means What to do
X Premium required The X account has no X Premium subscription, which X requires for articles. Subscribe the account to X Premium, or send the content as a regular post or thread.
PostEverywhere article limit Each X account can publish 2 X Articles in any rolling 24 hours. Only successful publishes count. The post fails with X_ARTICLE_DAILY_LIMIT. Reschedule the article for the next day.
X's own article limit X caps article actions at 10 per X account per 24 hours. The post is not failed: it is held and publishes automatically 5 minutes after X's reset, and the message states that time (UTC). It fails with X_ARTICLE_X_DAILY_LIMIT only if X is still refusing after three holds. Nothing, unless it fails; then try again the next day.

Caption character limits behind content_too_long

Platform Limit
Instagram 2,200
LinkedIn 3,000
Facebook 63,206
Pinterest description 500, title 100
Discord 2,000
YouTube title 100, description 5,000 (bytes)
TikTok 2,200 for a video caption (photo posts: description 4,000, title 90)
X 280 without X Premium, 25,000 with it (X's own count: a link counts 23, most emoji 2)
Threads 500
Bluesky 300
Google Business Profile 1,500

X Articles and WordPress posts have their own limits (see the platform guides). Telegram splits long text into several messages, so it has no limit here.

Published, but with a warning

A post that goes out with a problem, such as text cut to fit or a setting the platform did not apply, is not a failure. Each platform's entry in GET /v1/posts/{id} and GET /v1/posts/{id}/results has a warnings array (empty when there is nothing to report), each with a code and a message. Show these to the user. Each warning also fires the post.warning webhook.

Which errors are worth retrying

Recommended: check error.retryable (boolean) on the response: it tells you directly whether retrying makes sense, without needing to map status codes yourself.

Status retryable Reason
400 false Fix the request. The same payload will always fail.
401 false Fix your API key or generate a new one.
402 false Upgrade your plan or wait for credits to reset.
403 false Check your API key scopes or modify the request.
404 false The resource does not exist.
422 false Circuit breaker open: modify the request body before retrying (see above). Retrying the same payload will fail until the 6-hour TTL expires.
429 true Wait for the Retry-After header value, then retry.
500 true Retry with exponential backoff (see below).

How to back off and retry safely

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: 120

{
  "data": null,
  "error": {
    "message": "API rate limit exceeded (60 requests/min). Please slow down.",
    "code": "rate_limit_exceeded",
    "retryable": true,
    "details": { "limit": 60, "remaining": 0, "reset_at": "2026-04-12T14:31:00.000Z" }
  },
  "meta": {
    "request_id": "a1b2c3d4",
    "timestamp": "2026-04-12T14:30:00Z"
  }
}

Handling errors without losing posts

  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.

Related: reproducing errors safely · list endpoint parameters · failure events by webhook · newly added error codes

Make retries safe with an Idempotency-Key

Pass an Idempotency-Key header on POST /v1/posts and POST /v1/posts/bulk to make retries safe. If a network error or timeout leaves you unsure whether a post was created, send the same request again with the same key:

  • Same key, same body: you get the original response back, with the header Idempotent-Replayed: true. Nothing new is created and nothing publishes twice.
  • Same key, different body: 422 idempotency_key_reused.
  • First request still running: 409 idempotency_request_in_progress with Retry-After: 2.
  • First request failed with a 5xx: the key is released, so your retry runs normally.

Keys are any unique string up to 255 characters (a UUID works well), last 24 hours, and are scoped to your API key.

curl -X POST https://app.posteverywhere.ai/api/v1/posts \
  -H "Authorization: Bearer pe_live_your_key" \
  -H "Idempotency-Key: 6f1c2a9e-3b7d-4a51-9c0e-2d8f4b7a1e35" \
  -H "Content-Type: application/json" \
  -d '{"content": "Launching today", "account_ids": [1, 2]}'