> Fetch the index of every Tonbo docs page as Markdown at https://tonbo.dev/llms.txt

# Management API reference

Last updated 2 November 2026

Every operation of the Management API at `https://api.tonbo.dev/v1`, the API the `tonbo` CLI calls, generated from its OpenAPI document.

The document is public, at [openapi.json](https://api.tonbo.dev/v1/openapi.json) and, as written, [openapi.yaml](https://api.tonbo.dev/v1/openapi.yaml): give it to an OpenAPI client generator or a coding agent. It also holds the operations of Tonbo Yield’s own client and retired ones kept for their callers, which this page leaves out.

- [Account](#account)
- [API keys](#api-keys)
- [Databases](#databases)
- [Streams](#streams)
- [Usage](#usage)
- [Machines](#machines)
- [Errors](#errors)
- [Schemas](#schemas)

## Authentication

Every request carries a key as a bearer token, here from `TONBO_API_KEY`:

```shell
curl -sS https://api.tonbo.dev/v1/account \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

A personal key (`tbo_…`) presented directly, or an access token exchanged from any key with the https://api.tonbo.dev audience. The key acts as its member with the member's current role and the key's narrowing, re-read on every request.

[API keys and tokens](https://tonbo.dev/docs/platform/api-keys.md#direct) explains how to create a key, the token exchange and what a key can reach.

## Compatibility

Responses may gain fields; clients ignore fields they do not know. Request bodies stay closed: an unknown field is refused. A breaking change to an enforced operation ships as a new version, or with a compatibility window that names its consumer and removal condition.

## Errors

A refusal answers `application/problem+json`, a [Problem](#schema-problem) whose `code` names the refusal. The answers operations share:

| Answer | Status | When |
| --- | --- | --- |
| `Conflict` | 409 | Resource or idempotency conflict |
| `Forbidden` | 403 | The authenticated principal lacks the exact action |
| `MachineRefused` | 403, 409, 422, 429 | A refusal whose `code` and status are one of contracts/machines/machine-v2.json `errors` (such as `name_taken`, `shape_not_offered`, `machine_failed` or `create_rate_limited`), or a shared one such as `region_not_offered` (422, Machines are not offered in the region named or defaulted), `forbidden`, `validation_error` or `rate_limited`. A 429 (`device_report_throttled`, `create_rate_limited`, `rate_limited`) carries Retry-After. |
| `MemberUnauthorized` | 401 | Missing or invalid credentials (`unauthenticated`), or an Agent key presented directly (`exchange_required`): exchange it at `token_endpoint` and present the access token. |
| `NotFound` | 404 | Resource not found or outside the principal's Agent |
| `PaymentRequired` | 402 | The account's credit is exhausted (`payment_required`) |
| `PreconditionFailed` | 412 | The supplied If-Match value is stale (`precondition_failed`) |
| `PreconditionRequired` | 428 | The operation requires If-Match and the request has none (`precondition_required`, RFC 6585) |
| `RateLimited` | 429 | Request rate limit exceeded |
| `RegionUnavailable` | 503 | The region could not be reached or read; retry after `Retry-After` (`region_unavailable`). A region where the resource is not offered answers 422 `region_not_offered` instead. |
| `ValidationError` | 422 | Request validation failed |

## Account

- [`GET /account`](#get-account) Read the caller's account, membership and limits
- [`GET /regions`](#list-regions) List the regions new resources can be created in
- [`GET /members`](#list-members) List the account's people and Agents with their roles
- [`PATCH /members/{subject}`](#update-member) Change a member's role, or rename an Agent
- [`POST /agents`](#create-agent) Create an Agent, an identity with a role in the account

### Read the caller's account, membership and limits

`GET /v1/account` · CLI: `tonbo account show`

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The account | `data`: [Account](#schema-account) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/account" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### List the regions new resources can be created in

`GET /v1/regions` · CLI: `tonbo region list`

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Regions | `data`: array of [Region](#schema-region) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/regions" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### List the account's people and Agents with their roles

`GET /v1/members` · CLI: `tonbo member list`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Members | `data`: array of [Member](#schema-member) , `next_cursor`: [NextCursor](#schema-next-cursor) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/members" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Change a member's role, or rename an Agent

`PATCH /v1/members/{subject}` · CLI: `tonbo member update`

Owners and admins give a person or an Agent another role; only an owner grants `owner`. `name` renames an Agent: it follows the Agent name rule and is unique among the account's active Agents (409 otherwise), and the Agent keeps its id, keys, home region and public hostname. A person's name belongs to their own profile, so `name` on a person is refused with 422 `validation_error` on the field `name`. Both fields in one request change together or not at all. People change members; an Agent's credential is refused with 403.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `subject` | path | [MemberSubject](#schema-member-subject) | yes | The member to change, by the `subject` `GET /v1/members` lists: a person's user id, or `agent.<agent id>` for an Agent. |

#### Request body

`application/json`, required.

[UpdateMemberRequest](#schema-update-member-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The member | `data`: [Member](#schema-member) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X PATCH "https://api.tonbo.dev/v1/members/<subject>" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Create an Agent, an identity with a role in the account

`POST /v1/agents` · CLI: `tonbo agent create`

Owners and admins create Agents; only an owner grants `owner`. A replay with the same Idempotency-Key returns the Agent the first request created.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | A 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. |

#### Request body

`application/json`, required.

[CreateAccountAgentRequest](#schema-create-account-agent-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `201` | The Agent | `data`: [AccountAgent](#schema-account-agent) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/agents" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>"}'
```

## API keys

- [`GET /api-keys`](#list-api-keys) List your keys, or every key of the account for an owner or admin
- [`POST /api-keys`](#create-api-key) Create a key; its secret is in this answer only
- [`GET /api-keys/{key_id}`](#get-api-key) Read one key
- [`DELETE /api-keys/{key_id}`](#revoke-api-key) Revoke a key and every token minted from it

### List your keys, or every key of the account for an owner or admin

`GET /v1/api-keys` · CLI: `tonbo api-key list`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Live keys | `data`: array of [ApiKey](#schema-api-key) , `next_cursor`: [NextCursor](#schema-next-cursor) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/api-keys" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Create a key; its secret is in this answer only

`POST /v1/api-keys` · CLI: `tonbo api-key create`

A personal key acts as you; an Agent key (`member: agent.<agent id>`), which owners and admins create, acts as the Agent, whose role may not exceed yours. `read_only`, and either `buckets` or any of `databases`, `database_prefixes`, `streams` and `stream_prefixes`, narrow the key for its whole life: a different narrowing takes a new key. An access token never creates a key, and neither does a narrowed key.

#### Request body

`application/json`, required.

[CreateApiKeyRequest](#schema-create-api-key-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `201` | The key and its secret | `data`: [ApiKeyWithSecret](#schema-api-key-with-secret) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/api-keys" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>"}'
```

### Read one key

`GET /v1/api-keys/{key_id}` · CLI: `tonbo api-key show`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `key_id` | path | [Uuid](#schema-uuid) | yes | The key's `id`, as `GET /v1/api-keys` lists it. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The key | `data`: [ApiKey](#schema-api-key) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/api-keys/<key_id>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Revoke a key and every token minted from it

`DELETE /v1/api-keys/{key_id}` · CLI: `tonbo api-key revoke`

Your own key, or any key of the account for an owner or admin. A narrowed key may revoke itself (`tonbo logout`).

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `key_id` | path | [Uuid](#schema-uuid) | yes | The key's `id`, as `GET /v1/api-keys` lists it. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The revocation | `data`: object |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X DELETE "https://api.tonbo.dev/v1/api-keys/<key_id>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Databases

- [`GET /databases`](#list-databases) List the account's databases
- [`POST /databases`](#create-database) Create a database
- [`GET /databases/{name}`](#get-database) Read a database
- [`PATCH /databases/{name}`](#update-database) Publish a database for anonymous reads, or take it private
- [`DELETE /databases/{name}`](#delete-database) Delete a database and everything in it

### List the account's databases

`GET /v1/databases` · CLI: `tonbo database list`

Live rows of the listed kinds (contracts/streams-databases/v1.json kinds whose `api` is databases: a member's databases and each Machine's database, with `kind` and the owning Machine as `owner_id`), creating, active or deleting, in every region, by name. A key narrowed to databases sees only the databases it reaches, and a key narrowed to buckets, or by name to streams alone, sees none. `last_write_at` and `stored_bytes` come from each region's meter; `unavailable_regions` names the regions whose sizes could not be read, whose databases then show null sizes.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `prefix` | query | [DatabasePrefix](#schema-database-prefix) | no | Only databases whose name starts with this prefix. |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Databases | `data`: array of [Database](#schema-database) , `next_cursor`: [NextCursor](#schema-next-cursor) , `unavailable_regions`: array of [RegionId](#schema-region-id) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/databases" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Create a database

`POST /v1/databases` · CLI: `tonbo database create`

Any member creates databases; a narrowed key never does. A name is unique within the account across every region while a database holds it, and is free again once that database is deleted. The region is fixed at creation; without one, the database goes to the region whose `default` is true. An optional `expires_at`, at least 60 seconds after the creation and fixed for the database's life, has Tonbo delete the database within a minute after it passes, as a deletion would; a replay is not held to the 60 seconds. Databases and streams expire by the same rule; a Machine's database never expires. The answer is 201 once the database is active and 202 while it is still being created; read it again until it is active. A replay with the same Idempotency-Key answers the same database; the key reused for another name, region or expiry answers 409. An account holds at most `limits.databases_per_account` live databases (contracts/streams-databases/v1.json); past that the answer is 403 and names whom to contact. An account whose credit is exhausted answers 402.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | A 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. |

#### Request body

`application/json`, required.

[CreateDatabaseRequest](#schema-create-database-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `201` | The database, active | `data`: [Database](#schema-database) |
| `202` | The database, still being created | `data`: [Database](#schema-database) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `402` | [PaymentRequired](#error-payment-required) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/databases" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>"}'
```

### Read a database

`GET /v1/databases/{name}` · CLI: `tonbo database show`

A live database of the account, with its `url`, `expires_at` and `state`; clients that open a database by name, such as `@tonbo/sqlite-ursula/tonbo`, resolve it here. A key narrowed to databases reads the databases it names and those under its prefixes, and any other name answers 404 with the same status and body as a database that does not exist, so the narrowing does not disclose whether the account's other databases exist. A key narrowed to buckets, or by name to streams alone, reads none.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [ListedDatabaseName](#schema-listed-database-name) | yes | The database's name, or a Machine's database as `m.<machine id>`. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The database | `data`: [Database](#schema-database) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/databases/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Publish a database for anonymous reads, or take it private

`PATCH /v1/databases/{name}` · CLI: `tonbo database update`

Owners and admins, on an active database. Anonymous readers reach only the database's current incarnation. A Machine's database answers 409 `managed_by_machine` (contracts/streams-databases/v1.json `kinds.managed_refusal`).

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [ListedDatabaseName](#schema-listed-database-name) | yes | The database's name, or a Machine's database as `m.<machine id>`. |
| `If-Match` | header | string | no | Optional conditional update; omitted requests use last-write-wins. |

#### Request body

`application/json`, required.

[UpdateDatabaseRequest](#schema-update-database-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The database | `data`: [Database](#schema-database) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `412` | [PreconditionFailed](#error-precondition-failed) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X PATCH "https://api.tonbo.dev/v1/databases/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"public_read":false}'
```

### Delete a database and everything in it

`DELETE /v1/databases/{name}` · CLI: `tonbo database delete`

Any member deletes databases; a narrowed key never does. The answer is 200 once the database is deleted (`state` is `deleted`) and 202 while it is still being deleted. Its name is free again once it is deleted; its usage this month stays in `/usage/databases`. Repeating the request answers the database being deleted. A Machine's database is deleted with its Machine and answers 409 `managed_by_machine` (contracts/streams-databases/v1.json `kinds.managed_refusal`).

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [ListedDatabaseName](#schema-listed-database-name) | yes | The database's name, or a Machine's database as `m.<machine id>`. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The database, deleted | `data`: [Database](#schema-database) |
| `202` | The database, being deleted | `data`: [Database](#schema-database) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X DELETE "https://api.tonbo.dev/v1/databases/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Streams

- [`GET /streams`](#list-streams) List the account's streams
- [`POST /streams`](#create-stream) Create a stream
- [`GET /streams/{name}`](#get-stream) Read a stream
- [`PATCH /streams/{name}`](#update-stream) Publish a stream for anonymous reads, or take it private
- [`DELETE /streams/{name}`](#delete-stream) Delete a stream and everything in it

### List the account's streams

`GET /v1/streams` · CLI: `tonbo stream list`

Live raw streams (contracts/streams-databases/v1.json kinds whose `api` is streams), creating, active or deleting, in every region, by name. A key narrowed to streams sees only the streams it reaches, and a key narrowed to buckets, or by name to databases alone, sees none. `last_write_at` and `stored_bytes` come from each region's meter; `unavailable_regions` names the regions whose sizes could not be read, whose streams then show null sizes.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `prefix` | query | [StreamPrefix](#schema-stream-prefix) | no | Only streams whose name starts with this prefix. |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Streams | `data`: array of [Stream](#schema-stream) , `next_cursor`: [NextCursor](#schema-next-cursor) , `unavailable_regions`: array of [RegionId](#schema-region-id) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/streams" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Create a stream

`POST /v1/streams` · CLI: `tonbo stream create`

Any member creates streams; a narrowed key never does. A name is unique among the account's streams across every region while a stream holds it, and is free again once that stream is deleted; a stream and a database may share a name. The region is fixed at creation; without one, the stream goes to the region whose `default` is true for databases, since a stream lives in the same account bucket. The content type is fixed at creation (`application/octet-stream` when omitted) and every append must send exactly it. An optional `expires_at`, at least 60 seconds after the creation and fixed for the stream's life, has Tonbo delete the stream within a minute after it passes, as a deletion would; a replay is not held to the 60 seconds. The answer is 201 once the stream is active and 202 while it is still being created; read it again until it is active. A replay with the same Idempotency-Key answers the same stream; the key reused for another name, region, content type or expiry answers 409. An account holds at most `limits.streams_per_account` live streams (contracts/streams-databases/v1.json), counted apart from its databases; past that the answer is 403 and names whom to contact. An account whose credit is exhausted answers 402.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | A 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. |

#### Request body

`application/json`, required.

[CreateStreamRequest](#schema-create-stream-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `201` | The stream, active | `data`: [Stream](#schema-stream) |
| `202` | The stream, still being created | `data`: [Stream](#schema-stream) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `402` | [PaymentRequired](#error-payment-required) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/streams" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>"}'
```

### Read a stream

`GET /v1/streams/{name}` · CLI: `tonbo stream show`

A live stream of the account, with its `url`, `content_type`, `expires_at` and `state`. A key narrowed to streams reads the streams it names and those under its prefixes; a key narrowed to buckets, or by name to databases alone, reads none. Any name a key does not reach answers 404 with the same status and body as a stream that does not exist.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [StreamName](#schema-stream-name) | yes | The stream's name, without the `s.` its URL adds. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The stream | `data`: [Stream](#schema-stream) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/streams/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Publish a stream for anonymous reads, or take it private

`PATCH /v1/streams/{name}` · CLI: `tonbo stream update`

Owners and admins, on an active stream. Anonymous readers reach only the stream's current incarnation, with GET and HEAD (reads, long-poll and SSE tails); they never append.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [StreamName](#schema-stream-name) | yes | The stream's name, without the `s.` its URL adds. |
| `If-Match` | header | string | no | Optional conditional update; omitted requests use last-write-wins. |

#### Request body

`application/json`, required.

[UpdateStreamRequest](#schema-update-stream-request)

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The stream | `data`: [Stream](#schema-stream) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [Conflict](#error-conflict) | [Problem](#schema-problem) |
| `412` | [PreconditionFailed](#error-precondition-failed) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X PATCH "https://api.tonbo.dev/v1/streams/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"public_read":false}'
```

### Delete a stream and everything in it

`DELETE /v1/streams/{name}` · CLI: `tonbo stream delete`

Any member deletes streams; a narrowed key never does. The answer is 200 once the stream is deleted (`state` is `deleted`) and 202 while it is still being deleted. Its name is free again once it is deleted; its usage this month stays in `/usage/streams`. Repeating the request answers the stream being deleted.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | [StreamName](#schema-stream-name) | yes | The stream's name, without the `s.` its URL adds. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The stream, deleted | `data`: [Stream](#schema-stream) |
| `202` | The stream, being deleted | `data`: [Stream](#schema-stream) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS -X DELETE "https://api.tonbo.dev/v1/streams/<name>" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Usage

- [`GET /usage`](#get-usage) Read the account's rated usage of one month
- [`GET /usage/databases`](#list-database-usage) Read each database's usage of one month at list price
- [`GET /usage/streams`](#list-stream-usage) Read each stream's usage of one month at list price

### Read the account's rated usage of one month

`GET /v1/usage` · CLI: `tonbo usage show`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `period` | query | string | no | The calendar month in UTC; the current month when omitted. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Usage | `data`: [Usage](#schema-usage) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/usage" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Read each database's usage of one month at list price

`GET /v1/usage/databases` · CLI: `tonbo database usage`

One row per listed database version (name, stream creation time and counting history) metered in the month, rolled up by contracts/streams-databases/v1.json `kinds.values.<kind>.usage`: a Machine's logs and inbox streams are added into its database's row, and a Volume's ledger into `other`; in every region the account reaches, including databases deleted during the month, plus `other` for metered usage outside the account's databases (its streams, which `/usage/streams` lists, session processing and anything unattributed), so the rows and `other` add up to the account's metered total. Values are at list price, before the monthly allowance and credit; Usage & billing (`/usage`) shows what is charged.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `period` | query | string | no | The calendar month in UTC; the current month when omitted. |
| `sort` | query | one of `"-list_value"` or `"name"` | no | By list value, highest first (the default), or by name. |
| `prefix` | query | [DatabasePrefix](#schema-database-prefix) | no | Only databases whose name starts with this prefix. |
| `names` | query | string | no | Only these databases, 1 to 100 listed names, comma-separated (a database name or a Machine's database). |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Per-database usage | [DatabaseUsagePage](#schema-database-usage-page) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/usage/databases" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Read each stream's usage of one month at list price

`GET /v1/usage/streams` · CLI: `tonbo stream usage`

One row per stream version (name, stream creation time and counting history) metered in the month, in every region the account reaches, including streams deleted during the month, plus `other` for metered usage outside the account's streams (its databases, session processing and anything unattributed), so the rows and `other` add up to the account's metered total, as `/usage/databases` does for databases; no stream is a row of both. Values are at list price, before the monthly allowance and credit, which databases and streams share; Usage & billing (`/usage`) shows what is charged.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `period` | query | string | no | The calendar month in UTC; the current month when omitted. |
| `sort` | query | one of `"-list_value"` or `"name"` | no | By list value, highest first (the default), or by name. |
| `prefix` | query | [StreamPrefix](#schema-stream-prefix) | no | Only streams whose name starts with this prefix. |
| `names` | query | string | no | Only these streams, 1 to 100 stream names, comma-separated. |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Per-stream usage | [StreamUsagePage](#schema-stream-usage-page) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [ValidationError](#error-validation-error) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/usage/streams" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Machines Not open yet

> Machines are not open yet. These pages describe Machines as they will open; until then, a create answers [`region_not_offered`](https://tonbo.dev/docs/machines/troubleshooting/manage.md#region_not_offered) (422), “Machines are not offered in any region yet.”

- [`GET /machines`](#list-machines) List the account's Machines
- [`POST /machines`](#create-machine) Create a Machine
- [`GET /machines/{name}`](#get-machine) Read a Machine
- [`PATCH /machines/{name}`](#update-machine) Change a Machine's spec
- [`DELETE /machines/{name}`](#delete-machine) Delete a Machine
- [`POST /machines/{name}/sleep`](#sleep-machine) Put a Machine to sleep now
- [`GET /machines/{name}/activations`](#list-machine-activations) List a Machine's activations
- [`GET /machine-shapes`](#list-machine-shapes) List the Machine shapes

### List the account's Machines

`GET /v1/machines` · CLI: `tonbo machine list`

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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Machines | `data`: array of [Machine](#schema-machine) , `next_cursor`: [NextCursor](#schema-next-cursor) , `unavailable_regions`: array of [RegionId](#schema-region-id) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/machines" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

### Create a Machine

`POST /v1/machines` · CLI: `tonbo machine create`

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). 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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | A 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. |

#### Request body

`application/json`, required.

`machines/machine-v2.schema.json#/$defs/create`

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `201` | The Machine, no longer being created (a replay of a failed create carries its `failure`) | [MachineEnvelope](#schema-machine-envelope) |
| `202` | The Machine, still being created | [MachineEnvelope](#schema-machine-envelope) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `409` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `429` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/machines" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @request.json
```

### Read a Machine

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

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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | `machines/machine-v2.schema.json#/$defs/name` | yes | The Machine's name. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The Machine, with its ETag | [MachineEnvelope](#schema-machine-envelope) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
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`

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`. 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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | `machines/machine-v2.schema.json#/$defs/name` | yes | The Machine's name. |
| `If-Match` | header | string | yes | The 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`. |

#### Request body

`application/json`, required.

`machines/machine-v2.schema.json#/$defs/update`

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The Machine | [MachineEnvelope](#schema-machine-envelope) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `412` | [PreconditionFailed](#error-precondition-failed) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `428` | [PreconditionRequired](#error-precondition-required) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
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 @request.json
```

### Delete a Machine

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

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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | `machines/machine-v2.schema.json#/$defs/name` | yes | The Machine's name. |
| `If-Match` | header | string | no | The 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`. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `202` | The Machine, being deleted, or the device, forgotten | [MachineEnvelope](#schema-machine-envelope) |
| `204` | The name was released, no Machine having been recorded under it, or the device was forgotten, its region holding no row for it | none |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `412` | [PreconditionFailed](#error-precondition-failed) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
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`

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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | `machines/machine-v2.schema.json#/$defs/name` | yes | The Machine's name. |
| `If-Match` | header | string | no | The 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`. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | The Machine, unchanged, with nothing to do | [MachineEnvelope](#schema-machine-envelope) |
| `202` | The Machine, going to sleep | [MachineEnvelope](#schema-machine-envelope) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `412` | [PreconditionFailed](#error-precondition-failed) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
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`

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

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | `machines/machine-v2.schema.json#/$defs/name` | yes | The Machine's name. |
| `cursor` | query | string | no | Opaque cursor tied to the original query shape. |
| `limit` | query | integer | no | The 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. |

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Activations, newest first | `data`: array of `machines/machine-v2.schema.json#/$defs/activation_entry` , `next_cursor`: [NextCursor](#schema-next-cursor) |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `404` | [NotFound](#error-not-found) | [Problem](#schema-problem) |
| `409` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `422` | [MachineRefused](#error-machine-refused) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |
| `503` | [RegionUnavailable](#error-region-unavailable) | [Problem](#schema-problem) |

**Request**

```shell
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`

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.

#### Responses

| Status | Answer | Body |
| --- | --- | --- |
| `200` | Shapes | `data`: array of [MachineShape](#schema-machine-shape) , `default`: string |
| `401` | [MemberUnauthorized](#error-member-unauthorized) | [Problem](#schema-problem) |
| `403` | [Forbidden](#error-forbidden) | [Problem](#schema-problem) |
| `429` | [RateLimited](#error-rate-limited) | [Problem](#schema-problem) |

**Request**

```shell
curl -sS "https://api.tonbo.dev/v1/machine-shapes" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Schemas

### Account

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | [Uuid](#schema-uuid) | yes | The account's id; a database's and a stream's URL name it after the region's origin. |
| `kind` | one of `"user"` or `"organization"` | yes | `user` for a person's own account, `organization` for an organization's. |
| `name` | string | yes | The account's name as the console shows it. |
| `member` | object | yes | The caller's own membership. |
| `member.kind` | one of `"user"` or `"agent"` | yes | `user` when the key acts as a person, `agent` when it acts as an Agent. |
| `member.subject` | [MemberSubject](#schema-member-subject) | yes | Who the key acts as, a user id or `agent.<agent id>`. |
| `member.role` | [AccountRole](#schema-account-role) | yes | The caller's current role, which decides what the key may do. |
| `limits` | object | yes | How much of each resource the account may hold. |
| `limits.buckets` | integer | yes | Live buckets the account may hold. |

### AccountAgent

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | [Uuid](#schema-uuid) | yes | The Agent's id. |
| `subject` | [MemberSubject](#schema-member-subject) | yes | `agent.<id>`, the Agent as a member: name it as `member` to create a key for the Agent, and in `PATCH /v1/members/{subject}`. |
| `name` | string | yes | The Agent's name. |
| `role` | [AccountRole](#schema-account-role) | yes | The Agent's role. |
| `region` | string | yes | The Agent's home region, fixed at creation. |
| `created_at` | [Timestamp](#schema-timestamp) | yes | When the Agent was created. |

### AccountRole

A member's role in the account. A `member` reads the account, creates and deletes databases, streams and Machines, and manages their own keys. An `admin` also publishes databases and streams, changes members, creates Agents and their keys, revokes others' keys and reads usage. An `owner` also buys credit and grants `owner`. contracts/iam/account-roles-v1.json is the full table.

one of `"owner"`, `"admin"` or `"member"`

### AgentName

The Agent's one name, defined by contracts/agents/agent-name-v1.json. It is unique among active Agents within its account and an owner or admin may change it (updateMember); there is no separate display name or slug.

string

### ApiKey

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | [Uuid](#schema-uuid) | yes | The key's id, which `GET` and `DELETE /v1/api-keys/{key_id}` name. |
| `name` | string | yes | The key's name, unique, ignoring case, among its member's live keys. |
| `member` | [MemberSubject](#schema-member-subject) | yes | Who the key acts as, with that member's current role: a person's user id, or `agent.<agent id>` for an Agent key. |
| `member_kind` | one of `"user"` or `"agent"` | yes | `user` for a person's key, `agent` for an Agent key. |
| `last4` | string | yes | The secret's last four characters, to tell keys apart; the secret itself is shown only when the key is created. |
| `read_only` | boolean | yes | Whether the key is narrowed to read-only actions. |
| `buckets` | array of string | yes | The buckets the key is narrowed to; empty when it is not narrowed by bucket. |
| `databases` | array of [DatabaseName](#schema-database-name) | yes | The database names the key is narrowed to. A key whose five narrowing lists are all empty reaches everything its member does; otherwise it reaches only what the lists name. |
| `database_prefixes` | array of [DatabasePrefix](#schema-database-prefix) | yes | The database name prefixes the key is narrowed to. |
| `streams` | array of [StreamName](#schema-stream-name) | yes | The stream names the key is narrowed to, without `s.`. |
| `stream_prefixes` | array of [StreamPrefix](#schema-stream-prefix) | yes | The stream name prefixes the key is narrowed to. |
| `expires_at` | [Timestamp](#schema-timestamp) or null | yes | When the key stops working; null when it never expires. |
| `last_used_at` | [Timestamp](#schema-timestamp) or null | yes | Recorded at most once a minute. |
| `created_at` | [Timestamp](#schema-timestamp) | yes | When the key was created. |

### ApiKeyExpiration

How long a new key lives from its creation; `1_month` is 30 days, `6_months` 183 days and `1_year` 365 days, and `never` keeps it until it is revoked.

one of `"15_days"`, `"1_month"`, `"6_months"`, `"1_year"` or `"never"`

### ApiKeyWithSecret

Every field of [ApiKey](#schema-api-key), and:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | string | yes | The secret, shown once. `tbo_` followed by 50 base-58 characters, the last six a checksum. |

### ContentType

A stream's content type (contracts/streams-databases/v1.json `content_type`): one lowercase media type `type/subtype` without parameters, at most 127 bytes, never under `application/vnd.ursula.` (the formats Tonbo manages). It is the form Ursula normalizes a content type to, so every append sends exactly it; `application/json` streams are in JSON mode.

string

### CreateAccountAgentRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [AgentName](#schema-agent-name) | yes | The Agent's name; 409 when an active Agent of the account has it. |
| `role` | [AccountRole](#schema-account-role) | no | The Agent's role; `member` when omitted. Only an owner grants `owner`. |
| `region` | string | no | The Agent's home region; the only open region when omitted. |

### CreateApiKeyRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | A name to tell the key apart, 1 to 80 characters once spaces are trimmed; unique, ignoring case, among the member's live keys, so a second live key with the name answers 409. |
| `member` | [MemberSubject](#schema-member-subject) | no | Omit for your own personal key; `agent.<agent id>` for an Agent key. |
| `read_only` | boolean | no | Narrow the key to read-only actions, for its whole life. |
| `buckets` | array of string | no | Narrow the key to these registered buckets of the account, for its whole life. Never with databases, database_prefixes, streams or stream_prefixes. |
| `databases` | array of [DatabaseName](#schema-database-name) | no | Narrow the key to these databases, for its whole life; a name need not exist yet. Never with buckets. |
| `database_prefixes` | array of [DatabasePrefix](#schema-database-prefix) | no | Narrow the key to the databases whose name starts with one of these, for its whole life. Never with buckets. |
| `streams` | array of [StreamName](#schema-stream-name) | no | Narrow the key to these raw streams, by the name the streams API shows, for its whole life; a name need not exist yet. Never with buckets. A key narrowed to both databases and streams reaches both. |
| `stream_prefixes` | array of [StreamPrefix](#schema-stream-prefix) | no | Narrow the key to the raw streams whose name starts with one of these, for its whole life; each at most 83 bytes, the longest stream name. Never with buckets. |
| `expires_in` | [ApiKeyExpiration](#schema-api-key-expiration) | no | How long the key lives from now; it never expires when omitted. |

### CreateDatabaseRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [DatabaseName](#schema-database-name) | yes | The new database's name. 409 when a live database of the account in any region has it; a deleted database's name is free again. |
| `region` | [RegionId](#schema-region-id) | no | The database's region, fixed for its life; the region whose `default` is true when omitted. A region closed to databases answers 503. |
| `expires_at` | [Timestamp](#schema-timestamp) | no | When the database expires, an RFC 3339 time, fixed for its life; it never expires when omitted. A new database's expiry must be at least 60 seconds after its creation by the registry's clock (422 on `expires_at` otherwise); a replay of the Idempotency-Key must name the same time and is answered however near or past that time is. Once it has passed, Tonbo deletes the database within a minute, as a deletion would. |

### CreateStreamRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [StreamName](#schema-stream-name) | yes | The new stream's name, which its URL shows after `s.`. 409 when a live stream of the account in any region has it; a deleted stream's name is free again. |
| `region` | [RegionId](#schema-region-id) | no | The stream's region, fixed for its life; the region whose `default` is true when omitted. A region closed to databases, and so to streams, answers 503. |
| `content_type` | [ContentType](#schema-content-type) | no | The stream's content type, fixed for its life; `application/octet-stream` when omitted. |
| `expires_at` | [Timestamp](#schema-timestamp) | no | When the stream expires, an RFC 3339 time, fixed for its life; it never expires when omitted. A new stream's expiry must be at least 60 seconds after its creation by the registry's clock (422 on `expires_at` otherwise); a replay of the Idempotency-Key must name the same time and is answered however near or past that time is. Once it has passed, Tonbo deletes the stream within a minute, as a deletion would. |

### Database

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [ListedDatabaseName](#schema-listed-database-name) | yes | The database's name; a Machine's database is `m.<machine id>`. |
| `kind` | [DatabaseKind](#schema-database-kind) | yes | `database` for one a member created, `machine` for a Machine's own, which its Machine creates and deletes. |
| `owner_id` | [Uuid](#schema-uuid) or null | yes | The Machine that owns a `machine` row; null for a member's database. |
| `region` | [RegionId](#schema-region-id) | yes | The region the database lives in, fixed for its life. |
| `url` | string (uri) | yes | The database's data-plane address, `{region's database origin}/{account id}/{name}`; open it with the SQLite on Ursula client. |
| `expires_at` | [Timestamp](#schema-timestamp) or null | yes | When the database expires, fixed at creation; null when it never expires, and always for a Machine's database. Once it has passed, Tonbo deletes the database within a minute, as a deletion would, and its name is free again. |
| `incarnation` | string or null | yes | Ursula's Stream-Incarnation as a decimal; null while creating. A client that cached the URL resolves the database again when it changes. |
| `state` | one of `"creating"`, `"active"`, `"deleting"` or `"deleted"` | yes | `deleted` appears only in the answer to the deletion that completed it. |
| `public_read` | boolean | yes | Whether anonymous reads are served. |
| `created_at` | [Timestamp](#schema-timestamp) | yes | When the database was created. |
| `created_by` | [MemberSubject](#schema-member-subject) | yes | The member who created it, or for a Machine's database the member who created the Machine. |
| `last_write_at` | [Timestamp](#schema-timestamp) or null | yes | The last write the region's meter saw, up to a minute behind; null until a host first opens it (an open writes; the create does not), and when its region is unavailable. |
| `stored_bytes` | object | yes | What the region's meter last saw stored; null when not yet reported or unavailable. |
| `stored_bytes.log` | integer or null | yes | Bytes of the database's retained log, as Tonbo stores it; null when not reported. |
| `stored_bytes.snapshot` | integer or null | yes | Bytes of its snapshots; null when not reported. |
| `measured_at` | [Timestamp](#schema-timestamp) or null | no | When the region's meter last read `last_write_at` and `stored_bytes` (every minute while it is healthy); null when the database is not yet metered or its region is unavailable. Optional; absent from older answers. |
| `writes` | one of `"accepting"`, `"suspended"` or `null` | no | Whether Tonbo accepts writes to the database now. `suspended` while the account has no credit left, when writes answer 402 PAYMENT_REQUIRED and reads continue; writes resume shortly after a top-up. It is the account's state, the same for each of its databases and streams. Only in the answer for one database (show and update), never in a list; null when it could not be read. |

### DatabaseKind

The registry kind of a listed row (contracts/streams-databases/v1.json `kinds`). A `machine` row is managed by its Machine; deleting it or changing its visibility answers 409 `managed_by_machine`.

one of `"database"` or `"machine"`

### DatabaseName

A database's name (contracts/streams-databases/v1.json `name`): 1 to 85 lowercase letters, digits, dots, underscores and hyphens, starting and ending with a letter or digit, without `..`; `m.`, `vol.`, `s.`, `session.` and `proc.` are reserved, and `streams` and `new` (the console's create page) are not names. It appears unencoded in the database's URL and in key narrowing.

string

### DatabasePrefix

A database name prefix (contracts/streams-databases/v1.json `prefix`); it selects every name that starts with it.

string

### DatabaseUsage

Every field of [DatabaseUsageAmounts](#schema-database-usage-amounts), and:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [ListedDatabaseName](#schema-listed-database-name) | yes | The database's name; a Machine's database is `m.<machine id>`. |
| `kind` | [DatabaseKind](#schema-database-kind) | yes | `database` for one a member created, `machine` for a Machine's own. |
| `owner_id` | [Uuid](#schema-uuid) or null | yes | The Machine that owns a `machine` row, whose logs and inbox streams are added into it; null for a member's database. |
| `region` | [RegionId](#schema-region-id) | yes | The database's region. |
| `stream_created_at` | [Timestamp](#schema-timestamp) | yes | When Ursula created the stream version; with `counted_since`, the version the row is. |
| `counted_since` | string | yes | The Ursula usage history the version began counting in, 16 hex digits. |
| `state` | one of `"active"` or `"deleted"` | yes | `active` while this version of the database exists, `deleted` once it was deleted; a deleted version's usage in the month still counts. |
| `created_at` | [Timestamp](#schema-timestamp) or null | yes | Null for a metered version the registry does not know. |
| `deleted_at` | [Timestamp](#schema-timestamp) or null | yes | When this version was deleted; null while it is active. |

### DatabaseUsageAmounts

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `write_units` | string | yes | Write units, an integer as text. |
| `retention_gib_months` | string | yes | The retained log in GiB-months (GiB = 1024^3 bytes, month = retention_month_seconds), a decimal as text. |
| `snapshot_gib_months` | string or null | yes | Snapshots in GiB-months, a decimal as text; null when never reported. |
| `list_value_micros` | integer | yes | The value at list price in micro-dollars, before allowance and credit. |

### DatabaseUsagePage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `period` | string | yes | The calendar month counted, in UTC; the `period` asked for, or the current month. |
| `as_of` | [Timestamp](#schema-timestamp) or null | yes | The oldest meter checkpoint among the regions read; usage after it is not yet counted. Null before a region's first. |
| `currency` | `"usd"` | yes | `usd`: every `_micros` value is in millionths of a US dollar. |
| `basis` | `"at_list_price_before_allowance_and_credit"` | yes | Values are at list price, before the monthly allowance and credit apply; `GET /v1/usage` answers what the account is charged. |
| `pricing` | object | yes | The list prices of contracts/billing/streams-prices-v1.json the values use. |
| `pricing.write_unit_bytes` | integer | yes | The bytes of one write unit. A write counts one unit for each started unit of its bytes, and at least one. |
| `pricing.write_micros_per_million_units` | integer | yes | The price of a million write units, in micro-dollars. |
| `pricing.retention_micros_per_gib_month` | integer | yes | The price of keeping one GiB for one month, in micro-dollars. |
| `pricing.retention_unit_bytes` | integer | yes | The bytes of the GiB that price is quoted for, 1024^3. |
| `pricing.retention_month_seconds` | integer | yes | The seconds of the month that price is quoted for, 30 days. |
| `data` | array of [DatabaseUsage](#schema-database-usage) | yes | One row per database version metered in the month, in the order `sort` names. |
| `other` | [DatabaseUsageAmounts](#schema-database-usage-amounts) | yes | Metered usage outside the account's databases (its streams, session processing and anything unattributed), in every region read. |
| `next_cursor` | [NextCursor](#schema-next-cursor) | yes | The `cursor` of the next page, sent with the same other parameters; null on the last page. |
| `unavailable_regions` | array of [RegionId](#schema-region-id) | yes | Regions whose usage could not be read; their databases and `other` are missing from this answer. |

### ListedDatabaseName

The name of a row the databases API lists (contracts/streams-databases/v1.json kinds whose `api` is databases): a member's database or a Machine's.

[DatabaseName](#schema-database-name) or [MachineDatabaseName](#schema-machine-database-name)

### Machine

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

`machines/machine-v2.schema.json`

### MachineDatabaseName

A Machine's database, `m.<machine id>` (contracts/streams-databases/v1.json `kinds.values.machine.name_pattern`). Control creates and deletes it with its Machine.

string

### MachineEnvelope

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | [Machine](#schema-machine) | yes | The Machine. |

### MachineShape

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `shape` | `machines/machine-v2.schema.json#/$defs/shape` | yes | The shape's name, which a Machine's `shape` names. |
| `vcpus` | integer | yes | The most vCPUs a Machine of this shape uses. |
| `memory_gib` | number | yes | The most memory a Machine of this shape uses, in GiB. |
| `scratch_gib` | number | yes | The most scratch disk a Machine of this shape uses, in GiB. |
| `offered` | boolean | yes | Whether a Machine may name this shape now; a shape not offered answers 422 `shape_not_offered`. |
| `suspendable` | boolean | yes | Whether 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_hour` | integer | yes | The 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. |

### Member

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | one of `"user"` or `"agent"` | yes | `user` for a person, `agent` for an Agent. |
| `subject` | [MemberSubject](#schema-member-subject) | yes | The member's id in the account, which `PATCH /v1/members/{subject}` names. |
| `role` | [AccountRole](#schema-account-role) | yes | The member's role. |
| `name` | string or null | yes | A person's username, or the Agent's name; null when the person has none. |
| `display_name` | string or null | yes | A person's display name, or the Agent's name; null when the person has none. |
| `created_at` | [Timestamp](#schema-timestamp) | yes | When the person or Agent joined the account. |

### MemberSubject

A person's user id, or `agent.<agent id>` for an Agent; the `sub` of its access tokens.

string

### NextCursor

Opaque cursor of the next page; null on the last page.

string or null

### Problem

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | string (uri) | yes | A URI naming the kind of refusal, the same for every occurrence (RFC 9457); `code` names the same kind. |
| `title` | string | yes | A short summary of the kind of refusal, the same for every occurrence. |
| `status` | integer | yes | The HTTP status of the answer. |
| `code` | string | yes | The kind of refusal, such as `validation_error` or `not_found`; a client acts on it, never on `title` or `detail`. |
| `request_id` | string | yes | The answer's `X-Request-Id`; quote it when you ask Tonbo about the request. |
| `detail` | string | no | Explanation specific to this occurrence, when one exists. |
| `failure_code` | [TurnFailureCode](#schema-turn-failure-code) or null | no | Present only on `turn_failed`; null for a failure recorded without a code. |
| `token_endpoint` | string (uri) | no | Present only on `exchange_required`; where to exchange the key. |
| `errors` | array of [ValidationIssue](#schema-validation-issue) | no | The inputs the refusal is about, when it names any. |

### Region

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | [RegionId](#schema-region-id) | yes | The region's id, which a creation names as `region`. |
| `url` | string (uri) | yes | The origin of the data plane in this region; a database's URL is this origin, the account id and the database name, and a bucket's is this origin and the bucket name. |
| `default` | boolean | yes | Whether a database goes to this region when its creation names none. |

### RegionId

A region's id; `GET /v1/regions` lists the regions open to new resources.

string

### Stream

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [StreamName](#schema-stream-name) | yes | The stream's name, without the `s.` its URL adds. |
| `region` | [RegionId](#schema-region-id) | yes | The region the stream lives in, fixed for its life. |
| `url` | string (uri) | yes | The stream's data-plane address, `{region's Streams origin}/{account id}/s.{name}` (contracts/streams-databases/v1.json `stream_url`); append, read, tail and close it with any Durable Streams client. |
| `content_type` | [ContentType](#schema-content-type) | yes | The stream's content type, fixed at creation; every append sends exactly it. |
| `expires_at` | [Timestamp](#schema-timestamp) or null | yes | When the stream expires, fixed at creation; null when it never expires. Once it has passed, Tonbo deletes the stream within a minute, as a deletion would, and its name is free again. |
| `incarnation` | string or null | yes | Ursula's Stream-Incarnation as a decimal; null while creating. It changes when a stream of the same name is deleted and created again. |
| `state` | one of `"creating"`, `"active"`, `"deleting"` or `"deleted"` | yes | `deleted` appears only in the answer to the deletion that completed it. |
| `public_read` | boolean | yes | Whether anonymous reads are served. |
| `created_at` | [Timestamp](#schema-timestamp) | yes | When the stream was created. |
| `created_by` | [MemberSubject](#schema-member-subject) | yes | The member who created it. |
| `last_write_at` | [Timestamp](#schema-timestamp) or null | yes | The last write the region's meter saw, up to a minute behind; null before the first append (the create is not one), and when its region is unavailable. |
| `stored_bytes` | object | yes | What the region's meter last saw stored; null when not yet reported or unavailable. |
| `stored_bytes.log` | integer or null | yes | Bytes of the stream's retained messages, as Tonbo stores them; null when not reported. |
| `stored_bytes.snapshot` | integer or null | yes | Bytes of its snapshots; null when not reported. |
| `measured_at` | [Timestamp](#schema-timestamp) or null | no | When the region's meter last read `last_write_at` and `stored_bytes` (every minute while it is healthy); null when the stream is not yet metered or its region is unavailable. Optional; absent from older answers. |
| `writes` | one of `"accepting"`, `"suspended"` or `null` | no | Whether Tonbo accepts writes to the stream now. `suspended` while the account has no credit left, when writes answer 402 PAYMENT_REQUIRED and reads continue; writes resume shortly after a top-up. It is the account's state, the same for each of its databases and streams. Only in the answer for one stream (show and update), never in a list; null when it could not be read. |

### StreamName

A stream's name (contracts/streams-databases/v1.json `kinds.values.stream.name_pattern`'s capture group): the database name rule with at most 83 bytes, since its stream id is `s.` and the name. 1 to 83 lowercase letters, digits, dots, underscores and hyphens, starting and ending with a letter or digit, without `..`; `m.`, `vol.`, `s.`, `session.` and `proc.` are reserved, and `streams` and `new` are not names. It appears unencoded in the stream's URL after `s.`.

string

### StreamPrefix

A stream name prefix, under the name prefix rule (contracts/streams-databases/v1.json `prefix`); it selects every stream whose name starts with it.

string

### StreamUsage

Every field of [DatabaseUsageAmounts](#schema-database-usage-amounts), and:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | [StreamName](#schema-stream-name) | yes | The stream's name, without `s.`. |
| `region` | [RegionId](#schema-region-id) | yes | The stream's region. |
| `stream_created_at` | [Timestamp](#schema-timestamp) | yes | When Ursula created the stream version; with `counted_since`, the version the row is. |
| `counted_since` | string | yes | The Ursula usage history the version began counting in, 16 hex digits. |
| `state` | one of `"active"` or `"deleted"` | yes | `active` while this version of the stream exists, `deleted` once it was deleted; a deleted version's usage in the month still counts. |
| `created_at` | [Timestamp](#schema-timestamp) or null | yes | Null for a metered version the registry does not know. |
| `deleted_at` | [Timestamp](#schema-timestamp) or null | yes | When this version was deleted; null while it is active. |

### StreamUsagePage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `period` | string | yes | The calendar month counted, in UTC; the `period` asked for, or the current month. |
| `as_of` | [Timestamp](#schema-timestamp) or null | yes | The oldest meter checkpoint among the regions read; usage after it is not yet counted. Null before a region's first. |
| `currency` | `"usd"` | yes | `usd`: every `_micros` value is in millionths of a US dollar. |
| `basis` | `"at_list_price_before_allowance_and_credit"` | yes | Values are at list price, before the monthly allowance and credit apply; `GET /v1/usage` answers what the account is charged. |
| `pricing` | object | yes | The list prices of contracts/billing/streams-prices-v1.json the values use. |
| `pricing.write_unit_bytes` | integer | yes | The bytes of one write unit. A write counts one unit for each started unit of its bytes, and at least one. |
| `pricing.write_micros_per_million_units` | integer | yes | The price of a million write units, in micro-dollars. |
| `pricing.retention_micros_per_gib_month` | integer | yes | The price of keeping one GiB for one month, in micro-dollars. |
| `pricing.retention_unit_bytes` | integer | yes | The bytes of the GiB that price is quoted for, 1024^3. |
| `pricing.retention_month_seconds` | integer | yes | The seconds of the month that price is quoted for, 30 days. |
| `data` | array of [StreamUsage](#schema-stream-usage) | yes | One row per stream version metered in the month, in the order `sort` names. |
| `other` | [DatabaseUsageAmounts](#schema-database-usage-amounts) | yes | Metered usage outside the account's streams (its databases, session processing and anything unattributed), in every region read. |
| `next_cursor` | [NextCursor](#schema-next-cursor) | yes | The `cursor` of the next page, sent with the same other parameters; null on the last page. |
| `unavailable_regions` | array of [RegionId](#schema-region-id) | yes | Regions whose usage could not be read; their streams and `other` are missing from this answer. |

### Timestamp

An RFC 3339 time; answers give it in UTC.

string (date-time)

### TurnFailureCode

Why a Turn failed. deadline_exceeded: it ran for the full execution window. start_timeout: it could not start before its start deadline. aborted: it was aborted. agent_retired: its Agent was deleted or retired. account_deleted: its account entered deletion. key_revoked: the key that submitted it was revoked. funding_exhausted: its account could not fund the runtime or inference. usage_store_unavailable: runtime usage could not be recorded, so the runtime was stopped. runtime_retired: its runtime stopped before the Turn ended. runtime_uncertain: the platform could not establish whether the prompt ran, so it was not replayed. pi_failed: the agent itself failed the Turn. legacy_operation_ended: a Turn created before admission records whose operation ended without it; only during the migration.

one of `"deadline_exceeded"`, `"start_timeout"`, `"aborted"`, `"agent_retired"`, `"account_deleted"`, `"key_revoked"`, `"funding_exhausted"`, `"usage_store_unavailable"`, `"runtime_retired"`, `"runtime_uncertain"`, `"pi_failed"` or `"legacy_operation_ended"`

### UpdateDatabaseRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `public_read` | boolean | yes | `true` publishes the database: anyone with its URL reads it without a key, and writes still need one. `false` makes it private again. |

### UpdateMemberRequest

At least one field.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `role` | [AccountRole](#schema-account-role) | no | The member's new role. Only an owner grants `owner`, and an account always keeps at least one person as owner. |
| `name` | [AgentName](#schema-agent-name) | no | The Agent's new name; refused for a person. |

### UpdateStreamRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `public_read` | boolean | yes | `true` publishes the stream: anyone with its URL reads and tails it without a key, and appends still need one. `false` makes it private again. |

### Usage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `period` | string | yes | The calendar month counted, in UTC; the `period` asked for, or the current month. |
| `currency` | `"usd"` | yes | `usd`: every `_micros` amount is in millionths of a US dollar. |
| `total_micros` | integer | yes | The month's total, the sum of every line's `amount_micros`. |
| `lines` | array of object | yes | One line for each product, region, dimension and model charged in the month, in that order. |
| `lines[].product` | string | yes | The product charged, such as `streams` for databases and streams, or `machines`. |
| `lines[].region` | string | yes | The region the usage was in. |
| `lines[].dimension` | string | yes | What was measured, such as `write_units` or `retention_byte_seconds`. |
| `lines[].model` | string or null | yes | The model the line was priced for, for a product priced by model; null otherwise. |
| `lines[].quantity` | string | yes | Decimal quantity, as text. |
| `lines[].amount_micros` | integer | yes | The line's charge, in micro-dollars. |

### Uuid

A UUID in lowercase hex with hyphens.

string (uuid)

### ValidationIssue

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `field` | string | yes | The refused input, by its name in the request: a body field, a query parameter or a header such as `Idempotency-Key`. |
| `code` | string | yes | Why it was refused, such as `invalid`; `detail` says how to correct it. |
