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.
Reach 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.
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.
The query grammar, generated from the validator that enforces it. The same schema is published on its own at /api/query-schema.json, which is where a client should fetch it from — that address is cacheable and this document is not small.
TypeScript Definitions
Use the request body type in TypeScript.
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.
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.
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.
---
title: "Query the site"
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)."
url: "https://lateano.com/developers/api/reference/reading/pages.post"
author: "Colin Lateano"
---
# Query the site
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.
Reach 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).
```json
{
"openapi": "3.1.1",
"info": {
"title": "lateano.com",
"version": "1.0.0"
},
"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://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://lateano.com/api/problems/invalid-idempotency-key) | The Idempotency-Key header carries a value this resource will not accept |\n| 400 | [`malformed-body`](https://lateano.com/api/problems/malformed-body) | The request body did not parse |\n| 400 | [`missing-idempotency-key`](https://lateano.com/api/problems/missing-idempotency-key) | This resource requires an Idempotency-Key header |\n| 400 | [`missing-media-type`](https://lateano.com/api/problems/missing-media-type) | The request carried a body with no media type |\n| 404 | [`no-such-resource`](https://lateano.com/api/problems/no-such-resource) | There is nothing at this address |\n| 405 | [`method-not-allowed`](https://lateano.com/api/problems/method-not-allowed) | This resource does not answer that method |\n| 406 | [`not-acceptable`](https://lateano.com/api/problems/not-acceptable) | This resource cannot answer in any media type the request accepts |\n| 409 | [`conflicting-idempotency-key`](https://lateano.com/api/problems/conflicting-idempotency-key) | That Idempotency-Key already names a different submission |\n| 412 | [`precondition-failed`](https://lateano.com/api/problems/precondition-failed) | The condition the request carried does not hold |\n| 415 | [`unsupported-media-type`](https://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://lateano.com/api/problems/invalid-query) | The query parsed but does not describe a question this resource can answer |\n| 422 | [`invalid-submission`](https://lateano.com/api/problems/invalid-submission) | The submission parsed but is not something this resource can record |\n| 429 | [`too-many-requests`](https://lateano.com/api/problems/too-many-requests) | The courtesy quota for this caller is spent |\n| 500 | [`unexpected-failure`](https://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."
},
"QueryBody": {
"$ref": "#/components/schemas/ListEnvelope_QueryAnswer",
"description": "The list holds one answer for each query in the request."
},
"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."
},
"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."
},
"Collection": {
"type": "string",
"enum": [
"writing",
"glossary",
"demos"
]
},
"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."
}
}
}
}
```