Skip to content
ShaireDevelopers

Errors

One shape, seven statuses, and one of them carries more than a message.

The shape

Every error is JSON with a status and a message:

{
  "statusCode": 403,
  "message": "You do not have permission to edit this list."
}

Messages are translated against the request’s Accept-Language header, so do not match on their text. Branch on statusCode.

Validation carries more

A 400 adds an issues array naming the fields that failed and why:

{
  "statusCode": 400,
  "message": "Validation failed",
  "issues": [
    { "path": "title", "message": "String must contain at least 1 character(s)" },
    { "path": "assigneeIds.0", "message": "Invalid uuid" }
  ]
}

path is dotted, and array indices appear as segments. The issue text stays in English while the message beside it is translated, which is deliberate: the issues are for whoever is writing the client, and the message is for whoever is reading the screen.

Every status

Status Means
400 The body or the query did not validate. Read issues.
401 No credential, or one that is unknown, revoked or expired.
402 The workspace is over its plan’s seat count. See Permissions.
403 The token lacks the scope, or its owner lacks the permission.
404 No such object, or one you cannot see.
409 The write conflicts with the current state, such as a workflow transition that is not allowed.
429 Too many requests. See Rate limits.

Two of these are worth reading twice.

403 has two independent causes and answers the same way for both. The scope and the permission are asked separately, either can refuse, and the response does not say which did. Check GET /api/access/me against the scopes on your token before assuming it is the permission.

404 also covers objects that exist and are not yours to see. An API that answered 403 for those would confirm that a given identifier is real, which is a way of enumerating a workspace you have no access to.

The table above applies to every endpoint. Each page in the reference repeats 401 and 403 beside the route’s success response, and the rest come from a validation pipe, a guard and an exception filter that sit in front of every route, so they are true everywhere and worth handling once in a shared client rather than per call.