Management API: Machines

Last updated 2 November 2026

View .md

Machines are not open yet. These pages describe Machines as they will open; until then, a create answers region_not_offered (422), “Machines are not offered in any region yet.”

The account's Machines (contracts/machines/machine-v2.json). Exec and file actions on a Machine are not offered yet. The overview covers what every operation shares: authentication, roles, errors, pagination, idempotency and If-Match.

List the account's Machines

GET /v1/machines · CLI: tonbo machine list

Needs account.read, which owner, admin and member hold. A narrowed key may call it.

The account's Machines by name, except those being deleted: managed Machines and, in a personal account, its devices (kind byoc, which PUT /machines/self registers) from every region, whatever machines.open says; unavailable_regions names the regions whose Machines on the page could not be read. A device answers id, name, kind, region, byoc (with its key_state, derived from its current login key), created_at and updated_at; its byoc.hostname and byoc.device_id are answered to the device's person only, never to an Agent key. A narrowed key never reaches a Machine. An organization holds managed Machines only (contracts/machines/machine-v2.json kinds_by_account_kind), so its list never answers a byoc Machine or its fields, and its pages and cursors are those of its managed Machines alone.

Parameters

NameInTypeRequiredDescription
cursorquerystringnoOpaque cursor tied to the original query shape. At least 1 characters.
limitqueryintegernoThe most items one page holds, 1 to 200; 50 when omitted. A page may hold fewer; read on with next_cursor until it is null. Default 50. 1 to 200.

Response

StatusAnswer
200Machines
FieldTypeRequiredDescription
dataarray of Machineyes
next_cursorstring or nullyesOpaque cursor of the next page; null on the last page.
unavailable_regionsarray of stringyes
HeaderStatusDescription
WWW-Authenticate401Authentication scheme required by this API.
Retry-After422, 429Seconds before the client should retry.
Example 200 body
{
  "data": [
    {
      "id": "<id>",
      "name": "<name>",
      "kind": "managed",
      "region": "<region>",
      "byoc": {
        "os": "darwin",
        "arch": "arm64",
        "client": "yield",
        "client_version": "<client_version>",
        "harnesses": [
          "claude-code"
        ],
        "last_seen": "2026-11-02T09:30:00Z",
        "key_state": "active",
        "hostname": "<hostname>",
        "device_id": "<device_id>"
      },
      "hostnames": {
        "https": "<https>",
        "api": "<api>"
      },
      "image": {
        "reference": "<reference>",
        "repository": "<repository>",
        "digest": "<digest>",
        "state": "pulling",
        "failure": {
          "code": "image_unavailable",
          "message": "<message>"
        }
      },
      "command": [
        "<command>"
      ],
      "shape": "<shape>",
      "ports": [
        {
          "port": 1,
          "access": "private",
          "default": false
        }
      ],
      "env": {},
      "sleep_after_seconds": 600,
      "idle_action": "suspend",
      "stop_grace_seconds": 10,
      "database": {
        "name": "<name>",
        "url": "<url>"
      },
      "logs": {
        "url": "<url>"
      },
      "state": "creating",
      "failure": {
        "code": "<code>",
        "message": "<message>"
      },
      "status": {
        "power": "asleep",
        "snapshot": null,
        "sleep": {
          "action": "<action>",
          "reason": "<reason>"
        },
        "conditions": [
          {
            "type": "wake_blocked",
            "reason": "payment_required",
            "since": "2026-11-02T09:30:00Z",
            "retry_at": "2026-11-02T09:30:00Z"
          }
        ],
        "awake": {
          "since": "2026-11-02T09:30:00Z",
          "connections": {
            "https": 0
          },
          "last_client_activity_at": "2026-11-02T09:30:00Z",
          "sleeps_at_earliest": "2026-11-02T09:30:00Z",
          "activation": "<activation>",
          "processes": {
            "running": 0,
            "detached": 0
          },
          "holds": [
            {
              "hold": "<hold>",
              "source": "member",
              "activation": "<activation>",
              "until": "2026-11-02T09:30:00Z",
              "actor": "<actor>",
              "created_at": "2026-11-02T09:30:00Z"
            }
          ],
          "max_awake_until": "2026-11-02T09:30:00Z"
        },
        "spec_current": false,
        "delete_at": "2026-11-02T09:30:00Z"
      },
      "activation": {
        "id": "<id>",
        "wake_gen": 1,
        "state": "admitted",
        "started_at": "2026-11-02T09:30:00Z",
        "revision": 1
      },
      "revision": 1,
      "created_by": "<created_by>",
      "created_at": "2026-11-02T09:30:00Z",
      "updated_at": "2026-11-02T09:30:00Z"
    }
  ],
  "next_cursor": "<next_cursor>",
  "unavailable_regions": [
    "<unavailable_regions>"
  ]
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)

Retries

  • A read: sending it again changes nothing.
  • 422 and 429 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS "https://api.tonbo.dev/v1/machines" \
  -H "Authorization: Bearer $TONBO_API_KEY"

Create a Machine

POST /v1/machines · CLI: tonbo machine create

Needs machines.write, which owner, admin and member hold. A narrowed key may not.

Any member creates Machines (machines.write); a public port also needs machines.public, and a narrowed key never does. The image is the reserved platform reference tonbo.dev/platform (machine-v2.schema.json $defs.platform_image_reference), which resolves nothing, is stored with repository and digest null and reads as state ready, or a public registry reference, which resolves to its canonical repository and linux/arm64 digest, stored with the reference, and reads as state pulling until a node has pulled it (state is computed, never stored; see getMachine); a registry reference is refused before anything is written, with 422 image_platform_unsupported when it has no linux/arm64 manifest and validation_error when it is not a public reference the resolver can fetch under machine-v2.json image.distribution (a malformed reference, an unknown or unavailable image, a redirected manifest or an oversized document). A name another Machine of the account holds answers 409 name_taken. The name, the HTTPS label <name>-<account> and the SSH username are then reserved for the account, and the Machine is recorded in its region, which is fixed for its life; without region it goes to the account's default creation region. A region where Machines are not offered (machines.open in contracts/regions/cells-v1.json) answers the shared 422 region_not_offered, and a shape that is not offered 422 shape_not_offered, before anything is written. The answer is 202 while the Machine is being created and 201 in every other state (machine-v2.json creation.create_status). Both carry Location, the Machine's URL, and the 202 also Retry-After, the seconds to wait before reading the Machine there again (machine-v2.json creation.poll_after_seconds), until its state is active or failed. A replay with the same Idempotency-Key answers the same Machine as it is now, so a replay of a Machine whose creation failed answers 201 with its failure; the key reused for another request answers 409 idempotency_conflict. Refusals carry the codes and statuses of contracts/machines/machine-v2.json errors.

Parameters

NameInTypeRequiredDescription
Idempotency-KeyheaderstringyesA value you choose for this one creation, 1 to 255 characters, such as a new UUID. Send a new one for each resource you create, and the same one only to retry that request after a timeout or a lost answer: the retry answers what the first request created, in its current state, and never creates a second. A key reused for a different database, stream or Machine answers 409. 1 to 255 characters.

Request body

application/json, required. Unknown fields are refused.

The fields below are the contract’s, with their types and limits; Manage Machines explains what each one does.

FieldTypeRequiredDescription
namestringyes1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
regionstringnoMatches ^[a-z]{2}(-gov)?-[a-z]+-[0-9]+$.
imagestringyes1 to 512 characters.
commandarray of string or nullno1 to 64 items.
shapestringyesMatches ^[a-z]-[0-9]+$.
portsarray of objectnoAt most 8 items.
ports[].portintegeryes1 to 65535.
ports[].accessone of "private" or "public"yesDefault "private".
ports[].defaultbooleanyes
envobject of stringnoAt most 128 entries. Each key matches ^[A-Za-z_][A-Za-z0-9_]{0,127}$.
sleep_after_secondsintegernoDefault 600. 30 to 86400.
idle_actionone of "suspend" or "release"noDefault "suspend".
stop_grace_secondsintegernoDefault 10. 0 to 230.

Response

StatusAnswer
201The Machine, no longer being created (a replay of a failed create carries its failure)
202The Machine, still being created
FieldTypeRequiredDescription
dataMachineyesThe Machine.
HeaderStatusDescription
ETag201, 202The Machine's ETag, "<id>.<revision>" for a managed Machine and "<id>" for a device (contracts/machines/machine-v2.json etag), opaque to clients. An update names it in If-Match, and a delete or a sleep may.
Location201, 202The Machine's URL, GET /v1/machines/{name}.
Retry-After202, 403, 409, 422, 429The seconds to wait before reading the Machine at Location again (machine-v2.json creation.poll_after_seconds).
WWW-Authenticate401Authentication scheme required by this API.
Example 201 body
{
  "data": {
    "id": "<id>",
    "name": "<name>",
    "kind": "managed",
    "region": "<region>",
    "byoc": {
      "os": "darwin",
      "arch": "arm64",
      "client": "yield",
      "client_version": "<client_version>",
      "harnesses": [
        "claude-code"
      ],
      "last_seen": "2026-11-02T09:30:00Z",
      "key_state": "active",
      "hostname": "<hostname>",
      "device_id": "<device_id>"
    },
    "hostnames": {
      "https": "<https>",
      "api": "<api>"
    },
    "image": {
      "reference": "<reference>",
      "repository": "<repository>",
      "digest": "<digest>",
      "state": "pulling",
      "failure": {
        "code": "image_unavailable",
        "message": "<message>"
      }
    },
    "command": [
      "<command>"
    ],
    "shape": "<shape>",
    "ports": [
      {
        "port": 1,
        "access": "private",
        "default": false
      }
    ],
    "env": {},
    "sleep_after_seconds": 600,
    "idle_action": "suspend",
    "stop_grace_seconds": 10,
    "database": {
      "name": "<name>",
      "url": "<url>"
    },
    "logs": {
      "url": "<url>"
    },
    "state": "creating",
    "failure": {
      "code": "<code>",
      "message": "<message>"
    },
    "status": {
      "power": "asleep",
      "snapshot": null,
      "sleep": {
        "action": "<action>",
        "reason": "<reason>"
      },
      "conditions": [
        {
          "type": "wake_blocked",
          "reason": "payment_required",
          "since": "2026-11-02T09:30:00Z",
          "retry_at": "2026-11-02T09:30:00Z"
        }
      ],
      "awake": {
        "since": "2026-11-02T09:30:00Z",
        "connections": {
          "https": 0
        },
        "last_client_activity_at": "2026-11-02T09:30:00Z",
        "sleeps_at_earliest": "2026-11-02T09:30:00Z",
        "activation": "<activation>",
        "processes": {
          "running": 0,
          "detached": 0
        },
        "holds": [
          {
            "hold": "<hold>",
            "source": "member",
            "activation": "<activation>",
            "until": "2026-11-02T09:30:00Z",
            "actor": {
              "sub": "<sub>",
              "client_id": "<client_id>"
            },
            "created_at": "2026-11-02T09:30:00Z"
          }
        ],
        "max_awake_until": "2026-11-02T09:30:00Z"
      },
      "spec_current": false,
      "delete_at": "2026-11-02T09:30:00Z"
    },
    "activation": {
      "id": "<id>",
      "wake_gen": 1,
      "state": "admitted",
      "started_at": "2026-11-02T09:30:00Z",
      "revision": 1
    },
    "revision": 1,
    "created_by": "<created_by>",
    "created_at": "2026-11-02T09:30:00Z",
    "updated_at": "2026-11-02T09:30:00Z"
  }
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
409name_takenanother Machine of the account holds that nameNoPick another name, or delete the Machine that holds it. (more)
409idempotency_conflictThe Idempotency-Key was already sent with a different request.NoSend the request with a new Idempotency-Key, or send the request the key was first used for. (more)
409conflictThe name is held by another live resource of the account, the Idempotency-Key already created another resource or one since deleted, or the resource’s current state does not allow the change. detail says which, and errors names the field.NoPick another name or a new Idempotency-Key, or read the resource again and act on its current state. (more)
422image_platform_unsupportedthe image has no linux/arm64 manifest; the message names the platforms foundNoBuild the image for linux/arm64 and name it again. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422shape_not_offeredthe shape is not a shapes-v1 key or is not offered; like volumes_not_offered and the shared region_not_offered, nothing was fetched or writtenNoPick a shape GET /v1/machine-shapes offers. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)

Retries

  • Send a new Idempotency-Key with each request and the same one when you send it again: a repeat answers what the first request did, and the key reused for a different request answers 409 (see Idempotency).
  • 202, 403, 409, 422 and 429 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS "https://api.tonbo.dev/v1/machines" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>","image":"<image>","shape":"<shape>"}'

Read a Machine

GET /v1/machines/{name} · CLI: tonbo machine show

Needs account.read, which owner, admin and member hold. A narrowed key may call it.

A Machine of the account. A device (kind byoc, in a personal account only) is served whatever machines.open says and answers its byoc facts, hostname and device_id to its person only, with its id as its ETag ("<id>"), which a delete may name in If-Match. A managed Machine's ETag is "<id>.<revision>" (contracts/machines/machine-v2.json etag), opaque to clients: an update names it in If-Match, and a delete or a sleep may, so a tag read from one Machine never matches another that later takes its name. A Machine is read whatever machines.open says, so a closure never hides it; a region this server cannot reach answers 503 region_unavailable. Its image names the canonical repository a registry image is pulled from and a state computed from the pull: pulling until a node has pulled the digest while the Machine is creating or active, ready once one has (always for the platform reference tonbo.dev/platform, which is never pulled), failed with a failure whose code is one of contracts/machines/machine-v2.json image.failures, and not_pulled on a failed or deleting Machine that no node pulled, since nothing will. While the image is not ready, a request that would wake the Machine answers 503 image_pulling, or 409 image_failed once the pull failed. status.conditions says why a Machine is not doing what it should: wake_blocked when its last wake was refused and none has succeeded since, with the reason, since and, for a crash backoff, retry_at; creation_delayed once it has been creating for longer than contracts/machines/machine-v2.json creation.alert_after_seconds; egress_blocked while its outbound access is blocked; and volume_purged when the Volume its spec names was purged (in practice after 30 days of suspension past the owners' notice), so it boots without it until the spec names another Volume or none. activation.revision is the spec revision the live activation was admitted with. Every spec change advances the Machine's revision, but ports and sleep_after_seconds apply at once, so a lower activation.revision means a sleep is needed only when image, command, shape or env changed; GET /machines/{name}/activations says how each earlier activation ended. A managed Machine names its creator in created_by.

Parameters

NameInTypeRequiredDescription
namepathstringyesThe Machine's name. 1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".

Response

StatusAnswer
200The Machine, with its ETag
FieldTypeRequiredDescription
dataMachineyesThe Machine.
HeaderStatusDescription
ETag200The Machine's ETag, "<id>.<revision>" for a managed Machine and "<id>" for a device (contracts/machines/machine-v2.json etag), opaque to clients. An update names it in If-Match, and a delete or a sleep may.
WWW-Authenticate401Authentication scheme required by this API.
Retry-After429, 503Seconds before the client should retry.
Example 200 body
{
  "data": {
    "id": "<id>",
    "name": "<name>",
    "kind": "managed",
    "region": "<region>",
    "byoc": {
      "os": "darwin",
      "arch": "arm64",
      "client": "yield",
      "client_version": "<client_version>",
      "harnesses": [
        "claude-code"
      ],
      "last_seen": "2026-11-02T09:30:00Z",
      "key_state": "active",
      "hostname": "<hostname>",
      "device_id": "<device_id>"
    },
    "hostnames": {
      "https": "<https>",
      "api": "<api>"
    },
    "image": {
      "reference": "<reference>",
      "repository": "<repository>",
      "digest": "<digest>",
      "state": "pulling",
      "failure": {
        "code": "image_unavailable",
        "message": "<message>"
      }
    },
    "command": [
      "<command>"
    ],
    "shape": "<shape>",
    "ports": [
      {
        "port": 1,
        "access": "private",
        "default": false
      }
    ],
    "env": {},
    "sleep_after_seconds": 600,
    "idle_action": "suspend",
    "stop_grace_seconds": 10,
    "database": {
      "name": "<name>",
      "url": "<url>"
    },
    "logs": {
      "url": "<url>"
    },
    "state": "creating",
    "failure": {
      "code": "<code>",
      "message": "<message>"
    },
    "status": {
      "power": "asleep",
      "snapshot": null,
      "sleep": {
        "action": "<action>",
        "reason": "<reason>"
      },
      "conditions": [
        {
          "type": "wake_blocked",
          "reason": "payment_required",
          "since": "2026-11-02T09:30:00Z",
          "retry_at": "2026-11-02T09:30:00Z"
        }
      ],
      "awake": {
        "since": "2026-11-02T09:30:00Z",
        "connections": {
          "https": 0
        },
        "last_client_activity_at": "2026-11-02T09:30:00Z",
        "sleeps_at_earliest": "2026-11-02T09:30:00Z",
        "activation": "<activation>",
        "processes": {
          "running": 0,
          "detached": 0
        },
        "holds": [
          {
            "hold": "<hold>",
            "source": "member",
            "activation": "<activation>",
            "until": "2026-11-02T09:30:00Z",
            "actor": {
              "sub": "<sub>",
              "client_id": "<client_id>"
            },
            "created_at": "2026-11-02T09:30:00Z"
          }
        ],
        "max_awake_until": "2026-11-02T09:30:00Z"
      },
      "spec_current": false,
      "delete_at": "2026-11-02T09:30:00Z"
    },
    "activation": {
      "id": "<id>",
      "wake_gen": 1,
      "state": "admitted",
      "started_at": "2026-11-02T09:30:00Z",
      "revision": 1
    },
    "revision": 1,
    "created_by": "<created_by>",
    "created_at": "2026-11-02T09:30:00Z",
    "updated_at": "2026-11-02T09:30:00Z"
  }
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
404not_foundNo live resource of the key’s account has that name or id, or the key is narrowed away from it.NoCheck the name, and that the key belongs to the account that holds it; list the resources to see what exists. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)
503region_unavailableThe region could not be reached or read, or it takes no new resource of this kind. Every answer carries Retry-After.After Retry-AfterSend the request again after Retry-After; if detail says the region takes no new resource of this kind, name another region. (more)

Retries

  • A read: sending it again changes nothing.
  • 429 and 503 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS "https://api.tonbo.dev/v1/machines/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"

Change a Machine's spec

PATCH /v1/machines/{name} · CLI: tonbo machine update

Needs machines.write, which owner, admin and member hold. A narrowed key may not.

A managed Machine only: a device answers 409 machine_kind_mismatch, whatever machines.open says. If-Match is required, because env and ports are replaced whole: the change is written only at the revision it names, so a missing one answers 428 precondition_required, a stale one 412 precondition_failed, and a concurrent change is never lost; an If-Match that is not this Machine's ETag, another Machine's that had the name included, also answers 412 precondition_failed, and so does a bare revision ("<n>"), whose detail says to upgrade @tonbo/cli to 0.9.0 or later (machine-v2.json etag.bare_revision). A Machine whose creation failed answers 409 machine_failed and does not change. A region where Machines are not offered answers 422 region_not_offered, and a shape that is not offered 422 shape_not_offered. A change to image, command, shape or env applies at the next activation; ports and sleep_after_seconds apply at once. A changed image follows the create's rule: the platform reference tonbo.dev/platform resolves nothing, and any other reference resolves to its canonical repository and linux/arm64 digest, or is refused before anything is written as the create refuses it (422 image_platform_unsupported or validation_error). Naming an image, the same one included, resolves it again and returns its state to pulling, so a failed pull is retried. Making a port public also needs machines.public. Refusals carry the codes and statuses of contracts/machines/machine-v2.json errors.

Parameters

NameInTypeRequiredDescription
namepathstringyesThe Machine's name. 1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
If-MatchheaderstringyesThe Machine's ETag as read, "<id>.<revision>" (contracts/machines/machine-v2.json etag); a Machine update is written only to that Machine at that revision. Required, because env and ports are replaced whole; a missing one answers 428 precondition_required. At most 500 characters.

Request body

application/json, required. Unknown fields are refused.

The fields below are the contract’s, with their types and limits; Manage Machines explains what each one does.

FieldTypeRequiredDescription
imagestringno1 to 512 characters.
commandarray of string or nullno1 to 64 items.
shapestringnoMatches ^[a-z]-[0-9]+$.
portsarray of objectnoAt most 8 items.
ports[].portintegeryes1 to 65535.
ports[].accessone of "private" or "public"yesDefault "private".
ports[].defaultbooleanyes
envobject of stringnoAt most 128 entries. Each key matches ^[A-Za-z_][A-Za-z0-9_]{0,127}$.
sleep_after_secondsintegernoDefault 600. 30 to 86400.
idle_actionone of "suspend" or "release"noDefault "suspend".
stop_grace_secondsintegernoDefault 10. 0 to 230.
discard_snapshotbooleannoDefault false.

Response

StatusAnswer
200The Machine
FieldTypeRequiredDescription
dataMachineyesThe Machine.
HeaderStatusDescription
ETag200The Machine's ETag, "<id>.<revision>" for a managed Machine and "<id>" for a device (contracts/machines/machine-v2.json etag), opaque to clients. An update names it in If-Match, and a delete or a sleep may.
WWW-Authenticate401Authentication scheme required by this API.
Retry-After409, 422, 429, 503Seconds before the client should retry.
Example 200 body
{
  "data": {
    "id": "<id>",
    "name": "<name>",
    "kind": "managed",
    "region": "<region>",
    "byoc": {
      "os": "darwin",
      "arch": "arm64",
      "client": "yield",
      "client_version": "<client_version>",
      "harnesses": [
        "claude-code"
      ],
      "last_seen": "2026-11-02T09:30:00Z",
      "key_state": "active",
      "hostname": "<hostname>",
      "device_id": "<device_id>"
    },
    "hostnames": {
      "https": "<https>",
      "api": "<api>"
    },
    "image": {
      "reference": "<reference>",
      "repository": "<repository>",
      "digest": "<digest>",
      "state": "pulling",
      "failure": {
        "code": "image_unavailable",
        "message": "<message>"
      }
    },
    "command": [
      "<command>"
    ],
    "shape": "<shape>",
    "ports": [
      {
        "port": 1,
        "access": "private",
        "default": false
      }
    ],
    "env": {},
    "sleep_after_seconds": 600,
    "idle_action": "suspend",
    "stop_grace_seconds": 10,
    "database": {
      "name": "<name>",
      "url": "<url>"
    },
    "logs": {
      "url": "<url>"
    },
    "state": "creating",
    "failure": {
      "code": "<code>",
      "message": "<message>"
    },
    "status": {
      "power": "asleep",
      "snapshot": null,
      "sleep": {
        "action": "<action>",
        "reason": "<reason>"
      },
      "conditions": [
        {
          "type": "wake_blocked",
          "reason": "payment_required",
          "since": "2026-11-02T09:30:00Z",
          "retry_at": "2026-11-02T09:30:00Z"
        }
      ],
      "awake": {
        "since": "2026-11-02T09:30:00Z",
        "connections": {
          "https": 0
        },
        "last_client_activity_at": "2026-11-02T09:30:00Z",
        "sleeps_at_earliest": "2026-11-02T09:30:00Z",
        "activation": "<activation>",
        "processes": {
          "running": 0,
          "detached": 0
        },
        "holds": [
          {
            "hold": "<hold>",
            "source": "member",
            "activation": "<activation>",
            "until": "2026-11-02T09:30:00Z",
            "actor": {
              "sub": "<sub>",
              "client_id": "<client_id>"
            },
            "created_at": "2026-11-02T09:30:00Z"
          }
        ],
        "max_awake_until": "2026-11-02T09:30:00Z"
      },
      "spec_current": false,
      "delete_at": "2026-11-02T09:30:00Z"
    },
    "activation": {
      "id": "<id>",
      "wake_gen": 1,
      "state": "admitted",
      "started_at": "2026-11-02T09:30:00Z",
      "revision": 1
    },
    "revision": 1,
    "created_by": "<created_by>",
    "created_at": "2026-11-02T09:30:00Z",
    "updated_at": "2026-11-02T09:30:00Z"
  }
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
404not_foundNo live resource of the key’s account has that name or id, or the key is narrowed away from it.NoCheck the name, and that the key belongs to the account that holds it; list the resources to see what exists. (more)
409machine_kind_mismatchthe action's kinds does not include the named Machine's kind: a managed action named a byoc MachineNoName a Machine Tonbo runs: the operation does not apply to a personal device. (more)
409machine_failedan update or a sleep names a Machine whose creation failed (state failed); the detail names its failure.code, and the Machine does not change: delete it and create it againNoDelete the Machine and create it again. (more)
409conflictThe name is held by another live resource of the account, the Idempotency-Key already created another resource or one since deleted, or the resource’s current state does not allow the change. detail says which, and errors names the field.NoPick another name or a new Idempotency-Key, or read the resource again and act on its current state. (more)
412precondition_failedIf-Match does not name the resource’s current ETag: it changed since you read it. Nothing changed.NoRead the resource again, check that the change still applies, and send it with the new ETag. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422shape_not_offeredthe shape is not a shapes-v1 key or is not offered; like volumes_not_offered and the shared region_not_offered, nothing was fetched or writtenNoPick a shape GET /v1/machine-shapes offers. (more)
422image_platform_unsupportedthe image has no linux/arm64 manifest; the message names the platforms foundNoBuild the image for linux/arm64 and name it again. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
428precondition_requiredThe operation requires If-Match and the request sent none.NoRead the resource and send its ETag as If-Match. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)
503region_unavailableThe region could not be reached or read, or it takes no new resource of this kind. Every answer carries Retry-After.After Retry-AfterSend the request again after Retry-After; if detail says the region takes no new resource of this kind, name another region. (more)

Retries

  • It takes no Idempotency-Key; its description above says what sending it again does.
  • If-Match is required: send the ETag you read, and a 412 precondition_failed means the resource changed since; read it again and decide (see If-Match).
  • 409, 422, 429 and 503 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS -X PATCH "https://api.tonbo.dev/v1/machines/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "If-Match: <if-match>" \
  -H "Content-Type: application/json" \
  -d '{}'

Delete a Machine

DELETE /v1/machines/{name} · CLI: tonbo machine delete

Needs machines.write, which owner, admin and member hold. A narrowed key may not.

A managed Machine stops and is purged with its database and logs; its name is free at once for the account's next Machine, while its hostnames stay reserved to the account. The answer is 202 with the Machine being deleted, after which it is absent from the API, or 204 with no body when the name's create never reached its region and only the name was released. A region where Machines are not offered answers 422 region_not_offered, and one this server cannot reach 503 region_unavailable. A device (kind byoc) is forgotten instead, whatever machines.open says (contracts/machines/machine-v2.json kinds.byoc.forget): it is hidden and leaves the per-person cap at once, and its name is free once the forget drains; the answer is 202 with the device as it was, or 204 when its region held no row for it; a 204 names the kind in X-Tonbo-Machine-Kind. Forgetting a device that is not the caller's own (an Agent key's) also needs api_keys.others (actions delete byoc_requires_also), 403 otherwise. A device's key is not revoked: a device whose key is still active registers again at its next start. If-Match is optional; when sent it must be the Machine's current ETag (a device's is its id), or the answer is 412 precondition_failed and nothing is deleted, so a delete retried after the name was reused never deletes the new Machine. A name whose create never reached its region has no ETag to match.

Parameters

NameInTypeRequiredDescription
namepathstringyesThe Machine's name. 1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
If-MatchheaderstringnoThe Machine's ETag as read (contracts/machines/machine-v2.json etag); when sent, the request acts only on that Machine at that revision, and otherwise answers 412 precondition_failed. At most 500 characters.

Response

StatusAnswer
202The Machine, being deleted, or the device, forgotten
204The name was released, no Machine having been recorded under it, or the device was forgotten, its region holding no row for it
FieldTypeRequiredDescription
dataMachineyesThe Machine.
HeaderStatusDescription
X-Tonbo-Machine-Kind204The kind of the Machine the name held, so that a client words a released name (managed) and a forgotten device (byoc) apart.
WWW-Authenticate401Authentication scheme required by this API.
Retry-After422, 429, 503Seconds before the client should retry.
Example 202 body
{
  "data": {
    "id": "<id>",
    "name": "<name>",
    "kind": "managed",
    "region": "<region>",
    "byoc": {
      "os": "darwin",
      "arch": "arm64",
      "client": "yield",
      "client_version": "<client_version>",
      "harnesses": [
        "claude-code"
      ],
      "last_seen": "2026-11-02T09:30:00Z",
      "key_state": "active",
      "hostname": "<hostname>",
      "device_id": "<device_id>"
    },
    "hostnames": {
      "https": "<https>",
      "api": "<api>"
    },
    "image": {
      "reference": "<reference>",
      "repository": "<repository>",
      "digest": "<digest>",
      "state": "pulling",
      "failure": {
        "code": "image_unavailable",
        "message": "<message>"
      }
    },
    "command": [
      "<command>"
    ],
    "shape": "<shape>",
    "ports": [
      {
        "port": 1,
        "access": "private",
        "default": false
      }
    ],
    "env": {},
    "sleep_after_seconds": 600,
    "idle_action": "suspend",
    "stop_grace_seconds": 10,
    "database": {
      "name": "<name>",
      "url": "<url>"
    },
    "logs": {
      "url": "<url>"
    },
    "state": "creating",
    "failure": {
      "code": "<code>",
      "message": "<message>"
    },
    "status": {
      "power": "asleep",
      "snapshot": null,
      "sleep": {
        "action": "<action>",
        "reason": "<reason>"
      },
      "conditions": [
        {
          "type": "wake_blocked",
          "reason": "payment_required",
          "since": "2026-11-02T09:30:00Z",
          "retry_at": "2026-11-02T09:30:00Z"
        }
      ],
      "awake": {
        "since": "2026-11-02T09:30:00Z",
        "connections": {
          "https": 0
        },
        "last_client_activity_at": "2026-11-02T09:30:00Z",
        "sleeps_at_earliest": "2026-11-02T09:30:00Z",
        "activation": "<activation>",
        "processes": {
          "running": 0,
          "detached": 0
        },
        "holds": [
          {
            "hold": "<hold>",
            "source": "member",
            "activation": "<activation>",
            "until": "2026-11-02T09:30:00Z",
            "actor": {
              "sub": "<sub>",
              "client_id": "<client_id>"
            },
            "created_at": "2026-11-02T09:30:00Z"
          }
        ],
        "max_awake_until": "2026-11-02T09:30:00Z"
      },
      "spec_current": false,
      "delete_at": "2026-11-02T09:30:00Z"
    },
    "activation": {
      "id": "<id>",
      "wake_gen": 1,
      "state": "admitted",
      "started_at": "2026-11-02T09:30:00Z",
      "revision": 1
    },
    "revision": 1,
    "created_by": "<created_by>",
    "created_at": "2026-11-02T09:30:00Z",
    "updated_at": "2026-11-02T09:30:00Z"
  }
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
404not_foundNo live resource of the key’s account has that name or id, or the key is narrowed away from it.NoCheck the name, and that the key belongs to the account that holds it; list the resources to see what exists. (more)
412precondition_failedIf-Match does not name the resource’s current ETag: it changed since you read it. Nothing changed.NoRead the resource again, check that the change still applies, and send it with the new ETag. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)
503region_unavailableThe region could not be reached or read, or it takes no new resource of this kind. Every answer carries Retry-After.After Retry-AfterSend the request again after Retry-After; if detail says the region takes no new resource of this kind, name another region. (more)

Retries

  • It takes no Idempotency-Key; its description above says what sending it again does.
  • If-Match is optional: send the ETag you read, and a 412 precondition_failed means the resource changed since; read it again and decide (see If-Match).
  • 422, 429 and 503 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS -X DELETE "https://api.tonbo.dev/v1/machines/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"

Put a Machine to sleep now

POST /v1/machines/{name}/sleep · CLI: tonbo machine sleep

Needs machines.write, which owner, admin and member hold. A narrowed key may not.

machine-controller applies the Machine's effective idle action (status.sleep). An asleep Machine wakes on its next connection; there is no wake operation. The answer is 202 while an activation runs, with Retry-After, the seconds to wait before reading the Machine again (machine-v2.json sleep.poll_after_seconds), until its status.power is asleep; and 200 with the Machine unchanged when there is nothing to do: it is asleep, or still being created. A Machine whose creation failed answers 409 machine_failed, and a device 409 machine_kind_mismatch, whatever machines.open says. A region where Machines are not offered answers 422 region_not_offered. If-Match is optional; when sent it must be the Machine's current ETag, or the answer is 412 precondition_failed and nothing changes.

Parameters

NameInTypeRequiredDescription
namepathstringyesThe Machine's name. 1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
If-MatchheaderstringnoThe Machine's ETag as read (contracts/machines/machine-v2.json etag); when sent, the request acts only on that Machine at that revision, and otherwise answers 412 precondition_failed. At most 500 characters.

Response

StatusAnswer
200The Machine, unchanged, with nothing to do
202The Machine, going to sleep
FieldTypeRequiredDescription
dataMachineyesThe Machine.
HeaderStatusDescription
Retry-After202, 409, 422, 429, 503The seconds to wait before reading the Machine (getMachine) again, until its status.power is asleep (machine-v2.json sleep.poll_after_seconds).
WWW-Authenticate401Authentication scheme required by this API.
Example 200 body
{
  "data": {
    "id": "<id>",
    "name": "<name>",
    "kind": "managed",
    "region": "<region>",
    "byoc": {
      "os": "darwin",
      "arch": "arm64",
      "client": "yield",
      "client_version": "<client_version>",
      "harnesses": [
        "claude-code"
      ],
      "last_seen": "2026-11-02T09:30:00Z",
      "key_state": "active",
      "hostname": "<hostname>",
      "device_id": "<device_id>"
    },
    "hostnames": {
      "https": "<https>",
      "api": "<api>"
    },
    "image": {
      "reference": "<reference>",
      "repository": "<repository>",
      "digest": "<digest>",
      "state": "pulling",
      "failure": {
        "code": "image_unavailable",
        "message": "<message>"
      }
    },
    "command": [
      "<command>"
    ],
    "shape": "<shape>",
    "ports": [
      {
        "port": 1,
        "access": "private",
        "default": false
      }
    ],
    "env": {},
    "sleep_after_seconds": 600,
    "idle_action": "suspend",
    "stop_grace_seconds": 10,
    "database": {
      "name": "<name>",
      "url": "<url>"
    },
    "logs": {
      "url": "<url>"
    },
    "state": "creating",
    "failure": {
      "code": "<code>",
      "message": "<message>"
    },
    "status": {
      "power": "asleep",
      "snapshot": null,
      "sleep": {
        "action": "<action>",
        "reason": "<reason>"
      },
      "conditions": [
        {
          "type": "wake_blocked",
          "reason": "payment_required",
          "since": "2026-11-02T09:30:00Z",
          "retry_at": "2026-11-02T09:30:00Z"
        }
      ],
      "awake": {
        "since": "2026-11-02T09:30:00Z",
        "connections": {
          "https": 0
        },
        "last_client_activity_at": "2026-11-02T09:30:00Z",
        "sleeps_at_earliest": "2026-11-02T09:30:00Z",
        "activation": "<activation>",
        "processes": {
          "running": 0,
          "detached": 0
        },
        "holds": [
          {
            "hold": "<hold>",
            "source": "member",
            "activation": "<activation>",
            "until": "2026-11-02T09:30:00Z",
            "actor": {
              "sub": "<sub>",
              "client_id": "<client_id>"
            },
            "created_at": "2026-11-02T09:30:00Z"
          }
        ],
        "max_awake_until": "2026-11-02T09:30:00Z"
      },
      "spec_current": false,
      "delete_at": "2026-11-02T09:30:00Z"
    },
    "activation": {
      "id": "<id>",
      "wake_gen": 1,
      "state": "admitted",
      "started_at": "2026-11-02T09:30:00Z",
      "revision": 1
    },
    "revision": 1,
    "created_by": "<created_by>",
    "created_at": "2026-11-02T09:30:00Z",
    "updated_at": "2026-11-02T09:30:00Z"
  }
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
404not_foundNo live resource of the key’s account has that name or id, or the key is narrowed away from it.NoCheck the name, and that the key belongs to the account that holds it; list the resources to see what exists. (more)
409machine_failedan update or a sleep names a Machine whose creation failed (state failed); the detail names its failure.code, and the Machine does not change: delete it and create it againNoDelete the Machine and create it again. (more)
409machine_kind_mismatchthe action's kinds does not include the named Machine's kind: a managed action named a byoc MachineNoName a Machine Tonbo runs: the operation does not apply to a personal device. (more)
409conflictThe name is held by another live resource of the account, the Idempotency-Key already created another resource or one since deleted, or the resource’s current state does not allow the change. detail says which, and errors names the field.NoPick another name or a new Idempotency-Key, or read the resource again and act on its current state. (more)
412precondition_failedIf-Match does not name the resource’s current ETag: it changed since you read it. Nothing changed.NoRead the resource again, check that the change still applies, and send it with the new ETag. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)
503region_unavailableThe region could not be reached or read, or it takes no new resource of this kind. Every answer carries Retry-After.After Retry-AfterSend the request again after Retry-After; if detail says the region takes no new resource of this kind, name another region. (more)

Retries

  • No Idempotency-Key: a repeat requests the same sleep of the same Machine
  • If-Match is optional: send the ETag you read, and a 412 precondition_failed means the resource changed since; read it again and decide (see If-Match).
  • 202, 409, 422, 429 and 503 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS -X POST "https://api.tonbo.dev/v1/machines/<name>/sleep" \
  -H "Authorization: Bearer $TONBO_API_KEY"

List a Machine's activations

GET /v1/machines/{name}/activations · CLI: tonbo machine activations

Needs account.read, which owner, admin and member hold. A narrowed key may call it.

A managed Machine's activations, newest first: each boot or refused wake, with what woke it, the spec revision and shape it was admitted with, when, and its end (machine-v2.schema.json $defs.activation_end), null while it is live. An end's reason is why it ended: a refusal such as payment_required or crash_backoff, a stop such as idle_early, sleep_after, workload_exited (with the exit_code or signal) or funding_stop, or the node's. Any member reads it (account.read), whatever machines.open says; a narrowed key never does. A device answers 409 machine_kind_mismatch, and a region this server cannot reach 503 region_unavailable. The cursor is tied to the Machine, so it never pages another Machine that later takes the name.

Parameters

NameInTypeRequiredDescription
namepathstringyesThe Machine's name. 1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
cursorquerystringnoOpaque cursor tied to the original query shape. At least 1 characters.
limitqueryintegernoThe most items one page holds, 1 to 200; 50 when omitted. A page may hold fewer; read on with next_cursor until it is null. Default 50. 1 to 200.

Response

StatusAnswer
200Activations, newest first
FieldTypeRequiredDescription
dataarray of objectyes
data[].idstringyesMatches ^[0-9a-f]{12}7[0-9a-f]{3}[89ab][0-9a-f]{15}$.
data[].wake_genintegeryes1 to 9007199254740991.
data[].revisionintegeryesAt least 1.
data[].shapestringyesMatches ^[a-z]-[0-9]+$.
data[].started_atstring (date-time)yesMatches Z$.
data[].endobject or nullyes
data[].end.stateone of "released", "failed", "expired", "lost", "refused" or "suspended"yes
data[].end.reasonstringyesMatches ^[a-z][a-z0-9_]{0,63}$.
data[].end.exit_codeinteger or nullyes0 to 255.
data[].end.signalinteger or nullyes1 to 64.
data[].end.atstring (date-time)yesMatches Z$.
data[].secrets_generationintegernoAt least 0.
next_cursorstring or nullyesOpaque cursor of the next page; null on the last page.
HeaderStatusDescription
WWW-Authenticate401Authentication scheme required by this API.
Retry-After409, 422, 429, 503Seconds before the client should retry.
Example 200 body
{
  "data": [
    {
      "id": "<id>",
      "wake_gen": 1,
      "revision": 1,
      "shape": "<shape>",
      "started_at": "2026-11-02T09:30:00Z",
      "end": {
        "state": "released",
        "reason": "<reason>",
        "exit_code": 0,
        "signal": 1,
        "at": "2026-11-02T09:30:00Z"
      },
      "secrets_generation": 0
    }
  ],
  "next_cursor": "<next_cursor>"
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
404not_foundNo live resource of the key’s account has that name or id, or the key is narrowed away from it.NoCheck the name, and that the key belongs to the account that holds it; list the resources to see what exists. (more)
409machine_kind_mismatchthe action's kinds does not include the named Machine's kind: a managed action named a byoc MachineNoName a Machine Tonbo runs: the operation does not apply to a personal device. (more)
409conflictThe name is held by another live resource of the account, the Idempotency-Key already created another resource or one since deleted, or the resource’s current state does not allow the change. detail says which, and errors names the field.NoPick another name or a new Idempotency-Key, or read the resource again and act on its current state. (more)
422region_not_offeredThe resource is not offered in the region named, or in the account’s default region. Nothing was written.NoName a region where it is offered; GET /v1/regions lists the regions and what each offers. (more)
422validation_errorA parameter, header or body field breaks its rule; errors names each field.NoCorrect the fields errors names; the operation’s reference gives each one’s rule. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)
503region_unavailableThe region could not be reached or read, or it takes no new resource of this kind. Every answer carries Retry-After.After Retry-AfterSend the request again after Retry-After; if detail says the region takes no new resource of this kind, name another region. (more)

Retries

  • A read: sending it again changes nothing.
  • 409, 422, 429 and 503 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS "https://api.tonbo.dev/v1/machines/<name>/activations" \
  -H "Authorization: Bearer $TONBO_API_KEY"

List the Machine shapes

GET /v1/machine-shapes · CLI: tonbo machine shapes

Needs account.read, which owner, admin and member hold. A narrowed key may call it.

Every shape of contracts/machines/shapes-v1.json, with whether a Machine may name it now and its list price for an hour at full use, and the shape a client proposes by default. Served to any member whatever machines.open says; a narrowed key never reaches it.

Response

StatusAnswer
200Shapes
FieldTypeRequiredDescription
dataarray of MachineShapeyes
defaultstringyesshapes-v1.json default.
HeaderStatusDescription
WWW-Authenticate401Authentication scheme required by this API.
Retry-After429Seconds before the client should retry.
Example 200 body
{
  "data": [
    {
      "shape": "m-8",
      "vcpus": 1,
      "memory_gib": 1,
      "scratch_gib": 1,
      "offered": false,
      "suspendable": false,
      "ceiling_usd_micros_per_hour": 0
    }
  ],
  "default": "<default>"
}

Errors

StatusCodeWhenSend againWhat to do
401exchange_requiredThe key acts as an Agent, and an Agent’s key is never sent itself.NoExchange the key for a token at the endpoint the answer’s token_endpoint names, and send the token as Authorization: Bearer. (more)
401unauthenticatedThe request carries no key or token, or one that is invalid, expired or revoked.NoSend a valid key, or a token that has not expired, as Authorization: Bearer. (more)
403forbiddenThe key’s role does not hold the capability the operation needs, the key is narrowed away from the resource, or the account already holds as many of the resource as it may.NoUse a key whose role holds the capability, or one that is not narrowed; delete what you no longer need, or ask for a higher limit. (more)
429rate_limitedThe key or the account sent more requests than its rate allows.After Retry-AfterWait for the seconds Retry-After names, then send the request again; spread requests out if it recurs. (more)

Retries

  • A read: sending it again changes nothing.
  • 429 carry Retry-After: wait that many seconds, then send the same request again.
Request
curl -sS "https://api.tonbo.dev/v1/machine-shapes" \
  -H "Authorization: Bearer $TONBO_API_KEY"

Objects

Machine

A Machine as contracts/machines/machine-v2.schema.json defines it.

The fields below are the contract’s, with their types and limits; Manage Machines explains what each one does.

FieldTypeRequiredDescription
idstringyesMatches ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.
namestringyes1 to 40 characters. Matches ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$. Never "self".
kindone of "managed" or "byoc"yes
regionstringyesMatches ^[a-z]{2}(-gov)?-[a-z]+-[0-9]+$.
byocobjectno
byoc.osone of "darwin" or "linux"yes
byoc.archone of "arm64" or "amd64"yes
byoc.clientone of "yield"yes
byoc.client_versionstringyesAt most 64 characters. Matches ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$.
byoc.harnessesarray of one of "claude-code" or "codex"yesNo duplicates.
byoc.last_seenstring (date-time)yesMatches Z$.
byoc.key_stateone of "active", "revoked" or "expired"yes
byoc.hostnamestringno1 to 253 characters.
byoc.device_idstringnoMatches ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.
hostnamesobjectno
hostnames.httpsstringyesMatches ^https://[a-z][a-z0-9]*(?:-[a-z0-9]+)*-[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.tonbo\.sh$.
hostnames.apistringnoMatches ^https://api\.[a-z0-9-]+\.tonbo\.sh$.
imageobjectno
image.referencestringyes1 to 512 characters.
image.repositorystring or nullyesAt most 512 characters. Matches ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$.
image.digeststring or nullyesMatches ^sha256:[0-9a-f]{64}$.
image.stateone of "pulling", "ready", "failed" or "not_pulled"yes
image.failureobject or nullyes
image.failure.codeone of "image_unavailable", "image_platform_unsupported", "image_too_large" or "image_unsupported"yes
image.failure.messagestringyes1 to 512 characters. Matches ^[^\u0000-\u001f\u007f-\u009f]+$.
commandarray of string or nullno1 to 64 items.
shapestringnoMatches ^[a-z]-[0-9]+$.
portsarray of objectnoAt most 8 items.
ports[].portintegeryes1 to 65535.
ports[].accessone of "private" or "public"yesDefault "private".
ports[].defaultbooleanyes
envobject of stringnoAt most 128 entries. Each key matches ^[A-Za-z_][A-Za-z0-9_]{0,127}$.
sleep_after_secondsintegernoDefault 600. 30 to 86400.
idle_actionone of "suspend" or "release"noDefault "suspend".
stop_grace_secondsintegernoDefault 10. 0 to 230.
databaseobjectno
database.namestringyesMatches ^m\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.
database.urlstringyesMatches ^https://.
logsobjectno
logs.urlstringyesMatches ^https://.
stateone of "creating", "active", "deleting" or "failed"no
failureobject or nullno
failure.codestringyesMatches ^[a-z][a-z0-9_]*$.
failure.messagestringyesAt least 1 characters.
statusobjectno
status.powerone of "asleep", "waking", "awake" or "sleeping"yes
status.snapshotnullyes
status.sleepobjectyes
status.sleep.actionanyyes
status.sleep.reasonanyyes
status.conditionsarray of object, object, object, object or objectyes
status.awakeobject or nullyes
status.awake.sincestring (date-time)yesMatches Z$.
status.awake.connectionsobjectyes
status.awake.connections.httpsintegeryesAt least 0.
status.awake.last_client_activity_atstring (date-time) or nullyesMatches Z$.
status.awake.sleeps_at_earlieststring (date-time) or nullyesMatches Z$.
status.awake.activationstringnoMatches ^[0-9a-f]{12}7[0-9a-f]{3}[89ab][0-9a-f]{15}$.
status.awake.processesobjectno
status.awake.processes.runningintegeryesAt least 0.
status.awake.processes.detachedintegeryesAt least 0.
status.awake.holdsarray of objectno
status.awake.holds[].holdstringyesMatches ^[a-z0-9._-]{1,64}$.
status.awake.holds[].sourceone of "member" or "guest"yes
status.awake.holds[].activationstringyesMatches ^[0-9a-f]{12}7[0-9a-f]{3}[89ab][0-9a-f]{15}$.
status.awake.holds[].untilstring (date-time)yesMatches Z$.
status.awake.holds[].actorobject or nullyes
status.awake.holds[].actor.substringyesAt least 1 characters.
status.awake.holds[].actor.client_idstringyesAt least 1 characters.
status.awake.holds[].created_atstring (date-time)yesMatches Z$.
status.awake.max_awake_untilstring (date-time) or nullnoMatches Z$.
status.spec_currentbooleanno
status.delete_atstring (date-time) or nullnoMatches Z$.
activationobject or nullno
activation.idstringyesMatches ^[0-9a-f]{12}7[0-9a-f]{3}[89ab][0-9a-f]{15}$.
activation.wake_genintegeryes1 to 9007199254740991.
activation.stateone of "admitted", "funded", "dispatched", "starting", "failing", "running", "stopping" or "suspending"yes
activation.started_atstring (date-time)yesMatches Z$.
activation.revisionintegeryesAt least 1.
revisionintegernoAt least 1.
created_bystringnoMatches ^[a-z0-9._-]{1,64}$.
created_atstring (date-time)yesMatches Z$.
updated_atstring (date-time)yesMatches Z$.
Example Machine
{
  "id": "<id>",
  "name": "<name>",
  "kind": "managed",
  "region": "<region>",
  "byoc": {
    "os": "darwin",
    "arch": "arm64",
    "client": "yield",
    "client_version": "<client_version>",
    "harnesses": [
      "claude-code"
    ],
    "last_seen": "2026-11-02T09:30:00Z",
    "key_state": "active",
    "hostname": "<hostname>",
    "device_id": "<device_id>"
  },
  "hostnames": {
    "https": "<https>",
    "api": "<api>"
  },
  "image": {
    "reference": "<reference>",
    "repository": "<repository>",
    "digest": "<digest>",
    "state": "pulling",
    "failure": {
      "code": "image_unavailable",
      "message": "<message>"
    }
  },
  "command": [
    "<command>"
  ],
  "shape": "<shape>",
  "ports": [
    {
      "port": 1,
      "access": "private",
      "default": false
    }
  ],
  "env": {},
  "sleep_after_seconds": 600,
  "idle_action": "suspend",
  "stop_grace_seconds": 10,
  "database": {
    "name": "<name>",
    "url": "<url>"
  },
  "logs": {
    "url": "<url>"
  },
  "state": "creating",
  "failure": {
    "code": "<code>",
    "message": "<message>"
  },
  "status": {
    "power": "asleep",
    "snapshot": null,
    "sleep": {
      "action": "<action>",
      "reason": "<reason>"
    },
    "conditions": [
      {
        "type": "wake_blocked",
        "reason": "payment_required",
        "since": "2026-11-02T09:30:00Z",
        "retry_at": "2026-11-02T09:30:00Z"
      }
    ],
    "awake": {
      "since": "2026-11-02T09:30:00Z",
      "connections": {
        "https": 0
      },
      "last_client_activity_at": "2026-11-02T09:30:00Z",
      "sleeps_at_earliest": "2026-11-02T09:30:00Z",
      "activation": "<activation>",
      "processes": {
        "running": 0,
        "detached": 0
      },
      "holds": [
        {
          "hold": "<hold>",
          "source": "member",
          "activation": "<activation>",
          "until": "2026-11-02T09:30:00Z",
          "actor": {
            "sub": "<sub>",
            "client_id": "<client_id>"
          },
          "created_at": "2026-11-02T09:30:00Z"
        }
      ],
      "max_awake_until": "2026-11-02T09:30:00Z"
    },
    "spec_current": false,
    "delete_at": "2026-11-02T09:30:00Z"
  },
  "activation": {
    "id": "<id>",
    "wake_gen": 1,
    "state": "admitted",
    "started_at": "2026-11-02T09:30:00Z",
    "revision": 1
  },
  "revision": 1,
  "created_by": "<created_by>",
  "created_at": "2026-11-02T09:30:00Z",
  "updated_at": "2026-11-02T09:30:00Z"
}

MachineShape

A shape of contracts/machines/shapes-v1.json shapes, whether a Machine may name it now, and its list price.

FieldTypeRequiredDescription
shapestringyesThe shape's name, which a Machine's shape names. Matches ^[a-z]-[0-9]+$.
vcpusintegeryesThe most vCPUs a Machine of this shape uses. At least 1.
memory_gibnumberyesThe most memory a Machine of this shape uses, in GiB. Greater than 0.
scratch_gibnumberyesThe most scratch disk a Machine of this shape uses, in GiB. Greater than 0.
offeredbooleanyesWhether a Machine may name this shape now; a shape not offered answers 422 shape_not_offered.
suspendablebooleanyesWhether an idle Machine of this shape can suspend, keeping its memory to resume from. False while suspend is not offered, when an idle Machine is released instead.
ceiling_usd_micros_per_hourintegeryesThe shape's list price for an hour at full use, in micros of USD: its vCPUs, memory and scratch disk at contracts/billing/machine-prices-v1.json's hourly prices. Billing charges what a Machine uses, never more than this. At least 0.
Example MachineShape
{
  "shape": "m-8",
  "vcpus": 1,
  "memory_gib": 1,
  "scratch_gib": 1,
  "offered": false,
  "suspendable": false,
  "ceiling_usd_micros_per_hour": 0
}