# Errors (/docs/reference/errors)

> Every stable error code the API, the SDK and the CLI surface, with its HTTP status.



Errors answer `{ "error": { "code", "message", "retryAfterMs"?, "details"? } }`. Codes are stable; messages are not.

| Code                                              | HTTP status | Meaning                                                                                                                                                                                               |
| ------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`UNAUTHENTICATED`](#unauthenticated)             | 401         | The request carries no credential, or IAM does not accept the one it carries.                                                                                                                         |
| [`ACCESS_DENIED`](#access_denied)                 | 403         | IAM decided the call's `action @ resource` and denied it.                                                                                                                                             |
| [`CONFIRMATION_REQUIRED`](#confirmation_required) | 403         | The caller is an agent acting for a person, and that person must confirm the request first.                                                                                                           |
| [`NOT_FOUND`](#not_found)                         | 404         | The route, or the object the input names, does not exist or is not visible to the caller.                                                                                                             |
| [`INVALID_INPUT`](#invalid_input)                 | 400         | The body failed validation: a missing or unknown field, a wrong type, or a value outside its constraints.                                                                                             |
| [`INVALID_STATE`](#invalid_state)                 | 409         | The object exists but is in a state that does not allow the call, such as uncordoning a node being deleted or extending a released reservation.                                                       |
| [`CONFLICT`](#conflict)                           | 409         | The call raced a concurrent change, or reuses an `idempotencyKey` for a different request, or names something that already exists.                                                                    |
| [`CAPACITY_UNAVAILABLE`](#capacity_unavailable)   | 409         | No placement satisfies the request now: there are not enough free GPUs, or none that match its constraints, in the pools it may use.                                                                  |
| [`QUOTA_EXCEEDED`](#quota_exceeded)               | 409         | The request would take the tenant or project past a quota, or past a per-tenant limit such as the number of webhooks.                                                                                 |
| [`SPEND_LIMIT_REACHED`](#spend_limit_reached)     | 402         | An IAM budget covering this request is spent, and the pool refuses new work when a budget is exceeded.                                                                                                |
| [`UNSUPPORTED`](#unsupported)                     | 422         | The request is valid, but the driver, provider or deployment it lands on cannot do it: a provider with no power control, an instance that cannot be resized in place, or a deployment with no SSH CA. |
| [`UPGRADE_REQUIRED`](#upgrade_required)           | 426         | The peer speaks a protocol version older than the server's minimum.                                                                                                                                   |
| [`RATE_LIMITED`](#rate_limited)                   | 429         | Too many requests of this kind, from the caller or to a provider behind the call.                                                                                                                     |
| [`DRIVER_ERROR`](#driver_error)                   | 502         | A driver, a node command or a site agent failed, or answered with something compute cannot trust: a BMC operation that failed, an unattested result, or firmware and BIOS settings that did not take. |
| [`PROVIDER_ERROR`](#provider_error)               | 502         | A VPS or bare-metal provider's API refused or failed the call.                                                                                                                                        |
| [`INVALID_CONFIG`](#invalid_config)               | 500         | The deployment is configured wrongly, for example an option out of range or a module that needs another one.                                                                                          |
| [`NOT_IMPLEMENTED`](#not_implemented)             | 501         | The method or feature is planned for a later phase and not built in this release.                                                                                                                     |
| [`CSRF_REJECTED`](#csrf_rejected)                 | 403         | A POST lacks `content-type: application/json` or the `X-Better-Compute: 1` header, or a cookie-authenticated request has no `Origin`.                                                                 |
| [`UNTRUSTED_ORIGIN`](#untrusted_origin)           | 403         | A cookie-authenticated request, or a tunnel's WebSocket upgrade, came from an `Origin` other than the deployment's own.                                                                               |
| [`METHOD_NOT_ALLOWED`](#method_not_allowed)       | 405         | The HTTP method is not accepted on this path.                                                                                                                                                         |
| [`PAYLOAD_TOO_LARGE`](#payload_too_large)         | 413         | The request body is larger than the server accepts.                                                                                                                                                   |
| [`INTERNAL_ERROR`](#internal_error)               | 500         | An unexpected failure on the server.                                                                                                                                                                  |
| [`MISSING_DEPENDENCY`](#missing_dependency)       | 500         | An optional package or service the deployment needs for this call is not installed or not configured, such as PGlite for an embedded database or a remote IAM endpoint.                               |
| [`CONFIG_DRIFT`](#config_drift)                   | 409         | The fleet changed after the plan was made, so `fleet.apply` refused a plan whose `planHash` no longer matches.                                                                                        |
| [`UNAVAILABLE`](#unavailable)                     | 503         | A dependency the request needs is unreachable for now: the secret store's KMS, a node's gateway, or remote IAM.                                                                                       |
| [`TENANT_SUSPENDED`](#tenant_suspended)           | 403         | The IAM tenant is suspended, so its mutations are refused, or deleted, so every call is refused.                                                                                                      |

## UNAUTHENTICATED [#unauthenticated]

HTTP 401.

The request carries no credential, or IAM does not accept the one it carries.

Send a valid IAM session or API key. An expired key or a revoked session answers the same way; the message does not say which.

## ACCESS\_DENIED [#access_denied]

HTTP 403.

IAM decided the call's `action @ resource` and denied it.

Callers outside the input tenant (and outside the root tenant) are refused before any database access. Grant the action in IAM, or call as an identity that holds it. See [Actions and resources](/docs/guides/iam/actions-and-resources).

## CONFIRMATION\_REQUIRED [#confirmation_required]

HTTP 403.

The caller is an agent acting for a person, and that person must confirm the request first.

`details` carries `confirmationId`, the `action` and `resource`, and `expiresAt` when IAM set one. Nothing was changed. Once the person confirms, retry with the same `idempotencyKey`. See [Delegated confirmation](/docs/guides/iam/delegated-confirmation).

## NOT\_FOUND [#not_found]

HTTP 404.

The route, or the object the input names, does not exist or is not visible to the caller.

Lookups are scoped to the input `tenantId`, so an id that belongs to another tenant is not found.

## INVALID\_INPUT [#invalid_input]

HTTP 400.

The body failed validation: a missing or unknown field, a wrong type, or a value outside its constraints.

Unknown fields are refused, never ignored. The Input table of each method in the [API reference](/docs/reference/api) lists every accepted field.

## INVALID\_STATE [#invalid_state]

HTTP 409.

The object exists but is in a state that does not allow the call, such as uncordoning a node being deleted or extending a released reservation.

Read the object again and act on its current state. Retrying unchanged fails the same way.

## CONFLICT [#conflict]

HTTP 409.

The call raced a concurrent change, or reuses an `idempotencyKey` for a different request, or names something that already exists.

A concurrent change is safe to retry after reading the object again. A reused key with a different body is not: send a new key for a new request.

## CAPACITY\_UNAVAILABLE [#capacity_unavailable]

HTTP 409.

No placement satisfies the request now: there are not enough free GPUs, or none that match its constraints, in the pools it may use.

Retry later, relax the constraints, or book a later window. See [Placement](/docs/guides/capacity/placement).

## QUOTA\_EXCEEDED [#quota_exceeded]

HTTP 409.

The request would take the tenant or project past a quota, or past a per-tenant limit such as the number of webhooks.

Release something, or raise the quota with `quotas.set`. See [Quotas and usage](/docs/guides/capacity/quotas-and-usage).

## SPEND\_LIMIT\_REACHED [#spend_limit_reached]

HTTP 402.

An IAM budget covering this request is spent, and the pool refuses new work when a budget is exceeded.

`details` names the budget (`budgetId`, `name`) when IAM reports it. See [Billing and usage](/docs/guides/iam/billing-and-usage).

## UNSUPPORTED [#unsupported]

HTTP 422.

The request is valid, but the driver, provider or deployment it lands on cannot do it: a provider with no power control, an instance that cannot be resized in place, or a deployment with no SSH CA.

## UPGRADE\_REQUIRED [#upgrade_required]

HTTP 426.

The peer speaks a protocol version older than the server's minimum.

A daemon below `proto_min` is welcomed but receives only `daemon.upgrade` until it runs a supported version. See [Protocol](/docs/daemon/protocol).

## RATE\_LIMITED [#rate_limited]

HTTP 429.

Too many requests of this kind, from the caller or to a provider behind the call.

Wait `retryAfterMs` when the error carries it, then retry. The TypeScript client does this once when created with `retryRateLimited` (waits up to 5 seconds by default).

## DRIVER\_ERROR [#driver_error]

HTTP 502.

A driver, a node command or a site agent failed, or answered with something compute cannot trust: a BMC operation that failed, an unattested result, or firmware and BIOS settings that did not take.

The message names the step; `details` may carry the driver's own code (`driverCode`).

## PROVIDER\_ERROR [#provider_error]

HTTP 502.

A VPS or bare-metal provider's API refused or failed the call.

The message carries the provider's reason. Background work such as instance bootstraps retries it; a direct call does not.

## INVALID\_CONFIG [#invalid_config]

HTTP 500.

The deployment is configured wrongly, for example an option out of range or a module that needs another one.

This describes the server, not the request, so it answers 500. Fix the configuration and restart.

## NOT\_IMPLEMENTED [#not_implemented]

HTTP 501.

The method or feature is planned for a later phase and not built in this release.

`details` names the `feature` and the `phase` that delivers it. See the [roadmap](/docs/reference/roadmap).

## CSRF\_REJECTED [#csrf_rejected]

HTTP 403.

A POST lacks `content-type: application/json` or the `X-Better-Compute: 1` header, or a cookie-authenticated request has no `Origin`.

Browsers cannot send that header cross-site without a preflight, so it stops cross-site request forgery. Clients built on the SDK send it.

## UNTRUSTED\_ORIGIN [#untrusted_origin]

HTTP 403.

A cookie-authenticated request, or a tunnel's WebSocket upgrade, came from an `Origin` other than the deployment's own.

## METHOD\_NOT\_ALLOWED [#method_not_allowed]

HTTP 405.

The HTTP method is not accepted on this path.

API methods are `POST` only; `GET` serves `/health` and `/.well-known/better-compute`; tunnels need a WebSocket upgrade.

## PAYLOAD\_TOO\_LARGE [#payload_too_large]

HTTP 413.

The request body is larger than the server accepts.

## INTERNAL\_ERROR [#internal_error]

HTTP 500.

An unexpected failure on the server.

The server logs it with the request id (`x-request-id`); quote that id when reporting it.

## MISSING\_DEPENDENCY [#missing_dependency]

HTTP 500.

An optional package or service the deployment needs for this call is not installed or not configured, such as PGlite for an embedded database or a remote IAM endpoint.

Like `INVALID_CONFIG`, it describes the deployment, so it answers 500.

## CONFIG\_DRIFT [#config_drift]

HTTP 409.

The fleet changed after the plan was made, so `fleet.apply` refused a plan whose `planHash` no longer matches.

Plan again and review the new plan. `--fail-on-drift` in the CLI uses the same code. See [Fleet configuration](/docs/guides/fleet-config).

## UNAVAILABLE [#unavailable]

HTTP 503.

A dependency the request needs is unreachable for now: the secret store's KMS, a node's gateway, or remote IAM.

Nothing was changed. Retry after `retryAfterMs`.

## TENANT\_SUSPENDED [#tenant_suspended]

HTTP 403.

The IAM tenant is suspended, so its mutations are refused, or deleted, so every call is refused.

`details` carries the `tenantId` and its `state`. A suspension or deletion also covers the tenant's descendants, and root-tenant callers are exempt. Reads still work in a suspended tenant. See [Tenancy](/docs/guides/iam/tenancy).
