Admin
Organization, people, team, runtime, usage, and audit routes.
Manage organizations, people, teams, workers, jobs, schedules, usage, and audit. Everything under /v1/admin requires an owner or admin role for the target org/team.
Capability groups
| Group | Prefix | Manages |
|---|---|---|
| Organizations | /v1/admin/orgs | Org settings, slugs, and region/residency defaults. |
| People | /v1/admin/people | Directory, invitations, role assignment, and removal. |
| Teams | /v1/admin/teams | Team CRUD, membership, default model, and mode. |
| Workers | /v1/admin/workers | Registered worker fleet and host visibility. |
| Jobs | /v1/admin/jobs | Control-plane job queue: inspect, retry, cancel. |
| Schedules | /v1/admin/schedules | Region scheduling policy (the field is region, never lane). |
| Usage | /v1/admin/usage | Per-team spend and request counters. |
| Audit | /v1/admin/audit | Append-only audit log of privileged actions. |
Shared conventions
- List routes are cursor-paginated (
limit+ opaquecursor). - Mutations accept an
Idempotency-Key; reusing it with different parameters returns409 idempotency_conflict. - Updates to mutable resources use
If-Match; a stale revision returns412 precondition_failed. - All path identifiers (team, email, ids) must be URL path-escaped.
People & invitations
GET/v1/admin/people?team={team}→ 200
POST/v1/admin/people/invite→ 200
List directory members or send an invitation. Invites gate the device-login flow — an email with no active invitation fails login with 403 not_invited.
# List people
curl "https://api.s46.dev/v1/admin/people?team=%40s46%2Fengineering" \
-H "Authorization: Bearer $S46_ACCESS_TOKEN"
# Invite someone
curl -X POST https://api.s46.dev/v1/admin/people/invite \
-H "Authorization: Bearer $S46_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9b7c1e0a-2f4d-4c8e-bf1a-1e2d3c4b5a6f" \
-d '{ "email": "newhire@s46.dev", "team": "@s46/engineering", "role": "member" }'await fetch("https://api.s46.dev/v1/admin/people/invite", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ email: "newhire@s46.dev", team: "@s46/engineering", role: "member" }),
});import uuid
requests.post(
"https://api.s46.dev/v1/admin/people/invite",
headers={"Authorization": f"Bearer {access_token}",
"Idempotency-Key": str(uuid.uuid4())},
json={"email": "newhire@s46.dev", "team": "@s46/engineering", "role": "member"},
){
"email": "newhire@s46.dev",
"team": "@s46/engineering",
"role": "member"
}Usage & audit
GET/v1/admin/usage?team={team}→ 200
GET/v1/admin/audit?team={team}→ 200
Usage reports per-team spend and request counters; audit is an append-only log of privileged actions. Both are cursor-paginated.
curl "https://api.s46.dev/v1/admin/audit?team=%40s46%2Fengineering&limit=50" \
-H "Authorization: Bearer $S46_ACCESS_TOKEN"const audit = await fetch(
"https://api.s46.dev/v1/admin/audit?team=%40s46%2Fengineering&limit=50",
{ headers: { Authorization: `Bearer ${accessToken}` } },
).then((r) => r.json());audit = requests.get("https://api.s46.dev/v1/admin/audit",
params={"team": "@s46/engineering", "limit": 50},
headers={"Authorization": f"Bearer {access_token}"}).json(){
"data": [
{
"at": "2030-01-01T12:00:00Z",
"actor": "dscape@s46.dev",
"action": "people.invite",
"target": "newhire@s46.dev",
"team": "@s46/engineering"
}
],
"hasMore": false,
"nextCursor": null
}