API changelog
Every dated version of /api/ and what it changed, newest first.
Send the date you built against in Capa-Version, and a later change cannot move under your site; see Versions. The same list is served as JSON by GET /api/versions.
2026-10-01
Current version.
- First dated version of
/api/. (behaviour, REST:2026-10-01.initial) GET /api/entriesacceptsCapa-Page, naming the page a read is for. Optional, ignored when malformed, and never part of Vary. (request, REST:2026-10-01.capa-page-header)GET /api/pagesandGET /api/pages/{page}list a project's pages, declared by a model route or observed throughCapa-Page.GET /api/pages?entry={id}narrows the list to the pages that read one entry. (response, REST:2026-10-01.pages-routes)GET /api/previewverifies a preview token and answers the entry, model and path it names. (response, REST:2026-10-01.preview-verify)GET /api/entriesacceptsCapa-Schema, the checksum of the schema the calling code was generated from. Optional, ignored when malformed, and never part of Vary. (request, REST:2026-10-01.capa-schema-header)GET /api/pages/{page}gains insights: suggestions drawn from the page's own reads, each with the numbers behind it and the rewritten query where there is one.GET /api/pagesgains meta.insights holding the tenant-wide unused-entry rows. (response, REST:2026-10-01.page-insights)- Every queries[] row on
GET /api/pages/{page}gains selection, the Selection IR its select parses to against the model's current schema, and selectionError when it no longer parses. (response, REST:2026-10-01.page-query-selection) - Every queries[] row on
GET /api/pages/{page}gains url, the request that group most often made, with its filters, sort and limit rather than its select alone; null once the group has been folded into the daily rollup.GET /api/pages/{page}gains lastReadAt, the newest read of the page in the window. (response, REST:2026-10-01.page-detail-request-shape) GET /api/entries/{ns}andGET /api/entries/{ns}/{id}accept shape=flat: every relation is a { id, model } reference and every expanded entry appears once, in a top-level included object keyed by model namespace and then id. shape=tree, the default, is unchanged. (request, REST:2026-10-01.flat-shape)- A system key can be written $name wherever the grammar takes one: select=$tags,tags, where={"$tags":{"has":"news"}}, sort=-$createdAt, author.$id. $name always means the system key; the plain name means a model field of that name when there is one. (request, REST:
2026-10-01.system-key-sigil) - Each item of an array of image, video or file renders as a single media field does: id, url, alt, type, width and height, with alt filled from the file when the field has none. (response, REST:
2026-10-01.media-array-shape) - has, hasAny and hasAll take the type of the array's items: true or false for an array of true_false, ISO 8601 dates for an array of dates, compared as instants as a date field's are, and numbers for an array of numbers. Any other value is 400 invalid_filter_value. (request, REST and GraphQL:
2026-10-01.typed-array-filters) - For a production key, updatedAt is when the published version was saved, in the entry, in a sort and in a filter, so saving a draft changes nothing a production key can read. A development key reads the entry's own updatedAt. (response, REST and GraphQL:
2026-10-01.served-updated-at) - A malformed sort, or one with more than three keys, is 400 invalid_parameter with param sort, as limit is, rather than invalid_select. (response, REST:
2026-10-01.sort-parameter-code) - Every budget refusal states what the request measured and the limit it passed, in entries with thousands separators, such as: This request could return 5,025 entries, over the limit of 5,000. (response, REST:
2026-10-01.stated-limits) POST /api/graphqlandGET /api/graphqlserve typed, key-scoped reads of every model the key can read, with Relay connections, node(id:) and nodes(ids:), typed filters and sorts, persisted queries, and the same limits, error codes and cache keys as/api/entries. Fields are phased out by a laterCapa-Versionmarking them @deprecated(reason:) before any version removes them. (response, GraphQL:2026-10-01.graphql)- extensions.cost.requestedQueryCost is the most a document can cost: every relation list at the limit it reads, capped at the 5,000 entries a document may read, plus 500 for each scan. actualQueryCost adds 500 for each scan that ran, so it is never above requestedQueryCost. The 5,000-entry bound checked before the read still counts a list with no first as 10, and is extensions.capa.cost.nodesBound. (response, GraphQL:
2026-10-01.graphql-cost-bound) - A relation list with no limit:, or a nested GraphQL connection with no first, still reads up to 100 entries for each entry above it, but counts as 10 toward the 5,000-entry bound checked before the read, so a read that writes no number is not refused before it runs. A list that holds more than it was counted for stops the read at that list with query_too_complex, which names it. (request, REST and GraphQL:
2026-10-01.counted-default-limit) - A select, and each GraphQL root field, reads up to 5 levels of entries, the root counted, which is what legacy depth=4 reads. A sixth level is refused with query_too_complex, and its hint names the 5 levels of entries. (request, REST and GraphQL:
2026-10-01.entry-depth) - A GraphQL request error (a parse or validation failure, variables that do not coerce, a budget or a planner refusal) answers 200 with errors and no data under application/json, and keeps its 4xx under application/graphql-response+json. 401, 402, 403, 404, 405 and 429 keep their statuses under both. (response, GraphQL:
2026-10-01.graphql-error-status) - Each contains, startsWith, endsWith or ne condition on a model's own field, and each relation list sorted with sort:, costs 500 entries toward the 5,000 a request or document may cost, since each reads every entry it could match. An or may hold 3 such conditions; more is refused with query_too_complex. (request, REST and GraphQL:
2026-10-01.text-condition-scans) GET /api/entries/{ns}accepts before=end: the last limit entries of the list, in list order, with hasNext false and hasPrev set when entries come before them. GraphQL's last with no before, or with before: null, reads the same. (request, REST and GraphQL:2026-10-01.list-end)- A project's keys together run at most 4 GraphQL documents and
/api/entriesreads at once, and the last slot of the API process is kept for a project with none running. The next read waits for a slot as long as the database budget, then is 429 rate_limit_exceeded: This project has too many reads running at once across its keys. (behaviour, REST and GraphQL:2026-10-01.project-read-share) - __schema costs one entry for every 100 members of the key's schema, in requestedQueryCost and, when the answer is computed rather than kept, in actualQueryCost. A project may have 10 __schema answers computed at once and 1 a second after that; past it the document is 429 rate_limit_exceeded. A repeated introspection query, comments and whitespace aside, is answered from memory and not counted. (response, GraphQL:
2026-10-01.schema-cost) Capa-Persistaccepts pin; release=<id>; env=<env>. A project's stored documents are kept in this order: the pins of its latest 3 production releases, the documents a production key ran in the last 30 days, other pins, then the rest. A project keeps up to 2,000 documents and 16 MiB of them. (request, GraphQL:2026-10-01.persist-release)- An answer to a document that uses a deprecated field, argument, input field or enum value, inline or in variables, names each one in extensions.deprecations as { coordinate, reason } and in the
Capa-Deprecated-Reason header. (response, GraphQL:2026-10-01.deprecated-reason) GET /api/entries/{ns}and /{id} read select, filter[...], where, sort, limit, count, after, before and shape. Any other query parameter is 400 invalid_parameter, and the hint gives the/api/form of a legacy one: ?slug=/ is filter[slug]=/, ?page= is after=<page.next>. A name that starts with _ or utm_ is ignored. (request, REST:2026-10-01.unknown-parameters)- extensions.cost carries budget: { counted, limit } for every key: the figure the 5,000-entry limit is checked against before any SQL, and the limit. requestedQueryCost stays the most the document can read. (response, GraphQL:
2026-10-01.cost-budget)