Errors

Error Handling

What HTTP status codes mean for the Tixallo API, the current error response shape, and how to react to each class of failure.

Status code matrix

StatusMeaningWhen you'll see it
200 OKSuccessStandard successful GET.
201 CreatedResource createdReturned by create-ticket and add-message.
204 No ContentSuccess, empty bodyReserved for future delete-style endpoints.
400 Bad RequestInvalid input or malformed JSONUsed today for both invalid JSON and Zod validation failures on the request body.
401 UnauthorizedMissing, malformed, or revoked API keyAuthorization header missing, not a Bearer token, or the key has been revoked.
403 ForbiddenAuthenticated but not allowedReserved for future per-key scope checks.
404 Not FoundResource does not exist in this workspaceReturned when a ticket id is unknown or belongs to another workspace.
409 ConflictRequest collides with the resource’s stateReturned when posting a message to a ticket that has been merged.
422 Unprocessable EntityValidation errorPlanned. Today validation failures use 400 — see below.
429 Too Many RequestsRate limit exceeded60 requests per minute per API key. Includes a Retry-After header.
500 Internal Server ErrorUnexpected server failureSafe 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 error message. 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-After header. See rate limits.