Organizations, members and keys
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.