# Error reference

Every error is plain JSON with a machine code, plus a human `detail`
where it helps:

```json
{"error": "version-mismatch", "detail": "..."}
```

Some errors carry extra keys — `unknown-node` echoes the unresolved
entry under `name`, `ambiguous-cluster` lists the candidate `clusters`,
`rate-limited` carries `retry_after_s`. The OpenAPI contracts are the
authority; this page is the tour.

## Auth and addressing

| Code | Status | Meaning | Fix |
|---|---|---|---|
| `unauthorized` | 401 | missing, malformed, expired, or revoked token | mint or re-issue on the console's API Keys page |
| `session-required` | 403 | an API token on a console-only action: the [volume delete](https://docs.nationalcompute.com/api/k8s-bid.md#deleting-a-volume-is-a-console-action), [raising or clearing the spend limit](https://docs.nationalcompute.com/api/billing.md#writes), or [loosening auto reload](https://docs.nationalcompute.com/api/billing.md#writes) (enabling it, raising its amount, lowering its threshold); every token is refused | sign in to the console and use the Storage or Billing page |
| `reserved-arrangement` | 403 | a [billing write](https://docs.nationalcompute.com/api/billing.md#writes) for an organization whose credits the platform team manages | contact the platform team |
| `not-found` | 404 | no cluster of the right kind matches the token, the `{org}` in the path is not the token's organization, or a node, pod or volume named on a cluster page read is not in your organization | check the token's binding; a VM-bound token is refused on the k8s surfaces and vice versa |
| `ambiguous-cluster` | 409 | org-wide token, several clusters of that kind; or the public onboarding path (`access.nationalcompute.com/api/onboard/<name>`) when more than one cluster carries the name | pass `cluster` (query on GETs, body on writes); on the onboarding path fetch the kubeconfig by token, [`GET /api/k8s/cluster/kubeconfig`](https://docs.nationalcompute.com/api/k8s-cluster.md#the-kubeconfig) |

## Validation (422)

| Code | Meaning |
|---|---|
| `price-precision` | prices are USD, whole cents — at most 2 decimals |
| `bid-too-low` | a declare too low to ever clear the market that would *acquire* capacity; check the [market feed](https://docs.nationalcompute.com/api/market-feed.md) for what wins and raise the price. Scale-downs are never price-gated (a k8s limit price of `0` always withdraws) |
| `no-ssh-keys` | a VM capacity raise with no registered ssh key — the node would be unreachable ([register one first](https://docs.nationalcompute.com/api/vm-capacity.md#before-you-declare-register-an-ssh-key)) |
| `bad-request` | malformed body; the `detail` says exactly what — including a non-multiple `max_gpus` (whole nodes only, never rounded) and the retired per-node field spellings (the detail gives the conversion) |
| `card-cap` | a [checkout](https://docs.nationalcompute.com/api/billing.md#writes) above what a card can still carry this window; the summary's `card_window_remaining_usd` says how much that is, larger amounts go by wire transfer |
| `confirmation-mismatch` | a volume delete whose `confirm` is not the volume's storage name (its `name` field, shown under the label; the label is refused); nothing is destroyed ([shared storage volumes](https://docs.nationalcompute.com/api/k8s-bid.md#shared-storage-volumes)) |

## Concurrency and state (409)

| Code | Meaning |
|---|---|
| `version-mismatch` | your `expected_version` no longer matches — re-read, reconcile, retry ([conventions](https://docs.nationalcompute.com/api/index.md#versions-and-compare-and-set)) |
| `unknown-node` | a `release` or swap entry resolves to no node you hold; the entry is echoed under `name`, and **nothing is written** |
| `static-posture` | a `max_gpus` raise on a cluster not under market management — nothing would fill the new capacity; lowering and `release` still work |
| `swap-disabled`, `cluster-gated`, `node-pinned` | a swap the platform can't take right now — the surface isn't enabled for your site, the cluster is gated, or the node is pinned; the swap is refused whole |
| `volume-attached` | a delete of a shared volume that is attached to a live cluster; delete the cluster first (the volume detaches with it and is preserved by default) or opt to delete the storage with the cluster |
| `volume-held` | a delete of a preserved volume a node still holds; retry in a few minutes, contact support if it persists |
| `cluster-not-ready` | a [cluster page read](https://docs.nationalcompute.com/api/k8s-cluster-pages.md) or the [kubeconfig download](https://docs.nationalcompute.com/api/k8s-cluster.md#the-kubeconfig) on a cluster that is still being built or is not ready; retry |
| `card-window` | a [checkout](https://docs.nationalcompute.com/api/billing.md#writes) while the rolling card cap is spent (`card_window_remaining_usd` is `0`); wait for payments to age out of the window, or pay by wire transfer |
| `no-purchases` | the [payment portal](https://docs.nationalcompute.com/api/billing.md#writes) before the first purchase; nothing to manage yet |
| `no-card` | enabling [auto reload](https://docs.nationalcompute.com/api/billing.md#writes) with no card on file; the setup page saves one |

## Pacing and platform

| Code | Status | Meaning |
|---|---|---|
| `rate-limited` | 429 | faster than the per cluster write cadence, or more than 300 requests in one minute with this token; wait `retry_after_s` |
| `purchases-unavailable` | 503 | self serve purchases are off, so a [billing write](https://docs.nationalcompute.com/api/billing.md#writes) that needs them is refused; load credits through the console's Billing page or contact the platform team |
| `station-unavailable`, — | 503 | temporary platform condition — reads keep serving, retry writes later |

## Admission denials (Kubernetes)

These denials arrive from your cluster's API server at `kubectl apply`
time, not from this HTTP API — see
[launch admission](https://docs.nationalcompute.com/kubernetes.md#launch-admission) and
[the max cluster size](https://docs.nationalcompute.com/kubernetes.md#the-max-cluster-size):

| Denial | Meaning |
|---|---|
| `JobSizeTooSmall` | a pod template's GPU request is not a multiple of `gpus_per_node` on a cluster without packing onto the base load block; GPU workloads on the market occupy full nodes |
| `JobSizeNotNodeAligned` | on a cluster with packing onto the base load block enabled a pod template requests more GPUs than one node and not a whole number of nodes; pods smaller than one node and whole node multiples are admitted there |
| `BalanceTooLow` | the org balance does not cover two hours of the job at the current limit price; the message names the required and current balance |
| `StationOwned` | an edit of a Kueue object the platform owns — the ClusterQueue `market`, its flavors, admission check, request config and priority classes — these are read-only to cluster users; the max cluster size is set by the platform |

`BalanceTooLow` is also a queue reason: the market re-checks the same
two hours at every tick while the request waits, and a request the
balance can no longer fund stays pending under that reason — never
refused — until a top-up ([queue events](https://docs.nationalcompute.com/api/k8s-bid.md#queue-events)).

## Handling advice

- Treat the machine code as the contract; the `detail` text can change.
- On `409 version-mismatch`, always re-read before retrying — the state
  that moved under you may change what you want to write.
- On `429`, respect `retry_after_s` rather than tight-looping; writes
  are paced per cluster, so a second client on the same cluster shares
  your budget.
