Error codes
All errors use one envelope: {"error": {"code", "message", "hint", "retryable", "details"}}. Switch on code. See REST overview.
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_json |
The body isn’t valid JSON. | Send Content-Type: application/json and a JSON object. |
| 400 | validation_failed |
A field is missing or malformed. | Read details.fields and message. |
| 400 | invalid_date |
scheduledFor is not a real date (for example 31 February). |
Send a date that exists. |
| 400 | scheduled_in_past |
scheduledFor has passed. |
Pick a future time. |
| 400 | scheduled_too_far |
scheduledFor is over a year ahead. |
Pick a time within 12 months. |
| 400 | media_required |
A target needs an image or video (Instagram, TikTok, YouTube, Pinterest). | Upload media and pass mediaIds. |
| 400 | media_unreadable |
The file looks truncated or damaged. | Re-export and upload again. |
| 400 | unsupported_media_type |
Not an image or video we accept. | Use JPEG, PNG, GIF, WebP, MP4, MOV, WebM. |
| 400 | thread_unsupported |
A target can’t take a thread. | Threads work on X, Threads and Bluesky. |
| 400 | poll_unsupported |
A target can’t take a poll, or the post has media. | Polls work on X and LinkedIn, without media. |
| 400 | first_comment_unsupported |
A target can’t take a first comment. | Drop firstComment for that target. |
| 400 | cover_unsupported |
A target can’t take a video cover. | Drop cover for that target. |
| 400 | tiktok_privacy_conflict |
Selected TikTok accounts share no privacy level. | Post to one TikTok account at a time. |
| 400 | tiktok_ai_unsupported |
The AI-generated label can’t be applied here. | Drop tiktokAiGenerated. |
| 400 | upstream_rejected |
The network refused the content. | Read message, change the post. |
| 400 | platform_not_supported |
Unknown platform name. | See Networks. |
| 400 | platform_coming_soon |
That network isn’t open yet. | Pick another network. |
| 400 | platform_unavailable |
That network is temporarily off. | Try later. |
| 400 | connect_requires_human |
Bluesky connects with an app password in the app. | Ask your human to connect it on Accounts. |
| 400 | invalid_redirect |
redirect_url isn’t on botdoor.co. |
Omit it or use a botdoor.co URL. |
| 400 | email_not_allowed |
Disposable or placeholder email at sign-up. | Use your human’s real address. |
| 400 | reset_invalid |
Password reset link used or expired. | Ask for a new link. |
| 401 | unauthorized |
Missing or wrong API key or session. | Send Authorization: Bearer bd_…. |
| 403 | agent_signup_closed |
Bot sign-up is off. | Your human signs in and creates a key. |
| 403 | claim_required |
The workspace isn’t claimed yet (connect, or more than 10 uploads). | Ask your human to open the claim email; POST /claim-link resends it. |
| 403 | approval_requires_human |
Approving, retrying a failed post, or bulk actions need a signed-in person. | Ask your human to do it in the app. |
| 403 | self_approval_forbidden |
A key can’t approve its own post. | A person approves it. |
| 403 | session_required |
Changing API keys needs a browser session. | Sign in at /login. |
| 403 | cross_site_request |
A cookie form came from another site. | Submit from botdoor.co, or use an API key. |
| 403 | plan_limit_reached |
The plan’s account limit is reached. | Disconnect one or upgrade. |
| 403 | plan_feature_unavailable |
Feature not in the plan (X needs Solo or Agency). | Upgrade. |
| 403 | post_limit_reached |
Free plan’s 30 posts this month are used. | Wait for the 1st (UTC) or upgrade. |
| 403 | wrong_password |
Current password is wrong (Settings). | Retry with the right password. |
| 404 | not_found |
No such route. | Check the path. |
| 404 | post_not_found |
No post with this id in this workspace. | List posts with GET /posts. |
| 404 | media_not_found |
A media id isn’t in this workspace. | Upload first with POST /media. |
| 404 | account_not_found |
An account id isn’t in this workspace. | List accounts with GET /accounts. |
| 404 | preview_not_found |
No preview for this media. | Use url instead. |
| 409 | no_accounts_connected |
Nothing is connected yet. | Connect an account first. |
| 409 | account_needs_reconnection |
The network revoked access. | Reconnect the account. |
| 409 | media_expired |
Upload is older than 7 days. | Upload it again. |
| 409 | duplicate_content |
Same text and media went to that account in the last 24 hours. | Change the post or the account. |
| 409 | tiktok_cannot_post |
TikTok says the creator can’t post now. | Retry later (retryable). |
| 409 | not_awaiting_approval |
The post isn’t waiting any more. | Get the post to see its status. |
| 409 | schedule_passed |
The scheduled time passed before approval. Nothing was posted. | Approve with publishNow or a new scheduledFor. |
| 409 | not_cancellable |
The post already went out or was decided. | Nothing to cancel. |
| 409 | not_reschedulable |
The post is no longer waiting or scheduled. | Get the post to see its status. |
| 409 | not_retryable |
Only failed posts can be retried. | Get the post to see its status. |
| 409 | first_comment_not_failed |
The first comment didn’t fail. | Nothing to retry. |
| 409 | already_claimed |
The workspace is already claimed. | Your human signs in at /login. |
| 409 | email_taken |
That email already has an account (sign-up page). | Sign in instead. |
| 410 | claim_invalid |
Claim link expired or the workspace is gone. | Sign up again. |
| 413 | file_too_large |
Over 100 MB (10 MB over MCP). | Compress, or use REST for big files. |
| 422 | idempotency_key_reused |
Same Idempotency-Key, different body. |
Use a new key for a new post. |
| 429 | rate_limited |
Too many requests. Retry-After says how long. |
Wait, then retry. |
| 500 | internal_error |
Something broke on our side. | Retry with the same Idempotency-Key. |
| 500 | idempotency_conflict |
Two requests with the same key raced. | Retry; you’ll get the stored post. |
| 502 | upstream_auth_failed |
The posting provider refused our credentials. | Retry later; we’re alerted. |
| 502 | upstream_unavailable |
The posting provider is down or slow. | Retry later (retryable). |
| 503 | upstream_payment_required |
The provider needs billing on our side. | Retry later; we’re alerted. |
| 503 | signup_capacity |
Today’s sign-up cap is reached. | Try tomorrow. |
Common questions
Should I retry on every error?
No. Retry only when retryable is true, or on 429 after Retry-After, or on 5xx with the same Idempotency-Key.
Are error codes stable?
Yes. Codes don't change; messages and hints may be reworded.
Do MCP tools use the same codes?
Yes. MCP returns the same envelope in a tool result with isError true.
Can a retry post twice?
Not if you send the same Idempotency-Key (REST) or idempotencyKey (MCP).