Error Handling
Error envelope, stable codes, and HTTP status codes returned by the Onepostly API.
Error Response Format
Every non-2xx response returns a flat JSON envelope:
{
"error": "Post not found.",
"code": "NOT_FOUND"
}| Field | Stable | Description |
|---|---|---|
error | No | Human-readable message. Reworded freely between releases. Never branch client logic on this. |
code | Yes | Machine-readable code (e.g. VALIDATION_ERROR, INSUFFICIENT_WALLET). Use this for programmatic handling. |
param | Yes | Request field at fault, when applicable. Dotted path for nested fields (e.g. destinations.0.accountId). |
details | Yes | Additional structured context when available (e.g. retryAfterSeconds on rate limits). |
platform | Yes | Reserved for platform errors: the upstream platform that rejected the request (e.g. x, tiktok). |
Branch on code, not on the message. The error string exists for humans and may change at any time. The code is stable once shipped.
Stability Contract
codevalues are stable once shipped. Build retries, alerting, and i18n against them.- The
errormessage can change freely. Treat it as display-only. - New codes may be added at any time. Removing a code is a breaking change.
HTTP Status Codes
| Status | When It Happens | Handling |
|---|---|---|
400 / 409 | Missing fields, wrong types, invalid JSON, unmet preconditions, conflicts | Fix the request; retrying unchanged won't help |
401 | Missing or invalid API key | Check the key |
402 / 403 | Wallet balance can't cover the call, a read-only key hit a write route, or the workspace is blocked on billing | Top up, use a read_write key, or fix billing in the dashboard |
404 | Resource doesn't exist or isn't accessible under this key | Check the id |
429 | API quota or posting velocity exceeded | Sleep for Retry-After |
502 | Upstream social platform rejected or failed the request | Read the destination's errorCode / errorMessage |
500 / 503 | Unexpected server-side error | Safe to retry with backoff |
Common Error Codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Body or query failed schema validation. param points at the field (e.g. destinations.0.accountId). |
UNAUTHORIZED | 401 | API key missing, invalid, or revoked. |
FORBIDDEN | 403 | Read-only key used on a write route, or not allowed to manage this resource. |
INSUFFICIENT_WALLET | 402 | X action costs more than the wallet balance. Top up in the dashboard. |
PAYMENT_METHOD_REQUIRED | 402 | The workspace has no payment method on file and needs one to keep going. Add a card in the dashboard. |
PAYMENT_PAST_DUE | 402 | A charge on the workspace could not be collected. Every request is refused until it is paid. Update the card in the dashboard. |
NOT_FOUND | 404 | The referenced resource doesn't exist in this workspace. |
ACCOUNT_NOT_FOUND | 404 | The referenced connected account is unknown or belongs to another workspace. |
CONNECTION_INACTIVE | 400 | The target account is disconnected or expired. Reconnect it. |
CONNECTION_LIMIT_REACHED | 403 | The plan's connected-account cap is full. Disconnect one or upgrade. |
RATE_LIMITED | 429 | API quota or posting velocity exceeded. details carries limit, currentCount, retryAfterSeconds. |
VELOCITY_LIMITED | — | Per-account publishing cap hit (webhook + destination errorCode only). |
TOKEN_INVALID | 400 | The platform revoked the access token. Reconnect the account. |
TEXT_TOO_LONG | 400 | Text exceeds the platform's character limit. |
MEDIA_TOO_LARGE | 400 | Media file exceeds the platform's size limit. |
MEDIA_UNFETCHABLE | 400 | The media URL wasn't reachable from our servers. |
UNSUPPORTED_PLATFORM | 400 | The requested action isn't supported for this platform yet. |
NOT_PUBLISHED | 400 | Comments, engagement, or delete require a published post. |
PLATFORM_ERROR | 502 | Catch-all when an upstream platform call failed. The message names the platform. |
INTERNAL_SERVER_ERROR | 500 | Unexpected server-side error. |
This list is not exhaustive; new codes ship without notice. Handle unknown codes with a generic fallback.
A workspace blocked on billing is refused everything. While a workspace has no payment method on file, or a charge on it could not be collected, every API request returns 402. Reads and writes are refused alike, and MCP tool calls fail the same way. Analytics stop advancing, and posts that were already scheduled stay scheduled rather than publishing. The workspace resumes the moment billing is settled in the dashboard.
Platform Errors
When an upstream platform rejects a publish, the failure lands on the destination, not just the HTTP response. Every destination carries its own outcome:
{
"post": {
"id": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"status": "partial",
"destinations": [
{
"platform": "x",
"status": "published",
"externalPostId": "1960956321831854280",
"externalUrl": "https://x.com/acme/status/1960956321831854280",
"errorCode": null,
"errorMessage": null
},
{
"platform": "instagram",
"status": "failed",
"errorCode": "MEDIA_TOO_LARGE",
"errorMessage": "Video exceeds Instagram's 100 MB limit."
}
]
}
}Post status summarizes the destinations: published when all succeeded, partial when some did, failed when none did. A terminal failure also fires the post.platform.failed webhook carrying the same errorCode / errorMessage pair — see webhooks.
Publishing Is Not Retried
A publish attempt is final. When a destination cannot be published, Onepostly marks it failed with the platform's own errorCode and errorMessage, and never submits it again. Waiting will not change the outcome.
That makes the failing destination the whole story. To publish the content again, create a new post once you have fixed the cause. Conditions that clear on their own arrive as their own codes, so you can tell a temporary platform state from a permanent rejection.
| Code | Meaning | What to do |
|---|---|---|
RATE_LIMITED | The platform throttled the request | Wait out the window, then create a new post |
PLATFORM_UNAVAILABLE | The platform reported a transient server-side failure | Try again later |
MEDIA_PROCESSING_TIMEOUT | The platform did not finish processing the media in time | Try a smaller file, or post again |
Common Publishing Failures
| Error | Cause | Fix |
|---|---|---|
| Token expired / revoked | OAuth token needs refresh or was revoked | Check authHealth on the connection and reconnect. |
| Rate limited by platform | Too many posts to this account | Respect the velocity limits; space posts out. |
| Media rejected | Format, size, or aspect ratio not supported | Check the platform's requirements in its guide. |
| Duplicate content | Platform rejected identical text | Modify the text meaningfully before retrying. |
| Permissions missing | Account lacks the required scope | Reconnect with the proper scopes. |
Webhook Reliability
If you use webhooks to track post status:
- Deliveries are at-least-once; you may receive duplicates. Dedupe by the stable event id.
- Failed deliveries are retried up to 5 times on
408,429,5xx, and network errors. - Delivery history (status, attempts, last response) is available per endpoint through the API and dashboard.
Best Practices
- Branch on
code, never onerrortext. - Handle
429by sleeping forRetry-After(ordetails.retryAfterSeconds). - Treat
500/503as transient and retry with backoff; treat 4xx as caller-fixable. - Read the failing destination, not just post status — one post can succeed on X and fail on Instagram.
- Watch
authHealthon connections to catch token expirations before they fail a publish. - Use webhooks instead of polling for status changes.
- Log the full envelope (
code,param,details) when reporting issues.