AGENTS.md@api/openapi · diff
git:20261019.f04423f to git:20261020.8d47d7a
51 added, 53 removed. Audit A to A.
# Agent Instructions — `api/openapi`
- Scoped instructions for the OpenAPI 3.1 sources — the contract of record for
- the HTTP API. Defer to root [`AGENTS.md`](../../AGENTS.md) for repo-wide rules.
- Tooling mechanics (multi-file layout, bundling, the ogen `oneOf`-null
- workaround, pagination rules) live in [`readme.md`](readme.md) — read it
- before restructuring spec files.
+ The OpenAPI 3.1 sources, the contract of record for the HTTP API. Layout,
+ bundling, the ogen `oneOf`-null workaround and pagination how-tos are in
+ [`readme.md`](readme.md); read it before restructuring spec files.
## The generate loop
- `api/generated/` is ogen output — never hand-edit it. Change the spec here,
- then run `moon run server:generate` (or `go generate ./...`); CI enforces
- committed generated output via `server:check-generate`. The TypeScript client
- in `packages/api/src/generated/` regenerates from the same source.
+ - Never hand-edit `api/generated/` (ogen) or `packages/api/src/generated/`
+ (orval). Change the spec, then run `moon run server:generate`; CI checks the
+ committed output with `server:check-generate`.
+ - Lint the spec with `moon run server:openapi` (Redocly with the repo's
+ [`redocly.yaml`](../../redocly.yaml)).
- ## Wire casing — snake_case, gated
+ ## Wire casing: snake_case
- Wire fields, enum values, and parameters are `snake_case`. **Wrong casing
- fails silently at runtime**: an unknown property is dropped, not rejected —
- which is why lint gates exist instead of trusting review:
+ Wire fields, enum values and parameters are `snake_case`. Wrong casing fails
+ silently at runtime: an unknown property is dropped, not rejected.
- - `rule/wire-field-snake-case`, `rule/wire-enum-no-camel-case`, and
- `rule/wire-parameter-no-camel-case` in [`redocly.yaml`](../../redocly.yaml)
- (with documented carve-outs: JSON Schema keywords, `x-` extensions, RFC
- vocabularies like `S256`, header/cookie parameter names).
+ - `rule/wire-field-snake-case`, `rule/wire-enum-no-camel-case` and
+ `rule/wire-parameter-no-camel-case` in `redocly.yaml` gate it, with
+ documented carve-outs (JSON Schema keywords, `x-` extensions, RFC
+ vocabularies, header and cookie names).
- `moon run workspace:check-openapi-rules` runs fixture tests
- (`scripts/openapi-casing-rules.test.mjs`) that fail if the redocly rule
- config itself silently stops matching — keep the fixtures in sync when
- touching the rules.
+ (`scripts/openapi-casing-rules.test.mjs`) that fail when a rule silently
+ stops matching. Keep the fixtures in step with the rules.
- ## Error codes — stable, generated catalog
+ ## Error codes
- Errors are `{ code, message, details? }` per
- [`components/error-details.yaml`](components/error-details.yaml). Codes are
- stable, machine-readable, and dot-namespaced (`att.invalid_proof`); the
- per-endpoint catalog under `components/schemas/errors/` is **generated**
- (`gen_error_schemas` — do not hand-edit those files). Clients branch on
- `code`. The taxonomy and producer contract are
+ Errors are `{ code, message, details? }`
+ ([`components/error-details.yaml`](components/error-details.yaml)). Codes are
+ stable, dot-namespaced (`att.invalid_proof`), and what clients branch on. The
+ taxonomy and producer contract:
[ADR 030](../../docs/adrs/030-error-model-mapping-and-reporting.md) and
[`error-details-producers.md`](../../docs/design/api/error-details-producers.md).
+ - `components/schemas/errors/` holds one schema per error code, generated by
+ `gen_error_schemas` from the domain errors.
+ - Each operation's error set is `endpoints/**/<operationId>-error-response.yaml`,
+ generated by `gen_openapi_errors` from what the implementation can return.
+ - Never hand-edit either.
+
## Pagination and list queries
- Cursor-based only for new endpoints (`page_token` / `next_page_token`,
- `limit` default 20 max 100); list queries are `POST /<resource>/query` with
- structured `filter`/`sorting` bodies
+ New endpoints are cursor-based (`page_token` / `next_page_token`, `limit`
+ default 20, max 100), and list queries are `POST /<resource>/query` with
+ structured `filter` / `sorting` bodies
([ADR 027](../../docs/adrs/027-cursor-based-pagination.md),
- [ADR 031](../../docs/adrs/031-openapi-querying.md)). `POST /sessions/query`
- is the reference implementation; the canonical how-to is in
+ [ADR 031](../../docs/adrs/031-openapi-querying.md)). `POST /sessions/query` is
+ the reference implementation; the how-to is in
[`readme.md`](readme.md#api-design-conventions).
## Identifiers on the wire
- Resource ids are prefixed opaque strings (`user_01H…`, `proj_01H…`,
- [ADR 047](../../docs/adrs/047-dialect-id-generation.md)); ephemeral ids follow
+ Resource IDs are prefixed opaque strings (`user_01H…`, `proj_01H…`,
+ [ADR 047](../../docs/adrs/047-dialect-id-generation.md)); ephemeral IDs follow
[ADR 012](../../docs/adrs/012-ephemeral-id-api-representation.md). Use the
- prefixed form in `example:` fields — the retired dictionary-slug form
- (`river-8421`) must not reappear.
+ prefixed form in `example:` fields, never the retired dictionary slug
+ (`river-8421`).
## CLI commands — `x-zitadel-cli`
An operation the `zitadel` CLI runs names its command in `x-zitadel-cli`, on
- the line right after its `operationId` (`x-zitadel-cli: users create`). The API
- reference shows that command beside the TypeScript and cURL samples
- ([`apps/docs/lib/cli-sample.ts`](../../apps/docs/lib/cli-sample.ts)), and an
- operation's Markdown carries it for agents. The CLI's
+ the line after its `operationId` (`x-zitadel-cli: users create`); the API
+ reference shows it beside the samples. The CLI's
[`api-coverage` test](../../apps/cli/tests/unit/commands/api-coverage.test.ts)
fails until every command names exactly one operation it calls, and on a name
- no command answers to, so a new resource verb comes with its line here.
+ no command answers to.
## Descriptions — the first sentence stands alone
- Every operation and webhook has a `description` (Redocly's
- `operation-description`): it is the body of its API reference page, which
- shows all of it. Its first sentence, as plain text, is the page's
- description everywhere else: the metadata and the Open Graph image (cut at a
- word past 160 characters), `llms.txt` and the MCP server's page list
- ([`apps/docs/lib/description.ts`](../../apps/docs/lib/description.ts)). So the
- first sentence says on its own what the operation does, in 160 characters or
- fewer, and the description opens with it:
- `rule/operation-description-opens-with-sentence` in
- [`redocly.yaml`](../../redocly.yaml) rejects a description that starts with a
- heading, a quote (a `> **Deprecated**` note goes after the sentence), a list, a
- table, a code fence or HTML.
+ Every operation and webhook has a `description`, the body of its API reference
+ page. Its first sentence, as plain text, is the page's description everywhere
+ else: the metadata and Open Graph image (cut at a word past 160 characters),
+ `llms.txt` and the MCP server's page list
+ ([`apps/docs/lib/description.ts`](../../apps/docs/lib/description.ts)).
+
+ - Open with a sentence that says on its own what the operation does, in 160
+ characters or fewer.
+ - `rule/operation-description-opens-with-sentence` rejects a description that
+ starts with a heading, a quote (a `> **Deprecated**` note goes after the
+ sentence), a list, a table, a code fence or HTML.