NoteBugsDocs

Integration

Error handling

One shape of refusal, and what to do with each family of it.

The shape of a refusal

Every refusal, from any route and under any code, returns the same object:

{
  "error": "Card não encontrado.",
  "details": null
}
  • `error` is a sentence in Portuguese, written for whoever is on screen. Its wording can change without notice: do not build logic on the text.
  • `details` is only filled on schema refusals (422), and carries zod's fieldErrors and formErrors.
  • The HTTP code is the contract. That is what your integration should branch on.

The schema refusal, from the inside

{
  "error": "Dados inválidos.",
  "details": {
    "formErrors": ["Nada para atualizar."],
    "fieldErrors": {
      "name": ["Campo obrigatório."],
      "confirm": ["Envie { \"confirm\": \"APAGAR TENANT\" } para confirmar."]
    }
  }
}

fieldErrors is per body field; formErrors holds what belongs to no field, such as “nothing to update”, which is about the whole object.

The families, and what to do with each

SituationCodeWhat to do
Credential missing or expired401make a new token, or sign in again
Insufficient role403do not retry: the account does not reach it
Workspace out of reach403ask an admin for access
Resource does not exist404re-check the id; it may have been deleted
Duplicate name409pick another name within that workspace
Incompatible state409read the state before retrying (backup running, .zip not ready)
Invalid body422fix it from details
Business rule422read error: it names the rule
Rate limit429wait what Retry-After says
Bucket failure502check the object service's credential and connectivity

422 has two origins, and details tells them apart

With details, it was the schema: the body is malformed. Without details, it was a business rule checked inside the transaction: an epic from another project, a locked epic, a column outside the set.

What is safe to retry

  • 429 is the only refusal that asks for a retry, and Retry-After says when.
  • Every write is transactional: a refused request left no half effect behind. Retrying after fixing the body is safe.
  • Creation is not idempotent. Repeating a POST /api/cards that already worked creates a second card, with a new number.