---
title: Organizations, members and keys
description: Roles, permissions, API key scopes, and how the two intersect.
---

An **organization** is the unit every machine, key, snapshot and bill belongs to. A user gets a
personal one at signup and may belong to more; `POST /v1/auth/switch-organization` changes which
one their session is acting in.

Authentication is first-party. There is no hosted auth provider anywhere in this product.

## Two ways in

|                | Used by                            | Sent as                        |
| -------------- | ---------------------------------- | ------------------------------ |
| API key        | SDKs, CLI, integrations, your code | `Authorization: Bearer sk_...` |
| Session cookie | the dashboard                      | `computer_session`, httponly   |

The cookie is httponly and never readable from JavaScript. The API also refuses a
cookie-authenticated **write** that arrives without an `Origin` it recognises, which is why the
CLI signs in with a key rather than an email and password — it has no origin to present.

## Roles

Every membership has one role, and a role is a fixed set of permissions:

| Permission         | `owner` | `admin` | `member` |
| ------------------ | :-----: | :-----: | :------: |
| `machines:create`  |    ●    |    ●    |    ●     |
| `machines:control` |    ●    |    ●    |    ●     |
| `machines:delete`  |    ●    |    ●    |          |
| `snapshots:write`  |    ●    |    ●    |    ●     |
| `snapshots:delete` |    ●    |    ●    |          |
| `templates:write`  |    ●    |    ●    |          |
| `ports:write`      |    ●    |    ●    |    ●     |
| `secrets:write`    |    ●    |    ●    |    ●     |
| `members:manage`   |    ●    |    ●    |          |
| `api_keys:manage`  |    ●    |    ●    |          |
| `billing:manage`   |    ●    |         |          |

Reads are not in this table. Reading is gated by organization membership alone, because every
read is already organization-scoped: a member can list what the organization has.

`GET /v1/auth/me` returns the current principal, its active organization, and the resolved
permission list — render your UI off that rather than off a role name.

## Members and invitations

```
GET    /v1/members                        list members
PATCH  /v1/members/{membership_id}        change a role
DELETE /v1/members/{membership_id}        remove a member
GET    /v1/invitations                    pending invitations
POST   /v1/invitations                    invite by email, with a role
DELETE /v1/invitations/{invitation_id}    revoke
POST   /v1/invitations/accept             accept one
```

An invitation is `pending`, `accepted`, `revoked` or `expired`, and carries the role it will
grant. Members are a plan entitlement.

## API keys

```
GET    /v1/api-keys                  list
POST   /v1/api-keys                  create — the secret is in this response only
DELETE /v1/api-keys/{api_key_id}     revoke
```

The plaintext secret is returned **once, at creation, and never again**. Every later read shows
`prefix` — the first characters — for display and matching, plus `last_used_at` so an unused key
is visible before it is a liability.

### Scopes

A key carries scopes, and they are a **narrowing**, never a widening:

| Scope                                                                                            | Grants                                        |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------- |
| `machines:read`, `snapshots:read`, `templates:read`, `sessions:read`, `usage:read`, `ports:read` | nothing extra — reads are gated by membership |
| `machines:write`                                                                                 | `machines:create`, `machines:delete`          |
| `machines:control`                                                                               | `machines:control`                            |
| `snapshots:write`                                                                                | `snapshots:write`, `snapshots:delete`         |
| `templates:write`                                                                                | `templates:write`                             |
| `ports:write`                                                                                    | `ports:write`                                 |
| `secrets:write`                                                                                  | `secrets:write`                               |
| `sessions:write`                                                                                 | nothing — opening a session is not control    |

A key's scopes are **intersected with its creator's organization permissions at request time**.
A member's key can never delete a snapshot the member could not delete, and a key does not keep
a permission that was later taken away from the person holding it.

`sessions:write` granting nothing is deliberate. Creating or closing a session is not driving
the machine; that needs `machines:control`, which the input-lease route requires separately.

## Browser sessions

```
GET    /v1/auth/sessions                       every browser session for this user
DELETE /v1/auth/sessions/{auth_session_id}     revoke one
POST   /v1/auth/logout                         revoke the current one
```

Signup and login are rate-limited — failed logins per email, signups per IP — and a successful
login clears the counter, so a good login does not count against the next. No other route is
rate-limited by the API today; the gateway limits input messages per socket separately.
