Resource design sheets
Generated from docs/api_standards/resources/README.md. Edit that file, then regenerate: python docs/portfolio/_build/generate_reference_pages.py.
<!– The inventory is linked by absolute URL, not a relative path: this file is read in the repository and published as the handbook’s resource-sheets index, and no relative path is correct in both. –>
One page per resource family, written before the handlers, reviewed as the design.
WP-6 of docs/plans/20260811_documentation_system_execution.md, from docs/proposals/20260811_rest_api_documentation.md (R3).
Why these exist
An OpenAPI document is a reference, not a design. KAP’s is 7,559 lines and can tell you the shape of every request while answering none of the questions a reviewer actually asks:
- What is this resource, and what are its states?
- What happens on the second identical call?
- What does a client do with a 409?
- Who may see it, and what do they get if they may not?
Those answers decide whether the handlers are right, and none of them fit in a schema. A sheet is where they go, one per resource family — see the API Endpoint Inventory for the families and how many operations each holds.
The count is deliberately not restated here. It was, and said 125 while the specification said 147 — invisible for months because the check that guards these numbers read one line at a time and the claim wrapped across two.
A sheet is not a second copy of the spec. It never restates request or response shapes; the spec owns those and is generated. If a sheet and the spec disagree about a shape, the sheet has overstepped.
The nine sections
| Section | What it must state |
|---|---|
| Resource | What it is in the domain, and its identity — which id, which scope it lives under |
| Lifecycle | The states and the legal transitions. A resource with no states says so |
| Operations | method · path · purpose · auth · idempotent? · emits event? |
| Access | Which CONTRACT-API-001 case applies, and the not-permitted response |
| Errors | Each code this family returns and what the client should do |
| Pagination & limits | Only if it returns collections; the ceiling, and behaviour past it |
| Consistency | What a caller may assume after a write returns 2xx |
| Agent notes | Whether an AI engine may call it, and what a model needs told that a human would not |
| Rejected shapes | The one or two obvious alternatives and why not |
The last section is the one that makes a sheet re-readable in a year. A design without its rejected alternatives reads as arbitrary, and the next person re-proposes what was already decided against.
Frontmatter — the part a machine checks
---
resource: annotation_suggestions
access_case: dataset-scoped
operations:
- listImageAnnotationSuggestions
error_codes:
- image.not_found
---Checked by ai/tests/ci/test_resource_sheets.py:
- every
operations:entry is a realoperationIdinswagger.yaml— a sheet describing an operation that does not exist is the failure mode a prose document cannot detect on its own; - every
error_codes:entry is indocs/api_standards/error_codes.yaml; access_caseis from the closed set below.
Prose is not checked, and pretending otherwise would be worse than not checking. The frontmatter is the part that can go quietly wrong.
access_case
| Value | Meaning | Not-permitted response |
|---|---|---|
public |
No identity required | — |
owner-only |
Only the row’s owner | 404 |
org-scoped |
Any member of the owning organization | 404 |
dataset-scoped |
Anyone who can see the parent dataset | 404 |
admin |
Platform administrators | 403 |
404, not 403, for everything scoped. A 403 tells a caller the resource exists and is not theirs, which for a tenant-scoped resource is a disclosure. CONTRACT-API-001 owns this rule and its one named exception; the sheet declares which case applies, never redefines it.
When a sheet is required
- New resource family — yes, before the handlers.
- Breaking change to an existing family — yes, amend the sheet in the same change.
- Adding an operation to a family that has one — update the
operations:list; the prose usually does not move. - Adding an operation to a family with no sheet — not required. Backfilling 25 sheets by reading handlers would document what the code does, which the generated spec already does for free.