> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The v1 error envelope, which field to branch on, and what each status code means.

Every v1 error uses one envelope, whatever went wrong. The envelope puts the error under a single
`error` key:

```json theme={null}
{
  "error": {
    "detail": "Invalid page token.",
    "type": "validation",
    "code": "pagination.invalid_page_token",
    "errors": []
  }
}
```

| Field | Description |
| - | - |
| `error.detail` | A human-readable summary. |
| `error.type` | A broad category that the status code determines. The value is one of `validation`, `authentication`, `permission`, `not_found`, `conflict`, `rate_limited`, `runtime`. |
| `error.code` | A stable, machine-readable code. Branch your error handling on this field. |
| `error.errors` | Field-level details, with one entry for each rejected field. The list is empty when the failure isn't about a particular field. |

The nesting keeps the error's own fields apart from the top level of the response, so a field that
Lumos adds there later can never collide with one inside the error.

## `code` and `detail`

`code` is part of the API contract. `detail` is prose that Lumos rewords for clarity, so its exact
text can change. If you match on `detail`, a copy edit can break your code.

```python theme={null}
body = response.json()

# Correct
if body["error"]["code"] == "pagination.invalid_page_token":
    restart_from_first_page()

# Wrong
if "Invalid page token" in body["error"]["detail"]:
    restart_from_first_page()
```

`type` is useful for broad handling, such as deciding whether to retry at all. Use `code` when you
need to react to one specific failure.

## Field-level errors

A request that doesn't match the endpoint's schema returns `400` with `code` set to
`validation_error`, and so does a value the endpoint refuses, such as a `scheduled_at` in the past.
`errors` holds one entry for each rejected field. A `422` about one value names it in `errors` too.
`field` uses dotted and indexed paths into the request body, so `steps[2].inputs.group_id` points
to exactly the value to fix:

```json theme={null}
"errors": [
  {
    "field": "steps[2].inputs.group_id",
    "code": "missing",
    "message": "Field required"
  }
]
```

Each entry's `code` names the kind of failure, such as `missing` for a required field you didn't
send.

## Status codes

| Status | `type` | What it means |
| - | - | - |
| `400` | `validation` | The request is malformed, or a value is invalid. Check `code` and `errors`. |
| `401` | `authentication` | The token is missing, malformed, or no longer valid. |
| `403` | `permission` | The token is valid, but the user who owns it lacks permission. |
| `404` | `not_found` | Lumos can't find the resource. |
| `405` | `validation` | The path exists, but doesn't accept this HTTP method. The `Allow` header lists the methods it does. |
| `409` | `conflict` | The request is valid, but the resource's current state blocks it, such as a sync already running, a review already launched, a duplicate, or other data that still references it. Check `code` and `detail`. |
| `422` | `validation` | The request is well-formed, but asks for something the target can't do, such as syncing a manual app or naming a group that isn't in your organization. Check `code` and `detail`. |
| `429` | `rate_limited` | Your organization sent too many requests. See [Rate limits](https://docs.lumos.com/reference/v1/authentication.md#rate-limits). |
| `500` | `runtime` | Something failed on the Lumos side. Retry with backoff. |
| `501` | `runtime` | The app's integration doesn't support this request. |
| `502` | `runtime` | A third-party service the call depends on failed. Read `detail`. |

## Codes you'll see

Every status has a default `code`, used when no more specific one applies:

| Status | Default `code` |
| - | - |
| `400` | `validation_error` when `errors` names the rejected fields, otherwise `bad_request` |
| `401` | `unauthorized` |
| `403` | `forbidden` |
| `404` | `not_found` |
| `405` | `method_not_allowed` |
| `409` | `conflict` |
| `422` | `validation_error` |
| `429` | `rate_limited` |
| `500` | `internal_error` |
| `501`, `502` | `error` |

These failures carry a code of their own:

| `code` | Status | Returned by | Meaning |
| - | - | - | - |
| `pagination.invalid_page_token` | `400` | Every list endpoint | The `page_token` is empty, malformed, or has been altered. See [Pagination](https://docs.lumos.com/reference/v1/pagination.md). |
| `validation.unknown_query_parameter` | `400` | Every v1 endpoint | A query parameter isn't one the endpoint takes. `errors` has one entry for each, with `field` set to the parameter's name. |
| `access_request.entitlement_not_found` | `404` | [`POST /v1/access-requests`](https://docs.lumos.com/api-reference/v1/access-requests/create-access-request) | An ID in `entitlement_ids` isn't an entitlement in your organization. |
| `access_request.entitlement_not_requestable` | `400` | `POST /v1/access-requests` | The entitlement exists, but you can't request it on the app in `app_id`. |
| `access_request.custom_fields_invalid` | `400` | `POST /v1/access-requests` | A `custom_fields` answer is missing, has an unknown key, or breaks the field's rules. `errors` lists each one as `custom_fields.<reference_key>`. |
| `access_request.forbidden` | `403` | `POST /v1/access-requests` | The requester isn't allowed to file this request. |
| `access_request.<reason>` | `400` | `POST /v1/access-requests` | Lumos refused the request. [Filing a request](https://docs.lumos.com/reference/v1/objects/access-request.md#filing-a-request) lists the reasons. |
| `access_review.not_found` | `404` | Access Review endpoints | Your organization has no Access Review with this ID. |
| `app_review.not_found` | `404` | Access Review endpoints | The Access Review holds no app review with this ID. |
| `scope_filter.invalid` | `400` | [`POST /v1/access-reviews/{review_uuid}/apps/{app_review_uuid}:sync`](https://docs.lumos.com/api-reference/v1/access-reviews/sync-app-review) | A scope filter names a field the app review doesn't offer, or a value that field doesn't accept. |
| `app_review.sync_in_progress` | `409` | `POST /v1/access-reviews/{review_uuid}/apps/{app_review_uuid}:sync` | A sync is already running for this app review. |
| `app_review.launched` | `409` | `POST /v1/access-reviews/{review_uuid}/apps/{app_review_uuid}:sync` | The review has launched, so you can no longer sync its app reviews. |
| `subject.not_found` | `404` | [Get Subject](https://docs.lumos.com/api-reference/v1/access-reviews/get-subject.md) | The app review holds no subject with this ID. |
| `app_review.ai_disabled` | `400` | Get Subject | You sent `expand=ai_recommendation`, and AI Assist is off for this app review. |
| `app.admin_required` | `409` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | `in_app_store: true` was sent for an app with no admins. Set its admins with `PUT /v1/apps/{app_uuid}/admins`, then retry. |
| `app.api_unsupported` | `422` | `POST /v1/apps`, [Update App](https://docs.lumos.com/api-reference/v1/apps/update-app.md) with credentials, [Trigger App Sync](https://docs.lumos.com/api-reference/v1/apps/trigger-app-sync.md) | The API can't manage this integration, such as a CSV app whose column mapping is set up only in the Lumos UI. `detail` gives the reason. |
| `app.delete_blocked` | `409` | [`DELETE /v1/apps/{app_uuid}`](https://docs.lumos.com/api-reference/v1/apps/delete-app) | The app still has accounts, or other Lumos data still references it. |
| `app.field_not_clearable` | `422` | [Update App](https://docs.lumos.com/api-reference/v1/apps/update-app.md) | `null` was sent for `label_override`, `description_override` or `category_override` on a custom app, which has no default to show instead. |
| `app.manual_sync_unsupported` | `422` | [Trigger App Sync](https://docs.lumos.com/api-reference/v1/apps/trigger-app-sync.md) | The app is a manual or CSV app, which the API can't sync. |
| `app.on_prem_connector_unavailable` | `409` | `POST /v1/apps` | None of your connected on-prem agents run this connector. Install it on an agent, then retry. |
| `app.sync_in_progress` | `409` | [Update App](https://docs.lumos.com/api-reference/v1/apps/update-app.md) with credentials, [Trigger App Sync](https://docs.lumos.com/api-reference/v1/apps/trigger-app-sync.md) | A sync is running for the app. Update App also sends `Retry-After`. |
| `appstore_settings.custom_intake_fields_invalid` | `400` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | The `custom_intake_fields` list breaks a rule. `errors` lists every problem by position, such as `custom_intake_fields[2].reference_key`. |
| `entitlement.delete_blocked` | `409` | [`DELETE /v1/entitlements/{entitlement_uuid}`](https://docs.lumos.com/api-reference/v1/entitlements/delete-entitlement) | The entitlement has pending access requests or time-based access scheduled for removal, or a draft or published Access Policy references it. `detail` gives the reason. |
| `entitlement.not_found` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | `default_entitlement` isn't an entitlement in your organization. |
| `entitlement.not_manual` | `422` | `DELETE /v1/entitlements/{entitlement_uuid}` | Only entitlements from a manual source can be deleted. |
| `entitlement.not_requestable` | `422` | [Update Entitlement](https://docs.lumos.com/api-reference/v1/entitlements/update-entitlement.md), [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | The entitlement can't be requested. Update Entitlement returns it for a non-null `custom_approval_message_override`, and Update AppStore Settings for a `default_entitlement`. |
| `entitlement.wrong_app` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | `default_entitlement` belongs to another app. |
| `entitlement_in_use` | `409` | `DELETE /v1/entitlements/{entitlement_uuid}` | Accounts still hold the entitlement. `errors` has one entry for `active_accounts`, and its `message` is the number of accounts. |
| `entitlements.list_too_broad` | `422` | [`GET /v1/entitlements`](https://docs.lumos.com/api-reference/v1/entitlements/list-entitlements) | The list is too broad to return. Narrow it with `search`, or list one app's entitlements with [`GET /v1/apps/{app_uuid}/entitlements`](https://docs.lumos.com/api-reference/v1/apps/list-app-entitlements). |
| `group.not_found` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md), [Update Entitlement AppStore Settings](https://docs.lumos.com/api-reference/v1/entitlements/update-entitlement-appstore-settings.md), `PUT /v1/apps/{app_uuid}/admins` | A group ID isn't a group in your organization. |
| `knowledge.already_exists` | `409` | Knowledge endpoints | Another entry in your organization already has this `title`. |
| `knowledge.invalid_input` | `400` | Knowledge endpoints | The request isn't valid, such as an unknown generic tag, a file Lumos doesn't accept, or a patch with no changes. `detail` says which. |
| `knowledge.not_found` | `404` | Knowledge endpoints | No knowledge entry has this ID. |
| `permission.denied` | `403` | Knowledge endpoints | You don't have permission to read or change knowledge entries. |
| `request_validation_webhook.wrong_type` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md), [Update Entitlement AppStore Settings](https://docs.lumos.com/api-reference/v1/entitlements/update-entitlement-appstore-settings.md) | The inline webhook's type isn't `request-validation`. |
| `role.id_required` | `400` | [Add User Role](https://docs.lumos.com/api-reference/v1/users/add-user-role.md), [Remove User Role](https://docs.lumos.com/api-reference/v1/users/remove-user-role.md) | `role_id` names a built-in role, but not by its `id`, such as `APP_ADMIN` or `app_admin`. Send the `id` that `detail` gives, such as `app-admin`. |
| `task.action_unknown` | `400` | Task action endpoints | `action_type` isn't an action Lumos knows. |
| `task.transition_not_allowed` | `400` | [`POST /v1/tasks/{task_id}:perform-action`](https://docs.lumos.com/api-reference/v1/tasks/perform-task-action) | The action can't move the task to the `transition_to` status you sent. |
| `task.no_action_for_status` | `400` | [`POST /v1/tasks/{task_id}:complete`](https://docs.lumos.com/api-reference/v1/tasks/complete-task), [`:dismiss`](https://docs.lumos.com/api-reference/v1/tasks/dismiss-task), `:perform-action` | No action moves the task to that status, or the task doesn't offer that action in its current status, such as approving a task that's already approved. |
| `task.action_ambiguous` | `400` | `POST /v1/tasks/{task_id}:complete`, `:dismiss`, [`:reassign`](https://docs.lumos.com/api-reference/v1/tasks/reassign-task) | More than one action could do this. Name the one you mean with `POST /v1/tasks/{task_id}:perform-action`. |
| `task.already_in_status` | `409` | Task action endpoints | The task is already in the status you asked for. |
| `time_based_access.default_not_offered` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md) | `time_based_access.default` isn't one of the app's options, or `time_based_access.options` drops the current default without a new `default`. |
| `time_based_access.unknown_option` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md), [Update Entitlement AppStore Settings](https://docs.lumos.com/api-reference/v1/entitlements/update-entitlement-appstore-settings.md) | A label in `time_based_access.options` or `time_based_access_override.options` isn't an option your organization has. |
| `user.not_found` | `422` | `PUT /v1/apps/{app_uuid}/admins` | A user ID isn't a user in your organization. |
| `webhook.not_found` | `422` | [Update AppStore Settings](https://docs.lumos.com/api-reference/v1/apps/update-appstore-settings.md), [Update Entitlement AppStore Settings](https://docs.lumos.com/api-reference/v1/entitlements/update-entitlement-appstore-settings.md) | `request_validation_webhook_uuid` isn't an inline webhook in your organization. |
| `workflow.app_reassignment_not_allowed` | `400` | [`PUT /v1/workflows/{workflow_id}`](https://docs.lumos.com/api-reference/v1/workflows/update-workflow) | The update would move the workflow to a different app. |
| `workflow.approval_chain_delete_not_supported` | `422` | `DELETE /v1/workflows/{workflow_id}` | The workflow is an approval chain, which can't be deleted on its own. Edit the approval workflow that uses it instead. |
| `workflow.bind_conflict` | `409` | The app and entitlement workflow `PUT` endpoints, such as `PUT /v1/apps/{app_uuid}/approval-workflow` | Another app or entitlement owns or binds the workflow, or the workflow was deleted while the request ran. |
| `workflow.bound_delete_not_allowed` | `409` | `DELETE /v1/workflows/{workflow_id}` | An app or entitlement still binds the workflow. Unbind it, then retry. |
| `workflow.default_delete_not_allowed` | `409` | `DELETE /v1/workflows/{workflow_id}` | The workflow is an app's default workflow. |
| `workflow.type_change_not_allowed` | `400` | [Update Workflow](https://docs.lumos.com/api-reference/v1/workflows/update-workflow.md) | The update would change the workflow's `type`. |
| `workflow.type_mismatch` | `422` | The app and entitlement workflow `PUT` endpoints, such as `PUT /v1/apps/{app_uuid}/approval-workflow` | The workflow's `type` doesn't fit the binding, such as a provisioning workflow sent as an approval workflow. `errors` names `workflow_id`. |

Work that runs in the background reports its failure in the operation instead, with codes of its
own. [Long-running operations](https://docs.lumos.com/reference/v1/long-running-operations.md) lists them.

## Retries

Retry `429` and `500`. If the response includes `Retry-After`, wait that many seconds. If it
doesn't, retry with exponential backoff.

Don't retry `400`, `401`, `403`, `404`, `405`, `422`, or `501` until you change the request, or the
data it names. The same request fails the same way on a second attempt. Retrying a `401` with a
revoked token also counts against your rate limit.

A `409` can succeed once the state that `code` names changes, for example after a running sync
finishes or pending access requests resolve. Read `code` and `detail`, and inspect the resource
before you retry. A `502` can also succeed later, so read `detail` before you retry it.
