code and statusCode — never on the
human-readable message, which can change between releases.
Response shape
The envelope is identical across
/api/v1/*, dashboard endpoints, and
MCP-tool responses. There is no separate “user-facing” vs “API” shape.
Stability contract
codeandstatusCodeare stable. We will not change them without a major version bump. Add new codes to yourswitchas we ship new features; existing codes will keep their meaning.messageis not stable. Copy may be tightened, translated, or re-phrased between releases. Show it; never===it.- Extra fields are additive. A
PLAN_LIMIT_POSTSerror today carrieslimit+current; future versions may addresetAt. Treat the envelope as open-shape.
HTTP status codes
Error codes
The table below covers every domain code currently emitted by the API. Codes are grouped by area; the HTTP column tells you which status they ride on so you can short-circuit onstatusCode first when that’s
all you need.
Authentication & access
Plan limits & billing
Posts & publishing
Media
Generic & infrastructure
Handling errors in code
Retries
Network calls fail. The table below tells you whichcode values are
safe to retry verbatim, which need a delay, and which require user
action before they can succeed.
Rate-limit windows are 60 req/min (burst) and 1000 req/15min
(sustained) — see Rate limits. The
429
response always includes Retry-After.
A drop-in retry helper:
Idempotency
POST /api/v1/posts and the other create endpoints are not
automatically idempotent. A network blip mid-request can leave you
unsure whether the post landed.
Two recommended defenses:
-
Use
clientReferenceId. Pass a unique string (UUID, your own primary key) when creating a post. We dedupe within a 24-hour window — a retry with the sameclientReferenceIdreturns the original post instead of creating a duplicate. -
Search before retrying. If a retry feels risky, query
GET /posts?workspaceId=…&scheduledAfter=…before the second attempt and check whether the first one already wrote the row.
cancel and update, the operation is naturally idempotent — the
second call is a no-op on already-cancelled / unchanged data.
Webhooks
Postbreeze sends webhooks forpost.published, post.failed,
account.token_expired, and comment.received. All delivery is
at-least-once — a single event may be delivered more than once if
your endpoint times out, returns a 5xx, or we don’t receive a 2xx
within 10 seconds.
- Dedupe on
event.id(a ULID). Store the last N event IDs you’ve processed and short-circuit duplicates. - Return 2xx fast. Acknowledge first, process async. Anything over 5 seconds eats into the next retry’s window.
- We back off at 30s, 2min, 10min, 1hr, 6hr, 24hr. After six
failures, the delivery is marked
DEADand surfaces in Settings → Webhooks → Deliveries. No further attempts. - Signature: every request carries an
X-Postbreeze-Signatureheader —HMAC-SHA256(secret, rawBody)in hex. Verify before trusting the body.
Account health
account.token_expired fires when a connected social account’s OAuth
token can no longer be refreshed (revoked, password changed, scope
removed). The account stays in the workspace but SocialAccount.status
flips to TOKEN_EXPIRED and publishing to that account begins to fail
with 503 PLATFORM_AUTH_FAILED.
Recover by reconnecting the account from the dashboard. Scheduled posts
targeting it will retry automatically once the row returns to ACTIVE.
Best practices
- Branch on
code, never onmessage. Message text rotates; codes don’t. - Always log
requestId. It’s the only correlation key we have between your client and our server logs. Surface it in your own error reports. - Show plan-limit errors with context.
PLAN_LIMIT_*errors carrylimitandcurrent— surface “97/100 posts used this month” instead of a generic “quota reached.” Most users self-serve upgrade when shown the number. - Don’t retry 4xx. Anything in the 400-range is your request being wrong, not our server. Retrying wastes quota and slows your user down.
- Use
clientReferenceIdfor create calls. It’s the single cheapest way to make your integration idempotent. - Pin to a major version. All endpoints live under
/api/v1. We will introduce/api/v2before we break anything inv1.