# Errors (/api/errors)



Every failure returns a JSON body with the same shape, so a client can branch on one field rather than parsing prose.

## Error body

```json
{
  "code": "INSUFFICIENT_SCOPE",
  "message": "This credential is missing the bill:read scope."
}
```

| Field     | Type   | Notes                                                                                                           |
| --------- | ------ | --------------------------------------------------------------------------------------------------------------- |
| `code`    | string | A stable machine-readable identifier. Branch on this, never on `message`.                                       |
| `message` | string | Human-readable explanation. Wording may change at any time.                                                     |
| `details` | array  | Present on validation failures only. Each entry is `{ "path": …, "error": … }`, naming the offending parameter. |

`code` is drawn from a closed set:

| `code`               | Meaning                                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `VALIDATION`         | A parameter was present but unacceptable — an unknown enum value, an out-of-range `limit`, two mutually exclusive window parameters. |
| `MALFORMED_REQUEST`  | The request could not be parsed — bad JSON in a `POST` body, or a query string the server cannot read.                               |
| `UNAUTHENTICATED`    | No credential, or one that is expired or invalid.                                                                                    |
| `INSUFFICIENT_SCOPE` | The credential is valid but lacks a required [scope](/api/credentials#scopes).                                                       |
| `NOT_FOUND`          | No such resource *within the orgs you can reach*.                                                                                    |

## Status codes

| Status | Typical `code`       | What to do                                                                                        |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------- |
| `400`  | `MALFORMED_REQUEST`  | Fix the request itself. Retrying unchanged will not help.                                         |
| `401`  | `UNAUTHENTICATED`    | Supply a credential, or refresh an expired session. See [Authentication](/api/credentials).       |
| `403`  | `INSUFFICIENT_SCOPE` | Reissue the credential with the scope the endpoint names.                                         |
| `404`  | `NOT_FOUND`          | Check the id — but note it may exist in an org you cannot reach.                                  |
| `422`  | `VALIDATION`         | Read `details` for the offending parameter and correct it.                                        |
| `500`  | —                    | A control-plane fault. Safe to retry with backoff; every endpoint in this reference is read-only. |

<Callout type="info">
  A `404` is deliberately indistinguishable from "exists, but not yours". The API will not confirm that an id exists in an org you cannot see, so treat `404` as "not visible to this credential" rather than "does not exist".
</Callout>

## Retrying

Every endpoint documented here is a read, so retries are safe and cannot double-charge or mutate state. Retry `500` and `429` with exponential backoff; do not retry `400`, `401`, `403`, `404`, or `422` without changing the request, since the outcome is deterministic.

If you are rate limited the control plane returns `429`. Back off before retrying.

## Exit codes in the CLI

The `sling` CLI maps these responses onto a fixed exit-code table, so a script or agent can branch on `$?` without parsing output:

| Exit | Meaning                                                     | From                  |
| ---- | ----------------------------------------------------------- | --------------------- |
| `0`  | Success                                                     | `2xx`                 |
| `1`  | Unexpected CLI crash                                        | never an API outcome  |
| `2`  | Usage error — bad flags, conflicting parameters             | client-side, or `422` |
| `3`  | Not found                                                   | `404`                 |
| `4`  | Auth — missing, expired, or under-scoped credential         | `401`, `403`          |
| `5`  | Control-plane or transport failure                          | `5xx`                 |
| `6`  | Partial — telemetry incomplete, result still returned       | `200` with gaps       |
| `7`  | Rate limited                                                | `429`                 |
| `10` | Remote outcome failed — e.g. a run concluded unsuccessfully | `200`                 |

Note that `10` is not an error: the request succeeded and the *result* was a failure. It is what lets `sling runs show <id> --wait` gate a pipeline on a run's conclusion.
