The error envelope, HTTP status codes and the machine-readable error codes of the REST API and MCP tools.
Every error has the same JSON shape:
{
"success": false,
"message": "Validation failed",
"code": "validation_error",
"errors": [
{ "path": "platforms.0.content", "message": "X posts are limited to 280 characters (got 300)" },
{ "path": "platforms.1.mediaItems", "message": "Instagram requires at least one image or video" }
]
}| Field | Description |
|---|---|
success | Always false for errors. |
message | A plain-language explanation you can show to a developer. |
code | A stable, machine-readable code (snake_case). Branch on this, not on message. |
errors | Present when individual fields are wrong. path is a dot path into the request body or query, such as platforms.0.accountId. |
Validation is exhaustive: when a post has several problems across several targets, they all come back in one
400 response.
| Status | Meaning |
|---|---|
200 | Success. Also returned for an idempotent replay and for edits that leave a post as a draft. |
201 | A profile or webhook subscription was created. |
202 | A post was accepted (published, publishing, scheduled or saved as a draft). Check status for the outcome. |
400 | The request is invalid. See code and errors. |
401 | Missing or unknown API key. |
402 | Not enough credits. Nothing was created or changed. |
403 | The key was revoked, or the platform is not available. |
404 | The resource does not exist in this project (or the route does not exist). |
409 | The resource is in the wrong state for this action. |
413 | The request body is over 1 MB, or the media storage limit is reached. |
429 | Rate limit exceeded. Wait for Retry-After seconds. |
500 | Unexpected server error. Safe to retry with an idempotency key. |
| Code | Status | When |
|---|---|---|
unauthorized | 401 | The x-access-key header is missing or the key is unknown. |
key_revoked | 403 | The key was revoked in the dashboard. |
rate_limited | 429 | More than 120 requests in a minute for this project. |
payload_too_large | 413 | The JSON body is over 1 MB. |
invalid_json | 400 | The body is empty (where one is required) or not valid JSON. |
validation_error | 400 | A field is missing, has the wrong type, an unknown field was sent to an endpoint that rejects them, or a platform rule failed. |
not_found | 404 | The resource (or route) does not exist in this project. The message names it, for example Post not found. |
internal_error | 500 | Something went wrong on our side. |
| Code | Status | When |
|---|---|---|
platform_not_available | 403 | A platform BetterPost knows but does not support yet (youtube, tiktok, pinterest, bluesky, threads, googlebusiness). Message: Platform not available. |
invalid_platform | 400 | Any other unknown platform value. |
unknown_account | 400 | Every problem is an accountId that does not exist in this project. |
account_reauthorization_required | 400 | One or more target accounts must be reconnected first. errors lists only those accounts. |
duplicate_integration | 409 | Moving an account into a profile that already has the same account. |
profile_has_integrations | 409 | Deleting a profile that still has connected accounts. |
| Code | Status | When |
|---|---|---|
invalid_publish_mode | 400 | Not exactly one of publishNow: true, scheduledFor or saveAsDraft: true. |
invalid_timezone | 400 | timezone is not an IANA zone such as America/New_York. |
invalid_scheduled_for | 400 | scheduledFor is not an ISO 8601 date-time with a time part. |
scheduled_in_past | 400 | scheduledFor is not in the future. |
insufficient_credits | 402 | The request needs more credits than remain. Message: Not enough credits: need X, have Y. |
not_editable | 409 | Editing a post that is not a draft or scheduled, or that has already published to some platforms. |
not_scheduled | 409 | A scheduling endpoint was called for a post whose status is not scheduled. |
post_publishing | 409 | Deleting or retrying a post while it is still publishing. Try again when it finishes. |
no_failed_targets | 400 | Retrying a post that has no failed platforms. |
| Code | Status | When |
|---|---|---|
unsupported_mime_type | 400 | mimeType is not image/* or video/*. |
conflicting_size | 400 | Both size and sizeBytes were sent with different values. |
size_required | 400 | A media upload did not declare its size (size or sizeBytes). |
storage_limit_exceeded | 413 | The upload would take your account over its storage limit. |
media_not_found | 400 | DELETE /v1/media with a URL that is not in this project. |
channel_not_supported | 400 | A notification used the email channel. Only webhook is supported. |
invalid_url | 400 | A webhook URL is not https, cannot be resolved, or points to a private or local address. |
conflicting_filters | 400 | GET /v1/usage with both profileId and integrationId. |
These codes come from the MCP server tools or the sandbox consent page, not from REST endpoints. MCP tools return them as tool errors with the same message.
| Code | Status | When |
|---|---|---|
media_too_large | 413 | upload_media_from_url: the file is over 25 MB. |
media_fetch_failed | 400 | upload_media_from_url: the URL could not be fetched, redirected, or did not answer 2xx. |
invalid_redirect_uri | 400 | A connect request whose redirectUri is not an absolute http(s) URL. A defensive check: the REST route and get_connect_url normally reject such input earlier as a validation error. |
invalid_state | 400 | Consent page: the authorization link is invalid, expired or already used. |
invalid_input | 400 | Consent page: the handle or display name is empty or over 100 characters. |
Dashboard-only and admin-only actions have their own codes, which are not part of the API and are not listed here.
429): fix the request; retrying the same request gives the same answer.429: wait Retry-After seconds, then retry.5xx or network errors on POST /v1/posts: retry with the same idempotencyKey so the post is never
created or charged twice.202 with status: "partial" or "failed" is not an HTTP error: the request was accepted and some targets
failed. See Partial failures.