# Responses and errors

> The two response shapes of the up4 API: the data envelope on success and the error object on failure, with the status-code mapping and the request id to quote to support.

Source: https://upfour.io/docs/api/responses

A successful call returns `data`. A failed call returns `error`. You can tell them apart by the status code and by the one key at the top.

## Success

A 2xx response with a JSON body wraps its payload as `{ "data": … }`. A list puts an array in `data` and adds `pagination`; what that object means depends on the endpoint, see [pagination](https://upfour.io/docs/api/pagination.md).

```bash
curl -s https://app.upfour.io/v1/monitors/$MONITOR_ID \
  -H "Authorization: Bearer $UP4_API_KEY"
```

```json
{ "data": { "id": "…", "name": "Example", … } }

{
  "data": [ … ],
  "pagination": {
    "cursor": null,
    "next_cursor": null,
    "prev_cursor": null,
    "limit": 50,
    "has_more": false
  }
}
```

- The envelope applies to every call made with an API key. A response that already carries a top-level `data` or `error` is passed through unchanged.
- `204 No Content` has no body. File downloads such as report exports are not JSON and are not wrapped.
- New fields can appear in a response; the [changelog](https://upfour.io/docs/api/changelog.md) lists them as added. Ignore fields you do not know.

## Errors

Every 4xx and 5xx response uses one object, with `Content-Type: application/json`. Errors are not wrapped in `data`.

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "API token does not have the required scope",
    "request_id": "req_…",
    "details": { "required_scope": "monitor.write" }
  }
}
```

| Field | Meaning |
| --- | --- |
| `error.code` | A stable identifier in snake_case. Branch on this. |
| `error.message` | A sentence for people. It can change; do not match on it. |
| `error.request_id` | The id of this request, the same as the `X-Request-Id` response header. |
| `error.details` | Only present when there is more to say: `field` names the invalid field, `required_scope` the missing scope, `original_request_at` the first use of an idempotency key. |

Every code, with its status and cause, is in the [error catalogue](https://upfour.io/docs/api/errors.md).

## Status codes

A status without a more specific code gets the default code below. Many errors carry a specific code instead, such as `cursor_expired` or `idempotency_key_conflict`.

| Status | Default error.code | Usually means |
| --- | --- | --- |
| 400 | [bad_request](https://upfour.io/docs/api/errors#code-bad_request) | The request is malformed or a field is invalid. A field error adds details.field. |
| 401 | [unauthorized](https://upfour.io/docs/api/errors#code-unauthorized) | No valid key. See API keys and scopes. |
| 402 | [payment_required](https://upfour.io/docs/api/errors#code-payment_required) | The Company’s plan or billing state does not allow the action. |
| 403 | [permission_denied](https://upfour.io/docs/api/errors#code-permission_denied) | Refused. Scope errors use their own codes, see API keys and scopes. |
| 404 | [not_found](https://upfour.io/docs/api/errors#code-not_found) | The resource does not exist, or your key cannot see it. |
| 409 | [conflict](https://upfour.io/docs/api/errors#code-conflict) | The request conflicts with the current state. Idempotency conflicts use their own codes. |
| 429 | [rate_limit_exceeded](https://upfour.io/docs/api/errors#code-rate_limit_exceeded) | Too many requests. Wait for Retry-After. |
| 503 | [service_unavailable](https://upfour.io/docs/api/errors#code-service_unavailable) | A deliberate notice, for example a feature that is not available right now. The message is meant to be read. |
| other 5xx | [internal_error](https://upfour.io/docs/api/errors#code-internal_error) | Our fault. The message is always “Internal server error”; send us the request_id. |

Any other status falls back to `request_failed`.

## Request ids

Every response, success or failure, carries `X-Request-Id`. If you send the header, up4 uses your value; otherwise it generates `req_` followed by a UUID. Log it, and quote it when you contact support.

```bash
curl -si https://app.upfour.io/v1/me \
  -H "Authorization: Bearer $UP4_API_KEY" \
  -H "X-Request-Id: nightly-sync-2026-10-27" | grep -i '^x-request-id'
```

## Common mistakes

- Matching on `error.message`. Match on `error.code` and the status.
- Reading `data` on an error response. There is none.
- Expecting a body on `204`.
- Treating every `403` as “not allowed for good”. A missing scope or a read-only key can be fixed with a new key.
- Retrying a `5xx` or timed-out POST without an `Idempotency-Key`. With one, a retry returns the stored answer when the first request finished below 500; a `5xx` itself is not stored. See [idempotency](https://upfour.io/docs/api/idempotency.md).
