> ## 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 (Business)

> The commercial record of your organization's clusters, and who has SSH access to each.

* [List clusters](#list-clusters): `GET /organizations/{orgID}/clusters`
* [Read a cluster](#read-a-cluster): `GET /organizations/{orgID}/clusters/{clusterID}`
* [Read a cluster's access](#read-a-clusters-access): `GET /organizations/{orgID}/clusters/{clusterID}/access`

Business records how a cluster came to exist and how many nodes it should have. For its name, site, and runtime
readiness, read the same cluster ID from [Clusters (Platform)](/api-reference/platform/clusters).

## The cluster object

| Field | Type | Description |
| - | - | - |
| `id` | string | Cluster ID, a UUID. The same ID Platform uses. |
| `organization_id` | string | The owning organization. |
| `state` | string | `provisioning`, `active`, `releasing`, or `released`. |
| `reason_code` | string or null | A stable reason for the current state, when there is one. |
| `expected_node_count` | integer or null | How many nodes the cluster should have. |
| `assigned_node_count` | integer | How many nodes are assigned now. |
| `selected_scheduler` | string, optional | `sunk` (Slurm) or `kubernetes`, as purchased. For what is actually running, read [cluster options](/api-reference/platform/clusters#read-cluster-options). |
| `reservation_id` | string, optional | The reservation that created the cluster. |
| `infrastructure_ready_at` | timestamp or null | When the infrastructure first became ready. |
| `teardown_completed_at` | timestamp or null | When the cluster was torn down. |
| `cleanup_state` | string or null | Teardown progress. |
| `end_command_id` | string or null | The request that ended the cluster, if any. |
| `version` | integer | Increases with every change to this record. |
| `created_at`, `updated_at` | timestamp | RFC 3339 timestamps. |

## List clusters

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

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

Returns every cluster the organization owns, oldest first, in one response.

A member without `clusters.read` gets `200` with an empty list, not `403`. If you expect clusters and see none, check
your [permissions](/clusters/permissions#check-your-permissions).

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `orgID` | path | string | yes | Organization ID. |

### Response

| Field | Type | Description |
| - | - | - |
| `clusters` | array | [Cluster objects](#the-cluster-object), ordered by `created_at` and then `id`. |

### Example

```sh theme={null}
andromeda call business GET "/organizations/$ORG_ID/clusters" |
  jq -r '.clusters[] | [.id, .state, .assigned_node_count, .selected_scheduler] | @tsv'
```

```text theme={null}
0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b	active	2	sunk
```

### Errors

| Status | Code | When |
| - | - | - |
| 404 | `not_found` | You are not a member of the organization. |

## Read a cluster

```http theme={null}
GET /organizations/{orgID}/clusters/{clusterID}
```

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

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `orgID` | path | string | yes | Organization ID. |
| `clusterID` | path | string | yes | Cluster ID. The OpenAPI document calls this a prefixed ULID; it is a UUID. |

### Response

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

### Example

```sh theme={null}
andromeda call business GET "/organizations/$ORG_ID/clusters/$CLUSTER_ID"
```

```json theme={null}
{
  "id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "organization_id": "org_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3",
  "reservation_id": "reservation_01J8Z3Q4R5S6T7V8W9X0Y1Z2A9",
  "state": "active",
  "reason_code": null,
  "expected_node_count": 2,
  "assigned_node_count": 2,
  "selected_scheduler": "sunk",
  "infrastructure_ready_at": "2026-09-02T08:14:10Z",
  "teardown_completed_at": null,
  "cleanup_state": null,
  "end_command_id": null,
  "version": 4,
  "created_at": "2026-09-02T08:00:00Z",
  "updated_at": "2026-09-02T08:14:10Z"
}
```

### Errors

| Status | Code | When |
| - | - | - |
| 403 | `forbidden` | You lack `clusters.read`, or you are not a member of the organization. |
| 404 | `not_found` | No cluster with this ID belongs to the organization. |

## Read a cluster's access

```http theme={null}
GET /organizations/{orgID}/clusters/{clusterID}/access
```

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

Lists everyone who can log in to the cluster over SSH, and whether the cluster has applied the latest access change.
Every member who holds `clusters.ssh` appears, with or without a registered key.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `orgID` | path | string | yes | Organization ID. |
| `clusterID` | path | string | yes | Cluster ID. |

### Response

| Field | Type | Description |
| - | - | - |
| `cluster_id`, `organization_id` | string | The cluster and its organization. |
| `generation` | integer | Version of the access list. Increases with each change. |
| `state` | string | Delivery of this version to the cluster: `pending`, `accepted`, `converged`, `stale`, `rejected`, or `failed`. `converged` means the cluster has applied it. |
| `observation_state` | string or null | `pending`, `converged`, or `failed`, as last reported by the cluster. |
| `desired_generation`, `observed_generation` | integer or null | The cluster configuration version this access list was sent for, and the version the cluster last reported. |
| `source_hash`, `updated_at` | | Details of this version. |
| `users[].user_id` | string | The member. |
| `users[].username` | string | Their login name on the cluster. |
| `users[].access_mode` | string | `use`, or `admin` for members with `clusters.ssh.admin`. Both give the same login; `admin` does not give `sudo`. |
| `users[].desired_login_state` | string | `ready`, or `waiting_for_key` if the member has no active key. This is the intended state; `ssh-access` reports whether login works. |
| `users[].ssh_key_set_generation` | integer | Which version of the member's key set the cluster should use. |
| `users[].allocation` | object, optional | The member's POSIX identity. Present once the cluster has accepted this access list. |
| `users[].allocation.uid`, `.gid` | integer | The member's numeric user and group IDs. They never change and are never reused. |
| `users[].allocation.home` | string | `/home/<username>`. |
| `users[].allocation.shell` | string | The member's requested shell in this access list. Older access lists report `/bin/bash`. |

Kubernetes resource grants are not part of this list. To check whether your own keys and shell have been applied, use
[`ssh-access`](/api-reference/platform/access#read-ssh-access), which can report you ready before this list's `state` is
`converged`.

### Example

```sh theme={null}
andromeda call business GET "/organizations/$ORG_ID/clusters/$CLUSTER_ID/access" |
  jq '{state, users: [.users[] | {username, access_mode, desired_login_state, uid: .allocation.uid}]}'
```

```json theme={null}
{
  "state": "converged",
  "users": [
    {"username": "ada", "access_mode": "use", "desired_login_state": "ready", "uid": 100001}
  ]
}
```

### Errors

| Status | Code | When |
| - | - | - |
| 403 | `forbidden` | You lack `clusters.read`. |
| 404 | `not_found` | The cluster is not in the organization, or no access list has been sent to it yet. |


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