Status codes you'll see
| Code | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success | Consume the body |
| 202 | Accepted — queued for async processing | Poll the resource or wait for a webhook |
| 400 | Validation failed | Inspect `issues` array — bad input |
| 401 | No / invalid auth | Re-authenticate or refresh token |
| 402 | PLAN_LIMIT — plan gate tripped | Show upgrade nudge, see body.capability |
| 403 | Forbidden — RBAC failure | User lacks the required role |
| 404 | Not found in this workspace | Cross-workspace queries don't traverse tenants |
| 409 | Conflict — duplicate / wrong state | Inspect message; try GET first |
| 422 | Semantically invalid (e.g. workflow graph) | Inspect message; fix and retry |
| 429 | Rate limit | Back off; default limit is 100 req/min/IP |
| 500 / 502 / 503 | Platform / provider issue | Retry with back-off; check status page |
PLAN_LIMIT shape
HTTP 402
{
"message": "Plan Starter does not include the VOICE channel. Upgrade to unlock it.",
"code": "PLAN_LIMIT",
"planId": "STARTER",
"capability": "channel.VOICE",
"limit": null,
"current": null
}capability tells the UI which limit was hit so it can deep-link to the right upgrade nudge. limit and current are populated for numeric caps (activeWorkflows, teammates, etc.) so the UI can render "8 of 10 used."
Validation errors (400)
{
"message": "Validation failed",
"issues": [
{ "path": ["name"], "message": "String must be at least 1 character" }
]
}