Errors
Every error has the same JSON shape, a stable code to branch on, and a message you can show to people as is.
When a request fails, the API answers with a 4xx or 5xx status and a JSON body. It never answers with an HTML page.
{
"error": "Another LinkedIn post is already scheduled at that exact time. Pick a different time.",
"code": "time_taken",
"postId": "cmhjz4w2p0001l708xq3kd9vb",
"requestId": "req_8f14e45fceea167a5a36dedd4bea2543"
}| Field | Type | Description |
|---|---|---|
error | string | What went wrong and what to do, in plain words. Safe to show to the person using your app. The wording can change. |
code | string | Stable, machine-readable reason. Branch on this, not on error. |
requestId | string | The request's id, the same as the X-Request-Id header. Quote it when you contact support. See Request IDs. |
postId | string | Only present when the request was about a post that already exists, such as scheduling or publishing a draft. It names that post. |
Checks run before Postel saves anything. When POST /v1/posts with scheduledAt or publishNow fails, Postel removes the new draft again, so a retry never leaves a duplicate behind. That is also why those errors carry no postId.
Error codes
| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | invalid_request | The request isn't valid. A field is missing or has the wrong type, the body isn't JSON, scheduledAt is in the past, a media URL couldn't be used, or the Idempotency-Key is over 255 characters. error names the field. | Fix the request. Don't retry it unchanged. |
| 400 | text_too_long | A post, or one part of a thread, is over the network's character limit. | Shorten it, or split it into a thread on networks that have them. |
| 400 | platform_unavailable | Postel can't schedule or publish to this network through the API. | Use one of the networks in Publish and schedule posts. |
| 401 | invalid_api_key | The key is missing, malformed or revoked, or its owner left the workspace. | Create a new key in Settings → API & MCP. |
| 402 | plan_required | Scheduling and publishing need a paid plan. error includes the link to the billing page. | Upgrade the workspace, or save a draft instead. |
| 402 | out_of_credits | The workspace has no AI credits left for writing today. error says when writing works again. | Wait, or upgrade for more credits. |
| 404 | not_found | No post with this id in this workspace, or no viral post with this id in Postel's library. | Check the id and that the key is for the right workspace. |
| 409 | channel_not_connected | The post's network isn't connected in this workspace. | Connect it in Postel under Settings → Channels. |
| 409 | channel_expired | Postel's connection to the network expired. | Reconnect it in Postel under Settings → Channels. |
| 409 | time_taken | Another post on the same network is already scheduled at that exact time. | Pick a different time. |
| 409 | not_a_draft | Only drafts can be scheduled or published now. The post is scheduled, waiting for approval, or already being published. | Check the post with GET /v1/posts/{id}. |
| 409 | already_published | The post is already published. | Nothing to do. Don't publish it again. |
| 409 | approval_required | The workspace needs an owner or admin to approve posts, so publishing now is refused. | Schedule the post instead. It waits for approval in Postel. |
| 409 | not_a_member | The person who created the key is no longer in the workspace. | Create a new key. |
| 409 | request_in_progress | A request with the same Idempotency-Key is still running. | Wait for it to finish, then retry with the same key. |
| 422 | idempotency_key_reused | You reused an Idempotency-Key with a different request body. | Use a new key for a new request. |
| 422 | not_ready | The post can't be scheduled as it is, for example because the network needs media it doesn't have. | Fix the post in Postel, then schedule it. |
| 422 | invalid_content | The network's rules refuse the post, for example X's limit on posts with links. | Change the post. error says what to change. |
| 422 | not_supported | You asked for a take on a video. Takes work from X posts and formats for now. | Use an X post or a format. |
| 429 | rate_limited | Too many requests. See Rate limits. | Wait for the seconds in Retry-After, then retry. |
| 500 | server_error | Something broke on Postel's side. | Retry after a minute. |
| 502 | schedule_failed | Postel couldn't schedule the post this time. | Retry after a minute. |
| 502 | generate_failed | Postel couldn't write the post this time. Nothing was charged. | Retry after a minute. |
New codes can appear over time. Treat an unknown code by its status: show error, and retry only 429 and 5xx responses.
Retrying
- Retry
429,500and502with a backoff. On429, wait at least theRetry-Afterseconds. - Don't retry
400,401,402,404,409or422unchanged. They fail the same way again. - Send an
Idempotency-KeyonPOST /v1/posts,POST /v1/posts/generateandPOST /v1/viral/takes, so a retry after a timeout returns the first result instead of creating a second post.