API Error Code Catalog
This catalog covers stable codes reachable through API-key-authenticated /api/v1 routes. It excludes internal X Bookmarks codes and codes emitted only by dashboard sessions, OAuth, billing, or other non-API-key routes.
Codes can appear at the top level, inside warnings[], inside validation.errors[], or per URL in mediaResults[]. Match on the code, not the human-readable message.
For retry patterns and idempotency guidance, see Error Handling.
Request and scheduling codes
| Code | HTTP | Location | Meaning | Retryable? | Recovery |
|---|---|---|---|---|---|
IDEMPOTENCY_IN_FLIGHT |
409 | top-level code |
Another request with this idempotency key is still running, or its claim was superseded | Yes, shortly | Retry the identical request with the same key |
IDEMPOTENCY_KEY_CONFLICT |
422 | top-level code |
The key was already used with a different body or resource | No, unchanged | Replay the original request or use a new key for the new operation |
IDEMPOTENCY_BODY_TOO_COMPLEX |
400 | top-level code |
The body is too deeply nested to hash safely for idempotency | No | Simplify the request body or omit Idempotency-Key if safe retries are not required |
PLATFORM_SETTING_UNKNOWN |
400 | top-level code |
A platformSettings path is outside the strict allowlist |
No | Remove or correct the path named by field |
PLATFORMS_REQUIRED |
400 | top-level code |
Scheduling would leave the post with no target platforms | No | Add at least one platform, or keep the post as a draft |
SCHEDULED_TIME_IN_PAST |
400 | top-level code |
Strict mode rejected a time at least five minutes in the past | No | Send a future ISO 8601 UTC time |
SCHEDULED_TIME_COERCED |
200 | warnings[].code |
A past time was clamped to server time during the warn-first ramp | Not an error | Read the returned scheduledTime and fix the caller to send a future time |
MEDIA_VALIDATION_PENDING |
200 | warnings[].code |
Scheduling succeeded while bounded media probing remained transiently incomplete | Not an error | Monitor the post; media is checked again before publishing |
MEDIA_NOT_READY |
400 | top-level code |
Attached upload bytes are missing, still uploading, or previously failed validation | After fixing media | Complete/re-upload the media, then schedule again |
MEDIA_URL_RATE_LIMITED |
429 | top-level code |
The fixed-window allowance of 60 ingested URLs was exceeded | Yes | Wait the Retry-After seconds, then retry; preserve the idempotency key when applicable |
Plan and workspace limit codes
These top-level codes are LimitExceededError responses with HTTP 403.
| Code | Meaning | Retryable? | Recovery |
|---|---|---|---|
CONNECTIONS_OVER_LIMIT |
The workspace already has more connected channels than its plan permits | After account changes | Disconnect channels or upgrade before scheduling |
CHANNEL_LIMIT_REACHED |
Adding or attaching would exceed the channel allowance | After account changes | Disconnect a channel or upgrade |
SCHEDULE_HORIZON_REACHED |
The requested time is farther ahead than the plan permits | No, unchanged | Choose an earlier time or upgrade |
SCHEDULED_POST_LIMIT_REACHED |
The active scheduled-post queue is full | After capacity frees | Wait for or delete an existing scheduled post, or upgrade |
POST_LIMIT_REACHED |
The monthly account/connection post allowance is exhausted | After reset or upgrade | Wait for periodEnd or upgrade |
PLATFORM_NOT_AVAILABLE |
The plan does not allow one or more selected platforms | No, unchanged | Remove the platforms listed in the top-level disallowedPlatforms array or upgrade |
Media completion codes
These codes are returned by POST /complete-media/{mediaFileId}.
| Code | HTTP | Location | Meaning | Retryable? | Recovery |
|---|---|---|---|---|---|
INVALID_MEDIA_FILE_ID |
400 | top-level code |
mediaFileId is not a Mongo ObjectId |
No | Use the mediaId returned by get-upload-url |
MEDIA_FILE_NOT_FOUND |
404 | top-level code |
No media row belongs to the caller for that ID | No | Check the ID and API-key owner; upload again if it was deleted |
MEDIA_FILE_INDETERMINATE_TYPE |
400 | top-level code |
Neither stored type nor MIME prefix identifies image, video, or PDF | No | Re-upload with the correct Content-Type |
PROBE_FAILED |
400 | top-level code |
The stored media already failed byte-level validation | No | Re-upload a valid file |
MEDIA_FILE_MISSING_URL |
500 | top-level code |
The media row has no storage URL | No client-side retry contract | Upload again; contact support if newly created rows repeat this state |
PROBE_VERSION_UNVERIFIED |
503 | top-level code |
The object version could not be verified before or after probing | Yes | Retry complete-media after a few seconds |
PROBE_DOWNLOAD_FAILED |
503 | top-level code |
A transient S3/CDN download prevented probing | Yes | Retry complete-media after a few seconds |
PROBE_VERSION_CHANGED |
503 | top-level code |
The uploaded object changed while it was being validated | Yes, after upload stabilizes | Stop overwriting the presigned object and retry |
MEDIA_FILE_RACE |
409 | top-level code |
The row was deleted or failed concurrently during probing | No | Re-upload and use the new media ID |
mediaUrls per-URL codes
When create/update ingestion fails, the request returns HTTP 400 with error: "Media ingestion failed"; the URL/fetch codes below can appear in mediaResults[].code. Probe failures are forwarded into the same field using the applicable code from Media completion codes. The batch is all-or-nothing.
| Code | HTTP | Location | Meaning | Retryable? | Recovery |
|---|---|---|---|---|---|
MEDIA_URL_INVALID |
400 | mediaResults[].code |
The value is not a URL | No | Supply a valid public URL |
MEDIA_URL_PROTOCOL |
400 | mediaResults[].code |
The URL is not HTTPS | No | Use https:// |
MEDIA_URL_CREDENTIALS |
400 | mediaResults[].code |
The URL embeds a username or password | No | Use a credential-free URL, such as a signed HTTPS URL |
MEDIA_URL_PORT |
400 | mediaResults[].code |
The URL uses a non-default HTTPS port | No | Serve it on port 443 |
MEDIA_URL_BLOCKED_HOST |
400 | mediaResults[].code |
The host or resolved address is private/blocked | No | Use a publicly reachable host |
MEDIA_URL_DNS |
400 | mediaResults[].code |
DNS returned no usable address | After DNS is fixed | Correct DNS, then retry the whole request |
MEDIA_URL_REDIRECT |
400 | mediaResults[].code |
A redirect lacked a Location header |
After the origin is fixed | Use the final URL or fix the redirect |
MEDIA_URL_TOO_MANY_REDIRECTS |
400 | mediaResults[].code |
The fetch exceeded the redirect limit | No, unchanged | Use a direct URL |
MEDIA_URL_HTTP_ERROR |
400 | mediaResults[].code |
The remote server returned a non-200 response | After the origin is available | Check access/expiry and retry with a working URL |
MEDIA_URL_INGEST_TIMEOUT |
400 | mediaResults[].code |
The request-wide media ingestion budget expired | Conditionally | Use a faster origin, smaller media, or the presigned upload flow |
MEDIA_URL_TOO_LARGE |
400 | mediaResults[].code |
One image/video exceeds the URL-ingestion byte cap | No | Use a smaller file or get-upload-url |
MEDIA_URL_AGGREGATE_TOO_LARGE |
400 | mediaResults[].code |
Combined downloaded bytes exceed 300 MiB | No | Reduce the batch or use presigned uploads |
MEDIA_URL_UNSUPPORTED_FORMAT |
400 | mediaResults[].code |
Magic-byte detection found an unsupported format | No | Convert to a supported image/video format |
MEDIA_URL_FETCH_FAILED |
400 | mediaResults[].code |
An unexpected network/download failure occurred | Conditionally | Verify the URL and retry once; otherwise use presigned upload |
MEDIA_URL_PROBE_FAILED |
400 | mediaResults[].code |
Defensive fallback if a future failed probe supplies no specific code; current probeAndPersistMedia failure branches always supply one |
Not currently expected | If observed, convert or re-upload valid media and report the unexpected fallback |
MEDIA_URL_SKIPPED |
400 | mediaResults[].code |
This URL was not attempted after another URL failed | After fixing the root failure | Correct the failing URL and retry the complete batch |
MEDIA_URL_ROLLED_BACK |
400 | mediaResults[].code |
This URL succeeded, but its media was removed because another URL failed | After fixing the root failure | Correct the failing URL and retry the complete batch |
LinkedIn symbolic errors
These LinkedIn endpoints use the error field for stable symbolic values rather than a separate code field.
| Value | HTTP | Meaning | Retryable? | Recovery |
|---|---|---|---|---|
REACTION_ALREADY_EXISTS |
409 | The actor already has a reaction on the target post | No, unchanged | Delete the existing reaction, then apply the requested type |
LINKEDIN_TOKEN_EXPIRED |
401 | The stored LinkedIn access token is expired | No | Reconnect the LinkedIn account |
LINKEDIN_RATE_LIMITED |
429 | LinkedIn is rate-limiting the analytics resource/account | Yes | Wait the returned retryAfter seconds |
LINKEDIN_REQUEST_TIMEOUT |
504 | The backend request deadline elapsed while waiting for LinkedIn | Yes | Retry the read/interaction with normal backoff |
Post validation codes
Scheduling validation returns HTTP 400. These codes appear in validation.errors[].code; validation warnings, when present, use validation.warnings[].code.
| Code | Meaning | Recovery |
|---|---|---|
CONTENT_TOO_LONG |
Content exceeds the selected platform's character limit | Shorten it or change the target platform/account tier |
CONTENT_OR_MEDIA_REQUIRED |
The post has neither usable text nor media | Add content or media |
MEDIA_REQUIRED |
A selected platform requires media | Attach supported images or video |
MEDIA_SIZE_EXCEEDED |
A file exceeds the platform byte limit | Compress or replace it |
MEDIA_COUNT_EXCEEDED |
Too many media items are attached | Delete extras and schedule again |
MEDIA_TYPE_NOT_SUPPORTED |
The media type is unsupported for the platform | Convert or remove it |
MEDIA_DIMENSIONS_INVALID |
Image/video dimensions violate a platform rule | Resize the media |
MEDIA_ASPECT_RATIO_INVALID |
Aspect ratio violates a platform rule | Crop or resize the media |
VIDEO_REQUIRED |
The selected platform requires video | Attach a supported video |
VIDEO_DURATION_EXCEEDED |
Video is longer than the platform maximum | Trim it |
VIDEO_DURATION_TOO_SHORT |
Video is shorter than the platform minimum | Use a longer video |
IMAGES_NOT_SUPPORTED |
The selected platform does not accept images for this post type | Remove images or change the target |
DOCUMENTS_NOT_SUPPORTED |
The selected platform does not accept document media | Remove the document or target LinkedIn |
PLATFORM_SETTING_REQUIRED |
A required platform-specific setting is absent | Supply the field identified in the error |
PLATFORM_SETTING_INVALID |
A platform-specific setting has an invalid value | Use one of the reported accepted values |
PLATFORM_NOT_SUPPORTED |
The validator has no contract for the target platform | Remove the unsupported target |
All validation codes above are non-retryable with the unchanged request.
Workspace attachment codes
These lowercase codes are top-level response fields from POST /workspace/users or POST /workspace/users/attach.
| Code | HTTP | Meaning | Recovery |
|---|---|---|---|
email_exists |
409 | A user with the requested email already exists | Inspect the returned attachment status; use the attach flow when allowed |
missing_identifier |
400 | Neither email nor user ID was supplied | Send one identifier |
invalid_user_id |
400 | The user ID is malformed | Send a Mongo ObjectId |
invalid_email |
400 | The email format is invalid | Correct the email |
user_not_found |
404 | No active target user was found | Check the identifier |
identifier_mismatch |
400 | Supplied email and user ID identify different users | Correct one identifier |
user_inactive |
409 | The target user is inactive | Reactivate the user before attaching |
cannot_attach_self |
400 | The workspace owner was supplied as the target | Choose another user |
user_is_workspace_owner |
409 | The target owns a workspace | A workspace owner cannot be attached as managed |
user_in_another_workspace |
409 | The target already belongs to another workspace | Detach it from the other workspace first |
user_has_active_subscription |
409 | The target has a blocking subscription | Resolve that subscription before attaching |
attach_consent_required |
403 | Attaching an existing standalone user requires its consent token | Supply the target user's access token |
invalid_attach_consent |
403 | The consent token is invalid or revoked | Obtain a fresh token from the target user |
attach_consent_mismatch |
403 | The token belongs to another user | Supply the target user's own token |
Source coverage
- Top-level scheduling and idempotency responses:
backend/src/app/routes/apiRoute.js. - Plan/workspace entitlement responses:
backend/src/app/services/limitsService.js. - Scheduling guards and media state:
backend/src/app/helpers/scheduledPlatformsGuard.js,mediaStatusTransitions.js, andprobeMediaUpload.js. - Per-URL ingestion:
backend/src/app/helpers/mediaUrlIngest.jsandmediaUrlSecurity.js. - Validation codes actually emitted by scheduling:
backend/src/app/services/postValidationService.jsanderrors/ValidationError.js. - Workspace attachment responses:
backend/src/app/controllers/workspaceApiController.js. - LinkedIn interaction/analytics errors:
backend/src/app/controllers/linkedinController.js.