
## Machine-readable reference (OpenAPI)

The exhaustive, always-current reference for every route — paths, parameters,
request/response schemas, status codes, and the capability each route requires —
is a generated **OpenAPI 3.2** document. It is rendered from the live command
tree, so it never drifts from what the binary serves:

- **Controlplane operator API** — [`/openapi/controlplane-operator.json`](/openapi/controlplane-operator.json)
- **Enclave (broker) operator API** — [`/openapi/enclave-broker-operator.json`](/openapi/enclave-broker-operator.json)

This page is the orientation: the shape of the API, how authentication works, and
the model behind it. For the endpoint-by-endpoint contract, read the spec.

## Shape

The API is served by `dvx serve controlplane` over TLS. Responses are JSON, and
errors are RFC 9457 problem documents. Every authoring action is audited on a
first-class audit stream that is separate from application logging — both land
on stdout and are distinguished by a top-level `stream` key.

Alpha software: the surface is real but still landing, and mounting is
feature-gated. `GET /healthz` is always served. When no OIDC issuer is
configured the controlplane runs in skeleton mode and serves only a placeholder
that reports the operator API as not-yet-implemented. With an issuer configured,
the full IAM surface is mounted, and individual route families appear only when
their backing capability was wired at startup (policy authoring, config
authoring, metadata channels, agent provisioning, and broker registration are
each enabled independently). The OpenAPI document describes the **full** surface;
a given controlplane may mount a subset.

## Authentication

Every `/api/v1/…` route is wrapped by bearer authentication. There are two
authenticators, composed fail-closed: **Zitadel** (the OIDC issuer) for human and
machine principals, and — for the **agent-operator** caller class — the
controlplane's own short-lived token issuer. Which authenticator accepts a token
determines the caller's principal **kind**, structurally: an agent token can
never be read as a human principal, and no claim a caller sends can choose the
kind. Per-route capability checks are layered on top. In the OpenAPI document
this is the global `bearerAuth` security scheme, and each route records the
capability it needs as an `x-required-capability` extension.
`GET /api/v1/whoami` is the exception — it requires only a valid token and
returns the caller's own identity and effective capabilities. Capability targets
are derived from the route path, never from the request body, so a
cross-tenant id in a payload cannot widen authority — with one documented,
narrowing-only exception: **group-aware agent create**
(`POST /api/v1/orgs/{org}/agents`). There the body `group` enters the
authorization decision as the attribute *being authored* on the agent, not as a
borrowed coordinate, and only ever to **narrow** — the handler requires the
caller to already hold `agents:manage` over that group (and, on an Upsert, over
the existing agent's stored group), so a body group the caller does not manage
is a 403. The authorization *org* stays path-derived exactly as everywhere else.
See `docs/http-api-conventions.md` rule 6 and `docs/iam.md` §8.

The CLI dials the API as an authenticated client. `dvx whoami`, `dvx grant`,
`dvx policy`, `dvx config`, `dvx agent`, and `dvx enclaves` take a
`--controlplane` base URL plus caller-authentication flags: `--zitadel-key`
(a service-account key that mints its own token) with `--issuer` and optional
`--audience`, or a static `--token-file`; and `--ca-cert` to trust the API's TLS
certificate (`--insecure-skip-verify` for dev only).

### Agent operators

A running **agent** can call this API without ever holding a credential. Its
egress toward the controlplane is terminated at the enclave gateway (like all
agent egress); the gateway relays the agent's attested identity through the
enclave broker and presents a **60-second, controlplane-issued agent-operator
token** itself on the agent's behalf. The token never enters the agent's view,
so pod death stops presentation, and a contained agent is refused at the mint
before the controlplane is even consulted.

Authorization for this caller class is deliberately tiny: an org admin grants an
`agent:<uid>` principal the **`auditor`** role (`dvx grant create --org <org>
--principal agent:<uid> --role auditor`) — org-scoped only, validated against the
grant's own org. A kind condition on every capability-gated route refuses the
agent-operator kind *before* grants are even loaded, so even an over-granted
agent reaches exactly one surface: the **own-actor audit read**. An agent
auditing its own trail sees precisely the records attributed to
`agent:<uid>`, told apart from human operators in the audit record by a
first-class `actor_kind` field.

## Authoring desired state

The operator authors; the enclave converges. Writing through the authenticated
API (rather than the CLI's break-glass direct-to-DB path) publishes the change
and fires an SSE nudge so the target enclave re-pulls. Every change is attributed
to the human or workload that made it, and sovereign secret bytes never traverse
this API — they are authored enclave-side through the broker (`dvx secret`),
whose own surface is the enclave (broker) operator API spec linked above.

The enclave broker also serves the approval loop on this shape: pending
tool-call approvals list, decision, and a live SSE feed at
`/api/v1/approvals/*`, bearer-authenticated and org-scoped exactly like these
routes. Work it with `dvx approvals list/approve/deny/stream` — see
[Inspect LLM responses and approve tool calls](/guides/inspect-and-approve-tool-calls/).

## Audit

When an audit store is configured the controlplane also mounts a read-only audit
query API at `/api/v1/audit/*` — the same routes the enclave broker serves, over the
controlplane's own audit store. Query it with `dvx audit --source controlplane`.
Those routes are included in the OpenAPI document above.

Audit visibility is an **authorization** decision the server enforces, not a UI
filter: the scope is derived from the caller's grants and applied as a mandatory
predicate on every query **and** the live SSE stream. A caller may narrow within
its scope but can never widen it — a missing filter means "everything I'm allowed
to see", never "everything". Three read personas:

- **regular member** (`audit:read`) — own org, and **own actor only**. Records
  attributed to other principals, and un-attributed/system records, are not
  returned (fail-closed). An **agent auditor** lands here too, pinned to its own
  `agent:<uid>` actor — that own-actor read is the agent-operator class's entire
  surface (see "Agent operators" above).
- **org admin / auditor** (`audit:read:all`) — own org, **all actors**.
- **platform operator** (`platform:audit:read`) — a **standing** read of **every
  org's controlplane audit** in one call (the controlplane's own identity/policy
  audit is non-sovereign platform observability). Narrow to one org with `?org=`.
  This is a controlplane-only capability: it grants **no** access to any org's
  sovereign enclave audit, and it never confers standing authority inside a
  customer org for anything else.

Cross-org reads are **reciprocally transparent**: when a platform operator reads
an org's controlplane audit, that read is itself audited **into the target org's
own audit view**, so an org can always see who-outside-it accessed its records.

The enclave broker serves the same `/api/v1/audit/*` shape over the enclave's
**sovereign** audit store, but authorizes it **entirely enclave-locally** (from
the enclave's own grants, never the controlplane): an enclave org admin sees all
actors in that enclave, a member sees its own, and a caller with no enclave-local
audit grant is refused. There is no cross-enclave aggregation — a client that
wants several enclaves' audit calls each broker in turn.
