# Clusters (/docs/reference/api/clusters)



Clusters [#clusters]

Organization administrators can create, update, stop, resume, and delete clusters. Members can read cluster details.

Cluster endpoints require an authenticated user session or OAuth access token.
Pass the organization name as `organization` on every endpoint except the size
catalog. Organization members can read clusters and metrics; organization
admins are required to create, update, pause, resume, verify storage, or delete
clusters.

Cluster responses use this shape:

```json
{
  "id": "9f30c31c-74a7-4a08-aab0-494689ab5b31",
  "name": "production",
  "created_at": "2026-08-26 10:00:00+00",
  "status": {
    "phase": "ready",
    "ready": true,
    "message": null
  },
  "resources": {
    "shards": 1,
    "replicas": 2,
    "cpu_cores_per_replica": 2.0,
    "memory_bytes_per_replica": 8589934592,
    "minimum_size": { "cpu_cores": 2.0, "memory_bytes": 8589934592 },
    "maximum_size": { "cpu_cores": 64.0, "memory_bytes": 274877906944 }
  },
  "can_pause": true,
  "can_resume": false,
  "s3_storage": null,
  "s3_storage_access": {
    "external_id": "rawtree-5b71c457-7d83-474b-9db9-6dc48a1ec042",
    "resource_tag": "rawtree-dc1fb78e-341d-48f8-a183-9ecea12eb41d"
  },
  "idle_timeout_minutes": 15
}
```

`resources` can be `null` while resource information is unavailable. Use
`status.ready`, `can_pause`, and `can_resume` instead of inferring lifecycle
capabilities from the phase name.

`s3_storage_access` contains the saved, immutable `external_id` and `resource_tag` for this cluster's S3 storage access.
The object is `null` for older clusters without a saved identity.
`resource_tag` is the tag value for the fixed AWS tag key `rawtree.com/cluster`.
`s3_storage` is `null` for service-managed defaults, or contains `data` and
`backups` destinations for custom defaults. Both destinations always include
`bucket` and `path` in responses; an empty path means the bucket root. Responses do
not include the customer IAM role ARN.

GET `/v1/clusters` [#get-v1clusters]

List the organization's permanent clusters and their current status.

```text
GET /v1/clusters?organization=acme
```

The response contains an array of cluster objects:

```json
{
  "clusters": []
}
```

Use an API key, a permitted workload token, or a user bearer token. Database-role keys support only the operations listed in [Authentication](/docs/reference/authentication#existing-database-roles).

Parameters:

| Field          | Location | Type   | Required | Notes                                                        |
| -------------- | -------- | ------ | -------- | ------------------------------------------------------------ |
| `organization` | query    | string | No       | Organization name; required for users, optional for API keys |

Request:

```bash
# List permanent clusters and their current status.
curl -X GET "https://api.rawtree.com/v1/clusters" \
  -H "Authorization: Bearer $RAWTREE_API_KEY"
```

Expected response: **HTTP 200**.

```json
{
  "clusters": [
    {
      "id": "33333333-3333-4333-8333-333333333333",
      "name": "production",
      "created_at": "2026-11-09T10:00:00Z",
      "status": {
        "phase": "ready",
        "ready": true,
        "label": "Ready",
        "message": null
      },
      "can_pause": true,
      "can_resume": false,
      "idle_timeout_minutes": 30
    }
  ]
}
```

Errors: `400`, `401`, `403`. See [API errors](/docs/reference/errors).

GET `/v1/clusters/{cluster_id}` [#get-v1clusterscluster_id]

Get one cluster by the UUID returned from the cluster list.

```text
GET /v1/clusters/9f30c31c-74a7-4a08-aab0-494689ab5b31?organization=acme
```

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field          | Location | Type   | Required | Notes             |
| -------------- | -------- | ------ | -------- | ----------------- |
| `organization` | query    | string | Yes      | Organization name |

Request:

```bash
# Get a permanent cluster and its current status.
curl -X GET "https://api.rawtree.com/v1/clusters/33333333-3333-4333-8333-333333333333?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN"
```

Expected response: **HTTP 200**.

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "production",
  "created_at": "2026-11-09T10:00:00Z",
  "status": {
    "phase": "ready",
    "ready": true,
    "label": "Ready",
    "message": null
  },
  "can_pause": true,
  "can_resume": false,
  "idle_timeout_minutes": 30
}
```

Errors: `401`, `403`, `404`. See [API errors](/docs/reference/errors).

GET `/v1/clusters/sizes` [#get-v1clusterssizes]

List the supported per-replica resource sizes, replica limits, and default
autoscaling bounds. Call this endpoint before creating a cluster and pass one
of its `cpu_cores` and `memory_gib` pairs back to the create endpoint.

```json
{
  "min_number_of_replicas": 1,
  "max_number_of_replicas": 2,
  "sizes": [
    { "size": "large", "cpu_cores": 2, "memory_gib": 8 },
    { "size": "xlarge", "cpu_cores": 4, "memory_gib": 16 }
  ],
  "default_min_size": { "size": "large", "cpu_cores": 2, "memory_gib": 8 },
  "default_max_size": { "size": "xlarge", "cpu_cores": 4, "memory_gib": 16 }
}
```

The `size` label is for display. The resource pair is the stable create input.

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Request:

```bash
# List cluster sizes and replica limits.
curl -X GET "https://api.rawtree.com/v1/clusters/sizes?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN"
```

Expected response: **HTTP 200**.

```json
{
  "min_number_of_replicas": 1,
  "max_number_of_replicas": 1,
  "sizes": [
    {
      "size": "2 CPU / 8 GiB",
      "cpu_cores": 2,
      "memory_gib": 8
    }
  ],
  "default_min_size": {
    "size": "2 CPU / 8 GiB",
    "cpu_cores": 2,
    "memory_gib": 8
  },
  "default_max_size": {
    "size": "2 CPU / 8 GiB",
    "cpu_cores": 2,
    "memory_gib": 8
  }
}
```

Errors: `401`. See [API errors](/docs/reference/errors).

POST `/v1/clusters` [#post-v1clusters]

Create a cluster. `name`, `region_id`, `replicas`, and `size` are required.
Choose `region_id` from [`GET /v1/regions`](/docs/reference/api/regions#get-v1regions).
The API resolves the deployment. Callers do not need a deployment ID or routing headers.

The API rejects missing or null region IDs. Disabled regions and regions without a usable deployment cannot create clusters.
The API does not select another region.

`s3_storage_access` is optional. Omit it, pass `null`, or omit either of its
`external_id` and `resource_tag` fields to let the API generate the missing values
as independent `rawtree-<UUID>` identities. Custom `s3_storage` requires an
explicit `s3_storage_access.external_id` matching the IAM trust policy. `autoscaling`,
`idle_timeout_minutes`, and `s3_storage` are optional.

```text
POST /v1/clusters?organization=acme
```

```json
{
  "name": "production",
  "region_id": "11111111-1111-4111-8111-111111111111",
  "replicas": 2,
  "size": { "cpu_cores": 2, "memory_gib": 8 },
  "autoscaling": {
    "min_size": { "cpu_cores": 2, "memory_gib": 8 },
    "max_size": { "cpu_cores": 8, "memory_gib": 32 }
  },
  "idle_timeout_minutes": 15
}
```

Set `idle_timeout_minutes` to `0` to disable automatic idling, or use a value
from 15 through 43,200 minutes. Omit it to use the configured default. When
autoscaling is omitted, the size catalog's `default_min_size` and
`default_max_size` are used. The initial `size` must fall within those bounds.

To use customer-owned S3 storage, include `s3_storage`:

```json
{
  "name": "production",
  "region_id": "11111111-1111-4111-8111-111111111111",
  "replicas": 2,
  "size": {
    "cpu_cores": 2,
    "memory_gib": 8
  },
  "s3_storage_access": {
    "external_id": "rawtree-5b71c457-7d83-474b-9db9-6dc48a1ec042",
    "resource_tag": "rawtree-dc1fb78e-341d-48f8-a183-9ecea12eb41d"
  },
  "s3_storage": {
    "data": { "bucket": "acme-rawtree-data", "path": "rawtree/data" },
    "backups": { "bucket": "acme-rawtree-backups", "path": "rawtree/backups" },
    "role_arn": "arn:aws:iam::123456789012:role/RawTreeS3Access"
  }
}
```

Tables created normally use the cluster's configured storage. If cluster
creation returns an ambiguous response, list clusters and look for the requested
name before retrying.

Omit `s3_storage` or set it to `null` to use service-managed cluster defaults.
A custom `s3_storage` requires both `data` and `backups`, each with a `bucket`, plus
`role_arn`. Paths are optional and default to the bucket root. The two buckets
cannot be configured independently as custom and managed storage.

The API generates and saves missing identity values once at cluster creation.
Clients may supply explicit values when preparing IAM policies; the dashboard
keeps the values shown during setup when you switch storage modes. Read the
saved identity from `s3_storage_access` in the cluster response. Use the External ID in the
customer IAM role's trust policy. `resource_tag` contains the value for the fixed
AWS tag key `rawtree.com/cluster`. For custom database storage, apply
`rawtree.com/cluster=<resource_tag>` to both buckets and the customer IAM role.
These identity values cannot be changed after cluster creation. Top-level
`external_id`, `resource_tag`, and the old `database_s3_access` block are not accepted.

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field                  | Location | Type                             | Required | Notes                                                                                      |
| ---------------------- | -------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `organization`         | query    | string                           | Yes      | Organization name                                                                          |
| `name`                 | body     | string                           | Yes      | —                                                                                          |
| `region_id`            | body     | string                           | Yes      | Region from /v1/regions. The API resolves its deployment internally.                       |
| `replicas`             | body     | integer                          | Yes      | —                                                                                          |
| `size`                 | body     | CreateClusterSize                | Yes      | Resources per replica, one of the sizes returned by /v1/clusters/sizes.                    |
| `idle_timeout_minutes` | body     | integer or null                  | No       | Minutes without activity before automatically pausing. Omit to use the configured default. |
| `cluster_id`           | body     | string or null                   | No       | Optional client-generated cluster UUID. Omit to let the backend generate one.              |
| `autoscaling`          | body     | null or CreateClusterAutoscaling | No       | —                                                                                          |
| `s3_storage`           | body     | null or S3StorageInput           | No       | —                                                                                          |
| `s3_storage_access`    | body     | null or S3StorageAccessInput     | No       | —                                                                                          |

Request:

```bash
# Create a permanent cluster.
curl -X POST "https://api.rawtree.com/v1/clusters?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "production",
  "region_id": "11111111-1111-4111-8111-111111111111",
  "replicas": 2,
  "size": {
    "cpu_cores": 2,
    "memory_gib": 8
  }
}'
```

Expected response: **HTTP 200**.

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "production",
  "created_at": "2026-11-09T10:00:00Z",
  "status": {
    "phase": "unknown",
    "ready": false,
    "label": "Unknown",
    "message": null
  },
  "can_pause": false,
  "can_resume": false,
  "idle_timeout_minutes": 30
}
```

Errors: `400`, `401`, `403`. See [API errors](/docs/reference/errors).

POST `/v1/clusters/verify-s3-access` [#post-v1clustersverify-s3-access]

Verify that the service can assume the configured role and access both S3
destinations before creating the cluster. The `s3_storage` object has the same shape
as the create request. This pre-creation check also requires the proposed
`s3_storage_access.external_id` because the cluster has no saved identity yet.
Verification does not save it.

```text
POST /v1/clusters/verify-s3-access?organization=acme
```

```json
{
  "s3_storage_access": {
    "external_id": "rawtree-5b71c457-7d83-474b-9db9-6dc48a1ec042"
  },
  "s3_storage": {
    "data": { "bucket": "acme-rawtree-data", "path": "rawtree/data" },
    "backups": { "bucket": "acme-rawtree-backups", "path": "rawtree/backups" },
    "role_arn": "arn:aws:iam::123456789012:role/RawTreeS3Access"
  }
}
```

```json
{ "verified": true, "message": "S3 access verified." }
```

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field               | Location | Type                 | Required | Notes                                                                         |
| ------------------- | -------- | -------------------- | -------- | ----------------------------------------------------------------------------- |
| `organization`      | query    | string               | Yes      | Organization name                                                             |
| `s3_storage_access` | body     | S3StorageAccessInput | Yes      | The proposed identity; external\_id must match the customer IAM trust policy. |
| `s3_storage`        | body     | S3StorageInput       | Yes      | —                                                                             |

Request:

```bash
# Verify customer-owned S3 access for cluster creation.
curl -X POST "https://api.rawtree.com/v1/clusters/verify-s3-access?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "s3_storage_access": {
    "external_id": "rawtree-docs-example",
    "resource_tag": "33333333-3333-4333-8333-333333333333"
  },
  "s3_storage": {
    "data": {
      "bucket": "acme-rawtree-data",
      "path": "rawtree/data"
    },
    "backups": {
      "bucket": "acme-rawtree-backups",
      "path": "rawtree/backups"
    },
    "role_arn": "arn:aws:iam::123456789012:role/RawTreeS3Access"
  }
}'
```

Expected response: **HTTP 200**.

```json
{
  "verified": true,
  "message": "S3 access verified."
}
```

Errors: `400`, `401`, `403`. See [API errors](/docs/reference/errors).

PATCH `/v1/clusters/{cluster_id}` [#patch-v1clusterscluster_id]

Update the cluster name, automatic idle timeout, or both. Omitted properties
are unchanged.

```text
PATCH /v1/clusters/9f30c31c-74a7-4a08-aab0-494689ab5b31?organization=acme
```

```json
{
  "name": "primary",
  "idle_timeout_minutes": 60
}
```

Use `idle_timeout_minutes: 0` to disable automatic idling.

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field                  | Location | Type            | Required | Notes             |
| ---------------------- | -------- | --------------- | -------- | ----------------- |
| `organization`         | query    | string          | Yes      | Organization name |
| `name`                 | body     | string or null  | No       | —                 |
| `idle_timeout_minutes` | body     | integer or null | No       | —                 |

Request:

```bash
# Update cluster settings.
curl -X PATCH "https://api.rawtree.com/v1/clusters/33333333-3333-4333-8333-333333333333?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "idle_timeout_minutes": 30
}'
```

Expected response: **HTTP 200**.

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "production",
  "created_at": "2026-11-09T10:00:00Z",
  "status": {
    "phase": "unknown",
    "ready": false,
    "label": "Unknown",
    "message": null
  },
  "can_pause": false,
  "can_resume": false,
  "idle_timeout_minutes": 30
}
```

Errors: `400`, `401`, `403`, `404`. See [API errors](/docs/reference/errors).

POST `/v1/clusters/{cluster_id}/stop` [#post-v1clusterscluster_idstop]

Request that a running cluster pause. The response is the updated cluster
object; use its status and `can_pause`/`can_resume` fields to follow the
transition.

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field          | Location | Type   | Required | Notes             |
| -------------- | -------- | ------ | -------- | ----------------- |
| `organization` | query    | string | Yes      | Organization name |

Request:

```bash
# Stop a cluster.
curl -X POST "https://api.rawtree.com/v1/clusters/33333333-3333-4333-8333-333333333333/stop?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN"
```

Expected response: **HTTP 200**.

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "production",
  "created_at": "2026-11-09T10:00:00Z",
  "status": {
    "phase": "unknown",
    "ready": false,
    "label": "Unknown",
    "message": null
  },
  "can_pause": false,
  "can_resume": false,
  "idle_timeout_minutes": 30
}
```

Errors: `400`, `401`, `403`, `404`, `409`. See [API errors](/docs/reference/errors).

POST `/v1/clusters/{cluster_id}/resume` [#post-v1clusterscluster_idresume]

Request that a paused cluster resume. The response is the updated cluster
object. A conflicting lifecycle transition returns `409`.

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field          | Location | Type   | Required | Notes             |
| -------------- | -------- | ------ | -------- | ----------------- |
| `organization` | query    | string | Yes      | Organization name |

Request:

```bash
# Resume a cluster.
curl -X POST "https://api.rawtree.com/v1/clusters/33333333-3333-4333-8333-333333333333/resume?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN"
```

Expected response: **HTTP 200**.

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "production",
  "created_at": "2026-11-09T10:00:00Z",
  "status": {
    "phase": "unknown",
    "ready": false,
    "label": "Unknown",
    "message": null
  },
  "can_pause": false,
  "can_resume": false,
  "idle_timeout_minutes": 30
}
```

Errors: `400`, `401`, `403`, `404`, `409`. See [API errors](/docs/reference/errors).

DELETE `/v1/clusters/{cluster_id}` [#delete-v1clusterscluster_id]

Permanently delete a cluster.

```json
{ "deleted": true }
```

Use a user session or user OAuth token with `full_access`. API keys and workload tokens cannot use this method.

Parameters:

| Field          | Location | Type   | Required | Notes             |
| -------------- | -------- | ------ | -------- | ----------------- |
| `organization` | query    | string | Yes      | Organization name |

Request:

```bash
# Permanently delete a cluster.
curl -X DELETE "https://api.rawtree.com/v1/clusters/33333333-3333-4333-8333-333333333333?organization=team_alpha" \
  -H "Authorization: Bearer $RAWTREE_USER_TOKEN"
```

Expected response: **HTTP 200**.

```json
{
  "deleted": true
}
```

Errors: `400`, `401`, `403`, `404`. See [API errors](/docs/reference/errors).
