Errors
All VoxBurst API errors follow the same envelope:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Content exceeds maximum length for Twitter (280 characters)",
"details": {
"field": "content",
"platform": "TWITTER",
"maxLength": 280,
"actualLength": 312
}
}
}code is always a stable machine-readable string. message is human-readable and may change. details is present on VALIDATION_ERROR and IDEMPOTENCY_* codes — always branch on code, never message.
Global error codes
These codes can appear on any endpoint.
| HTTP | Code | Meaning | Retryable | User-fixable |
|---|---|---|---|---|
| 400 | BAD_REQUEST | Malformed request — missing required field, wrong type, or invalid ID format | No | Yes |
| 400 | VALIDATION_ERROR | Request body failed schema or semantic validation — see details for field-level info | No | Yes |
| 401 | UNAUTHORIZED | Missing or invalid API key | No | Yes — check key |
| 402 | LIMIT_EXCEEDED | Plan limit reached (accounts, posts, workspaces) | No | Yes — upgrade plan |
| 403 | AUTHORIZATION_ERROR | Valid key but insufficient scope for this endpoint | No | Yes — add scope |
| 404 | NOT_FOUND | Resource does not exist in this workspace | No | Yes — check ID |
| 409 | CONFLICT | State conflict — resource is in a status that doesn’t permit this operation | No | Yes — check status |
| 409 | IDEMPOTENCY_CONFLICT | Idempotency key reused with a different request body | No | Yes — use a new key |
| 409 | IDEMPOTENCY_IN_PROGRESS | A request with this key is currently in flight | Yes — wait and retry | — |
| 400 | IDEMPOTENCY_INVALID_KEY | Idempotency key format invalid (bad chars or wrong length) | No | Yes — fix key format |
| 429 | RATE_LIMITED | Rate limit exceeded — Retry-After header indicates when to retry | Yes — after delay | — |
| 500 | INTERNAL_ERROR | Unexpected server error | Yes — with backoff | No |
| 503 | SERVICE_UNAVAILABLE | Dependency unavailable (database, queue) | Yes — with backoff | No |
400 BAD_REQUEST is returned for malformed ID parameters (e.g. passing a non-cuid2 string as a post ID) — before any database lookup. Handle both 400 and 404 when an ID is user-supplied.
Posts errors
POST /v1/posts and PATCH /v1/posts/:id
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Content too long, invalid contentType, bad scheduledFor format, missing accountIds | Fix the failing field — details names it |
| 400 | VALIDATION_ERROR | scheduledFor is in the past (beyond 60s grace window) | Use a future timestamp |
| 400 | VALIDATION_ERROR | Both scheduledFor and queue: true provided | Use one or the other, not both |
| 400 | VALIDATION_ERROR | mediaIds contains an ID that doesn’t exist or isn’t READY | Poll media status before attaching |
| 400 | VALIDATION_ERROR | Thread sequences are non-contiguous or don’t start at 1 | Fix thread item sequence numbers |
| 402 | LIMIT_EXCEEDED | Workspace has reached its plan’s scheduled-post limit | Upgrade plan or publish/archive existing posts |
| 404 | NOT_FOUND | Account ID not found in this workspace | Use GET /v1/accounts to get valid IDs |
| 404 | NOT_FOUND | Persona ID not found | Use GET /v1/personas to get valid IDs |
| 409 | CONFLICT | Post is in a status that doesn’t allow editing (e.g. published) | Check post status — see state machine |
| 409 | CONFLICT | version token mismatch (optimistic concurrency) | Re-fetch the post, use the new updatedAt as version |
| 409 | CONFLICT | Duplicate scheduled post (same content + account + time fingerprint) | Change content or schedule time |
POST /v1/posts/:id/publish
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | X-Callback-Url is not a valid HTTPS URL | Fix the callback URL |
| 404 | NOT_FOUND | Post ID not found | Check the post ID |
| 409 | CONFLICT | Post is not in a publishable status | Post must be draft, scheduled, approved, failed, or partial |
POST /v1/posts/:id/retry
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Post ID not found | — |
| 409 | CONFLICT | Post is not failed or partial | Only failed/partial posts can be retried |
DELETE /v1/posts/:id
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Post ID not found | — |
| 409 | CONFLICT | Post is publishing or published — cannot be deleted | Unpublish first, then archive |
POST /v1/posts/validate
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | platforms array is empty or contains an invalid platform constant | Use valid Platform enum values (e.g. "TWITTER") |
Accounts errors
POST /v1/accounts/connect/:platform
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | INVALID_PARAM | callbackUrl is missing or not a valid URL | Provide a valid HTTPS callback URL |
| 402 | LIMIT_EXCEEDED | Plan limit on connected accounts reached | Upgrade plan or disconnect an unused account |
| 403 | PLATFORM_RESTRICTED | Platform is not enabled for this workspace | Contact support or upgrade to a plan that includes the platform |
POST /v1/accounts/callback/:platform
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | INVALID_PARAM | OAuth state token invalid or expired (>10 min old) | Restart the connect flow to get a fresh state token |
| 400 | INVALID_PARAM | Authorization code invalid | Restart the connect flow |
POST /v1/accounts/:id/refresh
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Account ID not found | — |
| 400 | VALIDATION_ERROR | Token refresh failed — platform rejected the refresh request | User must reconnect via the full OAuth flow |
POST /v1/accounts/:id/select-page
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | INVALID_PAGE_ID | Page not available for this account | Re-fetch pages with GET /v1/accounts/:id/pages |
| 400 | DESTINATIONS_NOT_LOADED | Account page list not yet loaded | Wait a moment and retry |
| 404 | NOT_FOUND | Account ID not found | — |
Media errors
POST /v1/media/upload
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | contentType not in the supported MIME types list | Use a supported MIME type — see Supported File Types |
| 400 | VALIDATION_ERROR | sizeBytes exceeds the per-type maximum | Compress or reduce the file |
| 503 | UPLOAD_UNAVAILABLE | S3 presign service temporarily unavailable | Retry after a short delay |
POST /v1/media/bulk-presign and POST /v1/media/bulk-video-upload-urls
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | REQUEST_TOO_LARGE | Combined batch size exceeds 20 GB | Split into smaller batches |
| 400 | VALIDATION_ERROR | More than 20 files in a single request | Split into multiple requests of ≤ 20 |
| 503 | UPLOAD_UNAVAILABLE | S3 presign service unavailable | Retry after a short delay |
GET /v1/media/:id and DELETE /v1/media/:id
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Media item not found in this workspace | Check the mediaId |
POST /v1/media/:id/validate
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Media item not found | — |
| 202 | (not an error) | Returns { "queued": false } if validation queue not configured | No action needed — validation skipped |
Webhook errors
POST /v1/webhooks
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | url is not a valid HTTPS URL | Use a valid https:// URL |
| 400 | VALIDATION_ERROR | events contains an unknown event type | Use a valid event type — see Events |
| 402 | LIMIT_EXCEEDED | Plan does not include custom webhooks (Pro+ required) | Upgrade to Pro or Agency plan |
| 403 | AUTHORIZATION_ERROR | webhooks:write scope not present on API key | Add webhooks:write scope |
POST /v1/webhooks/test
| HTTP | Code | When it occurs | Fix |
|---|---|---|---|
| 404 | NOT_FOUND | Webhook endpoint ID not found | — |
| 400 | VALIDATION_ERROR | Endpoint is disabled | Re-enable the endpoint first |
Error recovery cheatsheet
| Situation | Action |
|---|---|
429 RATE_LIMITED | Wait for Retry-After seconds, then retry identically |
500 INTERNAL_ERROR | Retry with exponential backoff (1s, 2s, 4s); if persists, contact support |
503 SERVICE_UNAVAILABLE | Retry with exponential backoff |
409 IDEMPOTENCY_IN_PROGRESS | Wait ~2 seconds, retry with the same Idempotency-Key |
409 CONFLICT (post status) | Fetch the post, check its current status, decide whether to wait or fix |
409 CONFLICT (version mismatch) | Re-fetch the post; use updatedAt as the new version token |
400 BAD_REQUEST (malformed ID) | Check that the ID starts with c and is 25 chars (cuid2 format) |
404 NOT_FOUND | Confirm the resource exists in the correct workspace |
401 UNAUTHORIZED | Regenerate your API key from the dashboard |
403 AUTHORIZATION_ERROR | Add the required scope to your API key |
Negative examples — common failures
These are the highest-friction validation cases, shown as failing requests with the exact error response.
Instagram post without media or contentType
curl -X POST https://api.voxburst.io/v1/posts \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "content": "Hello!", "accountIds": ["acc_instagram_abc"] }'{
"error": {
"code": "VALIDATION_ERROR",
"message": "Content is required for Instagram posts",
"details": { "platform": "INSTAGRAM", "reason": "MEDIA_REQUIRED" }
}
}Fix: Add "contentType": "IMAGE" and "media": ["media_id_here"]. Instagram requires at least one image or video on every post.
Twitter post over 280 characters
curl -X POST https://api.voxburst.io/v1/posts/validate \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "content": "This is a very long tweet that exceeds the 280 character limit. Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat duis aute irure dolor.", "platforms": ["TWITTER"] }'{
"valid": false,
"platforms": {
"TWITTER": {
"valid": false,
"errors": [
{ "code": "CONTENT_TOO_LONG", "message": "Content exceeds maximum length for Twitter (280 characters). Current: 321 characters." }
],
"warnings": []
}
}
}Fix: Shorten the content to ≤280 characters. Note that links count as 23 characters regardless of their actual length.
Scheduling in the past
curl -X POST https://api.voxburst.io/v1/posts \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "content": "Old post", "accountIds": ["acc_abc"], "scheduledFor": "2025-01-01T00:00:00Z" }'{
"error": {
"code": "VALIDATION_ERROR",
"message": "Scheduled time must be in the future",
"details": { "field": "scheduledFor" }
}
}Fix: Use a future UTC timestamp. VoxBurst allows a 60-second grace window for clock skew.
Media not yet READY
curl -X POST https://api.voxburst.io/v1/posts \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "content": "Photo post", "accountIds": ["acc_ig"], "contentType": "IMAGE", "media": ["media_still_processing"] }'{
"error": {
"code": "VALIDATION_ERROR",
"message": "Media media_still_processing is not ready for publishing (status: PROCESSING)"
}
}Fix: Poll GET /v1/media/:id until status is "READY" before attaching media to a post.
Bulk create over 50 posts
curl -X POST https://api.voxburst.io/v1/posts/bulk \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "posts": [ ... 51 items ... ] }'{
"error": {
"code": "VALIDATION_ERROR",
"message": "Maximum 50 posts per bulk request",
"details": { "field": "posts", "max": 50, "actual": 51 }
}
}Fix: Split into batches of ≤50 posts per request.
threadPosts sequences not contiguous
curl -X POST https://api.voxburst.io/v1/posts \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"content": "Thread",
"accountIds": ["acc_twitter"],
"contentType": "THREAD",
"threadPosts": [
{ "sequence": 1, "content": "First tweet" },
{ "sequence": 3, "content": "Third tweet — sequence 2 is missing!" }
]
}'{
"error": {
"code": "VALIDATION_ERROR",
"message": "Thread post sequences must be 1-based and contiguous",
"details": { "field": "threadPosts" }
}
}Fix: Use sequences 1, 2, 3 … with no gaps and no duplicates.
Malformed resource ID
curl https://api.voxburst.io/v1/posts/not-a-valid-id \
-H "Authorization: Bearer vb_live_xxxxx"{
"error": {
"code": "BAD_REQUEST",
"message": "Invalid path parameter 'id': Invalid ID format"
}
}Fix: VoxBurst IDs are cuid2 format: exactly 25 characters, starting with c, followed by 24 lowercase alphanumeric characters. Example: cmp6v6bhf000369v60htcu3vc. Both 400 and 404 should be handled when accepting user-supplied IDs.
Optimistic concurrency conflict (stale version)
curl -X PATCH https://api.voxburst.io/v1/posts/post_abc \
-H "Authorization: Bearer vb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{ "content": "Updated content", "version": "2026-06-01T10:00:00.000Z" }'{
"error": {
"code": "CONFLICT",
"message": "Post has been modified since you last loaded it. Re-fetch and retry.",
"details": { "field": "version" }
}
}Fix: Re-fetch the post with GET /v1/posts/:id, use the response’s updatedAt as the new version token, and retry the update.