# Errors

> The Shardflux error envelope and every error code and details.reason the API and the cell gateway return, with HTTP status, meaning and what to do.

## The error envelope

Every non-2xx response from the application API and from the cell gateway has the same body:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Try again later.",
    "request_id": "req-9b0e1d2c3f4a5b6c7d8e9f00",
    "retryable": true,
    "details": { "retry_after_seconds": 42 }
  }
}
```

| Field | Meaning |
| --- | --- |
| `code` | A closed set of snake_case codes (below). Branch on `code` and `details.reason`, never on `message`. |
| `message` | Safe, human-readable English. Show it when you have nothing better for a code. |
| `request_id` | The same value as the `X-Request-Id` response header. Include it when you contact support. |
| `retryable` | Whether the same request may succeed later. |
| `operation_id` | Optional. The lifecycle operation that caused the error; poll it with `GET /v1/operations/{id}`. |
| `details` | Optional. Per-code details, most often `reason`, plus fields such as `field`, `names` or `limit`. |

Treat a code or reason you do not know as a generic error: show `message` and use `retryable`. New reasons can appear
at any time within `/v1`.

A few responses do not have this body: a `502`, `503` or `504` from a load balancer can carry an HTML page, and a
service shutting down can answer `503` with a different JSON shape. Treat them as retryable. The SDKs raise
`ShardfluxProtocolError` for them.

## Retrying

| Situation | Retry? |
| --- | --- |
| `GET` or `HEAD`, and `retryable: true` or status 429, 502, 503 or 504 | Yes, with exponential backoff, honouring `Retry-After`. |
| A mutation sent with an `Idempotency-Key`, after a retryable error or a network failure | Yes, with the **same** key and the same body. |
| A mutation without an `Idempotency-Key` | Not automatically: the request may have taken effect. |
| `retryable: false` | No. Change the request, the plan or the credentials first. |
| `409` with an `operation_id` (`operation_in_progress`, `workspace_not_running`, `workspace_busy`) | Wait for the operation, then retry. |
| Cell `409 stale_epoch` | Once, after getting a new tool token. |
| An execution of a [file-first workspace](https://docs.shardflux.dev/concepts/file-first.md), after a network failure or a retryable error | Yes, with the **same** `execution_id` and body: the command runs at most once. |
| `409 conflict` with `revision_mismatch` or `tree_revision_mismatch` | Not as it is. Read the current state, then send a new request. |

The [TypeScript](https://docs.shardflux.dev/reference/typescript.md#retries-and-idempotency) and [Python](https://docs.shardflux.dev/reference/python.md#retries-and-idempotency)
SDKs apply these rules for you.

## Application API codes

Codes returned by `https://api.shardflux.dev/v1`:

| Code | HTTP | Retryable | Meaning | What to do |
| --- | --- | --- | --- | --- |
| `bad_request` | 400 | no | Malformed JSON, or `Content-Type: application/json` with an empty body. | Fix the request. Omit the header on a request without a body. |
| `token_invalid` | 400 | no | An emailed link (email verification, password reset, email change) is invalid, expired or already used. | Request a new link. |
| `unauthenticated` | 401 | no | The `Authorization` header is missing or not `Bearer`, or the API key or a person's CLI session is malformed, unknown, revoked or expired. | Check `SHARDFLUX_API_KEY`; create a new key in the console if it was revoked. For a session, sign in again (`shard auth login`). |
| `invalid_credentials` | 401 | no | Sign-in (`/v1/auth`) with a wrong email or password, or a wrong two-factor or recovery code. The same answer for an account that does not exist. | Check the credentials. Repeated wrong codes lock the second factor (`429`, `mfa_locked`). |
| `entitlement_required` | 402 | no | Your plan does not allow this start, build or volume (see [reasons](#402-entitlement_required)). | Change the plan or settle the payment. |
| `allowance_exhausted` | 402 | no | A compute allowance of your plan is used up and overage is off or paused, or overage reached its spend cap (see [reasons](#402-allowance_exhausted)). `details` has `reason`, `allowance`, `meter`, `unit`, `used`, `included`, `resets_at`, `plan`, `exhausted` and `spend_cap`. | Act on `details.reason`. Do not retry in a loop. |
| `quota_exceeded` | 403 | no | A plan limit was reached. `details.limit` names it, with `limit_value` and `current` (see [limits](#403-quota_exceeded)). | Suspend or delete something, or upgrade. |
| `forbidden` | 403 | no | The key may not do this: a tool it lacks (`details.denied_tools`, `permitted_tools`), an owner or admin action, an action only a person can take ("API keys cannot perform this action."), or a reason below. | Use a key with the permission, sign in as a person (`shard auth login`), or ask an owner or admin. |
| `mfa_required` | 403 | no | A person's session is still waiting for its second factor. | Complete the sign-in: `POST /v1/auth/mfa/challenge`, or `shard auth mfa --code <code>`. |
| `email_unverified` | 403 | no | A person's session before the email address is verified. | Verify it with the emailed link (`shard auth verify-email '<link>'`). |
| `step_up_required` | 403 | no | A sensitive action (exports, deletions, email and two-factor changes) without a password check in the last 10 minutes. | Step up (`POST /v1/auth/step-up`, or `shard auth step-up`), then retry. |
| `not_found` | 404 | no | The resource does not exist, or it is outside the key's project. The two are not distinguished. | Check the id, key or slug. |
| `conflict` | 409 | depends | The resource is in a state that refuses the request; `details.reason` says which (see [reasons](#409-conflict)). | Act on the reason. |
| `payload_too_large` | 413 | no | The request body is too large. | Send less. |
| `unsupported_media_type` | 415 | no | The body is not JSON. | Send `Content-Type: application/json`. |
| `validation_failed` | 422 | no | The request is invalid: `details.issues` (schema checks: `path` and `message`) or `details.field` and `details.reason` (see [reasons](#422-validation_failed)). | Fix the named field. |
| `idempotency_mismatch` | 422 | no | The `Idempotency-Key` was already used with a different request within 24 hours. | Use a new key for a new request. |
| `rate_limited` | 429 | yes | Too many requests. `Retry-After` gives the seconds to wait, also in `details.retry_after_seconds`. | Wait, then retry. |
| `internal_error` | 500 | yes | An unexpected server error. | Retry safe requests; report the `request_id` if it persists. |
| `dependency_unavailable` | 503 | yes | A dependency is temporarily unavailable (a reason may say which). | Retry with backoff. |
| `capacity_pending` | 503 | yes | Not returned as an HTTP error: it is an operation **state** (see [Operation errors](#operation-errors)). | Keep waiting for the operation. |
| `stale_epoch` | 409 | yes | Returned by the cell gateway (below). | Get a new tool token. |

Requests with an API key never receive `token_invalid`, `invalid_credentials`, `mfa_required`, `email_unverified` or
`step_up_required`: they come from `/v1/auth` and from requests with a person's CLI session (`Authorization: Bearer
sfu_...`; see [Authentication](https://docs.shardflux.dev/reference/http-api.md#a-persons-session)). The console's own routes also use
`csrf_failed`, which `/v1` never returns.

## Cell gateway codes

Codes returned by a workspace's `cell_endpoint` (exec, files, terminals, processes, git, browser):

| Code | HTTP | Retryable | Meaning | What to do |
| --- | --- | --- | --- | --- |
| `bad_request` | 400 | no | Invalid query or path parameters, or malformed JSON or unknown fields. | Fix the request. |
| `unauthenticated` | 401 | no | No tool token, or it is malformed, expired or revoked (`details.reason`: `revoked_watermark`, `revoked_workspace`, `revoked_api_key`, `revoked_api_key_tools`). | Get a new tool token and retry once. |
| `forbidden` | 403 | no | The token is for another workspace or lacks the tool (`details.tool`); or a named secret is not available (`secret_not_available`). | Request a token with the tool; check the key's permissions. |
| `not_found` | 404 | no | The workspace is unknown or deleted, or a file does not exist (on a file-first workspace also any path outside `/home/user`, `outside_tree_root`). | Stop, or fix the path. |
| `conflict` | 409 | depends | `stale_endpoint` (the workspace moved to another cell), a `session_id` or `Idempotency-Key` reused for a different request (`operation_id_reused`), an interrupted attempt whose outcome is unknown (`operation_interrupted`), `legacy_disk_layout`, `guest_feature_unavailable`, and the reasons of [file tools and hosts](#file-tools-parking-and-hosts) and [file-first workspaces](#file-first-workspaces). | `stale_endpoint`: read the workspace again and use its new `cell_endpoint`. Interrupted: reattach to the session instead of re-running. |
| `stale_epoch` | 409 | yes | The token was issued before the workspace's latest suspend, resume or move. | Get a new tool token and reconnect with your last offsets. |
| `workspace_not_running` | 409 | yes | The workspace is suspended or not on a host. The call was not executed. A suspended workspace's file reads are answered from its disk while a host still holds it; `offline_unavailable` and `offline_budget` say that such a read could not be. | Resume it (or open the key), then retry with a new token. |
| `workspace_busy` | 409 | yes | A lifecycle operation holds the workspace (`operation_id`; `details.operation_kind`; `details.tool_gate`: `read_only` or `closed`; or `details.reason: workspace_fenced`), or an execution of a file-first workspace runs (`execution_in_progress`, `details.execution_id`). The call was not executed. | Wait for the operation or the execution, then retry. During `read_only`, reads still work. |
| `validation_failed` | 422 | no | Invalid parameters (paths, a relative `cwd` (`invalid_cwd`), modes, signals, terminal size, a reserved `sfstart.` session id, `env_collision`), or a patch that does not apply (`edit_not_found`, `edit_ambiguous`, `edit_not_text`, `patch_invalid`). | Fix the request. |
| `payload_too_large` | 413 | no | The body, stdin (over 1 MiB), file write or patch (over 7 MiB) is too large (`details.max_bytes`). | Send less. |
| `range_not_satisfiable` | 416 | no | The `Range` is not a single `bytes=start-end`, or starts past the end of the file (`details.size`). | Request within the size. |
| `rate_limited` | 429 | yes | The workspace is at a resource limit, or there were too many secret resolutions. `Retry-After` gives the seconds to wait. | Back off and retry idempotent calls. |
| `dependency_unavailable` | 503 | yes | The workspace host or a gateway dependency is temporarily unavailable, or the disk changed during a read of a sleeping workspace (`offline_changed`). | Retry with backoff. |
| `service_unavailable` | 503 | yes | The gateway has too many concurrent streams, or is draining. Also: the workspace's host has no room to wake it right now (`host_capacity`), a wake failed (`wake_failed`), or no host can run an execution now (`no_execution_host`). Nothing was executed. | Retry after `Retry-After`, or a second. |
| `timeout` | 504 | yes | The workspace did not answer in time. | Retry idempotent calls. For exec, reattach to the session instead of starting it again. |
| `internal_error` | 500 | no | An unexpected gateway error. | Report the `request_id`. |

The cell enum also contains `capacity_pending`; the gateway never returns it.

## Reasons

`details.reason` refines a code. The tables list the reasons a request with an API key can meet; [the last
one](#with-a-persons-session) adds those of a person's session.

### 402 entitlement_required

| Reason | Meaning | What to do |
| --- | --- | --- |
| `no_plan` | The organization has no active plan. | Choose a plan in the console. |
| `limits_missing` | The plan lacks a limit the request needs (`details.missing`). | Upgrade. |
| `not_included` | The plan does not include this feature (shared volumes). | Upgrade. |
| `payment_past_due` | A payment is overdue and the grace period ended (`details.grace_until`). New starts are refused; running workspaces are not affected. | An owner or billing member updates the payment method. |
| `unpaid` | The subscription is unpaid. | As above. |
| `operator_hold` | New starts are on hold for the organization. | Contact support. |

### 402 allowance_exhausted

New opens, resumes and forks are refused, and the organization's running workspaces are suspended with their files,
memory and processes kept. Nothing is deleted. See [Overage](https://docs.shardflux.dev/limits.md#overage-opt-in).

| Reason | Meaning | What to do |
| --- | --- | --- |
| `allowance_used` | The plan's CPU-hours or RAM GiB-hours allowance is used up, and overage is off or not available on the plan. | Wait for `resets_at`, upgrade, or have an owner or billing member turn on overage in the console. |
| `overage_paused` | The allowance is used up, and overage is on but paused while a plan payment is past due. | An owner or billing member pays the invoice or updates the payment method. |
| `spend_cap_reached` | Overage charges reached the spend cap for this billing period. | Wait for `resets_at`, have an owner or billing member raise the cap (up to the plan price), or upgrade. |

| `details` field | Meaning |
| --- | --- |
| `reason` | One of the reasons above. |
| `allowance`, `meter`, `unit` | The first used-up allowance (`cpu_hours` or `ram_gib_hours`), its meter (`cpu_seconds` or `memory_gib_seconds`), and the unit of `used` and `included`. |
| `used`, `included` | This period's usage and the plan's allowance, in `unit`. |
| `used_meter_units`, `included_meter_units` | The same in meter units (CPU-seconds or GiB-seconds). |
| `exhausted` | Every used-up allowance, each with `allowance`, `meter`, `used`, `included` and `unit`. |
| `resets_at` | The end of the billing period, when the allowances and the spend cap reset. |
| `plan` | The plan key, for example `developer`. |
| `spend_cap` | `null` when overage is not available on the plan. Otherwise `cap_minor` (the configured cap, `null` if never set), `effective_cap_minor` (the cap that applies: `cap_minor` limited to the plan price, or the plan price when no cap is set), `charges_minor` (overage charged this period) and `currency`. Amounts are in minor units (cents). |

### 403 quota_exceeded

| `details.limit` | Meaning |
| --- | --- |
| `concurrent_workspaces` | Running workspaces allowed by the plan (open, resume and fork count). Suspend or delete one. |
| `concurrent_template_builds` | Unfinished template builds. Wait for one to finish. |
| `shared_volumes_max` | Shared volumes of the organization. |
| `shared_volume_gib_max` | The size of one shared volume. |
| `volume_attachments_per_workspace` | Volumes attached to one workspace. |

### 403 forbidden

| Reason | Meaning |
| --- | --- |
| `template_dev_mode_role` | Drafts and test instances (their creation, lifecycle calls and tool tokens) need an owner, an admin or an API key with a tool permission. |
| `secret_not_available` | An exec or terminal start names a secret (`details.names`) this workspace may not use; nothing was started. Also returned for starts while a bound secret's status is `not_allowed`. |

### 404 not_found

| Reason | Meaning |
| --- | --- |
| `draft_not_found` | The template has no draft. |
| `version_not_found` | No such template version. |
| `path_not_found` | The path is not in the version's file tree (`details.path`). |
| `checkpoint_not_found` | No such checkpoint of the workspace. |
| `draft_state_not_found` | No such draft state. |
| `package_not_found` | No package with that name in the ecosystem. |
| `log_not_available` | The build has no full log yet; use the log tail on the build. |
| `log_expired` | The build's full log is past its retention. |

### 409 conflict

| Reason | Retryable | Meaning | What to do |
| --- | --- | --- | --- |
| `operation_in_progress` | yes | Another lifecycle operation runs on the workspace (`active_operation_id`, `active_operation_kind`). | Wait for it, then retry. |
| `workspace_not_running` | yes | A tool token was requested while the workspace is suspending, or while a resume that was asked for has not started yet; or a browser stream while it is not running (`observed_state`, `desired_state`). A suspended workspace does get a tool token: the cell answers it only the reads of the workspace's disk. | Open or resume the workspace, then request the token. |
| `workspace_deleted` | no | The workspace is deleted; keys are never reused. | Use a new key. |
| `not_running` | no | Suspend, or suspend when idle, of a workspace that is not running. | Refresh the workspace. |
| `already_running`, `not_suspended` | no | Resume of a workspace that is not suspended. | Nothing to do. |
| `not_snapshottable`, `not_forkable`, `not_resettable` | no | The workspace is not running or suspended. | Refresh the workspace. |
| `key_in_use` | no | The fork's target key is taken. | Choose another key. |
| `lifetime_mismatch` | no | The key is open with another `lifetime` (`lifetime`, `requested_lifetime`). | Omit `lifetime`, or use another key. |
| `session_lifetime` | no | A session workspace cannot be suspended, now or when idle, and has no idle policy. | Close it, or fork it to keep its state. |
| `not_session` | no | Close of a persistent workspace. | Suspend or delete it instead. |
| `legacy_disk_layout` | no | Reset, save as template and changes need a `layered` workspace. | Use a layered workspace. |
| `layout_unsupported` | no | The template version needs a disk layout that is not available here, or it cannot run a file-first workspace (`disk_layouts`). | Use another template version. |
| `not_supported_for_mode`, `mode_mismatch` | no | A call that the workspace's mode does not have, or a reopen with the other mode (see [file-first workspaces](#file-first-workspaces)). | Use what the mode offers, or omit `mode`. |
| `template_not_layered` | no | A draft's base is not layered-capable. | Choose another base. |
| `draft_exists` | no | The template already has a draft (`workspace_id`). | Use that draft. |
| `draft_stale` | no | The template got a newer version since the draft was opened (`latest_version`, `draft_base_version`). | Start a new draft from the latest version. |
| `build_in_progress` | yes | Another build of the template is unfinished (`build_id`). | Wait for it, then publish. |
| `build_finished` | no | Cancel of a build that already finished. | Nothing to do. |
| `file_list_unavailable` | no | The version has no file list (published before file lists existed). | Open a draft or test instance to see its files. |
| `file_list_indexing` | yes | The file list is still being indexed. | Retry shortly. |
| `guest_feature_unavailable` | no | The workspace's template cannot do this (for example list changes). | Use a newer template version. |
| `package_index_unavailable` | pip: yes; apt: no | The base has no apt index, or the pip name index is not built yet. | Type the package name; the build checks it. |
| `template_archived`, `version_archived` | no | The template or version is archived. | Use another template or version. |
| `name_taken` | no | A secret with that name exists in the scope. | Choose another name, or rotate the existing secret. |
| `secret_deleted` | no | The secret is deleted. | Create it again. |
| `concurrent_rotation` | yes | Another rotation of the secret ran at the same time. | Retry. |
| `version_mismatch` | no | Egress policy or spend policy update with a stale `If-Match` (`current_version`). | Read the policy again and re-apply. |
| `name_in_use` | no | A live volume of the project has that name. | Choose another name. |
| `volume_attached` | no | Delete of a volume that is still attached (`attachments`). | Detach it, or delete with `force`. |
| `volume_creating`, `volume_not_available` | yes while creating | The volume is not `available` yet (`volume_state`). | Wait until it is available. |
| `volume_not_shared` | no | The volume belongs to another project. | Use a volume of this project. |
| `already_attached`, `detach_in_progress`, `attach_in_progress` | `attach_in_progress`: yes | The volume is already attached with another path or mode, or an attach or detach is still running. | Detach first, or retry shortly. |
| `mount_path_conflict` | no | The mount path equals or nests with another mount of the workspace. | Choose another path. |

Two retryable `409`s have no reason: a request whose `Idempotency-Key` is still in progress, and an open of a key that
another open is creating at the same moment. Retry the same request (with the same key).

### 422 validation_failed

Workspaces, inputs and secrets:

| Reason | Meaning |
| --- | --- |
| `reserved_key_prefix` | Workspace keys starting with `sf:` are reserved. |
| `mode_not_available` | This deployment does not offer file-first workspaces (`field: mode`). |
| `not_supported_for_mode` | `lifetime: "session"` for a file-first workspace, which is always persistent (`field: lifetime`). |
| `confirm_destructive_required` | Reset needs `{"confirm_destructive": true}`. |
| `secret_not_available` | A secret name to bind is unknown or not usable by this workspace (`details.names`); nothing changed. |
| `env_collision` | A bound secret has the name of a template environment variable or text input (`details.name`). |
| `input_required` | A required input is missing (`details.names`, `details.kind`). |
| `input_unknown` | The template declares no input with that name (`details.names`). |
| `input_invalid` | An input value is not a string, is a secret input, is over 4096 bytes, or contains CR, LF or NUL. |
| `egress_widening` | A workspace egress policy allows more than the template's egress ceiling (`outside`). |
| `reserved_prefix`, `nul_character`, `too_large` | Secret names starting with `SHARDFLUX_`, a value with a NUL byte, or a value over the size limit. |

Workspace tools (the cell gateway):

| Reason | Meaning |
| --- | --- |
| `invalid_cwd` | An exec, execution or terminal start named a `cwd` that is not an absolute path (or has a NUL byte, or is over 4096 bytes); `details.field` is `cwd`. Relative paths are not resolved: the message names the absolute path the `cwd` likely means, e.g. `use "/home/user/app"`. Nothing ran. |

Templates and builds:

| Reason | Meaning |
| --- | --- |
| `invalid_recipe` | The recipe does not match the recipe v2 schema (`details.field`). |
| `invalid_path`, `invalid_package`, `invalid_host` | A path, package name or host in the recipe is invalid (`details.field`). |
| `invalid_settings` | `settings` do not match the settings schema, or `settings.defaults` was given together with `defaults`. |
| `base_not_found`, `base_archived`, `base_not_published`, `architecture_not_supported` | The recipe's base cannot be used. |
| `base_not_layered` | A recipe v2 build needs a layered-capable base. |
| `base_required` | A draft of a template without a published version needs `base`. |
| `language_unavailable` | The base does not offer that language or version (`available`). |
| `language_conflict` | The base already has another version of the language. |
| `too_many_files`, `too_many_steps`, `too_many_hosts`, `recipe_too_large` | The recipe is over a size limit (`limit`). |
| `platform_owned_path` | A file destination is owned by the platform. |
| `upload_required` | A file entry still names a local `from` path: upload it and send `upload: "sha256:<hex>"`. |
| `upload_missing` | The organization has no upload with that SHA-256. |
| `upload_digest_mismatch` | The same SHA-256 was announced with another size, or the bytes do not match. |
| `upload_too_large` | An upload is over 5 GiB. |
| `extra_hosts_without_auto`, `allow_hosts_without_allowlist`, `allowlist_empty` | The build network settings are inconsistent. |
| `services_unsupported` | The base's guest agent cannot run services; remove `settings.services` or use a newer base. |
| `invalid_defaults` | An idle timeout without `lifetime: "session"`. |
| `update_policy_not_available` | `update_policy: "auto"` is not available. |
| `too_many_acknowledged_findings` | More than 200 acknowledged scan findings. |
| `platform_template_slug`, `reserved_template_slug`, `slug_not_supported_by_builder` | The slug cannot be used for an organization template. |

Egress policy validation returns every problem in `details.issues`, each with `path`, `reason` and `message`.

Spend policy (overage and its spend cap, changed by an owner or billing member; `details.field` names the field):

| Reason | Meaning |
| --- | --- |
| `overage_unavailable` | The plan has no overage (Free, or no active subscription). |
| `spend_cap_required` | Turning overage on needs a spend cap. |
| `spend_cap_below_minimum` | The cap is below $1 (`min_minor`). |
| `spend_cap_above_plan_price` | The cap is above the plan price (`max_minor`). |
| `spend_cap_below_charges` | Overage already charged more than that this period (`charges_minor`). Turning overage off is always allowed. |

### 503 dependency_unavailable

| Reason | Meaning |
| --- | --- |
| `package_search_unavailable`, `package_index_loading` | Package search is temporarily unavailable. Retry. |
| `uploads_not_configured` | Template uploads are not available in this environment. |
| `build_log_bucket_not_configured`, `signing_failed` | The full build log cannot be served right now; use the log tail on the build. |

### File tools, parking and hosts

Reasons of the cell gateway for [search, patches and reads of sleeping workspaces](https://docs.shardflux.dev/guides/files.md) and for
[parked workspaces](https://docs.shardflux.dev/concepts/lifecycle.md#idle-running-workspaces-are-parked):

| Code and reason | Retryable | Meaning | What to do |
| --- | --- | --- | --- |
| `409 conflict` `revision_mismatch` | no | A patch's `expected_revision` is not the file's current revision (`details.current_revision`, `absent` for a missing file). Nothing changed. | Read the file again and redo the edit. |
| `422 validation_failed` `edit_not_found` | no | An edit's old text does not occur in the file (`details.index`, counting from 0). Nothing changed. | Fix that edit. |
| `422 validation_failed` `edit_ambiguous` | no | An edit's old text occurs more than once (`details.index`). Nothing changed. | Include more surrounding text, or set `replace_all`. |
| `422 validation_failed` `edit_not_text` | no | The file is not UTF-8 text, so edits cannot apply. | Replace it with `content`. |
| `422 validation_failed` `patch_invalid` | no | The patch does not have exactly one of `edits` and `content`. | Fix the request. |
| `409 workspace_not_running` `offline_unavailable`, `offline_budget` | yes | A read of a suspended workspace could not be answered from its disk. | Resume the workspace and read again. The SDKs, the CLI and the MCP server do. |
| `503 dependency_unavailable` `offline_changed` | yes | The disk changed during a read of a sleeping workspace, because it started running. | Retry: the running workspace answers. |
| `503 service_unavailable` `host_capacity` | yes | The workspace's host has no room to wake it right now (`Retry-After`). The call was not executed. | Retry after `Retry-After`. |
| `503 service_unavailable` `wake_failed` | yes | Waking the parked workspace failed; its state is intact. The call was not executed. | Retry. |
| `409 workspace_busy` `workspace_fenced` | yes | A lifecycle operation is taking over the workspace. The call was not executed. | Wait for the operation, then retry. |
| `409 conflict` `host_feature_unavailable` | no | The workspace runs on a host that does not have this call yet (`details.feature`: `file_search` or `file_patch`); reads and stats there come without revisions. It lasts until the workspace runs on an upgraded host. | Use another way: `grep` in a command instead of a search, a read and a write instead of a patch. |

### File-first workspaces

Reasons of the API and the cell gateway for [file-first workspaces](https://docs.shardflux.dev/concepts/file-first.md):

| Code and reason | From | Retryable | Meaning | What to do |
| --- | --- | --- | --- | --- |
| `409 conflict` `not_supported_for_mode` | API, cell | no | The call does not exist for the workspace's mode (`details.mode`, `details.operation`): suspend, resume, snapshot, fork, reset, save as template, volumes, idle policies, exec sessions, terminals, processes, git, browser, changes or keepalives of a file-first workspace, or reading an execution of a processful one. | Use what the mode offers. |
| `422 validation_failed` `not_supported_for_mode` | API, cell | no | `lifetime: "session"` when opening a file-first workspace, or `execution_id` / `output_limit_bytes` in an exec of a processful workspace. | Leave the field out. |
| `409 conflict` `mode_mismatch` | API | no | The key belongs to a workspace of the other mode (`mode`, `requested_mode`). A mode never changes. | Omit `mode`, or use another key. |
| `422 validation_failed` `mode_not_available` | API | no | This deployment does not offer file-first workspaces. | Use a processful workspace. |
| `409 conflict` `layout_unsupported` | API | no | The template version has no layered disks, which executions need (`disk_layouts`). | Use a layered template version. |
| `409 conflict` `tree_revision_mismatch` | cell | no | `If-Match` names another revision than the tree's (`details.current_tree_revision`). Nothing changed. | Read what changed, then retry with the new revision. |
| `404 not_found`, `422 validation_failed` `outside_tree_root` | cell | no | A read (404) or a change (422) of a path outside `/home/user` (`details.tree_root`). | Use a path under `/home/user`. |
| `409 workspace_busy` `execution_in_progress` | cell | yes | An execution runs (`details.execution_id`); a second execution or a file change must wait. | Retry when it has ended. The SDKs and the CLI wait. |
| `409 conflict` `execution_id_reused` | cell | no | The execution id was already used for a different request. | Use a new id for a new command. |
| `503 service_unavailable` `no_execution_host` | cell | yes | No host has room for the execution now (`Retry-After`). Nothing ran, and the id stays unused. | Retry with the same id. The SDKs and the CLI do. |

An execution that ended `failed` or `lost` published nothing. Its result carries `error` with `code`, `message`,
`retryable` (whether a new execution may succeed) and `details.reason`, for example `exec_failed_to_start`,
`lease_expired`, `host_unreachable`, `host_restarted`, `tree_moved`, `blob_missing`, `blob_corrupt`,
`layout_unsupported` or `guest_feature_unavailable`.

### With a person's session

Account actions on `/v1` need a person's CLI session (`sfu_...`, from `/v1/auth` or `shard auth login`); an API key
gets `403 forbidden` for them. These are the reasons they add to the tables above:

| Code and reason | Meaning | What to do |
| --- | --- | --- |
| `409 conflict` `subscription_exists` | Checkout for an organization that already has a subscription. | Change plans in the billing portal (`POST .../billing/portal-sessions`, `shard billing portal`). |
| `409 conflict` `checkout_superseded` | A concurrent checkout request for another plan replaced this one. | Retry. |
| `409 conflict` `subscription_active`, `checkout_pending` | Organization deletion while a subscription is active (`billing_state`, `current_period_end`) or a Checkout session is open (`checkout_id`, `expires_at`). | Cancel the subscription in the billing portal, or let the checkout finish or expire. |
| `409 conflict` `organization_ownership` | Account deletion while you are the only owner of an organization with other members, or its only member (`details.organizations`). | Transfer ownership of those organizations, or delete them. |
| `409 conflict` `last_owner` | A role change or removal would leave the organization without an owner. | Make someone else an owner first. |
| `409 conflict` `concurrent_invitation` | An invitation to the same address is being created at the same moment. | Retry. |
| `409 conflict` `export_expired` | The data export's download is past its expiry (`expires_at`). | Create a new export. |
| `409 conflict` `export_too_large` | An audit export would exceed its row limit (`max_rows`, `matched_rows_at_least`). | Narrow the time window or the filters. |
| `422 validation_failed` `plan_not_purchasable` | Checkout for a plan that cannot be bought (`field: plan_key`). | Choose a plan from `GET /v1/billing/catalog`. |
| `429 rate_limited` `mfa_locked` | Too many wrong two-factor codes (`retry_after_seconds`). | Wait, or reset the password by email. |

## Operation errors

A failed operation (`state: failed` or `canceled`) carries `error: {code, message, retryable, details}`. The SDKs raise
it as `OperationFailedError` (`errorCode`, `retryable`).

| `error.code` | Retryable | Meaning | What to do |
| --- | --- | --- | --- |
| `capacity_unavailable` | yes | No host could admit the start within 15 minutes of its creation. Nothing was started; a suspended workspace stays suspended. `details`: `reason`, `waited_seconds`, `deadline_seconds`, `required`. | Send the same request again later. |
| `invalid_resources` | no | The start needs more CPU, memory or disk than any host has (`details.reason: exceeds_host_class`, `required`, `host_class`, `exceeded`). | Open with smaller caps. |
| `startup_failed` | yes | A start command or service of the template failed (`details.reason`: `startup_failed`, `service_not_ready`, `secrets_unavailable`, `secret_not_available`, `guest_feature_unavailable`). The workspace keeps running for inspection; `startup` names the step, exit code and output tail. | Fix the cause; the next open runs the failed step again. |
| `layout_unsupported` | no | The host cannot run the template's disk layout. | Use another template version. |
| `dependency_failed` | no | The operation depended on one that failed (for example a draft capture behind a test instance). | Check the other operation. |
| `layer_capture_failed` | no | Capturing a draft state or a template source failed. | Retry the capture. |

Operation `state_reason` values are progress, not errors: `no_ready_host` while `capacity_pending` (waiting for a
host), `template_downloading` while `running` (the host is fetching the template).

Template builds from a workspace or a draft report `failure.code` on the build: `source_unavailable` (the capture
failed or the checkpoint is gone), `resource_limit` (`details.limit`: `template_org_bytes_max` or
`template_file_entries_max`), `scan_failed` (the credential scan found secrets: the paths are listed; build again with
`acknowledged_scan_findings`), `compatibility_failed` and `publish_failed`.

## Client errors

The clients add their own errors for failures that never reached the API:

- TypeScript SDK: `ExecStartError` (0.10.0+: a command that could not start; a `ShardfluxApiError` with
  `exec_failed_to_start`), `OperationFailedError`, `OperationTimeoutError`, `ShardfluxProtocolError`, `ToolArgumentError`,
  `CheckoutTimeoutError` (0.9.0+) and the template errors ([reference](https://docs.shardflux.dev/reference/typescript.md#errors)).
  `NotSupportedForModeError` and `TreeRevisionMismatchError` (0.9.0+) are `ShardfluxApiError`s for
  `not_supported_for_mode` and `tree_revision_mismatch`; the SDK raises the first before sending a call the mode does
  not have (`local: true`).
- Python SDK: the same classes under `ShardfluxError`, with `CheckoutTimeoutError`, `NotSupportedForModeError` and
  `TreeRevisionMismatchError` from 0.5.0, and `ExecStartError` from 0.6.0 ([reference](https://docs.shardflux.dev/reference/python.md#errors)).
- CLI: exit codes and local codes such as `usage`, `interrupted` and (0.5.0+) `checkout_ended` and
  `execution_failed` (an execution that ended `failed` or `lost`, exit 6) ([reference](https://docs.shardflux.dev/reference/cli.md#exit-codes)).
- MCP server: `invalid_arguments`, `workspace_pinned`, `timeout`, `operation_failed` and others. An error result
  carries the refusal's `reason`, and from 0.4.0 a `hint` for the file-first and host refusals
  ([reference](https://docs.shardflux.dev/reference/mcp.md#errors)).
