Admin
GET /api/admin/admins
Section titled “GET /api/admin/admins”List Admins
List all platform admins, joined to user + adder info.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | PlatformAdminResponse[] |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/admins
Section titled “POST /api/admin/admins”Add Admin
Grant platform-admin access to an existing, already-registered user.
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
string | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | Successful Response | PlatformAdminResponse |
| 422 | Validation Error | HTTPValidationError |
DELETE /api/admin/admins/{user_id}
Section titled “DELETE /api/admin/admins/{user_id}”Remove Admin
Revoke platform-admin access. Guards: no self-removal, no removing the last admin.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | Successful Response | |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/allowed-emails
Section titled “GET /api/admin/allowed-emails”List Allowed Emails
List all registration-allowlist entries.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | AllowedEmailResponse[] |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/allowed-emails
Section titled “POST /api/admin/allowed-emails”Add Allowed Email
Add an entry to the registration allowlist.
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
string | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | Successful Response | AllowedEmailResponse |
| 422 | Validation Error | HTTPValidationError |
DELETE /api/admin/allowed-emails/{allowed_email_id}
Section titled “DELETE /api/admin/allowed-emails/{allowed_email_id}”Remove Allowed Email
Remove a registration-allowlist entry.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| allowed_email_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | Successful Response | |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/orgs
Section titled “GET /api/admin/orgs”List Orgs
List/search all organizations, cross-tenant, with per-org member counts, plan, suspension state and remaining credits.
Subscription and balance are LEFT joins on purpose: an org with neither is
exactly the state worth spotting from the list (no subscription = every
entitlement denied), so it stays a row with null plan rather than vanishing.
credits_remaining is the gate’s own expression — see OrgListItem.
It folds the overdraft grace in, so credits_balance (owned) and
credits_overdraft (lent) ride alongside it: a single figure that mixes
the two reads as more credits than the org has.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| limit | query | integer | no | |
| offset | query | integer | no | |
| plan_code | query | string | no | Only orgs on this plan. Pass __none__ for orgs with no subscription row at all — every entitlement is denied for those, and they need a backfill. |
| q | query | string | no | Search org name/slug (substring, case-insensitive) |
| suspended | query | boolean | no | true = only suspended orgs, false = only active. Omit for both. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgListResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/orgs/{org_id}
Section titled “GET /api/admin/orgs/{org_id}”Get Org
Org detail plus its member roster.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgDetailResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/orgs/{org_id}/audit
Section titled “GET /api/admin/orgs/{org_id}/audit”Get Org Audit
Change history for one org — who changed what, when.
Merges the two trails that can say something about an org, newest first:
admin_audit_log— actions taken through this admin surface, plus webhook-driven plan changes recorded with a system actor;audit_log— Layer-2 semantic business events service code records against the org (token.revoked, …).
Deliberately not included: Layer-1 raw row capture (audit.record_version).
It has no org column to scope by and one page-load of agent traffic would
bury every deliberate change in row diffs.
The merge happens in Python rather than SQL: the two tables share no column
names or types, and both are small enough here that over-fetching
limit + offset from each is cheaper than the UNION plumbing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| limit | query | integer | no | |
| offset | query | integer | no | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgAuditResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/orgs/{org_id}/members
Section titled “POST /api/admin/orgs/{org_id}/members”Add Org Member
Add an EXISTING, already-registered user to an org. Never creates users or sends invites.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | ||
| role | string | no |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | Successful Response | OrgMemberInfo |
| 422 | Validation Error | HTTPValidationError |
PATCH /api/admin/orgs/{org_id}/members/{user_id}
Section titled “PATCH /api/admin/orgs/{org_id}/members/{user_id}”Update Org Member Role
Change a member’s role. Refuses if it would leave the org with 0 owners.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes | |
| user_id | path | string (uuid) | yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
| role | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgMemberInfo |
| 422 | Validation Error | HTTPValidationError |
DELETE /api/admin/orgs/{org_id}/members/{user_id}
Section titled “DELETE /api/admin/orgs/{org_id}/members/{user_id}”Remove Org Member
Remove a member from an org. Refuses to remove the last OWNER.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes | |
| user_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | Successful Response | |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/orgs/{org_id}/suspend
Section titled “POST /api/admin/orgs/{org_id}/suspend”Suspend Org
Suspend an organization — blocks regular member access via get_current_org.
Idempotent: calling this on an already-suspended org is a no-op (keeps the
original suspended_at timestamp, 200, no duplicate audit entry) rather
than 409 — this is a “make it so” action, safe to double-click from the UI.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgSuspensionResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/orgs/{org_id}/unsuspend
Section titled “POST /api/admin/orgs/{org_id}/unsuspend”Unsuspend Org
Lift a suspension. Idempotent no-op if the org isn’t currently suspended.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | OrgSuspensionResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/orgs/{org_id}/usage
Section titled “GET /api/admin/orgs/{org_id}/usage”Get Org Usage
Usage summary for one org — same shape/service as the self-serve
/api/orgs/{org_id}/usage/summary endpoint, just without membership gating.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes | |
| window | query | string | no | Counter/cost window: 24h, 7d, 30d (default 30d) |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | UsageSummaryResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/subscriptions/backfill
Section titled “POST /api/admin/subscriptions/backfill”Backfill Subscriptions
Give every org that lacks an org_subscriptions row one. Re-runnable.
The repair path for a window that already happened: orgs created between the
plans/org_subscriptions migration (which backfilled everyone alive at
the time) and the org-creation hook have no subscription, so they resolve to
UNSUBSCRIBED — every feature denied, every count cap 0. Creation now
subscribes in the same transaction as the org insert, but that fixes nothing
retroactively.
Each org subscribed here also receives the plan’s credit grant and overdraft floor, in the same transaction — a subscription without them is an org that cannot do any work (plan §4.4). The default plan (enterprise) allocates nothing, so the usual repair run writes no ledger rows.
Idempotent, and cheap to run blind: the underlying INSERT ... SELECT only
sees orgs with no subscription row, so a second call inserts nothing, returns
created: 0 and writes no audit entry. The grant carries its own
once-ever-per-org-per-plan key on top, so it cannot double-allocate even if the
subscription row is later recreated. It never modifies an existing
subscription — a plan change is a separate, deliberate admin action, not
something a repair sweep should do by accident. That makes this safe to run
after any deploy, and safe to double-click.
It is not a way to top an org up: an org that already has a subscription
gets nothing here, and one whose grant went missing is repaired with an
adjustment.
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
| org_id | string (uuid) | no | Repair only this organization. Omit to repair every organization that has no subscription row. |
| plan_code | string | no | Plan to place missing subscriptions on. Defaults to CREDITS_DEFAULT_NEW_ORG_PLAN — the same plan org creation uses — so a repaired org is indistinguishable from a freshly created one. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | SubscriptionBackfillResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/users/{user_id}/deactivate
Section titled “POST /api/admin/users/{user_id}/deactivate”Deactivate User
Disable a user’s account. Guard: no self-deactivate.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | UserStatusResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/users/{user_id}/reactivate
Section titled “POST /api/admin/users/{user_id}/reactivate”Reactivate User
Re-enable a previously deactivated user’s account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| user_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | UserStatusResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/admin/waitlist
Section titled “GET /api/admin/waitlist”List Waitlist
List waitlist signups, most recently asked first.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| limit | query | integer | no | |
| status | query | WaitlistStatus | no | Only rows in this status. Omit for all. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | WaitlistListResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/waitlist/{waitlist_id}/dismiss
Section titled “POST /api/admin/waitlist/{waitlist_id}/dismiss”Dismiss From Waitlist
Decline a waitlist signup.
Keeps the row. Deleting it would let the same address walk straight back into the pending queue on its next submit, and the decision would have to be taken again every time.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| waitlist_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | WaitlistEntryResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/waitlist/{waitlist_id}/invite
Section titled “POST /api/admin/waitlist/{waitlist_id}/invite”Invite From Waitlist
Admit somebody from the waitlist and tell them so.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| waitlist_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | WaitlistEntryResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/admin/waitlist/{waitlist_id}/resend
Section titled “POST /api/admin/waitlist/{waitlist_id}/resend”Resend Waitlist Invite
Mail an already-admitted person again.
For the row whose admission stuck but whose mail did not. Refuses on a row that was never admitted — sending “your invitation is ready” to somebody who is still refused at the door is worse than sending nothing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| waitlist_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | WaitlistEntryResponse |
| 422 | Validation Error | HTTPValidationError |