# Errors

> The problem details format, every error code the API returns, and what to do about each one.

Errors are JSON in the `application/problem+json` format. Branch on `code`, which is stable; `message` is for people and may change.

```json
{
  "type": "https://api.vmyard.example/problems/insufficient_scope",
  "title": "Forbidden",
  "status": 403,
  "code": "insufficient_scope",
  "message": "The API key preset does not allow this action.",
  "remediation": "Use an API key whose preset includes this action.",
  "correlation_id": "0d9f3b9e-5c1a-4b7e-9e0b-2f4c6a8d1e3f",
  "field_errors": {}
}
```

| Field | Meaning |
| --- | --- |
| `code` | The machine-readable error code. |
| `status` | The HTTP status, repeated. |
| `message` | What went wrong, in words. |
| `remediation` | What to do next. |
| `correlation_id` | Quote it when you contact support. It is also sent as the `x-correlation-id` header. |
| `field_errors` | Field name → problem, for invalid requests. |
| `retry_after_seconds` | When a retry can succeed; also sent as `Retry-After`. |

The SDKs turn problems into typed errors: `BadRequestError` (400), `AuthenticationError` (401), `PaymentRequiredError` (402), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `PayloadTooLargeError` (413), `UnprocessableEntityError` (422), `RateLimitError` (429), and `ServerError` (5xx), all subclasses of `APIError` with `code`, `message`, `remediation`, `correlationId`, `fieldErrors`, and `retryAfterSeconds` (snake_case in Python).

## Authentication and access

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 401 | `unauthenticated` | No valid session. Sign in again, or send an API key. |
| 401 | `invalid_api_key` | The key is malformed, unknown, expired, or revoked. Create or rotate a key. |
| 403 | `csrf_invalid` | A browser-session change is missing the `x-csrf-token` header, or it is wrong. Refresh and retry once. |
| 403 | `session_required` | This route needs a signed-in browser session; API keys can't use it (key management, sign-out, device approval, sign-in methods). |
| 403 | `insufficient_scope` | The key's preset doesn't allow this route. See [presets](/docs/guides/api-keys#presets). |
| 403 | `organization_mismatch` | `X-Organization-ID` doesn't match the API key's organization. Omit it or send the right one. |
| 403 | `forbidden` | Your role can't do this (for example a Viewer starting a machine, or a non-admin reading webhooks). |
| 403 | `organization_suspended` | The organization is suspended, so nothing can create, start, or reach into its machines. Reads, billing, keys, and settings still work. Contact support. See [Suspended organizations](/docs/guides/billing#suspended-organizations). |
| 403 | `recent_auth_required` | Linking or removing a sign-in method needs a sign-in within the last 15 minutes. Sign in again. |
| 404 | `resource_not_found` | Nothing with that ID in this organization. |

## Requests

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 405 | `method_not_allowed` | That HTTP method isn't supported on this path. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was already used with a different body. Retry the original body, or use a new key. |
| 413 | `body_too_large` | The request body is too large. |
| 422 | `invalid_request` | A field is missing or invalid. `field_errors` names it. On `GET /v1/machines` this includes an unknown `sort`, `order`, `state`, or `desired_state`, a `q` over 128 characters, and a cursor made for another sort or order: start again from the first page. |
| 429 | `rate_limited` | Too many attempts (sign-in, email codes, device codes, or more than 5 organizations created in 24 hours). Wait `Retry-After` seconds. |
| 500 | `internal_error` | Something failed on VMYard's side. Retry later; quote the `correlation_id`. |
| 503 | `creates_quiesced` | Machine creation is paused on this server. Try again later. |

## Machines, commands, and files

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 409 | `machine_deleted` | The machine was deleted; its settings can't change. |
| 409 | `machine_not_created` | The machine's create is still pending or didn't succeed, so only a delete is accepted. |
| 409 | `command_not_started` | The background command's start operation hasn't succeeded, so it can't be killed yet. Wait for it. |
| 422 | `timeout_exceeds_runtime_limit` | `timeout_ms` is above the maximum (600,000). Lower it, or use a background command. |
| 404 | `file_not_found` | No file at that path. |
| 413 | `file_too_large` | The file is over 32 MiB. |
| 422 | `file_not_text` | The file isn't UTF-8. Read it with `encoding=base64`. |
| 502 | `guest_action_failed` | The machine didn't complete a read. Check the operation and retry. |
| 404 | `path_not_found` | The folder to list or download doesn't exist in the machine. List its parent to see what does. |
| 422 | `not_a_directory` | The path to list or download is a file. Read it with `GET /v1/machines/{id}/files` instead. |
| 422 | `path_not_allowed` | The path resolves, through a symlink, outside `/work`, `/root`, and `/home`. Browse the symlink's target by its own path. |
| 422 | `listing_too_large` | The listing is too large to return. Ask for a smaller `depth` or a deeper `path`. |
| 413 | `archive_too_large` | The folder is over 512 MiB. Download smaller folders, or copy it over [SSH](/docs/guides/ssh). |
| 503 | `files_unavailable` | This server doesn't browse files live. Read and write files by path instead. |
| 409 | `machine_not_running` | The machine must be running for this action. Start it and wait until it is observed running. |
| 409 | `machine_not_stopped` | The machine must be stopped for this action. Stop it and wait until the stop succeeds. |
| 410 | `output_expired` | The command's stored output was removed after the retention period. Its exit code, flags, timing, and sizes remain. See [Command history](/docs/guides/command-history#retention). |

## Web terminal

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 403 | `origin_not_allowed` | The terminal WebSocket came from a page other than the console's configured address. Open the console from that address. |
| 403 | `terminal_ticket_invalid` | The terminal ticket is missing, expired (30 seconds), already used, or for another machine or session. Mint a new one. |
| 409 | `terminal_refused` | The machine (16) or its host (64) has too many terminals. Close some. |
| 429 | `terminal_limit_reached` | You or the organization have the maximum number of open terminals. Close one first. |
| 503 | `terminals_unavailable` | This deployment has no runtime for terminals. |
| 503 | `runtime_unavailable` | The machine's host can't be reached. Retry shortly. |

Opening a terminal also needs a key preset above `read_only` (`insufficient_scope`), and a machine that is running (`machine_not_running`). See [Web terminal](/docs/guides/web-terminal#errors).

## Coding agents

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 403 | `organization_suspended` | The organization is suspended, so agents can't run in its machines. Contact support. |
| 404 | `conversation_not_found` | No conversation with that ID on this machine. List them with `GET /v1/machines/{id}/agents/conversations`. |
| 409 | `agent_turn_running` | The conversation already has a running turn. Steer it, interrupt it, or wait for it to end. |
| 409 | `provider_mismatch` | A conversation keeps the agent it started with. Leave `provider` out, or start a new conversation. |
| 409 | `no_active_turn` | Nothing is running to steer or interrupt. Send a prompt instead. |
| 503 | `agents_unavailable` | This deployment or the machine's host can't run agents. |

See [Coding agents](/docs/guides/agents).

## Desktop

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 403 | `desktop_ticket_invalid` | The desktop ticket is missing, expired (30 seconds), already used, or for another machine, session, or a terminal. Mint a new one. |
| 429 | `desktop_limit_reached` | The machine has 4 viewers, or you or the organization have the maximum number of open desktops. Close one first. |
| 503 | `desktop_unavailable` | This deployment has no desktops, or the runtime refused: the machine's image has no desktop, or its host is at a limit. |
| 503 | `browser_unavailable` | The runtime refused browser mode: the machine's image has no browser session, or its host predates browser mode. Open the full desktop instead. |

Opening a desktop also needs a key preset above `read_only` (`insufficient_scope`), the console's address (`origin_not_allowed`), and a running machine (`machine_not_running`). See [Desktop](/docs/guides/desktop).

## Machine metrics

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 409 | `machine_not_running` | Live metrics need a running or sleeping machine. Read the one-minute history instead. |
| 503 | `runtime_unavailable` | The machine's host couldn't be reached for live samples. One-minute history still works; retry later. |
| 503 | `metrics_unavailable` | This server has no runtime for live samples. Use the one-minute history. See [Machine metrics](/docs/guides/machine-metrics). |

## Snapshots and templates

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 409 | `snapshot_pending` | The push is still running, so it can't be deleted yet. Wait for it to settle. |
| 409 | `snapshot_not_ready` | The template's push hasn't finished, or failed. Wait, or use another. |
| 409 | `snapshot_limit_reached` | The machine has 20 live API pushes. Delete one first. |
| 409 | `template_limit_reached` | The organization has 10 live templates. Delete one first. |
| 409 | `template_name_taken` | Another live template in the organization has that name. |
| 422 | `unknown_template` | A create named a template that doesn't exist in the organization. |
| 404 | `no_snapshot` | The machine has no ready snapshot yet. Push one, or wait for the next [automatic snapshot](/docs/guides/automatic-snapshots). |

A fork needs a running machine and a snapshot or template a stopped one (`machine_not_running`, `machine_not_stopped` above). See [Snapshots and tiers](/docs/guides/snapshots), [Templates](/docs/guides/templates), and [Fork and restore](/docs/guides/fork-and-restore).

## Organizations and alerts

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 403 | `waitlisted` | VMYard is invite-only and this account isn't approved yet. Join the waitlist at the problem's `waitlist_url`; you get an email when you're in. Reads, stopping and deleting machines, billing, and settings still work. |
| 409 | `waitlist_decision_conflict` | (Operators) The waitlist entry can't move that way: only a pending entry can be rejected, and only an approved one revoked. |
| 409 | `already_member` | The invited person, or you, already belong to the organization. |
| 409 | `invitation_closed` | The invitation was already accepted, revoked, or has expired. Ask for a new one. |
| 403 | `invitation_email_mismatch` | None of your sign-in addresses is the invited one. Sign in with the invited address, or link it first. |
| 409 | `last_owner` | The organization's only owner can't be removed or demoted. Transfer ownership first. |
| 409 | `subscription_active` | An organization with a renewing plan can't be deleted. Cancel the plan and wait for its period to end. |
| 503 | `invitations_unavailable` | This deployment can't send invitations: email or the console's address (`ui_base_url`) isn't configured. |
| 409 | `organization_name_taken` | You already created an organization with that name (names are unique per creator, ignoring case). |
| 400 | `invalid_unsubscribe_token` | The unsubscribe link is damaged, or the server's session secret was rotated since it was sent. Change the alert from the console's Alerts page instead. See [Email alerts](/docs/guides/email-alerts#unsubscribe-links). |

## Ports and SSH

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 503 | `ingress_unavailable` | This deployment has no edge configured, so ports and SSH aren't available. |
| 409 | `port_limit_reached` | The machine already has 16 exposed ports. Close one first. |
| 404 | `port_not_exposed` | Expose the port before asking for a token. |

## Billing and limits

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 402 | `billing_required` | The spendable balance is at or below the minimum balance. Buy credits or a plan. |
| 402 | `spend_cap_reached` | This month's credit spend reached the spend cap. Raise or remove it, or wait for next month. |
| 402 | `card_verification_required` | The deployment asks trials to verify a card before machines start. An Owner or Billing member starts a [card check](/docs/guides/billing#card-check); the card is not charged. |
| 403 | `trial_machine_class_not_allowed` | Trials can use only `small` and `default`. Choose a smaller size, or buy credits or a plan. |
| 409 | `plan_not_priced` | The plan has no current price. Choose another from `/v1/plans`. |
| 429 | `limit_reached` | A concurrency or start-rate limit. `field_errors.limit` says which; start windows send `Retry-After`. |
| 409 | `insufficient_credit` | (Operators) A claw-back is larger than the organization's credit balance. |
| 409 | `billing_customer_missing` | The payment portal needs a completed purchase first. |
| 409 | `payment_method_missing` | Automatic refill needs a default payment method. Add one in the payment portal. |
| 409 | `subscription_missing` | There is no paid plan subscription to cancel or resume. |
| 502 | `payment_provider_error` | The payment provider failed. Retry later. |
| 503 | `billing_unavailable` | Online payments aren't configured on this deployment. |
| 400 | `invalid_signature` | (Stripe events endpoint) The `Stripe-Signature` didn't verify. |
| 400 | `invalid_event` | (Stripe events endpoint) The event couldn't be read. |

See [Usage, billing, and limits](/docs/guides/billing#limits).

## Environments

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 409 | `environment_name_taken` | Another environment in the organization has that name. |
| 409 | `environment_limit_reached` | The organization already has 50 environments. Delete one first. |
| 409 | `environment_upgrade_conflict` | A machine's environment version changed while the upgrade was being made. Retry. |
| 404 | `variable_not_found` | The environment has no variable with that name. |
| 422 | `unknown_environment` | A create named an environment that doesn't exist, or combined `environment_id`, `environment`, and `no_env`. |
| 503 | `environments_unavailable` | This deployment has no environment encryption keys, so environments can't be changed or revealed. |

See [Environments](/docs/guides/environments).

## Webhooks

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 409 | `webhook_limit_reached` | The organization already has 10 endpoints. Delete one first. |
| 503 | `webhooks_unavailable` | This deployment has no keys to encrypt signing secrets, so endpoints can't be created and secrets can't be rotated. Secrets are never stored in plaintext. |

## Sign-in

These come back from the email and linking routes:

| Status | Code | Meaning and fix |
| --- | --- | --- |
| 400 | `invalid_code` | The email code is wrong, expired, used, or replaced by a newer one. Request a new code. |
| 404 | `unsupported_provider` | That sign-in method isn't configured. |
| 409 | `identity_in_use` | That identity already belongs to another user. |
| 409 | `last_login_method` | You can't remove your only sign-in method. Link another first. |
| 503 | `email_unavailable` | The email couldn't be sent. Retry later. |
| 503 | `oidc_unavailable` | The sign-in provider couldn't be reached. |
| 401 | `oidc_failed` | The provider's answer couldn't be verified. Start again. |

Browser sign-in redirects to `/sign-in?error=<code>` with one of `access_denied`, `identity_in_use`, `oidc_failed`, `oidc_unavailable`, `session_expired`, or `unsupported_provider`. See [Sign-in methods](/docs/guides/sign-in#sign-in-errors).

## Device sign-in

`POST /v1/auth/device/token` uses the OAuth error shape, `400 {"error", "error_description"}`, not problem details: `authorization_pending`, `slow_down`, `access_denied`, `expired_token`, `invalid_grant`, `invalid_request`, and `unsupported_grant_type`. See [CLI device login](/docs/guides/cli-login#the-protocol).

## Operation error codes

An [operation](/docs/concepts/operations) that ends `failed`, `uncertain`, or `correlation_miss` has an `error_code`. These are the ones you are most likely to see:

| `error_code` | Meaning |
| --- | --- |
| `exec_nonzero` | The command exited with a non-zero code. `exec_result` has the output. |
| `exec_timed_out` | The command ran past `timeout_ms`. |
| `exec_cancelled` | The command was cancelled. |
| `exec_no_exit_status` | The command ended without an exit status. |
| `setup_prerequisite_failed` | The create (or the environment apply it waits for) failed, so the setup script never ran. |
| `environment_prepare_failed`, `environment_decrypt_failed`, `environment_keys_unavailable` | Installing the machine's [environment](/docs/guides/environments) failed. The machine keeps running. |
| `environment_prerequisite_failed` | The create failed, so the environment was never installed. |
| `invalid_transition` | The machine's observed state doesn't allow this action. |
| `fleet_error` | The machine's host refused or failed the request, for example closing a port that isn't exposed, or an [internet access](/docs/guides/networking) change on a host without guest networking or with internet access turned off. Nothing changed. |
| `restart_unsupported` | The machine's host can't restart its machines yet, so the [restart](/docs/concepts/lifecycle#restart) never started. Nothing changed and the machine kept running; stop and start it instead. |
| `network_mismatch` | The host answered an internet access change with a different policy than the one requested, so VMYard did not report success. Read the machine's `network` before retrying. |
| `runtime_timeout`, `runtime_transport` | The host didn't answer in time or couldn't be reached. |
| `correlation_missing`, `correlation_mismatch`, `correlation_duplicate`, `ingress_mismatch` | The host's view of the machine didn't match the request, so VMYard did not report success. |
| `snapshot_outcome_unknown` | A snapshot push or template save stayed uncertain through five attempts. The machine is unchanged; take a new snapshot. |
| `snapshot_copy_discarded` | An [automatic snapshot](/docs/guides/automatic-snapshots) lost its copy of the running machine before the upload finished. The machine is unchanged; the next interval takes another. |
| `guest_inventory_unavailable` | VMYard couldn't yet confirm the machine's state after a guest action; the operation stays `uncertain` and is retried. |

Treat any other code as a failure too, and quote the operation ID when you ask for help.
