Skip to content

docs(api): declarative apis: endpoints (ADR-0121) have no developer page — and api/plugin-endpoints.mdx is a different thing with a near-identical name #10215

Description

@os-elon

Part of #10206

Notation note (2026-08-20): placeholders below are written in {braces}, not angle brackets. GitHub's body sanitizer on this repo eats angle-bracket-delimited text, and it silently ate this card's namespace rule when it was first filed — gate 1 read /api/v1/apps//. Corrected and read back.

The gap

api is a metadata kind (ApiEndpointSchema, ADR-0121), authorable as defineStack({ apis }), and live from protocol 17 — the stack describe string says so: 'API Endpoints — declared endpoints are live from protocol 17; each is gated at publish (ADR-0121, #5040)'. An entry that passes its gates serves requests as soon as it is published.

Documentation for a developer who wants to expose an endpoint:

  • references/api/endpoint.mdx — 69 generated lines;
  • protocol/kernel/http-protocol.mdx — normative implementer text, the only place ADR-0121 is named in the hand-written tree;
  • and that is all.

Meanwhile content/docs/api/plugin-endpoints.mdx — the page whose title a developer will click — is a catalog of built-in plugin routes (/auth, /workflow, /automation, /ui, /realtime, /notifications, /ai, /i18n, /graphql, /storage). Nothing on it concerns authoring an endpoint. The naming collision actively sends the reader to the wrong page.

This one carries real risk, not just inconvenience: authRequired: false on an entry is, in ADR-0121's own words, an anonymous, internet-reachable execution entry point. Undocumented is the wrong state for that.

File surface

  • content/docs/api/declarative-endpoints.mdx — new
  • content/docs/api/index.mdx — edit (Cards + the sentence that disambiguates the two pages)
  • content/docs/api/plugin-endpoints.mdx — edit, intro paragraph and "See also" only: state in one sentence what this page is not, and link the new page. ⛔ Do not restructure or re-title it.
  • content/docs/api/meta.json — one added entry

⛔ Nothing outside content/docs/api/. Do not edit protocol/kernel/http-protocol.mdx — cross-link it.

What to write

Read ApiEndpointSchema (packages/spec/src/api/endpoint.zod.ts), the apis: docblock in packages/spec/src/stack.zod.ts, and the publish-time judge (validateApiEndpointDeclarations / identityFreeEndpointGateFailure). Cover:

  • Which channel (ADR-0121 D3) — a caller inside the platform (session, UI, AI/MCP, SDK) invokes an action; a caller outside it (a partner system, an inbound webhook) reaches an apis: endpoint. Lead with this; it is the decision the reader actually has to make.
  • The four gates, because publish/validate fails naming the endpoint and the key, and a developer needs to recognise each failure:
    1. Namespace (D1/D2) — path must be /api/v1/apps/{manifest.namespace}/{subpath}, where {manifest.namespace} is the package's own declared namespace. ⚠️ Take the exact spelling from stack.zod.ts / the validator rather than from this line: no built-in domain lives under apps/, and two packages cannot collide because their namespaces differ, so route ownership is structural rather than a maintained list.
    2. Supported subset — only object_operation (with both objectParams.object and .operation) and flow (with a target) execute in 17.x; script and proxy are refused pending their own rulings, and mapping transform is refused because no transformation registry exists;
    3. PolicyauthRequired: false requires rateLimit.enabled: true (D6 makes an armed budget the paired obligation of anonymity), an armed budget must be usable, cacheTtl must be non-negative and GET-only;
    4. Uniqueness — one claim per METHOD + path within a stack.
  • Anonymous endpoints — give this its own section with the security framing above, not a bullet.
  • api is code-only (api 注册表条目声明 allowRuntimeCreate: true,但运行时创建的端点匹配器永远看不见 —— 声明的能力运行时不兑现(真实 boot 实测) #5488): allowRuntimeCreate: false, allowOrgOverride: false. PUT /meta/api/:name is refused with 403 NOT_CREATABLE before any body validation; endpoints are validated on the artifact route (stack compile, loader ingest, publishPackage). Say this, because a developer who tries the runtime metadata API will otherwise not understand the 403.
  • A worked example end to end, and where /openapi.json describes the result.

⛔ Non-goals

  • Do not document script or proxy targets as available. They are refused today; a doc that presents them as coming soon is a promise this card has no authority to make.
  • Do not touch packages/spec/**.

Gates (derived at dispatch time via node scripts/pm/dispatch-gates.mjs, not recalled)

check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:published-readme-links, check:role-word, check:cross-package-test-inputs, and the spec liveness set (check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs). Re-derive against your actual diff; check:adr-anchors / check:adr-links may also apply once you cite ADR-0121 decision letters — cite only letters that are real headings of that ADR.

⚠️ Adding a hand-written page turns check:docs-audit-scope RED until you run node scripts/docs-audit/check-audit-scope.mjs --write, which writes one line into .claude/workflows/docs-accuracy-audit.js. That is expected and required (see #9866). Commit exactly what --write produces, name it as a declared deviation in the PR body, and do not flip the PR ready or enqueue it — .claude/** is a governed surface, so the PR is human-merge-only.

Acceptance

  • A developer who wants a partner system to call into their app finds the page from the API sidebar, and is not misdirected by plugin-endpoints.
  • Each of the four gates is stated in terms the reader can match against the error they will see.
  • The anonymous-endpoint obligation is impossible to miss.
  • Named gates green.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions