staqlydocs
REST API

Errors

Understand an error response from staqly and decide whether to fix, retry or give up.

Edit on GitHub

Every error from the REST API uses one JSON shape with a stable code. Read this page when you write error handling for an automation, or when a call fails and you want to know why.

Handle an error

Read the code, not the message

{
  "error": {
    "code": "validation_failed",
    "message": "targets.ch_7h2kq9xw3nbvd5yhzm6a.content: at most 280 characters for this channel (312)",
    "fields": { "targets.ch_7h2kq9xw3nbvd5yhzm6a.content": "at most 280 characters for this channel (312)" }
  }
}

Switch on error.code; the message is for humans and may change.

Decide what to do

The code isDo this
validation_failed, bad_request, connect_failedFix the request (see fields and the message); don't retry as is
unauthorized, forbidden, csrf_failedFix the credentials, the key's scopes or sign in again
conflict, already_*, app_not_configured, provider_soon, provider_disabledReload; the state already changed, or the platform is not ready on this server
rate_limited, code_cooldown, busy, unavailable, idempotency_in_progressWait (Retry-After when present) and retry
plan_limit_exceeded, x_posts_exhaustedDon't retry: the plan, the month or the bought X posts have to change
plan_expiredDon't retry: the workspace is read-only until a plan is paid for
not_available, *_disabledDon't retry: the feature is off on this server
code_expired, link_expired, link_used, link_revoked, invitation_expired, state_expired, passkey_expiredStart the step again (a new code, link, QR code or invitation)
internal, bad_gateway, timeout, payment_provider_error, ai_provider_error, mail_failed, google_unavailableRetry later; report it with the request_id

The full list is below.

Keep the request_id when reporting a problem

Error responses also carry a top-level request_id (the same value as the X-Request-Id header) and, when the request sent a W3C traceparent header, its trace_id. They find the matching log lines — see Logs & tracing.

{
  "error": { "code": "not_found", "message": "post not found" },
  "request_id": "api-1/abc-000042",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}

Error shape

FieldMeaning
error.codeStable, machine-readable; switch on this, not on message
error.messageHuman-readable explanation
error.fieldsPer-field problems (validation errors)
error.detailsStructured data for some errors (plan limits)
request_idSame as the X-Request-Id response header
trace_idPresent when the request sent a traceparent header

Error codes

Grouped by what they are about. Every code keeps its HTTP status.

Requests and credentials

StatuscodeWhen
400bad_requestMalformed JSON or unknown fields in the body
401unauthorizedMissing, invalid, expired or revoked credentials (Staqly Community Edition: the access token is missing or out of date)
403forbiddenThe key lacks a scope ("missing required scope posts:publish"), the person's role doesn't allow it, or the operation isn't available to this caller (device links and the recovery key need a dashboard session)
403csrf_failedA cookie-authenticated unsafe request without a valid X-CSRF-Token
404not_foundUnknown ID (or one in another workspace)
409conflictState conflict, see below
409already_active, already_inactive, already_enabled, already_disabled, already_revokedYou asked for the state it already has; nothing changed
409idempotency_in_progressA request with the same Idempotency-Key is still running
413too_largeBody or file over the limit (Limits)
415unsupported_media_typeUpload type not allowed (e.g. SVG, HTML), or a request content type other than JSON / multipart
422validation_failedInvalid input; see fields
422idempotency_mismatchIdempotency-Key reused with a different request
429rate_limitedToo many requests (rate limits); Retry-After says how many seconds to wait
500internalUnexpected server error (logged server-side with the request_id)
502bad_gatewayThe dashboard's /api proxy can't reach the API
503unavailableA listing ran longer than 5 seconds; retry, or add filters
503busyThe database was locked by another process (e.g. a backup); retry after Retry-After (1 s)

Posts and targets

StatuscodeWhen
409conflictThe post is published or publishing, or two edits raced; see below
409post_changedThe post changed since your expected_version (post was modified (version 3, expected 2)): reload it and apply your edit again
409provider_disabledA new post or a new target on a channel whose platform is turned off on this server. Targets the post already had keep working
422validation_failedA target over its platform's limits is keyed targets.<channel_id>.content or targets.<channel_id>.settings.<key>; see below

A target that fails while publishing doesn't answer an error code: the post's target turns failed with the platform's message in its error (see Publishing errors).

Channels and platforms

StatuscodeWhen
409provider_soonThe platform is in the catalog but not available in this build yet, or an OAuth platform has no app keys on this server
409provider_disabledThe platform is turned off on this server (PROVIDER_<ID>_ENABLED=false)
409app_not_configuredAn OAuth sign-in started for a platform without a developer app; see Developer apps
410state_expiredThe platform's sign-in took too long; start again
410link_revokedA connect link was turned off; ask for a new one
409default_groupDeleting the Default channel group, which every workspace keeps
422connect_failedThe platform refused the credentials (the platform did not accept these details: ...)

Accounts and sign-in (cloud)

StatuscodeWhen
401code_invalidA wrong email code; details.attempts_left says how many tries remain
401google_failed, google_cancelled, login_expired, login_state_mismatchThe Google sign-in failed, was cancelled, took over 10 minutes or came back to another browser; start it again
401passkey_invalidThe passkey's answer could not be checked
401passkey_clonedThe passkey's sign counter went backwards, a sign it was copied; it is refused
401invalid_linkA device link (QR code) is not valid, or its person can no longer open the workspace
403sign_in_with_accountThe workspace belongs to an account: sign in with Google, an email code or a passkey, not with an API key or recovery key
403signup_closedSign-up is closed on this server: join by invitation
403email_unverifiedThe Google account has no verified email address
403invitation_email_mismatchThe invitation is for another address than the one you signed in with
409email_existsThe person already has an email address: change it instead of adding one
409identity_takenThat Google account or address already signs in someone else
409change_not_verifiedChanging the address before confirming the current one (or after its 10 minutes)
409last_sign_in_methodRemoving the only way left to sign in
409passkey_existsThis passkey is already added
409already_member, already_invitedThe address is already a member, or has an open invitation (use Resend)
409invitation_closedThe invitation was already accepted or revoked
409last_ownerA workspace needs at least one owner
409account_ownerThe account owner is always an owner of its workspaces
409accounts_disabled, google_disabled, email_sign_in_disabled, passkeys_disabledThat sign-in, or accounts at all, aren't available on this server (Accounts)
409sign_in_with_accountPOST /v1/workspaces on a server with accounts: new workspaces start on the sign-in page
409conflictpeople in an account recover access with their sign-in methods: a recovery key exists only for workspaces without an account
410code_expiredThe code expired, was used, was replaced by a newer one or had too many wrong tries
410invitation_expired, invitation_invalidAsk for a new invitation
410link_used / link_expiredA device link was already used / is older than 5 minutes
410passkey_expiredThe passkey prompt is older than 5 minutes
429code_cooldownA code was sent less than 30 seconds ago; details.resend_in says when to ask again
503mail_failedThe code could not be mailed
503google_unavailableGoogle could not be reached

Plans and billing

StatuscodeWhen
402plan_limit_exceededOver a cloud plan limit, see below
402plan_expiredThe Starter trial or a paid period ended unpaid: the workspace is read-only (reads and exports still work) until a plan is paid for; see Plans
402x_posts_exhaustedPublishing to X on a cloud plan whose included and bought X posts are used up (Your X posts for this period are used up...); details has needed, left and period_end. A scheduled post fails its X target with the same reason. See Plans
409not_availableBilling is not enabled on this server (/v1/billing*), or plans were asked for on the Community Edition
409conflictA checkout for a workspace that already has a subscription, or the billing portal before any checkout
410goneA workspace export or backup is no longer available (after 24 hours)
502payment_provider_errorStripe rejected a checkout or portal request; retry later, the operator sees the reason in the logs
502ai_provider_errorOpenAI did not answer a media editor AI request, or sent no picture; retry
409ai_key_rejectedOpenAI no longer accepts the workspace's AI key; an admin replaces it under Integrations
504timeoutOpenAI took too long on a media editor AI request; try again or ask for a smaller picture

Common errors in detail

402 plan_limit_exceeded

Cloud workspaces are limited by their plan. The error tells you which limit was hit:

{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "The Starter plan allows 5 channels; upgrade the workspace plan to continue.",
    "details": { "limit": "channels", "max": 5, "used": 5, "plan": "starter" }
  }
}
details.limitTriggered by
posts_per_monthCreating a post (people, agents, duplicates, posts a schedule fetched from your endpoint)
channelsConnecting a channel
alertsCreating an alert
webhooksCreating a webhook
api_keysCreating an API key
storage_bytesUploading a file
max_upload_bytesUploading a file larger than the plan allows
data_exportAn activity log export (GET /v1/events/export) on a plan without data export (no max / used); the workspace export works on every plan, data only on Starter

Don't retry a 402 automatically — it won't succeed until the plan changes or the month rolls over.

401 unauthorized in the Community Edition

"local mode needs the access token": send Authorization: Bearer {ACCESS_TOKEN}; "invalid access token": the token was rotated, print the current one with staqly serve token (Docker: docker exec staqly server token). See Authentication.

409 conflict

MessageMeaning
this post is published; duplicate it to post it againPublished posts are read-only
this post is being published right nowWait for the publish to settle
only a scheduled post can be taken off the scheduleCancel applies to scheduled posts
only a failed target can be retriedThe target is pending, publishing or published
this target is not waiting for a result (it is published)POST .../targets/{channel}/result for a target that is not publishing
only targets on your own automation take reported resultsThe target's channel is not provider custom
a channel this post is already published on cannot be removedA PATCH to targets dropped a published target
post was modified (version 3, expected 2), post was modified concurrentlyYour expected_version is stale, or two edits raced; reload and retry
this channel needs reconnecting: send its credential fields again or go through its sign-in pageEnabling a channel in needs_reconnect
this channel's provider is not part of this buildTesting or changing a channel whose platform this server no longer has
only a failed job can be retried, only a queued job can be cancelledJob state
collection is already archived, collection is not archivedYou asked for the state it already has (see below)
the Default collection cannot be archived / deletedEvery workspace keeps its Default collection
a collection with this name already existsCollection names are unique per workspace
collection is archivedArchived collections are read-only

A state that is already set can't be set again. Archiving an archived collection, unarchiving an active one, enabling an enabled webhook or alert (or disabling a disabled one), revoking a revoked API key or session, reporting a result a target already has, retrying a job that is not failed, cancelling a job that is not queued, and choosing the plan the workspace is already on all answer 409 and change nothing: no new version, no event, no webhook. A PATCH that changes other fields as well is accepted; the unchanged state field is then ignored.

422 on posts and targets

Message (field)Meaning
must be in the future / must be within a year (publish_at)The time is in the past or too far out
a scheduled post needs a time (publish_at)status: scheduled without publish_at
add at least one channel before scheduling / before publishing (targets)Add a channel first
channel ch_... is listed twice, unknown channel, this channel is turned off (targets, targets.<channel_id>)Fix the target list
this channel's provider is not part of this build (targets.<channel_id>)The server no longer has the channel's platform
at most 280 characters for this channel (312) (targets.<channel_id>.content)The text (the target's own, else the post's) is over the provider's limit
a missing or wrong provider setting (targets.<channel_id>.settings.<key>)See the provider's settings_fields
unknown media id, the same file is listed twice, at most N files (media)Fix the media list
avatar.png (med_...) is in Deleted: restore it to use it again (media)A file in Deleted can't be added to a post, and a post that carries one can't be scheduled or published (publish now, a retry, a collection schedule's run): restore it (POST /v1/media/{id}/restore) or take it out
set times (HH:MM) with optional weekdays and tz, or every_minutes (5 to 10080) (rule)A recurring schedule's rule is incomplete
unknown event pattern post.shipped; ...A webhook or alert subscribed to an event that doesn't exist

GET /v1/posts/{id}/preview reports the same problems as warnings per target without failing.

Publishing errors

Publishing happens later, in a job, so its failures don't come back as an HTTP error: the target turns failed, its error says why, post.target_failed is emitted (and post.failed once every target settled with at least one failed) and an alert can tell you. Staqly sorts platform answers into three kinds:

KindExampleWhat happens
Try againthe platform is down, its rate limit, a network errorRetried after about 1, 5 and 15 minutes; then failed with a Retry
Refusedduplicate text, a link or media the platform doesn't allow, a suspended accountFailed at once with the platform's reason: retrying the same post would not help, so change it first
Sign-inthe platform no longer accepts the accountFailed, and the channel becomes Needs reconnect

Messages staqly itself writes on a failed target:

errorMeaning
channel is turned offThe channel was disabled before its time came
channel needs reconnectingReconnect the channel, then Retry the target
channel was removedThe channel was deleted while the post waited
No result reported in timeYour own automation did not report within the collection's processing timeout
provider not available in this buildThe server no longer has the channel's platform
stored credentials cannot be readThe server's secret key changed; reconnect the channel (see Security)

Each platform's page lists what it refuses, e.g. X.

Next steps

On this page