# Errors
URL: https://docs.fluenta.io/en/docs/errors

The shape of every error response and each code the API can answer: its HTTP status, when it appears and what to do.



Every error response has the same shape:

```json
{
  "statusCode": 404,
  "code": "ORGANIZATION_NOT_FOUND",
  "message": "Organización no encontrada"
}
```

* **Branch on `code`.** A code keeps its name once published.
* **`message` is in Spanish and is for the person.** Show it as it comes; do
  not parse it in your program.
* Some codes add fields that name what failed: `scope`, `feature`, `roles`,
  `field`, `maxDays`, `supported`, `reason`, `retryAfterSeconds`, `details`.
  The tables say which.
* A refused write changed nothing.

## Requests that fail validation [#requests-that-fail-validation]

A query or a body that fails its validation answers 400 `INVALID_REQUEST`
with `details`: one row per failure.

```bash
curl 'https://api.fluenta.io/v1/external/me/usage?days=0' \
  -H 'Authorization: Bearer fluenta_live_xxxxxxxxxxxxxxxx'
```

```json
{
  "statusCode": 400,
  "code": "INVALID_REQUEST",
  "details": [{ "field": "days", "message": "days must not be less than 1" }],
  "message": "La solicitud no es válida: revisa «days»."
}
```

`field` is the query parameter or the body field, as a path (`page`,
`items.0.name`). It is `null` when the failure names none. Each row's
`message` comes as the validation worded it.

## 400: the request [#400-the-request]

| Code                      | When                                                                                                                              | What to do                                                                                    |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `INVALID_REQUEST`         | The query or the body fails its validation. Carries `details`.                                                                    | Fix the fields `details` names. Do not retry the request unchanged.                           |
| `INVALID_DAY`             | `day`, `from` or `to` is not a real `YYYY-MM-DD` day. `field` says which.                                                         | Send a real day, on the plant's clock.                                                        |
| `RANGE_INVALID`           | `from` is after `to`.                                                                                                             | Swap the dates.                                                                               |
| `RANGE_TOO_LARGE`         | The range covers more days than `maxDays`: 92, or 31 in `getPauses`.                                                              | Ask for a shorter range, or split it into several.                                            |
| `GROUP_BY_UNSUPPORTED`    | The figure is not grouped by that `groupBy`. `supported` lists the ones it has.                                                   | Use one from `supported`.                                                                     |
| `FILTER_UNSUPPORTED`      | A filter (`moduleId`, `operatorId`, `operationId`) the figure does not have under that grouping, or more than one filter at once. | Send one filter and a grouping from `supported`. If `supported` comes empty, drop the filter. |
| `OPERATION_REQUIRED`      | `getStationActivity` with `view=events` and no `operationId`.                                                                     | Add the `operationId` of the «puesto» (station).                                              |
| `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is not 1 to 255 visible ASCII characters, without spaces.                                            | Send a valid key.                                                                             |

## 401: the credential [#401-the-credential]

| Code                | When                                                                                                                                               | What to do                                                                                                               |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `INVALID_API_TOKEN` | The header is missing, or the credential is not one of this API, is unknown, expired or revoked, or its user is inactive. One body for every case. | Do not retry. Create a new token in «Configuración» (Settings) → «Tokens de API» (API tokens), or connect the app again. |

## 402: the subscription [#402-the-subscription]

| Code          | When                                       | What to do                                                                                                                       |
| ------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `ORG_BLOCKED` | The organization's subscription is paused. | Do not retry: the organization answers again once its subscription is no longer paused. Your other organizations are unaffected. |

## 403: the permission, the plan or the role [#403-the-permission-the-plan-or-the-role]

| Code                 | When                                                                                                                    | What to do                                                                                                                                                          |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INSUFFICIENT_SCOPE` | The credential lacks the permission the route asks for. `scope` says which.                                             | A token never gains a permission: create a new one that has it. A connected app connects again with the permission ticked. See [Permissions](/en/docs/permissions). |
| `PLAN_LIMIT`         | The organization's plan does not include what you asked for. `feature` names the plan feature that is missing.          | Do not retry. `getPlan` lists what the plan allows. See [Plans](/en/docs/plans).                                                                                    |
| `ROLE_NOT_ALLOWED`   | On a write: your role in that organization does not make that change in the dashboard. `roles` lists the roles that do. | A permission never widens a role. Ask someone with one of those roles to make the change.                                                                           |

## 404: something that does not exist or is out of your reach [#404-something-that-does-not-exist-or-is-out-of-your-reach]

A row of another organization and a row that does not exist answer the same.
The API does not say which of the two it is.

| Code                        | When                                                                              | What to do                                                     |
| --------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `ORGANIZATION_NOT_FOUND`    | The organization does not exist, or your user does not belong to it. Never a 403. | Take the `organizationId` from `getMe` or `listOrganizations`. |
| `MODULE_NOT_FOUND`          | The «módulo» (production line) is not in that organization.                       | Take the id from `listModules` or `getFloor`.                  |
| `OPERATION_NOT_FOUND`       | The «puesto» (`stationId` or `operationId`) is not in that organization.          | Take the id from `listStations` or `listOrganizationStations`. |
| `OPERATOR_NOT_FOUND`        | The «operario» (operator) is not in that organization.                            | Take the id from `listOperators`.                              |
| `REFERENCE_NOT_FOUND`       | The «referencia» (an order lot) is not in that organization.                      | Find it with `searchReferences`, `getProgram` or `listOnHold`. |
| `CATALOGUE_ENTRY_NOT_FOUND` | That entry is not in the organization's catalogue.                                | Take the id from `listCatalogue`.                              |

## 409: still running, or already changed [#409-still-running-or-already-changed]

| Code                      | When                                                                                                                                     | What to do                                                           |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `IDEMPOTENCY_IN_PROGRESS` | The same `Idempotency-Key` is still being answered. Carries `Retry-After`.                                                               | Wait that many seconds and retry the same request with the same key. |
| `PREVIEW_STALE`           | On a delete: the confirmation ran out (it lasts 15 minutes), or what the preview showed has changed. `reason` is `expired` or `changed`. | Ask for the preview again and show it to the person.                 |

## 422: a write that cannot be done as sent [#422-a-write-that-cannot-be-done-as-sent]

| Code                     | When                                                                                                                                | What to do                                                                                                                                 |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `CONFIRMATION_REQUIRED`  | A delete without the `confirmationToken` of its preview, or with one that is not this delete's. `reason` is `missing` or `invalid`. | Call `previewDeleteReference` or `previewDeleteCatalogueEntry`, show the answer to the person, and send the `confirmationToken` unchanged. |
| `IDEMPOTENCY_KEY_REUSED` | That `Idempotency-Key` already answered another request.                                                                            | Use a new key for each distinct write.                                                                                                     |

## 429: the rate limit [#429-the-rate-limit]

| Code           | When                                                                                                                    | What to do                                                                 |
| -------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `RATE_LIMITED` | The credential spent a budget of its rate limit. Carries the `Retry-After` header, and `retryAfterSeconds` in the body. | Wait that many seconds and retry. See [Rate limits](/en/docs/rate-limits). |

## 503: the query took too long [#503-the-query-took-too-long]

| Code                | When                                                                                 | What to do               |
| ------------------- | ------------------------------------------------------------------------------------ | ------------------------ |
| `ANALYTICS_TIMEOUT` | A range of `getEfficiency`, `getProduction` or `getBilling` ran past its time limit. | Ask for a shorter range. |

## What to retry [#what-to-retry]

* **Yes:** a 429, after `Retry-After`. A 409 `IDEMPOTENCY_IN_PROGRESS`, with
  the same key.
* **Yes, with care:** a write that ended in a timeout or a 5xx may have run or
  not. If you sent it with an `Idempotency-Key`, retry that same request with
  the same key: the API returns the first answer, with
  `Idempotency-Replayed: true`, and does not write twice. That is why it pays
  to send a fresh key with every write.
* **No:** any other 4xx. Repeating the same request answers the same.

A write can also answer a refusal of the dashboard's own rules, with a `code`
of its own that is not in these tables. Handle it by its HTTP status and show
its `message`.
