Appearance
Capacity HTTP API
This is the reference for the per-Domain capacity snapshot endpoint GET /v1/domains/{domainId}/capacity — GetDomainCapacity. It maps the operation to its OpenAPI schema, the operator authentication seam, the ReBAC oracle-prevention gate, the response shape, and the closed Problem.code taxonomy. The wire-contract origin is api/openapi/plexsphere-v1.yaml; this doc is a map, not a duplicate contract.
The capacity surface is the read side of the platform's capacity-and-scale collector. On a fixed interval the collector samples each Domain's usage against its target on six catalogued axes — Node enrolment, SSE fan-out, Secret Store reads, mediated sessions, observability ingest, and action executions — and records a threshold crossing when a dimension reaches a share of its target. This endpoint returns the latest sampled snapshot: one used / target reading per dimension in canonical order. The Dashboard capacity view and operators reading scale headroom consume the pull. For the operator runbook — the sample interval, the threshold alerts, and the capacity_exceeded refusal contract on the throttled write surfaces — see ../../operations/capacity.md.
Operation
| Method | Path | Operation ID | Tag | Auth | ReBAC/authz gate | Audit relation |
|---|---|---|---|---|---|---|
| GET | /v1/domains/{domainId}/capacity | GetDomainCapacity | capacity | operator bearer | Domain-read on the addressed Domain, checked before the snapshot read | domain.capacity.read (only on a 403 denial) |
- The authorization gate is checked before the snapshot read. The handler confirms the caller holds Domain-read permission on the addressed Domain before it ever touches the collector, so a caller who lacks the relation cannot use the endpoint as a Domain-id oracle: an unauthorised request and a request for a never-sampled Domain are indistinguishable from the caller's side because the
403lands first. An unauthorised caller receives403with thePermissionDeniedshape and reasoninsufficient_relation. (The OpenAPI403prose names the required permission "domain-view"; in the codebase this is the Domain-read relation thatGetDomainauthorises against.) - The audit relation
domain.capacity.readis stamped only on the403denial — the security-relevant event of a caller attempting to read a Domain's capacity without the relation. A successful read is not audited. - The handler ships behind a fail-closed deferred-wiring gate — until the production composition root supplies the collector-backed snapshot provider and the ReBAC checker, every request returns
501 capacity_not_provisionedso log scrapers can alert on the unwired state. The surface is either fully wired or fully off.
Authentication
The operation uses the operator bearer scheme, the same seam every other /v1/domains/{domainId}/... operation authenticates against — not the per-Node NSK scheme the node-facing surfaces use. The middleware resolves the Principal from the presented credential and attaches it to the request context. A request that carries no resolved Principal is refused with 401 unauthenticated before the Domain-read gate runs.
Path parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
domainId (path) | string (uuid) | yes | The addressed Domain (UUIDv7). A zero or otherwise invalid UUID is refused with 400 invalid_domain_id. |
Success response
A 200 OK carries a DomainCapacitySnapshot — the latest sampled snapshot for the addressed Domain.
| Field | Type | Meaning |
|---|---|---|
sampled_at | string (date-time) | RFC 3339, the wall-clock instant the collector last sampled the Domain's usage. The snapshot is unavailable (503) until the first sample completes. |
dimensions | array of DomainCapacityDimensionReading | One reading per catalogued dimension, in the canonical order the Dashboard capacity view renders. |
Each DomainCapacityDimensionReading carries five required fields:
| Field | Type | Meaning |
|---|---|---|
dimension | CapacityDimension enum | The capacity-and-scale axis this reading covers. |
unit | CapacityUnit enum | The unit used and target are expressed in. |
used | number | Latest sampled usage in the dimension's unit — an absolute tally for count, a sustained rate for the _per_second units. |
target | number | The per-Domain target in the dimension's unit. A value of 0 means no target is configured. |
ratio | number | used / target, the fraction of the target consumed (0 when no target). A value at or above 0.8 is the threshold the collector records a crossing for. |
Dimensions and units
CapacityDimension is a closed enum: nodes, sse_fanout, secret_reads, mediated_sessions, observability_ingest, action_executions. CapacityUnit is a closed enum: count, events_per_second, reads_per_second, bytes_per_second. Each dimension reports in a fixed unit:
dimension | unit |
|---|---|
nodes | count |
sse_fanout | events_per_second |
secret_reads | reads_per_second |
mediated_sessions | count |
observability_ingest | bytes_per_second |
action_executions | count |
Example
The 200 body the spec carries (the six readings in canonical order):
json
{
"sampled_at": "2026-05-25T10:00:00Z",
"dimensions": [
{ "dimension": "nodes", "unit": "count", "used": 8200, "target": 10000, "ratio": 0.82 },
{ "dimension": "sse_fanout", "unit": "events_per_second", "used": 540, "target": 1000, "ratio": 0.54 },
{ "dimension": "secret_reads", "unit": "reads_per_second", "used": 3100, "target": 10000, "ratio": 0.31 },
{ "dimension": "mediated_sessions", "unit": "count", "used": 120, "target": 500, "ratio": 0.24 },
{ "dimension": "observability_ingest", "unit": "bytes_per_second", "used": 2097152, "target": 5242880, "ratio": 0.4 },
{ "dimension": "action_executions", "unit": "count", "used": 60, "target": 1000, "ratio": 0.06 }
]
}Error taxonomy
Error responses use the shared Problem envelope (application/problem+json); the 403 path uses the PermissionDenied shape. Only the 503 arm carries a Retry-After header. The closed Problem.code set this surface emits:
| HTTP status | Problem.code | Trigger | Retry-After |
|---|---|---|---|
| 400 | invalid_domain_id | Path {domainId} is not a non-zero UUID. | no |
| 401 | unauthenticated | No resolved Principal — the caller is not authenticated. | no |
| 403 | (PermissionDenied, reason insufficient_relation) | Caller lacks Domain-read permission on the addressed Domain; surfaced before the snapshot read so the endpoint is not a Domain-id oracle. | no |
| 500 | (Problem) | A snapshot-read fault or an authorization-check fault. | no |
| 501 | capacity_not_provisioned | The capacity snapshot surface is not yet wired in this build (deferred-wiring state). | no |
| 503 | capacity_snapshot_unavailable | The collector has not produced a snapshot for the Domain yet (no sample has completed since boot); the Retry-After header carries the seconds to wait, derived from the sample interval. | yes (Retry-After) |
The 403 arm uses the PermissionDenied body shape; every other arm uses the Problem shape. The 500 arm is the transport's defensive fallback for an unexpected server-side failure — the wire body stays generic and no backend or driver text is interpolated into it.
Note on Problem.dimension
The shared Problem schema carries an optional dimension member, but this capacity snapshot endpoint never sets it. The member is populated exclusively by the capacity_exceeded 429 refusals on the Observability Ingest and Action Orchestrator write surfaces — values observability_ingest and action_executions respectively — so a client can attribute a throttle to a specific scale axis without string-matching detail. A 200 capacity snapshot response and the Problem arms of this endpoint never carry dimension. For the capacity_exceeded refusal contract on those throttled write surfaces, see the operator runbook ../../operations/capacity.md.
Cross-references
./index.md— platform-wide/v1HTTP surface map and the tag table this surface sits in.../../operations/capacity.md— operator runbook: the sample interval, the threshold-crossing alerts, and thecapacity_exceededrefusal contract on the throttled write surfaces.../../../api/openapi/plexsphere-v1.yaml— authoritative OpenAPI contract; this doc is a map, not a duplicate.