> ## Documentation Index
> Fetch the complete documentation index at: https://docs.andromeda.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Cluster API calls are made as a signed-in person with an OIDC bearer token. There are no API keys, and service-account tokens are refused (Business returns 401, Platform returns 403).
> Platform takes the organization as the query parameter businessOrgId on most cluster routes, but as organizationId on /clusters/{clusterId}/ssh-access and /clusters/{clusterId}/kubeconfig.
> The Business API uses snake_case. The Platform API uses camelCase, except /ssh-access and /machines, which use snake_case.
> Business write requests need Idempotency-Key and Audit-Reason headers.
> Storage capacity can grow but cannot shrink, including disk and PVC resizing. Each purchase adds capacity.
> A cluster's storage can be Ceph, WEKA, or both, or NFS on its own. The Ceph quota, the purchased bytes, and Slurm's default 100 GiB shared-volume request are three different quantities.
> Organization permissions cover every cluster the organization owns. A Kubernetes resource grant shares one cluster or namespace, possibly with someone outside the organization, and lets them read that cluster and fetch its kubeconfig. A grant does not give Slurm SSH, Slurm configuration, or organization storage inventory access. Slurm SSH requires organization membership and clusters.ssh.
> The SSH login name is <cluster-id>.<username>. The gateway supports shells, commands, SFTP (including modern scp), and forwarding to loopback ports on the login node. Legacy scp and rsync need their programs on the login node. Remote forwarding and non-loopback destinations are refused.
> Use POST /me/ssh-keys for personal key registration. POST /organizations/{orgID}/ssh-keys is a deprecated alias and requires no membership. Read the preferred shell from GET /me as user.login_shell; update it with PUT /me/login-preferences.
> GET /clusters/{clusterId}/ssh-access returns 200 with ready and reason, even when access is not ready. ready: true does not mean compute nodes or Slurm accounting are ready. To confirm a key or shell change reached a cluster, require observation_current: true and compare ssh_key_set_generation with generation from GET /me/ssh-keys, and shell with user.login_shell from GET /me.
> Adding or removing a login account restarts the cluster's login nodes, Slurm controller, and accounting service, then its compute nodes one at a time; jobs running on a restarting node end. An account is added or removed when a person with an active key gains or loses SSH access, or a person with SSH access registers their first key or revokes their last. Other key changes and shell changes restart no nodes.

# Clusters (Platform)

> Cluster state, scheduler and storage readiness, and assigned machines.

* [List clusters](#list-clusters): `GET /clusters`
* [Read a cluster](#read-a-cluster): `GET /clusters/{clusterId}`
* [Read cluster options](#read-cluster-options): `GET /clusters/{clusterId}/options`
* [List a cluster's machines](#list-a-clusters-machines): `GET /clusters/{clusterId}/machines`

Business keeps a separate, commercial view of the same clusters: which reservation created each one, and its node
counts. See [Clusters (Business)](/api-reference/business/clusters). Both use the same cluster ID.

## The cluster object

| Field | Type | Description |
| - | - | - |
| `id` | string | Cluster ID, a UUID. Also the first part of your SSH login name. |
| `metadata.createdAt`, `metadata.updatedAt` | timestamp | RFC 3339 timestamps. |
| `spec.name` | string | Human-readable name. |
| `spec.site.id`, `spec.site.name` | string | The site (data center) the cluster runs in. |
| `spec.businessOrganization.id` | string | The organization that owns the cluster. |
| `spec.version` | string | Target Kubernetes version. |
| `spec.versionGeneration` | integer | Increases each time the target Kubernetes version changes. |
| `spec.desiredGeneration` | integer | Increases each time the desired cluster configuration changes. |
| `spec.schedulerGeneration` | integer | Increases each time the [Slurm configuration](/api-reference/platform/scheduler) changes. `0` if none was ever written. |
| `spec.apps` | object, optional | Andromeda operator overrides of the cluster's managed components. Usually absent. |
| `status.lifecycleState` | string | `provisioning`, `active`, `releasing`, or `released`. |
| `status.lifecycleVersion` | integer | Increases on each lifecycle transition. |
| `status.kubeletVersion` | string | Kubernetes version on the cluster's nodes. Empty until first observed. |

`active` means the cluster is provisioned. It does not mean the scheduler or storage is ready; read
[cluster options](#read-cluster-options) for that.

## List clusters

```http theme={null}
GET /clusters
```

**API:** Platform. **Permission:** `clusters.read`.

With `clusters.read`, you see all of the organization's clusters. Without it, you see only the clusters you hold
Kubernetes resource grants on. See
[Access to a shared cluster](/clusters/permissions#access-to-a-shared-cluster).

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `businessOrgId` | query | string | yes | Organization ID. |
| `limit` | query | integer | no | 1 to 200. Default 50. |
| `after` | query | string | no | The `nextPageToken` from the previous page. |

### Response

| Field | Type | Description |
| - | - | - |
| `items` | array | [Cluster objects](#the-cluster-object), ordered by `id`. |
| `nextPageToken` | string, optional | Pass as `after` to get the next page. Absent on the last page. |

### Example

```sh theme={null}
andromeda call platform GET "/clusters?businessOrgId=$ORG_ID"
```

```json theme={null}
{
  "items": [
    {
      "id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "metadata": {"createdAt": "2026-09-02T08:00:00Z", "updatedAt": "2026-09-02T08:14:10Z"},
      "spec": {
        "name": "training-h200",
        "site": {"id": "0199a0f0-1111-7222-8333-944455566677", "name": "nyc3"},
        "businessOrganization": {"id": "org_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3"},
        "version": "v1.33.4",
        "versionGeneration": 1,
        "desiredGeneration": 3,
        "schedulerGeneration": 1
      },
      "status": {"lifecycleState": "active", "lifecycleVersion": 2, "kubeletVersion": "v1.33.4"}
    }
  ]
}
```

To pick a cluster for the commands in these docs:

```sh theme={null}
export CLUSTER_ID=$(andromeda call platform GET "/clusters?businessOrgId=$ORG_ID" |
  jq -r '.items[] | select(.status.lifecycleState == "active") | .id' | head -n 1)
```

### Errors

| Status | Code | When |
| - | - | - |
| 400 | `invalid_organization` | `businessOrgId` is missing. |
| 400 | `invalid_pagination` | `after` is not a valid token. |
| 403 | `forbidden` | You lack `clusters.read` and hold no grants on the organization's clusters. |

## Read a cluster

```http theme={null}
GET /clusters/{clusterId}
```

**API:** Platform. **Permission:** `clusters.read` in the owning organization, or a live Kubernetes resource grant
on this cluster. The grant path does not require organization membership.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `clusterId` | path | string | yes | Cluster ID. |
| `businessOrgId` | query | string | yes | Organization ID. |

### Response

A [cluster object](#the-cluster-object).

### Example

```sh theme={null}
andromeda call platform GET "/clusters/$CLUSTER_ID?businessOrgId=$ORG_ID" | jq .status
```

```json theme={null}
{"lifecycleState": "active", "lifecycleVersion": 2, "kubeletVersion": "v1.33.4"}
```

### Errors

| Status | Code | When |
| - | - | - |
| 400 | `invalid_organization` | `businessOrgId` is missing. |
| 403 | `forbidden` | You lack `clusters.read` and hold no grant on this cluster. |
| 404 | `not_found` | No cluster with this ID belongs to the organization. |

## Read cluster options

```http theme={null}
GET /clusters/{clusterId}/options
```

**API:** Platform. **Permission:** `clusters.read` in the owning organization, or a live Kubernetes resource grant
on this cluster.

Reports which scheduler and storage the cluster runs, and whether each is ready. It reads from the latest observation
of the cluster; with none in the last two minutes, `state` is `unknown`. Use it to see whether a new cluster is ready for work.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `clusterId` | path | string | yes | Cluster ID. |
| `businessOrgId` | query | string | yes | Organization ID. |

### Response

| Field | Type | Description |
| - | - | - |
| `state` | string | `available`: the fields below are current. `unknown`: no observation in the last two minutes, so configured values and readiness are omitted. `unavailable`: the cluster is not `provisioning` or `active`. |
| `reason` | string, optional | Why `state` is not `available`, for example `observation_stale` or `cluster_not_active`. |
| `observedAt` | timestamp, optional | When the cluster was last observed. |
| `scheduler.configured.state` | string | `configured`, `none`, or `unknown`. |
| `scheduler.configured.value` | string, optional | `sunk` (Slurm) or `kubernetes` (Kubernetes only). Present when `state` is `configured`. |
| `scheduler.ready` | condition, optional | Readiness of the configured scheduler. |
| `scheduler.supported` | object | `{"kubernetes": true, "sunk": true}`: the choices Andromeda offers. Not specific to this cluster. |
| `storage.configured.ceph`, `storage.configured.weka`, `storage.configured.nfs` | string | `configured`, `not_configured`, or `unknown`. Ceph and WEKA can coexist; NFS excludes both. |
| `storage.quotaBytes` | integer, optional | The Ceph quota the cluster reports, in bytes. Absent when the cluster has no Ceph quota or has not reported one. |
| `storage.ready` | condition, optional | Readiness of the cluster's Ceph or NFS storage. |
| `storage.supported` | object | `{"ceph": true, "nfs": true}`: the storage types you can buy. Not specific to this cluster. WEKA can appear under `configured` but cannot be bought. |
| `storage.purchase` | object | How to buy more storage for this cluster: `method`, `path` (on the Business API), and `target_cluster_id`. Optional `admittedQuotaBytes` is the total quota from accepted purchases. See [Increase storage capacity](/api-reference/platform/storage#increase-storage-capacity). |

A **condition** has `type`, `status` (`True`, `False`, or `Unknown`), `observedGeneration`, and optionally `reason`,
`message`, and `lastTransitionTime`. The scheduler condition's `type` is `SUNKReady` for Slurm and
`ControlPlaneAvailable` for Kubernetes. The storage condition's `type` is `StorageReady`. A condition is present only
when it describes the cluster's current configuration.

### Example

```sh theme={null}
andromeda call platform GET "/clusters/$CLUSTER_ID/options?businessOrgId=$ORG_ID"
```

```json theme={null}
{
  "state": "available",
  "observedAt": "2026-09-30T18:02:11Z",
  "scheduler": {
    "supported": {"kubernetes": true, "sunk": true},
    "configured": {"state": "configured", "value": "sunk"},
    "ready": {"type": "SUNKReady", "status": "True", "observedGeneration": 3}
  },
  "storage": {
    "supported": {"ceph": true, "nfs": true},
    "configured": {"ceph": "configured", "weka": "not_configured", "nfs": "not_configured"},
    "quotaBytes": 1000000000000,
    "ready": {"type": "StorageReady", "status": "True", "observedGeneration": 3},
    "purchase": {
      "admittedQuotaBytes": 1000000000000,
      "method": "POST",
      "path": "/organizations/org_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3/purchases",
      "target_cluster_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  }
}
```

To check that Slurm is up:

```sh theme={null}
andromeda call platform GET "/clusters/$CLUSTER_ID/options?businessOrgId=$ORG_ID" |
  jq -e '.scheduler.ready.status == "True"'
```

### Errors

| Status | Code | When |
| - | - | - |
| 400 | `invalid_organization` | `businessOrgId` is missing. |
| 403 | `forbidden` | You lack `clusters.read` and hold no grant on this cluster. |
| 404 | `not_found` | No cluster with this ID belongs to the organization. |

## List a cluster's machines

```http theme={null}
GET /clusters/{clusterId}/machines
```

**API:** Platform. **Permission:** `clusters.read` in the owning organization, or a live Kubernetes resource grant
on this cluster.

Lists the physical machines currently assigned to the cluster, with their declared hardware and latest health report.
Retired machines and machines released from the cluster are not listed.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `clusterId` | path | string | yes | Cluster ID. |
| `businessOrgId` | query | string | yes | Organization ID. |
| `limit` | query | integer | no | 1 to 200. Default 50. |
| `after` | query | string | no | The `next_page_token` from the previous page. |

### Response

This endpoint uses `snake_case`.

| Field | Type | Description |
| - | - | - |
| `organization_id` | string | The organization. |
| `cluster_id` | string | The cluster. |
| `items[].id` | string | Machine ID, a UUID. |
| `items[].name` | string | Machine name. |
| `items[].lifecycle_state` | string | `registered`, `admitted`, or `quarantined`. |
| `items[].hardware.gpu_model` | string, optional | GPU model. |
| `items[].hardware.gpu_count` | integer, optional | Number of GPUs. |
| `items[].hardware.cpu_cores` | integer, optional | Logical CPU cores. |
| `items[].hardware.memory_gib` | integer, optional | Memory, in GiB. |
| `items[].hardware.ib_speed` | string, optional | InfiniBand link speed. |
| `items[].observed` | object, optional | The latest report from the machine. Absent if it has never reported. |
| `items[].observed.ready` | boolean | Whether the machine reported itself ready. |
| `items[].observed.phase` | string | The machine's reported operating phase. |
| `items[].observed.freshness` | string | `fresh`, `stale` (the machine has missed its expected heartbeat), or `unknown`. |
| `items[].observed.observed_at` | timestamp | When the report was received. |
| `items[].observed.generation` | integer | Increases with each report. |
| `next_page_token` | string, optional | Pass as `after` to get the next page. Absent on the last page. |

### Example

```sh theme={null}
andromeda call platform GET "/clusters/$CLUSTER_ID/machines?businessOrgId=$ORG_ID" |
  jq -r '.items[] | [.name, .hardware.gpu_model, .hardware.gpu_count, .observed.freshness] | @tsv'
```

```text theme={null}
gpu-node-01	NVIDIA H200	8	fresh
gpu-node-02	NVIDIA H200	8	fresh
```

### Errors

| Status | Code | When |
| - | - | - |
| 403 | `forbidden` | You lack `clusters.read` and hold no grant on this cluster. |
| 404 | `not_found` | No cluster with this ID belongs to the organization. |
| 422 | `invalid_request` | `businessOrgId` is missing. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.