# Errors

> Errors arrive as application/problem+json (RFC 9457) with the real HTTP status and a stable code to program against.

Page: https://safeonuba.com/en/developers/errors · SafeOnuba API v1 (`api_version: "2026-09-24"`), in preview: the contract may change.

Errors arrive with the **real HTTP status** and a body in `application/problem+json`, the format of [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457). The exception is the token endpoint, which follows the OAuth2 format (see [Token errors](https://safeonuba.com/en/developers/authentication#token-errors)).

## The body

| Field | What it is |
| --- | --- |
| `type` | URI that identifies the type of problem. |
| `title` | Human-readable summary. |
| `status` | The HTTP status, repeated. |
| `detail` | Explanation of this specific case. |
| `instance` | The path that failed. |
| `code` | **Stable error code, to program against.** The text of `title` and `detail` may change; `code` won’t. |

```json
{
  "type": "https://api.safeonuba.com/problems/insufficient-scope",
  "title": "Falta un permiso del token",
  "status": 403,
  "detail": "Esta petición necesita el scope alerts:write.",
  "instance": "/v1/alerts/0f6c2b7e-3a1d-4c55-9b0e-7d2f4a8c1e90",
  "code": "insufficient_scope"
}
```

Program against `status` and `code`, never against the text.

## Codes

| Code | When | What to do |
| --- | --- | --- |
| `401` | The token is missing, has expired or has been revoked. | Request a new token and retry once. |
| `403` | The token lacks the scope the route asks for, or the site isn’t your client’s. | Request the token with that scope; if your client doesn’t have it, ask for it. Don’t retry. |
| `404` | It doesn’t exist, or your identity can’t see it (another site, another contractor). | Don’t retry. |
| `409` | The transition isn’t possible: for example, acknowledging or closing an alert that is already closed. In the sandbox, also too many open scenarios. | Read the resource again: it’s probably already in the state you wanted. |
| `422` | The request doesn’t meet the contract: a missing field, a value out of range, a webhook URL that isn’t `https`. | Fix the request. `detail` says what fails. |
| `429` | You’ve gone over a rate limit. | Wait as long as `Retry-After` says (see [Rate limits](https://safeonuba.com/en/developers/rate-limits)). |

> **A 404 doesn’t always mean “it doesn’t exist”**
>
> The API sees the same as a dashboard user with those permissions. An alert from another site, or from a contractor your identity doesn’t cover, responds `404` just like one that doesn’t exist. It doesn’t reveal that it exists.

## To retry or not

- `401`: yes, once, with a new token.
- `429`: yes, after `Retry-After`.
- Network errors and `5xx`: yes, with increasing waits. Reads can be repeated without any problem. Before repeating a `PUT /v1/alerts/{id}`, read the alert again: the first one may well have got through.
- `403`, `404`, `409` and `422`: no. Repeating the same request will give the same result.
