Skip to main content
Every Postbreeze endpoint returns the same JSON envelope on failure. Branch your client code on 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

  • code and statusCode are stable. We will not change them without a major version bump. Add new codes to your switch as we ship new features; existing codes will keep their meaning.
  • message is not stable. Copy may be tightened, translated, or re-phrased between releases. Show it; never === it.
  • Extra fields are additive. A PLAN_LIMIT_POSTS error today carries limit + current; future versions may add resetAt. 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 on statusCode 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 which code 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:
  1. 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 same clientReferenceId returns the original post instead of creating a duplicate.
  2. 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.
For cancel and update, the operation is naturally idempotent — the second call is a no-op on already-cancelled / unchanged data.

Webhooks

Postbreeze sends webhooks for post.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 DEAD and surfaces in Settings → Webhooks → Deliveries. No further attempts.
  • Signature: every request carries an X-Postbreeze-Signature header — 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 on message. 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 carry limit and current — 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 clientReferenceId for 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/v2 before we break anything in v1.