The super-admin tier sits above every tenant. It authenticates with a single
X-Admin-Key header and does two kinds of thing: raise a tenant’s ceilings, and
expose read-only operational state — recent activity, a performance series, and
instantaneous gauges — so an incident can be diagnosed from an HTTP client
rather than a host sign-in. None of it appears on the public health probe.
Every route below shares the same auth. A missing or invalid key is 401
admin_denied; the tier is rate limited as a whole (429 admin_rate_limited).
POST /v1/admin/tenants/{id}/limits
Lift a tenant’s limits. Auth: X-Admin-Key. Returns 201.
{id} is the tenant id.
Request
{
"maxWorkspaces": 50,
"maxWallets": 2000
}| Field | Type | Required | Notes |
|---|---|---|---|
maxWorkspaces |
integer | no | New workspace ceiling. Non-negative |
maxWallets |
integer | no | New wallet ceiling. Non-negative |
At least one field is required; an empty body is 400. Only these two ceilings
are liftable — maxUnlockedWorkspaces is deliberately out of reach. The raise is
one-directional: a value below the current ceiling is refused with 409
limit_not_raised, never silently applied.
The change is persisted in state.json, so it survives a restart even though
tenants.json is mounted read-only.
Response
{
"tenant": "acme",
"limits": { "maxWorkspaces": 50, "maxWallets": 2000 },
"changed": ["maxWorkspaces"]
}limits is the effective ceiling after the call; changed lists only the
fields that actually moved, so a no-op raise returns an empty array.
Errors
| Status | Code |
|---|---|
| 400 | invalid_body, invalid_slug |
| 401 | admin_denied — a missing or invalid X-Admin-Key |
| 404 | tenant_not_found |
| 409 | limit_not_raised — the value is below the current ceiling (a value equal to it is accepted, changing nothing) |
| 429 | admin_rate_limited |
GET /v1/admin/diagnostics
An instantaneous operational snapshot. Auth: X-Admin-Key. Returns 200.
Cheap in-process gauges — no locks, no tenant work. Uptime and core version;
process memory, open file descriptors, and cumulative event-loop lag; live
session and lease counts; and an activity block reporting the fill and drop
counters of the activity log below.
{
"uptimeSec": 37,
"coreVersion": "<wative-core version>",
"process": {
"rssBytes": 118321152,
"heapUsedBytes": 40286992,
"heapTotalBytes": 43204608,
"externalBytes": 3893242,
"openFds": 23,
"openFdLimit": 524288,
"eventLoopLagMeanMs": 20.3,
"eventLoopLagMaxMs": 196.35
},
"sessions": { "sessions": 0, "unlockedWorkspaces": 0, "unlockedWorkspacesCap": 32, "liveLeases": 0, "durableLeases": 0, "leasesPerWorkspaceCap": 64 },
"activity": { "enabled": true, "eventsCaptured": 3, "eventsDropped": 0, "eventsHeld": 3, "eventCap": 2000, "perfSamplesHeld": 0, "perfCap": 900, "lastSeq": 3, "lastCrashHeld": 0 },
"buffer": { "enabled": true, "batchSize": 50, "lowWatermark": 25 }
}The event-loop figures here are cumulative over the process lifetime, so they
cannot place a stall in time. Use GET /v1/admin/metrics for a timestamped
series. The buffer block reports whether wallet buffer mode is on (`BUFFER_SIZE
0`) and its dials, so the mode is confirmable without host access.
Errors
| Status | Code |
|---|---|
| 401 | admin_denied |
| 429 | admin_rate_limited |
GET /v1/admin/activity
The recent-activity ring — a bounded, in-memory log of what the service has been
doing. Auth: X-Admin-Key. Returns 200, cache-control: no-store.
One entry per HTTP request (method, route template, status, latency, request id,
tenant, and any account or wallet binding), session open/close/evict events, and
— for every error — the internal reason the response body and the warning log
deliberately omit. That last field is what lets a failure be diagnosed by its
request id alone.
Query
| Field | Type | Notes |
|---|---|---|
limit |
integer | Max events returned, newest kept. Optional |
kind |
string | Filter by kind — one of request, error, session, mint, lease, ratelimit, provision, lifecycle, fatal; any other value is 400 invalid_body. Optional |
sinceSeq |
integer | Only events with a higher seq — feed the previous response’s lastSeq back for a gap-free incremental tail. Optional |
scope |
string | current (default) reads the live ring; lastcrash reads the previous run’s reloaded dump |
Response
{
"events": [
{
"seq": 42,
"ts": 1789715615345,
"kind": "request",
"method": "POST",
"route": "/v1/auth/token",
"status": 401,
"latencyMs": 6,
"requestId": "53e33bc8-c052-4760-a617-47926dcca0db",
"tenant": "acme"
},
{
"seq": 43,
"ts": 1789715615346,
"kind": "error",
"code": "session_expired",
"status": 401,
"reason": "no token lifetime remains",
"requestId": "53e33bc8-c052-4760-a617-47926dcca0db"
}
],
"lastSeq": 43
}Fields are redacted as they enter the ring: bearer tokens, JWTs, API keys, and 32-byte hex secrets never reach it, and overlong values are capped. Wallet addresses and workspace/account slugs are recorded — they are admin-tier diagnostic aids, not secrets. Passwords and key material are never captured.
The ring is in memory and clears on restart, except that its tail is written to
state/activity-crash.json on shutdown and from the fatal handler, then reloaded
at boot and served under scope=lastcrash — so the events leading to a crash
survive the restart.
Errors
| Status | Code |
|---|---|
| 400 | invalid_body — an unknown or malformed query parameter |
| 401 | admin_denied |
| 429 | admin_rate_limited |
GET /v1/admin/metrics
The performance series. Auth: X-Admin-Key. Returns 200, cache-control: no-store.
A short, bounded time-series sampled on an interval: event-loop lag (mean and max per interval, so a stall lands on a timestamp), process memory, and file descriptor count.
Query
| Field | Type | Notes |
|---|---|---|
limit |
integer | Max samples returned, newest kept. Optional |
Response
{
"samples": [
{
"ts": 1789715630419,
"loopLagMeanMs": 20.16,
"loopLagMaxMs": 21.2,
"rssBytes": 118321152,
"heapUsedBytes": 40410712,
"externalBytes": 3893242,
"openFds": 22
}
]
}Errors
| Status | Code |
|---|---|
| 400 | invalid_body |
| 401 | admin_denied |
| 429 | admin_rate_limited |
Related
- Workspaces — where those ceilings are spent
- Quota — a tenant reading its own usage against these limits