Queries
This documentation site is powered by GraphQL. Every page you are reading is also
a row of data: the Markdown that renders this site is loaded into a live, typed
GraphQL API and served back as queryable Doc records. You can ask the docs
questions — "which pages are in the graphql section?", "search for idempotent",
"give me the heading outline of the queries page" — and get back exactly the fields
you select.
The API is read-only and open. There is no API key, no Authorization header,
and no mutations. Just send the GraphQL envelope — a query string and an optional
variables object — as JSON.
Every query goes to a single endpoint, over POST with a JSON body:
POST http://localhost:4000/graphql
Run the server with npm run graphql, then open
http://localhost:4000/graphql for the GraphiQL
explorer (introspection is on). The port is configurable with $PORT (default
4000). The examples below use localhost:4000 — substitute your own host if you
deploy it elsewhere.
The query catalog
These are the root query fields of the schema (type Query):
| Field | Arguments | Returns |
|---|---|---|
meta | — | ApiMeta! |
sections | — | [Section!]! |
docs | section: String, search: String, limit: Int = 100 | [Doc!]! |
doc | id: ID, slug: String | Doc |
search | query: String!, limit: Int = 20 | [Doc!]! |
A response mirrors exactly the fields you select — nothing more. Ask for the fields you need, parse the shape you asked for. The full type definitions are in Schema overview.
meta — what's in here
meta returns a single ApiMeta describing the API: its name, a one-line
description, the total docCount, the list of sections, and the endpoint path.
It is the cheapest call to confirm the server is up and see the shape of the corpus:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query { meta { name docCount sections endpoint } }"}'
{
"data": {
"meta": {
"name": "Keen Docs GraphQL API",
"docCount": 49,
"sections": ["chat", "graphql", "keen-extension", "onboarding", "organization", "root", "tooling"],
"endpoint": "/graphql"
}
}
}
Add description to the selection for the longer blurb. meta takes no arguments.
sections — the table of contents
sections returns one Section per top-level area of the docs, each with a name,
a docCount, and a docs list. Select just name and docCount for a compact
table of contents:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query { sections { name docCount } }"}'
{
"data": {
"sections": [
{ "name": "chat", "docCount": 7 },
{ "name": "graphql", "docCount": 6 },
{ "name": "keen-extension", "docCount": 15 },
{ "name": "onboarding", "docCount": 10 },
{ "name": "organization", "docCount": 9 },
{ "name": "root", "docCount": 1 },
{ "name": "tooling", "docCount": 1 }
]
}
}
Each Section also exposes its full docs list, so you can fan out from a section
straight into its pages — sections { name docs { id title } } — in one round trip.
Keep nested selections shallow; very deep queries are rejected by the depth limit
(see Errors and limits).
docs — list and filter
docs is the workhorse list query. With no arguments it returns every page (capped
at limit, default 100). Three optional arguments narrow it:
section: String— restrict to one section (e.g."graphql","chat").search: String— keep only pages whose title, description, or body contains the term (case-insensitive substring match).limit: Int = 100— cap the number of results returned.
The arguments combine. Here they are passed as variables — section and
search together, capped at limit: 3:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query Docs($section: String, $search: String, $limit: Int) { docs(section: $section, search: $search, limit: $limit) { id title slug } }","variables":{"section":"graphql","search":"introspection","limit":3}}'
{
"data": {
"docs": [
{ "id": "graphql/agents", "title": "Drive the docs from an agent", "slug": "/docs/graphql/agents" },
{ "id": "graphql/errors-and-limits", "title": "Errors and limits", "slug": "/docs/graphql/errors-and-limits" },
{ "id": "graphql/index", "title": "GraphQL API", "slug": "/docs/graphql" }
]
}
}
Drop search to list a whole section — docs(section: "chat") returns all seven
chat pages. Drop section to search the whole corpus. The Doc type carries far
more than id/title/slug: description, section, category, wordCount,
headings, and the full body. Select only the slices you need.
doc — fetch one page
doc returns a single Doc (or null). Look it up by either its stable id (the
path under docs/ without extension, e.g. "chat/conversation") or its site slug
(e.g. "/docs/graphql/queries"). This example fetches by id and pulls the heading
outline along with metadata:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query Doc($id: ID!) { doc(id: $id) { id title slug section wordCount headings { depth text anchor } } }","variables":{"id":"chat/conversation"}}'
{
"data": {
"doc": {
"id": "chat/conversation",
"title": "Conversation",
"slug": "/docs/chat/conversation",
"section": "chat",
"wordCount": 712,
"headings": [
{ "depth": 1, "text": "Conversation", "anchor": "conversation" },
{ "depth": 2, "text": "URL shape", "anchor": "url-shape" },
{ "depth": 2, "text": "What's on the page", "anchor": "what-s-on-the-page" }
]
}
}
}
The headings array above is abbreviated — chat/conversation has eight headings in
total. Each anchor is a slugified, deep-linkable id: lowercased, with every run of
non-alphanumeric characters collapsed to a single -. To fetch by URL instead, pass
slug:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query { doc(slug: \"/docs/graphql/queries\") { id title section } }"}'
{
"data": {
"doc": {
"id": "graphql/queries",
"title": "Queries",
"section": "graphql"
}
}
}
Pass id, slug, or both; a request matching nothing resolves to "doc": null.
Add body to the selection to retrieve the full Markdown of a page (frontmatter
stripped) — ideal for feeding one page into an agent's context.
search — full-text search
search is the discovery query. It scans every page's title, description, and body
for the query term (required) and returns matching Docs, capped at limit
(default 20). Use it when you do not already know a page's id:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query Search($query: String!, $limit: Int) { search(query: $query, limit: $limit) { id title slug section } }","variables":{"query":"introspection","limit":20}}'
{
"data": {
"search": [
{ "id": "graphql/agents", "title": "Drive the docs from an agent", "slug": "/docs/graphql/agents", "section": "graphql" },
{ "id": "graphql/errors-and-limits", "title": "Errors and limits", "slug": "/docs/graphql/errors-and-limits", "section": "graphql" },
{ "id": "graphql/index", "title": "GraphQL API", "slug": "/docs/graphql", "section": "graphql" },
{ "id": "graphql/queries", "title": "Queries", "slug": "/docs/graphql/queries", "section": "graphql" },
{ "id": "graphql/running-the-api", "title": "Running the API", "slug": "/docs/graphql/running-the-api", "section": "graphql" },
{ "id": "graphql/schema-overview", "title": "Schema overview", "slug": "/docs/graphql/schema-overview", "section": "graphql" }
]
}
}
A narrower term shrinks the result set — searching for "idempotent" returns just
the pages that mention it:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query { search(query: \"idempotent\") { id title } }"}'
{
"data": {
"search": [
{ "id": "graphql/errors-and-limits", "title": "Errors and limits" },
{ "id": "graphql/queries", "title": "Queries" }
]
}
}
search and docs(search:) use the same matcher; the difference is that search
is search-first (the term is required) while docs is list-first (the term is one
optional filter among several). Result sets reflect the live corpus, so exact counts
shift as pages are added or edited.
Precise field selection
Because the response shape is your selection set, an agent fetches only what it needs
in one round trip. The same doc call can be a one-field lookup or a full-page pull:
# Minimal: just confirm a page exists and get its URL.
query { doc(id: "graphql/queries") { slug } }
# Rich: pull everything for an agent's working context.
query {
doc(id: "graphql/queries") {
title
description
section
wordCount
headings { depth text anchor }
body
}
}
Select narrowly by default and widen only when you need the body. Deeply nested or
oversized selections are bounded by the server's limits.
Response envelope
Every query returns the standard GraphQL envelope. On success:
{ "data": { "…": "…" } }
On failure, an errors array, each entry carrying a machine-readable
extensions.code. Querying a field that does not exist on a type fails validation:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query { doc(id: \"graphql/index\") { id nope } }"}'
{
"errors": [
{
"message": "Cannot query field \"nope\" on type \"Doc\".",
"locations": [{ "line": 1, "column": 39 }],
"extensions": { "code": "GRAPHQL_VALIDATION_FAILED" }
}
]
}
Branch on extensions.code rather than parsing the message text. The endpoint is
guarded by graphql-armor with bounded query complexity — maxDepth 10, maxAliases
20, maxTokens 1500, and a cost limit of 8000 — so keep selections shallow and
focused. The full reference is in Errors and limits.