{"openapi":"3.1.1","info":{"title":"lateano.com","version":"1.0.0"},"servers":[{"url":"https://uat.lateano.com"}],"tags":[{"name":"Reading","description":"The site as data. The GET addresses below are the default and are cacheable; the query grammar is the escape hatch for a question no address spells."},{"name":"Discovery","description":"What this API will accept and what it can refuse, as resources rather than as prose. Both are generated from the code that enforces them."},{"name":"Feedback","description":"The site’s only write, and the two public reads beside it. The site stores a submission for Colin and confirms it to the sender. No resource serves a submission back."}],"paths":{"/api/pages":{"post":{"operationId":"pages.post","summary":"Query the site","tags":["Reading"],"description":"Answers a list of questions in one request — a selection, the facets to count, and how many rows to return. Each question is answered independently and the answers come back in the order they were asked.\n\nReach for a `GET` address first where one spells the question you have — those are cacheable and this is not. The grammar itself is published at [`/api/query-schema.json`](/api/query-schema.json).","parameters":[{"name":"x-http-method-override","in":"header","required":true,"schema":{"type":"string","enum":["QUERY"]},"description":"`QUERY` is sent as `POST` carrying `x-http-method-override: QUERY`, because the edge in front of this API refuses a bare `QUERY` with a 405 before any code here runs. It is required rather than an alternative spelling: the request cannot be made without it."}],"requestBody":{"required":true,"description":"The query grammar, generated from the validator that enforces it. The same schema is published on its own at [`/api/query-schema.json`](https://uat.lateano.com/api/query-schema.json), which is where a client should fetch it from — that address is cacheable and this document is not small.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryGrammar"}}}},"responses":{"200":{"description":"Query the site","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryBody"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}},"get":{"operationId":"pages.get","summary":"Search the site","tags":["Reading"],"description":"The cacheable spelling of a query. The whole question lives in the URL, so this address has a representation fixed by it — it carries an `ETag` and answers a conditional request, which the `QUERY` spelling beside it cannot.\n\nIt answers the same envelope `QUERY` does, through the same grammar: the parameters below are assembled into a query and handed to the one validator, so the caps, the enums and the refusals are identical.\n\n**Reach for this one first.** What it cannot spell is a boolean tree, a batch, a lookup by slug, or a pasted passage — those need the grammar at `POST /api/pages`.\n\nA request naming neither a search nor a filter is refused rather than answered: the whole corpus is not a question.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`. Because `Allow` names `QUERY` and the wire will not carry one, that answer also carries `method-override-required`, saying which method to send it as and under which header. This document already publishes that as a required parameter, so a caller reading it here does not need the field.","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"The text to search for. **Optional, and absent is not the same as empty:** a request carrying only filters is a question too — \"everything under this tag\" — and is answered as one, while `?q=` names a clause with no text and is refused exactly as the same empty clause in a request body is.\n\nNo length is published here. The grammar at [`/api/query-schema.json`](/api/query-schema.json) states its own, and restating it would be the second rendering this document exists to prevent."},{"name":"in","in":"query","required":false,"schema":{"type":"string","enum":["title","body","description","aliases","tags","all"],"default":"all"},"description":"Which field `q` searches. Omitted means `all`."},{"name":"collection","in":"query","required":false,"schema":{"type":"string","enum":["writing","glossary","demos"]},"description":"Narrow to one division of the site."},{"name":"tag","in":"query","required":false,"schema":{"type":"string"},"description":"Narrow to the pages carrying one tag, **by slug rather than by title**. Tags are content rather than a fixed set, so a slug no tag carries is answered with an empty result rather than refused. The vocabulary is at [`/api/tags`](/api/tags)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10},"description":"How many rows to return. **Refused rather than clamped**, which is the opposite of `?limit` on the list resources: a value this grammar does not admit is answered with a refusal naming the dimension it exceeded.\n\nNo `minimum` or `maximum` is published here, and their absence is deliberate. The ceiling is not a bound on this field — it is a budget spent across every query in a request, which JSON Schema has no vocabulary for, so the published grammar at [`/api/query-schema.json`](/api/query-schema.json) states its own limits and nothing here restates them."},{"name":"facets","in":"query","required":false,"schema":{"type":"string"},"description":"Which dimensions to count, as one `,`-separated value rather than a repeated key — one of `collection` or `tags`, or both."}],"responses":{"200":{"description":"Search the site","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryBody"}}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/site-pages":{"get":{"operationId":"sitePages.get","summary":"Every page no collection holds","tags":["Reading"],"description":"The site’s own pages — its documentation, its about page and its indexes — each with its address, its title and what it is about.\n\nA collection is a group of pages a query can narrow to, and these belong to none, so a search never returns them. This address is how their routes are learned, and `/api/pages/{+path}` then answers about one. The site root is `/api/pages/index`, the name its markdown twin takes as well, because a path of one separator expands to no segment.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"How many items to return. Omitted, the whole list comes back. A value that is not a whole number of at least 1 reads as no bound rather than as an empty page. `total` in the body says how many exist either way."}],"responses":{"200":{"description":"Every page no collection holds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitePagesBody"}}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/query-schema.json":{"get":{"operationId":"querySchema.get","summary":"The query grammar","tags":["Discovery"],"description":"The schema the query surface validates against, as JSON Schema 2020-12 — generated from the validator itself, so it cannot describe a grammar the API does not enforce.\n\nIt states its own limits inside itself: nesting depth and the totals counted across a whole request have no JSON Schema vocabulary, so a query this schema admits may still be refused.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[],"responses":{"200":{"description":"The query grammar","content":{"application/json":{}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/openapi.json":{"get":{"operationId":"openapi.get","summary":"This description","tags":["Discovery"],"description":"The document you are reading, as OpenAPI 3.1 — every address, every method, every response shape and the whole refusal catalogue, derived from the same route table that serves them.\n\n**It is assembled per request and names the origin that answered.** So the `servers` entry and every problem `type` URI in it are addresses on *this* deployment: fetch it from a preview and you get a description of that preview, not of production.\n\nThe response schemas come from the TypeScript types the handlers return; the request grammar is the same object served at [`/api/query-schema.json`](/api/query-schema.json), embedded as a component rather than `$ref`d across the network, because a tool that bundles this document has no way to fetch it.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[],"responses":{"200":{"description":"This description","content":{"application/json":{}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/.well-known/api-catalog":{"get":{"operationId":"catalog.get","summary":"The API catalogue","tags":["Discovery"],"description":"Every addressable resource of this API as one linkset, at the well-known address RFC 9727 registers for it — so a consumer that knows only the hostname can find the rest without reading anything written for a person.\n\n**`application/linkset+json` and nothing else.** RFC 9727 § 4.2 requires that format, and this resource carries an `ETag` — so publishing the same bytes under `application/json` as well would put two negotiated representations under one validator, and a client that stored one and revalidated asking for the other would be told to reuse the wrong label. Send `Accept: application/linkset+json`, or no `Accept` at all; `application/json` alone is refused.\n\n**Two resources are missing from it, and deliberately.** A linkset entry has to be a URI, and a template with a hole in it is not one — so the page and problem resources are described here, in this document, rather than linked there. The catalogue points at this document for that reason.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[],"responses":{"200":{"description":"The API catalogue","content":{"application/linkset+json":{}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/pages/{path}":{"get":{"operationId":"page.get","summary":"One page","tags":["Reading"],"description":"What a page cannot say about itself — its canonical address, its indexing directive, the collection it belongs to, and the pages it references.\n\nIt does not return the page as HTML. A caller asking for `text/markdown` is answered with the page’s published Markdown, under a validator of its own, and a `Link` naming the address that Markdown publishes itself at.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"The value filling `{path}`. **This API's own template spells it `{+path}`, and OpenAPI has no way to say so** — § 3.5 forbids an unescaped `/` in a path parameter, and there is no spelling for a value that may span several segments. So the value goes in with its separators intact and each segment percent-encoded on its own. Send `writing/deep-modules`, not `writing%2Fdeep-modules` — the two address different things, and only the first is this resource.\n\nNamed as a deviation rather than left to read as conformance."}],"responses":{"200":{"description":"One page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageBody"}}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/collections":{"get":{"operationId":"collections.get","summary":"Every collection","tags":["Reading"],"description":"The divisions of the site a query can narrow to, and how many pages each holds. The set is closed and derived from what a page is, so it does not grow with the corpus.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"How many items to return. Omitted, the whole list comes back. A value that is not a whole number of at least 1 reads as no bound rather than as an empty page. `total` in the body says how many exist either way."}],"responses":{"200":{"description":"Every collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionsBody"}}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/tags":{"get":{"operationId":"tags.get","summary":"Every tag in use","tags":["Reading"],"description":"The site’s tag vocabulary — each tag’s slug, the title a reader reads, and how many documents carry it in each collection. A tag nothing carries is absent.\n\nThis is the list the grammar cannot stand in for. The collections are a closed set published in [`/api/query-schema.json`](/api/query-schema.json), so a caller reading the grammar already knows them; tags are content, and no schema can enumerate them.\n\nNarrow a query with the **slug**, not the title. The whole vocabulary comes back in one response unless `?limit` names a bound; `total` reports the whole of it either way, so a bounded answer says what it left out.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"How many items to return. Omitted, the whole list comes back. A value that is not a whole number of at least 1 reads as no bound rather than as an empty page. `total` in the body says how many exist either way."}],"responses":{"200":{"description":"Every tag in use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagsBody"}}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/problems/{slug}":{"get":{"operationId":"problem.get","summary":"One problem type","tags":["Discovery"],"description":"The page a refusal’s `type` URI resolves to. Every problem this API can report has one, generated from the same catalogue row the refusal was built from.\n\nMatch on the **slug** — the last path segment — rather than on the whole URI: the host follows whichever deployment answered, and the slug does not.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The value filling `{slug}`, as one path segment. The API's own template spells it `{+slug}`, which admits several — but this resource is served by a single-segment route and answers `no-such-resource` for anything longer, so the OpenAPI spelling above is the exact one and nothing is lost in the translation."}],"responses":{"200":{"description":"One problem type","content":{"text/html; charset=utf-8":{}}},"304":{"description":"The representation this request already holds is still current, so it is not sent again. No body. Read the validator off `ETag` and send it back as `If-None-Match`.","headers":{"etag":{"schema":{"type":"string"}},"cache-control":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}},"/api/feedback":{"get":{"operationId":"feedback.get","summary":"Recent feedback","tags":["Feedback"],"description":"The most recent submissions, newest first, in the public projection — which has no field the message could occupy. Follow the `Link` header’s `rel=\"next\"` for the page after this one. `partial` is `true` where the run that built this answer did not reach the end of the archive: the rows are then an arbitrary subset rather than the newest, and `total` counts that subset.\n\nThis resource answers `HEAD` as well, with the same headers and no body.\n\nIt also answers `OPTIONS`, which returns `Allow`, a `Link` to this description, and — where the resource takes one — the query formats it reads, as `Accept-Query`. Because `Allow` names `QUERY` and the wire will not carry one, that answer also carries `method-override-required`, saying which method to send it as and under which header. This document already publishes that as a required parameter, so a caller reading it here does not need the field.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10},"description":"How many items this response carries. **Clamped rather than refused**, so any integer is accepted: a value above 25 is answered with 25, and one below zero with zero. `total` in the body says how many exist either way."}],"responses":{"200":{"description":"Recent feedback","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackListBody"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}},"post":{"operationId":"feedback.post","summary":"Submit feedback · Feedback in aggregate","tags":["Feedback"],"description":"This address answers `POST` and `QUERY`, and both are sent as `POST`. The `x-http-method-override` header is what says which — see the sections below, and the parameter for the exact spelling.\n\n### `POST` — Submit feedback\n\nRecords one piece of feedback about this site or its tools, and confirms what was recorded. A submission is not served back at any address. A retry carrying the same `Idempotency-Key` confirms the same submission rather than recording a second one.\n\n### `QUERY` — Feedback in aggregate\n\nWhat the submissions add up to — counts by kind and severity, and the subjects named most often. Written by a scheduled run rather than computed per request, so it lags the list above.","parameters":[{"name":"idempotency-key","in":"header","required":false,"schema":{"type":"string","minLength":1},"description":"A value you choose, unique to this submission. It is what lets a retry after a timeout confirm the same submission rather than recording a second one. A quoted spelling is unwrapped; an empty value is refused."},{"name":"x-http-method-override","in":"header","required":false,"schema":{"type":"string","enum":["QUERY"]},"description":"`QUERY` is sent as `POST` carrying `x-http-method-override: QUERY`, because the edge in front of this API refuses a bare `QUERY` with a 405 before any code here runs. Send it to reach the `QUERY` operation; leave it off to reach the `POST` one. It is the only thing distinguishing the two."}],"requestBody":{"required":true,"description":"One of two shapes, chosen by the method-override header above.","content":{"application/json":{"schema":{"oneOf":[{"title":"POST","type":"object","required":["kind","severity","message"],"additionalProperties":false,"properties":{"kind":{"type":"string","enum":["missing","broken","confusing","inaccurate","other"],"description":"What the message is about: something that should be here and is not (missing), something here that does not work (broken), something here that is hard to follow (confusing), something here that is not true (inaccurate), or none of those (other)."},"severity":{"type":"string","enum":["high","medium","low"],"description":"How much it matters."},"message":{"type":"string","minLength":1,"maxLength":2000,"description":"The feedback itself."},"about":{"type":"string","maxLength":128,"description":"A tool name or a page on this site the feedback is about."}}},{"title":"QUERY","type":"object","additionalProperties":false,"maxProperties":0,"description":"An empty object. This aggregate takes no parameters — a key sent here is refused rather than ignored, because a key accepted and ignored would answer a question next to the one asked."}]}}}},"responses":{"200":{"description":"Feedback in aggregate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateBody"}}}},"201":{"description":"Submit feedback","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionBody"}}}},"303":{"description":"The request accepted `text/html` — a browser form post. The submission is recorded and the browser is sent on with a `GET`. No body.","headers":{"location":{"schema":{"type":"string"}}}},"default":{"description":"Every other status this operation answers with is a refusal, and carries `problem+json` (RFC 9457).\n\nEvery refusal this API can make, whatever the status. Match on the `type` URI’s last segment — the slug below — rather than on the whole URI, which follows whichever deployment answered.\n\n| Status | Slug | Title |\n| --- | --- | --- |\n| 400 | [`invalid-idempotency-key`](https://uat.lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://uat.lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://uat.lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://uat.lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://uat.lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://uat.lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://uat.lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://uat.lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://uat.lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://uat.lateano.com/api/problems/unsupported-media-type) | The request body is in a media type this resource does not read |\n| 422 | [`invalid-query`](https://uat.lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://uat.lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://uat.lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://uat.lateano.com/api/problems/unexpected-failure) | The resource failed while answering |","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDocument"}}}}}}}},"components":{"schemas":{"QueryGrammar":{"anyOf":[{"anyOf":[{"type":"object","properties":{"match":{"$ref":"#/components/schemas/QueryGrammar/$defs/__schema0"},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["match"],"additionalProperties":false},{"type":"object","properties":{"like":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["like"],"additionalProperties":false},{"type":"object","properties":{"traverse":{"type":"object","properties":{"from":{"type":"string","minLength":1,"maxLength":8000},"follow":{"type":"string","enum":["outgoing","incoming","both"]},"depth":{"type":"integer","minimum":1,"maximum":5}},"required":["from","follow","depth"],"additionalProperties":false},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["traverse"],"additionalProperties":false},{"type":"object","properties":{"slug":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000}},"required":["slug"],"additionalProperties":false},{"type":"object","properties":{"path":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000}},"required":["path"],"additionalProperties":false},{"type":"object","properties":{"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"additionalProperties":false}]},{"type":"object","properties":{"batch":{"minItems":1,"maxItems":10,"type":"array","items":{"anyOf":[{"type":"object","properties":{"match":{"$ref":"#/components/schemas/QueryGrammar/$defs/__schema0"},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["match"],"additionalProperties":false},{"type":"object","properties":{"like":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["like"],"additionalProperties":false},{"type":"object","properties":{"traverse":{"type":"object","properties":{"from":{"type":"string","minLength":1,"maxLength":8000},"follow":{"type":"string","enum":["outgoing","incoming","both"]},"depth":{"type":"integer","minimum":1,"maximum":5}},"required":["from","follow","depth"],"additionalProperties":false},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"required":["traverse"],"additionalProperties":false},{"type":"object","properties":{"slug":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000}},"required":["slug"],"additionalProperties":false},{"type":"object","properties":{"path":{"type":"string","minLength":1,"maxLength":8000},"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000}},"required":["path"],"additionalProperties":false},{"type":"object","properties":{"collection":{"type":"string","enum":["writing","glossary","demos"]},"tag":{"type":"string","minLength":1,"maxLength":8000},"limit":{"type":"integer","minimum":1,"maximum":9007199254740991},"facets":{"minItems":1,"type":"array","items":{"type":"string","enum":["collection","tags"]}}},"additionalProperties":false}]}}},"required":["batch"],"additionalProperties":false}],"$defs":{"__schema0":{"anyOf":[{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":1000},"in":{"type":"string","enum":["title","body","description","aliases","tags","all"]}},"required":["text","in"],"additionalProperties":false},{"type":"object","properties":{"all":{"minItems":1,"type":"array","items":{"$ref":"#/components/schemas/QueryGrammar/$defs/__schema0"}}},"required":["all"],"additionalProperties":false},{"type":"object","properties":{"any":{"minItems":1,"type":"array","items":{"$ref":"#/components/schemas/QueryGrammar/$defs/__schema0"}}},"required":["any"],"additionalProperties":false},{"type":"object","properties":{"not":{"$ref":"#/components/schemas/QueryGrammar/$defs/__schema0"}},"required":["not"],"additionalProperties":false}]}},"title":"lateano.com query","description":"A query that satisfies this schema may still be refused at 422. Nesting depth, and the totals counted across a whole request — text clauses, caller-supplied text and declared results — have no vocabulary in JSON Schema and are enforced by the API. See the invalid-query problem type for what each refusal names."},"PageBody":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/PageInfo"}},"required":["page"],"additionalProperties":false,"description":"An envelope with one key rather than a bare `PageInfo`. The resource can then gain a top-level field."},"PageInfo":{"type":"object","properties":{"path":{"type":"string"},"canonical":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"indexing":{"type":"string","description":"The page's own `robots` directive, so a consumer can honour it."},"collection":{"anyOf":[{"$ref":"#/components/schemas/Collection"},{"type":"null"}],"description":"`null` for a page no document backs — the homepage, an archive, About."},"date":{"type":["string","null"],"description":"A post's publish date or a term's last review; `null` for a page no document backs, or for a Demo."},"tags":{"type":"array","items":{"type":"string"},"description":"Tag titles as a reader types them. Empty rather than `null` for a page carrying none; empty and absent are one fact to every reader."},"aliases":{"type":"array","items":{"type":"string"},"description":"Other names for a term. Empty for a post and for a page nothing authored."},"twin":{"type":["string","null"],"description":"The markdown twin's address, or `null` where the page has none."},"outgoingReferences":{"type":"array","items":{"$ref":"#/components/schemas/PageReference"},"description":"One resolved page edge."},"referencingPosts":{"type":"array","items":{"$ref":"#/components/schemas/PageReference"}},"related":{"type":"array","items":{"$ref":"#/components/schemas/PageReference"}}},"required":["path","canonical","title","description","indexing","collection","date","tags","aliases","twin","outgoingReferences","referencingPosts","related"],"additionalProperties":false,"description":"What any address on this site can say about itself."},"Collection":{"type":"string","enum":["writing","glossary","demos"]},"PageReference":{"type":"object","properties":{"path":{"type":"string"},"title":{"type":"string"}},"required":["path","title"],"additionalProperties":false},"CollectionsBody":{"$ref":"#/components/schemas/ListEnvelope_CollectionInfo"},"ListEnvelope_CollectionInfo":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CollectionInfo"}},"count":{"type":"number","description":"How many items this response carries."},"total":{"type":"number","description":"How many items exist across every response."}},"required":["items","count","total"],"additionalProperties":false,"description":"The one body shape every list resource returns."},"CollectionInfo":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/Collection"},"title":{"type":"string"},"path":{"type":"string"},"count":{"type":"number","description":"How many documents a search of this collection looks through."}},"required":["collection","title","path","count"],"additionalProperties":false},"TagsBody":{"$ref":"#/components/schemas/ListEnvelope_TagInfo","description":"`count` equals `total` unless a caller named a bound, and `total` is the whole vocabulary either way."},"ListEnvelope_TagInfo":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TagInfo"}},"count":{"type":"number","description":"How many items this response carries."},"total":{"type":"number","description":"How many items exist across every response."}},"required":["items","count","total"],"additionalProperties":false,"description":"The one body shape every list resource returns."},"TagInfo":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"count":{"type":"number","description":"How many documents carry it, across every collection."},"byCollection":{"type":"object","properties":{"writing":{"type":"number"},"glossary":{"type":"number"},"demos":{"type":"number"}},"required":["writing","glossary","demos"],"additionalProperties":false,"description":"Each entry is the answer `{ tag, collection }` returns. Total over `Collection` with zeros, so a reader never tells an absent key from a zero."}},"required":["slug","title","count","byCollection"],"additionalProperties":false,"description":"One row of `listTags` — a tag in use."},"SitePagesBody":{"$ref":"#/components/schemas/ListEnvelope_PageSummary","description":"The pages no collection holds. `count` equals `total` unless a caller named a bound."},"ListEnvelope_PageSummary":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PageSummary"}},"count":{"type":"number","description":"How many items this response carries."},"total":{"type":"number","description":"How many items exist across every response."}},"required":["items","count","total"],"additionalProperties":false,"description":"The one body shape every list resource returns."},"PageSummary":{"type":"object","properties":{"path":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"}},"required":["path","title","description"],"additionalProperties":false,"description":"One row of `listPages` — a page no collection holds."},"QueryBody":{"$ref":"#/components/schemas/ListEnvelope_QueryAnswer","description":"The list holds one answer for each query in the request."},"ListEnvelope_QueryAnswer":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/QueryAnswer"}},"count":{"type":"number","description":"How many items this response carries."},"total":{"type":"number","description":"How many items exist across every response."}},"required":["items","count","total"],"additionalProperties":false,"description":"The one body shape every list resource returns."},"QueryAnswer":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/QueryRow"}},"matched":{"type":"number","description":"How many documents the query matched, before any bound reduced them to `results`."},"facets":{"type":"array","items":{"$ref":"#/components/schemas/FacetGroup"}}},"required":["results","matched","facets"],"additionalProperties":false,"description":"`facets` is empty rather than absent for a query that asked for none, and one group per dimension in the order asked. An empty group is a real answer, so \"did not ask\" and \"asked and nothing matched\" stay apart."},"QueryRow":{"type":"object","properties":{"path":{"type":"string"},"canonical":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"collection":{"$ref":"#/components/schemas/Collection"},"date":{"type":["string","null"]},"twin":{"type":["string","null"],"description":"The markdown twin's address, and where the whole document is fetched from."}},"required":["path","canonical","title","description","collection","date","twin"],"additionalProperties":false,"description":"`date` is a post's publication or a term's last review, and `null` for a Demo, which has none. `twin` is `null` for a page that publishes no twin, rather than an address that 404s."},"FacetGroup":{"type":"object","properties":{"facet":{"$ref":"#/components/schemas/Facet"},"counts":{"type":"array","items":{"$ref":"#/components/schemas/FacetCount"}}},"required":["facet","counts"],"additionalProperties":false,"description":"Counts over every matched document, not over the returned rows; counts over a `limit`-sized page describe the page, which a caller can already derive. Ordered by count and then by value, so the same query answers the same way twice."},"Facet":{"type":"string","enum":["collection","tags"]},"FacetCount":{"type":"object","properties":{"value":{"type":"string"},"count":{"type":"number"}},"required":["value","count"],"additionalProperties":false,"description":"How many documents carried one value of a facet."},"FeedbackListBody":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicFeedback"}},"count":{"type":"number","description":"How many items this response carries."},"total":{"type":"number","description":"How many items exist across every response."},"partial":{"type":"boolean","description":"`true` where the rows are a subset rather than the newest, because the run that wrote them did not reach the end of the archive. `total` then counts the subset."}},"required":["count","items","partial","total"],"additionalProperties":false,"description":"`PublicFeedback`, not `Feedback`: the public type has no field that prose can occupy, so a new field cannot leak a message."},"PublicFeedback":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/Kind"},"severity":{"$ref":"#/components/schemas/Severity"},"about":{"type":["string","null"],"description":"A Tool name, a site path, or the caller's own text where it matched neither. `surface` alone separates the cases. This is the one public field that carries text a stranger wrote. A renderer must escape it, or must escape interpolation by default. The value also reaches agents over the Tool surface, where escaping does not apply."},"surface":{"anyOf":[{"$ref":"#/components/schemas/Surface"},{"type":"null"}]},"receivedAt":{"type":"string"}},"required":["kind","severity","about","surface","receivedAt"],"additionalProperties":false,"description":"No `message`, no `requestId`: absent from the type, not filtered out of it, so no field exists that prose could occupy. The projection is written field by field, which makes a new `Feedback` field a compile error until its public fate is decided — a spread would compile, because TypeScript applies no excess-property check to one."},"Kind":{"type":"string","enum":["missing","broken","confusing","inaccurate","other"]},"Severity":{"type":"string","enum":["high","medium","low"]},"Surface":{"type":"string","enum":["api","content","site","demo"]},"AggregateBody":{"type":"object","properties":{"aggregate":{"$ref":"#/components/schemas/Aggregate"}},"required":["aggregate"],"additionalProperties":false},"Aggregate":{"type":"object","properties":{"total":{"type":"number"},"partial":{"type":"boolean","description":"`true` where one `list` did not reach the end. Pathnames are `sha256(key)` and `list` returns lexicographic order, so a partial subset is arbitrary with respect to time — \"the newest N\" can miss the actual newest."},"cells":{"type":"array","items":{"$ref":"#/components/schemas/AggregateCell"},"description":"The cross, sparse and ranked. Cells with no submissions are absent, not zero."},"about":{"type":"array","items":{"$ref":"#/components/schemas/AboutCount"},"description":"Resolved values only — Tool names and site paths, already ours and already published. Nothing a caller wrote reaches this list; the unresolved tail counts toward the unclassified bucket as a number, not a string."}},"required":["total","partial","cells","about"],"additionalProperties":false},"AggregateCell":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/Kind"},"severity":{"$ref":"#/components/schemas/Severity"},"surface":{"anyOf":[{"$ref":"#/components/schemas/Surface"},{"type":"null"}],"description":"`null` is the unclassified bucket, a cell like any other; its size is the only measurement of how often `about` arrives at all."},"count":{"type":"number"}},"required":["kind","severity","surface","count"],"additionalProperties":false,"description":"One cell of the `kind × severity × surface` cross."},"AboutCount":{"type":"object","properties":{"about":{"type":"string"},"count":{"type":"number"}},"required":["about","count"],"additionalProperties":false,"description":"One ranked `about` value."},"SubmissionBody":{"type":"object","properties":{"about":{"type":["string","null"]},"receivedAt":{"type":"string"}},"required":["about","receivedAt"],"additionalProperties":false,"description":"The confirmation a submitter receives. `about` carries the resolved value rather than the value the caller sent, so the reply states what the message was filed against. The type comes from `Feedback`, so the two spellings cannot come apart."},"ProblemDocument":{"type":"object","properties":{"type":{"type":"string","description":"§ 3.1.1. Absolute, on the origin that answered, and it resolves."},"title":{"type":"string","description":"§ 3.1.3. The catalogue row's, unchanged from occurrence to occurrence."},"status":{"type":"number","description":"§ 3.1.2. The same number as the status line, derived rather than passed."},"instance":{"type":"string","description":"§ 3.1.5 RECOMMENDS an absolute URI here. This field carries a full path from the root, which is the fallback form the same section permits."},"detail":{"type":"string","description":"§ 3.1.4. This occurrence only, bounded by `DETAIL_MAX`, absent when there is nothing to add."}},"required":["type","title","status","instance"],"additionalProperties":false,"description":"The schema generator publishes this declaration as the error schema, so a field here reaches the reference. RFC 9457 § 3.1 makes all five members optional, and this API narrows four of them to required: § 3.1.1 reads an absent `type` as `about:blank` and discards the resolvable URI. An optional member is absent, never present and `undefined`, because `JSON.stringify` drops the value and the wire cannot tell the two apart."}}}}