Every HTTP-serving daemon exposes its API at GET /openapi.json. The document is generated from resreg.Resource.RowType reflection — one walk over the registry produces components/schemas from struct fields and paths./v1/<name> for the four CRUD verbs. Drift between handler and doc is structurally impossible because both read the same struct.
OpenAPI 3.1 JSON. Public — no auth gate. Cached for the process lifetime; reflection runs once per daemon. Paste a URL into any conformant tool (Swagger UI, Redoc, Stoplight Elements, an SDK generator) and the daemon's surface is browsable, callable, and codegen-ready.
| Daemon | URL | Owned resources |
|---|---|---|
gated |
https://<host>/openapi.json via proxyd, or http://gated:8080/openapi.json intra-container |
groups, acl, acl_membership, routes, web_routes, scheduled_tasks, secrets, network_rules |
proxyd |
http://proxyd:8080/openapi.json |
proxyd_routes |
onbod |
http://onbod:8080/openapi.json |
onboarding_gates |
webd |
http://webd:8080/openapi.json |
none — forwards routes CRUD to proxyd; doc is informational |
dashd |
http://dashd:8080/openapi.json |
none — HTMX operator UI; cold-tier CRUD lives in gated |
timed |
http://timed:8080/openapi.json |
none — reads scheduled_tasks; gated owns the table |
Daemons that own no resources still emit a valid OpenAPI document so external tooling can introspect them uniformly. The schemas + paths blocks are empty but info, servers, and the standard components/responses (400, 401, 403, 404, 409, 500) are present.
Each cold-tier resource is one Go struct with db:, json:, and yaml: tags — see gated for the registry. resreg.OpenAPI(daemon, baseURL, resources) walks the registered resources and emits:
components.schemas.<Name> per RowType. Property name from the json: tag; type inferred from the Go kind (string, integer, boolean, array, object). omitempty removes the field from required.GET /v1/<name> (list), POST /v1/<name> (create), PATCH /v1/<name>/{pk} (update), DELETE /v1/<name>/{pk} (delete). Composite PKs collapse to one URL parameter; the description flags the encoding so clients know to URL-encode separators.components.responses and $ref-d from every operation.servers[] entry from baseURL.Spec: specs/5/36-yaml-manifests.md §"OpenAPI emission". Subsumes the former specs/5/4-openapi-discoverable.md (folded into this spec).
The JSON is a regular text file — curl + jq works fine for most lookups:
curl -s https://<host>/openapi.json | jq '.paths | keys'
curl -s https://<host>/openapi.json | jq '.components.schemas.Routes'
For an interactive renderer, point Swagger UI or Redoc at the URL. Neither is bundled with arizuko; pick the tool your operator workflow already uses.
Any OpenAPI 3.1 codegen tool (openapi-generator, swagger-codegen, oapi-codegen) can consume the spec to produce a typed client. Run the generator against the live daemon's URL or save the JSON and run offline. Re-run on every release — the spec moves with the struct, so clients stay current by re-generating.
auth.Authorize per grants; the OpenAPI doc doesn't reflect per-caller scope.POST with a body that names the PK fields.