# Units API errors

Every refusal carries a stable `code`, a human `description`, `details` (`code: description`) and a `docs_url` that points to the matching section below. The figures that let a program act are in the body: `retry_after_seconds` (also the `Retry-After` header), and for `no-warm-unit`, `warm_ready` and `warm_starting`.

```json
{
  "code": "no-warm-unit",
  "description": "No warm unit is ready, accepting and unallocated",
  "details": "no-warm-unit: No warm unit is ready, accepting and unallocated",
  "docs_url": "https://docs.zaroz.cloud/units/errors#no-warm-unit",
  "retry_after_seconds": 5,
  "warm_ready": 0,
  "warm_starting": 2
}
```

Switch on `code`, never on `description`. A refusal is never a partial success, and a limit never stops a unit that is already running.

## policy-not-code

**HTTP 409.** The policy is not in `code` mode.

What to do: Set `mode: "code"` on the policy. Reads and label changes work in every mode.

## unit-input-differs

**HTTP 409.** A running unit already has this key with different `labels` or `user_data`.

What to do: Use `PATCH` for labels; for `user_data` stop the unit and start it with the new value, or allocate.

## no-warm-unit

**HTTP 409.** No warm unit is ready. The body has `warm_ready`, `warm_starting`, `retry_after_seconds`.

What to do: Wait `retry_after_seconds` and retry, send `if_none: "create"`, or raise `warm_units`.

## region-unreachable

**HTTP 503.** The region cannot be reached. An allocation was undone.

What to do: Running units keep serving. Retry with backoff.

## container-unit-limit

**HTTP 409.** The Workload already runs its maximum of units with a pod.

What to do: Destroy or stop a unit, or raise `max_units` on the policy.

## stopped-unit-limit

**HTTP 409.** The Workload holds the maximum of 1,000 stopped units.

What to do: Destroy stopped units you no longer need.

## org-unit-limit

**HTTP 409.** Your organization is at its unit limit across all Workloads.

What to do: Free a unit elsewhere, or ask support to raise the limit (default 50).

## start-rate

**HTTP 429.** Too many unit starts this hour (default 600 per organization). `Retry-After` is set.

What to do: Wait `Retry-After`. Reuse units (allocate, start a stopped one) instead of creating new ones.

## spend-cap

**HTTP 409.** Starting would exceed the monthly spend cap for extra units.

What to do: Raise `spend_cap_cents` on the policy. Allocating an already-warm unit is still allowed; its refill is refused.

## not-provisioned

**HTTP 409.** The service does not run units yet.

What to do: Finish setting up the Workload first.

## container-busy

**HTTP 503.** Another change to this Workload's units is in progress. `Retry-After` is set.

What to do: Retry after the header.

## invalid-idempotency-key

**HTTP 400.** `Idempotency-Key` must be 1 to 64 printable ASCII characters.

What to do: Shorten or clean the key.

## invalid-unit-key

**HTTP 400.** The key is not 1 to 40 lowercase letters, digits or hyphens, starting and ending with a letter or digit.

What to do: Fix the key.

## reserved-unit-key

**HTTP 400.** The key is reserved: `u` plus digits, starting `w-` or `zaroz`, or a route word such as `allocate`.

What to do: Choose another key.

## too-many-labels

**HTTP 400.** More than 16 labels.

What to do: Send at most 16.

## invalid-label-key

**HTTP 400.** A label key does not match `^[a-z0-9]([a-z0-9_.-]*[a-z0-9])?$` or is over 32 characters.

What to do: Fix the key.

## label-value-too-long

**HTTP 400.** A label value is over 64 characters.

What to do: Shorten it, or put it in `user_data`.

## invalid-label-value

**HTTP 400.** A label value has control characters.

What to do: Remove them.

## labels-too-large

**HTTP 400.** The label map is over 2 KiB serialized.

What to do: Send fewer or shorter labels.

## user-data-too-large

**HTTP 400.** `user_data` is over 64 KiB serialized.

What to do: Store the payload elsewhere and pass a reference.

## user-data-too-deep

**HTTP 400.** `user_data` is nested over 32 levels.

What to do: Flatten it.

## cursor-expired

**HTTP 410.** (events) The cursor is older than the 30 days kept. The body has `resume_after`.

What to do: Resume from `resume_after`.

## Shared refusals

These refusals are not specific to units: every route of the API can return them, and a Units route links its own `docs_url` to the section below.

## resource-not-found

**HTTP 404.** The Workload, unit, webhook or log you asked for does not exist, or your token cannot see it.

What to do: Check the identifier and the organization the token belongs to.

## invalid-field-value

**HTTP 400 or 422.** A query parameter or body field is malformed or out of range. `details` names the field.

What to do: Fix the field named in `details`. Nothing was changed.

## business-rule-violation

**HTTP 409 or 422.** The request is well formed, but a rule of the platform refuses it in the current state.

What to do: Read `details`: it states the rule and what to change. Retrying the same request will not help.

## rate-limit-exceeded

**HTTP 429.** Too many requests for this route. `Retry-After` is set.

What to do: Wait `Retry-After` and retry.

## service-unavailable

**HTTP 503.** A dependency the request needs is not reachable right now. Nothing was changed.

What to do: Retry with backoff. Running units keep serving.

## internal-server-error

**HTTP 500.** The platform failed while handling the request.

What to do: Retry once. If it persists, contact support with the time of the request.

## log-store-unavailable

**HTTP 503.** The log store cannot be read right now. Live units are not affected.

What to do: Retry with backoff.

## unit-not-running

**HTTP 409.** The unit is not running, so it has no live output to read or stream.

What to do: Start the unit, or read its stored logs instead.

## code-units-exist

**HTTP 409.** Units your code created still exist, so the Workload cannot leave code mode.

What to do: Destroy those units, then repeat the change.
