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

# Permissions

> Read organization permissions and personal cluster grants, check cluster access, and replace member permissions.

For which permission each task needs, see the [permissions guide](/clusters/permissions).

* [List permissions and profiles](#list-permissions-and-profiles): `GET /permission-profiles`
* [Read a member's permissions](#read-a-members-permissions): `GET /organizations/{orgID}/members/{userID}/permissions`
* [Replace a member's permissions](#replace-a-members-permissions): `PUT /organizations/{orgID}/members/{userID}/permissions`
* [List your cluster grants](#list-your-cluster-grants): `GET /me/resource-grants?kind=kubernetes_cluster`
* [Check cluster access](#check-cluster-access): `GET /resource-access`

## List permissions and profiles

```http theme={null}
GET /permission-profiles
```

**API:** Business. **Permission:** none.

Returns the fixed catalog of organization permissions and the named profiles built from it. The catalog is the same
for every organization.

### Response

| Field | Type | Description |
| - | - | - |
| `permissions[].key` | string | Permission key, for example `clusters.ssh`. |
| `permissions[].description` | string | What the permission allows. |
| `permissions[].implies` | array of strings | Keys that holding this key also grants. |
| `profiles[].name` | string | Profile name, for example `Engineer`. |
| `profiles[].description` | string | Who the profile is for. |
| `profiles[].permissions` | array of strings | The keys the profile grants. A profile is a preset; it is not stored on the member. |

### Example

```sh theme={null}
andromeda call business GET /permission-profiles | jq '.profiles[] | select(.name == "Engineer")'
```

```json theme={null}
{
  "name": "Engineer",
  "description": "Use clusters over SSH and Kubernetes",
  "permissions": ["clusters.kubernetes", "clusters.read", "clusters.ssh", "reservations.read"]
}
```

## Read a member's permissions

```http theme={null}
GET /organizations/{orgID}/members/{userID}/permissions
```

**API:** Business. **Permission:** none to read your own. Reading another member's needs `members.manage`, which
owners and admins hold.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `orgID` | path | string | yes | Organization ID. |
| `userID` | path | string | yes | The member's user ID, `user_` + ULID. Use your own from [`GET /me`](/api-reference/business/identity#read-the-signed-in-user). |

### Response

| Field | Type | Description |
| - | - | - |
| `organization_id` | string | The organization. |
| `user_id` | string | The member. |
| `role` | string | `owner`, `admin`, or `member`. |
| `permissions` | array of strings | Keys granted to the member directly. For an owner or admin this is `organization.owner` or `organization.admin`, which imply every other key. |

### Example

```sh theme={null}
andromeda call business GET "/organizations/$ORG_ID/members/$USER_ID/permissions"
```

```json theme={null}
{
  "organization_id": "org_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3",
  "user_id": "user_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
  "role": "member",
  "permissions": ["clusters.read", "clusters.ssh"]
}
```

### Errors

| Status | Code | When |
| - | - | - |
| 403 | `forbidden` | You asked for another member's permissions without `members.manage`. |
| 404 | `not_found` | The organization or member does not exist, or you are not a member. |

## Replace a member's permissions

```http theme={null}
PUT /organizations/{orgID}/members/{userID}/permissions
```

**API:** Business. **Permission:** `members.manage`, and every key you add. Owners and admins qualify.

<Warning>
  This sets the member's permissions to exactly the list you send. Keys the member held and you leave out
  are removed. To add one key, read the current list and send it with the new key included.
</Warning>

Changes to SSH and Kubernetes permissions reach the organization's running clusters automatically. Adding or
removing someone's SSH access can restart compute nodes and end running jobs; see
[Access changes restart nodes](/clusters/ssh#access-changes-restart-nodes).

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `orgID` | path | string | yes | Organization ID. |
| `userID` | path | string | yes | The member's user ID. Find it with [`GET /organizations/{orgID}/members`](/api-reference/business/identity#list-an-organizations-members). |
| `Idempotency-Key` | header | string | yes | 1 to 200 characters. See [Retrying writes](/api-reference/overview#retrying-writes). |
| `Audit-Reason` | header | string | yes | 1 to 500 characters, stored with the change. |

### Request body

| Field | Type | Required | Description |
| - | - | - | - |
| `permissions` | array of strings | yes | The complete set of keys. Order and duplicates do not matter. `organization.owner` and `organization.admin` are refused; those come from the role. |

### Response

`200` with the member's permissions after the change, in the same shape as
[Read a member's permissions](#read-a-members-permissions).

### Example

```sh theme={null}
echo '{"permissions": ["clusters.kubernetes", "clusters.read", "clusters.ssh"]}' |
  andromeda call \
    --header "Idempotency-Key: $(uuidgen)" \
    --header 'Audit-Reason: Kubernetes access for the training team' \
    --data @- \
    business PUT "/organizations/$ORG_ID/members/$MEMBER_ID/permissions"
```

```json theme={null}
{
  "organization_id": "org_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3",
  "user_id": "user_01J8Z3Q4R5S6T7V8W9X0Y1Z2A8",
  "role": "member",
  "permissions": ["clusters.kubernetes", "clusters.read", "clusters.ssh"]
}
```

### Errors

| Status | Code | When |
| - | - | - |
| 400 | `invalid_request` | A header is missing, a key is unknown, or the list contains `organization.owner` or `organization.admin`. |
| 403 | `forbidden` | You lack `members.manage`, or you are adding a key you do not hold. |
| 404 | `not_found` | The member is not in the organization. |
| 409 | `invalid_state` | The member is an owner or admin. Their permissions come from the role. |
| 409 | `idempotency_mismatch` | The `Idempotency-Key` was used before with a different body. |

## List your cluster grants

```http theme={null}
GET /me/resource-grants?kind=kubernetes_cluster
```

**API:** Business. **Permission:** none. No organization membership is needed.

Returns your live resource grants, including grants on clusters in organizations you do not belong to. Organization
permissions are not included. Owners and admins manage grants separately from member permissions. A grant does not
make you a member of the organization or give you Slurm SSH or configuration access. See
[Access to a shared cluster](/clusters/permissions#access-to-a-shared-cluster).

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `kind` | query | string | no | `kubernetes_cluster` returns only cluster grants. Without it, the response can also include machine grants. |

### Response

| Field | Type | Description |
| - | - | - |
| `grants` | array | Your live grants. |
| `grants[].id` | string | Grant ID. |
| `grants[].resource_kind`, `.resource_id` | string | `kubernetes_cluster` and the cluster ID, for a cluster grant. |
| `grants[].user_id` | string | Your user ID. |
| `grants[].username` | string | Your primary email address. Not your Slurm username. |
| `grants[].relation` | string | `kubernetes.viewer`, `kubernetes.developer`, or `kubernetes.admin`. |
| `grants[].scope` | string, optional | Namespace. Absent for a whole-cluster grant. |
| `grants[].role_id` | string, optional | A custom role that narrows `relation`. |
| `grants[].expires_at` | timestamp, optional | When the grant expires. Absent when it does not expire. |
| `grants[].granted_by`, `.granted_at` | | Who granted access and when. |

### Example

```sh theme={null}
andromeda call business GET '/me/resource-grants?kind=kubernetes_cluster' |
  jq '.grants[] | {resource_id, relation, scope, expires_at}'
```

## Check cluster access

```http theme={null}
GET /resource-access?kind=kubernetes_cluster&id={clusterID}&gate={gate}
```

**API:** Business.

Checks whether you can reach a cluster, through an organization permission or a live resource grant.

### Parameters

| Name | In | Type | Required | Description |
| - | - | - | - | - |
| `kind` | query | string | yes | `kubernetes_cluster`. |
| `id` | query | string | yes | Cluster ID. |
| `gate` | query | string | yes | `read` to see the cluster, or `kubernetes` for its kubeconfig and Kubernetes sign-in. |
| `scope` | query | string | no | Namespace. Without it, a `200` means you can reach some part of the cluster, not necessarily every namespace. |

### Response

`200` with `user_id`, `kind`, `id`, `gate`, and `via` (`organization`, `grant`, or `staff`), plus `grant_id` and
`expires_at` when access comes from a grant. Kubernetes still checks your role and namespace on each request, so
individual `kubectl` commands can be refused.

### Example

```sh theme={null}
andromeda call business GET "/resource-access?kind=kubernetes_cluster&id=$CLUSTER_ID&gate=kubernetes"
```

### Errors

| Status | When |
| - | - |
| 403 | You have no access to the cluster through this gate. |


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