Status code matrix
| Status | Meaning | When you'll see it |
|---|---|---|
| 200 OK | Success | Standard successful GET. |
| 201 Created | Resource created | Returned by create-ticket and add-message. |
| 204 No Content | Success, empty body | Reserved for future delete-style endpoints. |
| 400 Bad Request | Invalid input or malformed JSON | Used today for both invalid JSON and Zod validation failures on the request body. |
| 401 Unauthorized | Missing, malformed, or revoked API key | Authorization header missing, not a Bearer token, or the key has been revoked. |
| 403 Forbidden | Authenticated but not allowed | Reserved for future per-key scope checks. |
| 404 Not Found | Resource does not exist in this workspace | Returned when a ticket id is unknown or belongs to another workspace. |
| 409 Conflict | Request collides with the resource’s state | Returned when posting a message to a ticket that has been merged. |
| 422 Unprocessable Entity | Validation error | Planned. Today validation failures use 400 — see below. |
| 429 Too Many Requests | Rate limit exceeded | 60 requests per minute per API key. Includes a Retry-After header. |
| 500 Internal Server Error | Unexpected server failure | Safe to retry with backoff. |
Current error response shape (live)
Today every error response is a small JSON object with a single error string. Validation failures additionally include a details object derived from the request schema.
// 401
{ "error": "Unauthorized" }
// 404
{ "error": "Not found" }
// 409 (posting a message to a merged ticket)
{ "error": "Ticket has been merged" }
// 400 (Zod validation on create-ticket)
{
"error": "Invalid input",
"details": {
"fieldErrors": {
"customer_email": ["Invalid email"]
},
"formErrors": []
}
}
// 429
{ "error": "Rate limit exceeded", "limit": 60, "window_seconds": 60 }Planned error envelope
A future revision is likely to wrap errors in a richer envelope with a stable machine-readable code. Don't depend on this yet — but write your client so it can adopt the new shape without re-plumbing.
{
"error": {
"code": "validation_error",
"message": "The customer_email field is required.",
"details": {
"customer_email": "Required"
}
}
}Client guidance
- Branch on status codes, not on the exact
errormessage. Wording can change. - Retry 5xx with exponential backoff (e.g. 1s, 2s, 4s, 8s, capped at 60s) and a small jitter.
- Don't retry 400, 401, or 403 without fixing the request — the same call will keep failing.
- For 404, check that the resource id belongs to the workspace whose key you're using.
- For 409, refresh the resource (e.g. follow the merged ticket) and retry against the new state.
- For 429, honour the
Retry-Afterheader. See rate limits.