# Errors

Every refusal is an RFC 9457 Problem, served as `application/problem+json`. Its `code` is stable: branch on that, not on the title or the detail.

## The shape of a Problem

```json
{
  "type": "https://rent.dev/problems/unsupported_parameter",
  "title": "Unsupported parameter",
  "status": 400,
  "code": "unsupported_parameter",
  "docs_url": "https://rent.dev/docs/errors#unsupported_parameter",
  "detail": "Unsupported parameter: region.",
  "request_id": "req_01JCZ9Q4M8V7K2T3N5P6R8S0W1",
  "errors": [
    {
      "path": "region",
      "message": "Unsupported parameter."
    }
  ]
}
```

| Member | Type | Description |
| --- | --- | --- |
| `type` | `string` | Required. URI identifying the problem type. |
| `title` | `string` | Required. |
| `status` | `integer, 100 to 599` | Required. |
| `code` | `insufficient_balance \| account_frozen \| key_limit_reached \| payment_required \| payment_failed \| unsupported_parameter \| invalid_request \| unauthenticated \| forbidden \| not_found \| method_not_allowed \| conflict \| idempotency_in_flight \| gone \| idempotency_key_reused \| rate_limited \| internal_error \| not_implemented \| vendor_error \| no_capacity \| vendor_timeout \| claim_invalid \| claim_proof_invalid \| checks_failed \| node_exists \| supply_link_unavailable \| node_revoked \| node_suspended \| node_replay \| probe_in_progress \| payout_proof_required \| agent_upgrade_required` | Required. Stable machine-readable error code. |
| `detail` | `string` |  |
| `instance` | `string` |  |
| `request_id` | `string` |  |
| `docs_url` | `string` |  |
| `balance_required_micros` | `integer` | Additional balance, in micro-USD, that would let the request succeed. |
| `hold_id` | `string` | hold_ followed by a 26-character ULID. |
| `retry_after` | `integer, at least 0` | Seconds to wait before retrying; mirrors the Retry-After header. |
| `topup` | `TopupLinks` |  |
| `errors` | `array of object` | Per-field details. |

`docs_url` points at the code's entry on this page. `request_id` is also in the `X-Request-Id` response header of every call.

## Codes

| Code | Status | Title |
| --- | ---: | --- |
| [insufficient_balance](#insufficient_balance) | 402 | Insufficient balance |
| [account_frozen](#account_frozen) | 402 | Account frozen |
| [key_limit_reached](#key_limit_reached) | 402 | Key spend limit reached |
| [payment_required](#payment_required) | 402 | Payment required |
| [payment_failed](#payment_failed) | 402 | Payment failed |
| [unsupported_parameter](#unsupported_parameter) | 400 | Unsupported parameter |
| [invalid_request](#invalid_request) | 400 | Invalid request |
| [unauthenticated](#unauthenticated) | 401 | Authentication required |
| [forbidden](#forbidden) | 403 | Forbidden |
| [not_found](#not_found) | 404 | Not found |
| [method_not_allowed](#method_not_allowed) | 405 | Method not allowed |
| [conflict](#conflict) | 409 | Conflict |
| [idempotency_in_flight](#idempotency_in_flight) | 409 | Request with this Idempotency-Key in flight |
| [gone](#gone) | 410 | Gone |
| [idempotency_key_reused](#idempotency_key_reused) | 422 | Idempotency-Key reused with a different payload |
| [rate_limited](#rate_limited) | 429 | Rate limited by the vendor |
| [internal_error](#internal_error) | 500 | Internal error |
| [not_implemented](#not_implemented) | 501 | Not implemented |
| [vendor_error](#vendor_error) | 502 | Vendor error |
| [no_capacity](#no_capacity) | 503 | No capacity |
| [vendor_timeout](#vendor_timeout) | 504 | Vendor timeout |
| [claim_invalid](#claim_invalid) | 410 | Claim token used or expired |
| [claim_proof_invalid](#claim_proof_invalid) | 401 | Claim proof does not verify |
| [checks_failed](#checks_failed) | 422 | A blocking check failed |
| [node_exists](#node_exists) | 409 | Host already enrolled as a node |
| [supply_link_unavailable](#supply_link_unavailable) | 503 | Node link unavailable |
| [node_revoked](#node_revoked) | 410 | Node revoked |
| [node_suspended](#node_suspended) | 403 | Node suspended |
| [node_replay](#node_replay) | 409 | Node request replayed |
| [probe_in_progress](#probe_in_progress) | 409 | Probe in progress |
| [payout_proof_required](#payout_proof_required) | 403 | Payout change needs a signed nonce |
| [agent_upgrade_required](#agent_upgrade_required) | 400 | Node agent too old |

### insufficient_balance

402. The balance cannot cover the hold this call needs. Add at least `balance_required_micros` through one of the `topup` links, then send the request again.

### account_frozen

402. The balance is below zero, so no new hold is taken. Top up until it is positive.

### key_limit_reached

402. This API key has reached its `spend_limit_micros`, counting what its running sandboxes hold. Raise the limit with `PATCH /v1/keys/{id}`, from a key without a limit, or use another key.

### payment_required

402. The x402 route was called without a payment. Sign the requirement in the `PAYMENT-REQUIRED` header and send the request again with `PAYMENT-SIGNATURE`.

### payment_failed

402. The x402 payment was refused; `detail` says why. Sign a new payment and send it again.

### unsupported_parameter

400. The request names something this API, or the endpoint the sandbox runs on, cannot do. `errors` names the parameter. Remove it and retry.

### invalid_request

400. The request is malformed. `errors` lists each field and what is wrong with it. An id in `routing.allow` that names no endpoint is one such field: `detail` lists the ids that exist.

### unauthenticated

401. No credentials, or a key that is unknown or revoked. Send `Authorization: Bearer rent_sk_…`. `POST /v1/accounts` creates an account and a key.

### forbidden

403. The access token lacks the scope this route needs; `detail` names it. Ask for a token with that scope. An API key with a spend limit gets this on a call that changes keys, agent keys, wallets or people, on one that changes how the account pays (setting auto-recharge or starting to save a payment method), on storing or deleting a vendor key, and on extending or resuming a sandbox another key started: use a key without a limit, or sign in. [The agent quickstart](/docs/agents#authenticate) lists what such a key can still do.

### not_found

404. Nothing has this id on your account. A resource that belongs to another account answers the same.

### method_not_allowed

405. The path exists but does not take this method.

### conflict

409. The resource is not in the state this call needs, such as pausing a sandbox that is not running. Read it and decide again.

### idempotency_in_flight

409. The first request with this `Idempotency-Key` is still running. Wait and send the same request again.

### gone

410. The pay link has expired. Any new 402 carries a fresh one.

### idempotency_key_reused

422. This `Idempotency-Key` was already used with a different request. Use a new key for a new request. On a signup it also means the account that key created has been used since, so no new API key is issued for it.

### rate_limited

429. A vendor's rate limit, passed through. Wait `retry_after` seconds and send the request again.

### internal_error

500. A fault on rent.dev's side. Retry, and quote `request_id` if it keeps happening.

### not_implemented

501. The route is documented but not served in this environment. `detail` says which.

### vendor_error

502. The vendor behind the endpoint failed. Send the request again.

### no_capacity

503. No endpoint can take the request now. Retry after `retry_after` seconds, or allow another endpoint in `routing.allow`. On a GPU pod it means RunPod has none of that GPU left: retry later, or ask for another `gpu_type` or `cloud_type`.

### vendor_timeout

504. The vendor did not answer in time. Read the resource before retrying, since the call may have gone through.

### claim_invalid

410. A claim token works once, before it expires. When `up` made the enrolment, run `rentdev-node up --re-enroll`. When the token was made elsewhere, make a new one there and run the same line again with it.

### claim_proof_invalid

401. The node key's signature over the claim token does not verify. Start again with a fresh enrolment.

### checks_failed

422. A blocking check failed on the node. `checks` lists each result with its fix; fix them and send the claim again before the token expires.

### node_exists

409. A live node already holds this host's fingerprint. Revoke that node first, then claim again.

### supply_link_unavailable

503. The node's tunnel could not be set up; `detail` names the limit when one was hit. Retry after `retry_after` seconds. When `owner_action_required` is true, rent.dev has to act first.

### node_revoked

410. The node is revoked for good. Enrol the machine again with `rentdev-node up --re-enroll`. A machine set up with a token made elsewhere holds no account key, and `--re-enroll` never makes one: make a new token there and run the same install line with it on stdin and `--re-enroll` added.

### node_suspended

403. The node is suspended and may only send beats and events. Fix what `rentdev-node doctor` reports, then run it again. Once its checks pass, it asks for a probe itself.

### node_replay

409. The node request reused a sequence number or nonce. Sign it again with a new one.

### probe_in_progress

409. A probe of this node is already running. Wait for it to finish, then send the request again.

### payout_proof_required

403. Changing payout details needs `signed_nonce`, signed by the account's agent key. Get a nonce from `GET /v1/accounts/nonce`, sign it and send the change again.

### agent_upgrade_required

400. The node agent is older than the platform accepts. Run `rentdev-node upgrade`.

## On the E2B-compatible paths

The E2B paths answer in E2B's own shape, `{code, message}`, so its SDKs can read them. A refusal from rent.dev carries the Problem code as `error_code` and keeps the Problem's extra members; see [E2B compatibility](/docs/e2b#billing).

## Over MCP

A tool that is refused answers an `isError` result whose structured content is the same Problem; see [the MCP server](/docs/mcp#results).
