Users

Invites and org member management — all admin-gated. See Users and quotas for the CLI-level walkthrough and the blast-radius warning on removal.

POST /v1/invites — create an invite

Auth: admin.

Request body

Field Type Required Notes
email string yes The invitee's email.
role string yes "admin" or "user".
ttl_seconds integer no Seconds until the invite expires. 0 defaults to 72 hours (259200). Must be between 900 (15m) and 2,592,000 (720h) if set — anything outside that range is rejected.
$ curl -s https://api.stagdb.com/v1/invites -X POST -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","role":"user","ttl_seconds":604800}'

Response 201 Created

{"invite_token": "9c2f7e51d4a8b3660f1e8d2c5a9b4370c6e1f8a25d3b7c90e4f6a1d82b5c3e70"}

invite_token is a bare hex string, shown once and never retrievable again — the server stores only its hash. This is the raw value that goes into POST /v1/invites/accept's token field. The adj1.… self-contained invite code you see in the CLI's output is a client-side wrapper the CLI builds around this raw token (base64url-packing the server URL, org slug, and this token) — the API itself never produces or consumes the adj1.… form.

Inviting the same email again is not an error — each call creates an additional pending invite (any of them can be accepted, and each is individually revocable via DELETE /v1/invites/{id} below).

Status When
400 email empty, role not admin/user, or ttl_seconds outside 15m–720h — wire body is the generic {"error": "invalid input"} in all three cases.
403 Caller is not an admin.

GET /v1/invites — list invites

Auth: admin.

$ curl -s https://api.stagdb.com/v1/invites -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "invites": [
    {
      "id": "1f2e3d4c-5b6a-4978-8c9d-0e1f2a3b4c5d",
      "email": "[email protected]",
      "role": "user",
      "expires_at": "2026-07-26T21:03:12Z",
      "accepted_at": null,
      "created_at": "2026-07-19T21:03:12Z"
    }
  ]
}

Includes both pending and already-accepted invites; accepted_at is null until accepted.

Status When
403 Caller is not an admin.

DELETE /v1/invites/{id} — revoke an invite

Auth: admin.

$ curl -s https://api.stagdb.com/v1/invites/1f2e3d4c-5b6a-4978-8c9d-0e1f2a3b4c5d -X DELETE \
    -H "Authorization: Bearer $TOKEN" -o /dev/null -w '%{http_code}\n'
204

Invalidates a pending invite. Has no effect on an invite that's already been accepted (the user already exists — use disable/remove instead).

Response: 204 No Content.

Status When
400 {id} is not a valid UUID.
403 Caller is not an admin.
404 No such invite in this org.

GET /v1/users — list members

Auth: admin.

$ curl -s https://api.stagdb.com/v1/users -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "users": [
    {
      "id": "7a1e9c3d-5b2f-4c8a-9d0e-2f3a4b5c6d7e",
      "email": "[email protected]",
      "display_name": "Alex Park",
      "role": "admin",
      "status": "active",
      "created_at": "2026-06-02T14:03:11Z"
    },
    {
      "id": "3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b",
      "email": "[email protected]",
      "display_name": "Dana Lin",
      "role": "user",
      "status": "active",
      "created_at": "2026-06-05T10:22:41Z"
    }
  ]
}
Status When
403 Caller is not an admin.

PATCH /v1/users/{id} — disable or enable a member

Auth: admin.

Request body

Field Type Required Notes
status string yes "disabled" or "active".
$ curl -s https://api.stagdb.com/v1/users/3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b -X PATCH \
    -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"status":"disabled"}'

Response 200 OK

{
  "id": "3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b",
  "email": "[email protected]",
  "role": "user",
  "status": "disabled",
  "created_at": "2026-06-05T10:22:41Z"
}

Disabling a member blocks future logins and revokes their existing sessions — their running instances are untouched. This is the reversible hold; see DELETE /v1/users/{id} below for the destructive alternative.

You cannot change your own status — an admin disabling themselves could lock the org out via its last admin, so the request is rejected (a 400; on the wire the body is the generic {"error": "invalid input"}). Have another admin do it.

Status When
400 {id} not a valid UUID; status not active/disabled; or {id} is your own user id (self-status change rejected — generic "invalid input" body).
403 Caller is not an admin.
404 No such user in this org.

DELETE /v1/users/{id} — remove a member

Auth: admin.

$ curl -s https://api.stagdb.com/v1/users/3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b -X DELETE \
    -H "Authorization: Bearer $TOKEN" -o /dev/null -w '%{http_code}\n'
202

This destroys every running instance the member owns. Removal revokes the member's access immediately and reaps (destroys) their live instances asynchronously in the data plane — there is no undo. It's safe to call twice: already-removed members and already-absent instances are tolerated, not re-errored.

Response: 202 Accepted, empty body — the removal and instance teardown proceed asynchronously.

You cannot remove yourself — the request is rejected with a 400 (generic {"error": "invalid input"} body on the wire), same rationale as the self-status guard above.

Status When
400 {id} not a valid UUID, or {id} is your own user id (self-removal rejected — generic "invalid input" body).
403 Caller is not an admin.
404 No such user in this org.

Next