Skip to main content

Drive the docs from an agent

This docs website is powered by GraphQL. Every page you are reading — including this one — is a record in a live GraphQL API that ships with the docs repo. An AI agent does not have to scrape HTML or guess at URLs: it introspects a typed schema, searches the corpus, and pulls the exact page body it needs, all from a single endpoint.

An agent is, in practice, an HTTP client with a prompt. To build with these docs it needs only two things — the endpoint and the schema — and it can then write its own queries, read exactly the fields it asked for, and turn the documentation into context on demand.


The two things an agent needs

  1. The endpoint — one URL for every operation. Run it locally with npm run graphql and it serves at:

    POST http://localhost:4000/graphql

    The port is configurable via $PORT (default 4000). GraphiQL is enabled at the same URL for interactive exploration.

  2. The schema — introspection is on, so the agent fetches the schema live (below) instead of carrying it in the prompt.

There is no API key, no Authorization header, and no login. This is a public, read-only API over public documentation — every query just works.


The request shape

Every call is the same: a POST to /graphql, Content-Type: application/json, and a JSON body with a query and optional variables.

curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"{ meta { name docCount sections } }"}'
{
"data": {
"meta": {
"name": "Keen Docs GraphQL API",
"docCount": 49,
"sections": ["chat","graphql","keen-extension","onboarding","organization","root","tooling"]
}
}
}

The body has up to three standard fields: query (required — always a query; the API is read-only, no mutations), variables (optional object referenced by $name), and operationName (optional, when the document defines more than one operation). Send one operation per request — a JSON array body is rejected with "Batching is not supported." (BAD_REQUEST).


Step 1 — introspect the schema once

Because introspection is enabled, an agent discovers every entry point in a single call and caches it:

curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"{ __type(name: \"Query\") { fields { name args { name } } } }"}'
{
"data": {
"__type": {
"fields": [
{ "name": "docs", "args": [ { "name": "section" }, { "name": "search" }, { "name": "limit" } ] },
{ "name": "doc", "args": [ { "name": "id" }, { "name": "slug" } ] },
{ "name": "sections", "args": [] },
{ "name": "search", "args": [ { "name": "query" }, { "name": "limit" } ] },
{ "name": "meta", "args": [] }
]
}
}
}

That one response gives the agent the whole API: list and filter docs, fetch one doc, enumerate sections, full-text search, and read metadata. The full type model lives in Schema overview.

A Doc carries everything needed to use a page, not just locate it — id, slug, title, section, headings { depth text anchor }, the full Markdown body, and wordCount. Because GraphQL returns exactly the selection set, the agent decides per call between a cheap index (id title slug) and the full body.


Step 2 — search, then fetch the body

The agent loop is tight and predictable: introspect once → search a topic → fetch the matching page's body → use it.

Search for a topic and get a lightweight index back:

curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query Find($q: String!, $n: Int) { search(query: $q, limit: $n) { id title slug } }","variables":{"q":"agent","n":3}}'
{
"data": {
"search": [
{ "id": "chat/agents", "title": "Agent picker", "slug": "/docs/chat/agents" },
{ "id": "chat/conversation", "title": "Conversation", "slug": "/docs/chat/conversation" },
{ "id": "chat/index", "title": "Chat", "slug": "/docs/chat" }
]
}
}

Fetch the body of the best hit by id, plus whatever the agent wants to reason over:

curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"query Page($id: ID!) { doc(id: $id) { title section wordCount headings { depth text anchor } body } }","variables":{"id":"chat/agents"}}'
{
"data": {
"doc": {
"title": "Agent picker",
"section": "chat",
"wordCount": 250,
"headings": [
{ "depth": 1, "text": "Agent picker", "anchor": "agent-picker" },
{ "depth": 2, "text": "URL shape", "anchor": "url-shape" }
],
"body": "# Agent picker\n\nAfter a Consumer picks a Space on `/`, they land here..."
}
}
}

The body is plain Markdown the agent drops straight into its context window; the headings give deep-link anchors (slug + #anchor) to cite. Same loop for every section in the corpus.


Why GraphQL fits agents

  • Typed and self-describing. The server validates each operation against the schema before executing it. A misspelled field returns an explicit error — and because blockFieldSuggestion is off, the error names the right field, so an agent self-corrects without a human in the loop:

    { "errors": [ { "message": "Cannot query field \"titel\" on type \"Doc\". Did you mean \"title\"?", "extensions": { "code": "GRAPHQL_VALIDATION_FAILED" } } ] }
  • One endpoint, no credential. Sections, pages, search, and metadata are all reachable through the same POST /graphql. No per-resource base URL, no key to provision or rotate. The agent memorizes one URL.

  • Precise selection — no over-fetch. The response mirrors the selection set exactly: ask for id title slug across many docs, then body for just the one page you will read.

  • Introspectable. The schema is queryable at runtime, so the agent learns the API from the API — new fields are usable the moment they ship, with no client regeneration.


Limits

Query complexity is bounded by graphql-armor. The configured limits sit well above any normal docs query:

GuardLimit
maxDepth10
maxAliases20
maxTokens1500
costLimit8000
blockFieldSuggestionoff (suggestions kept, to help agents self-correct)

A query that exceeds a guard is rejected before execution with an errors[] entry — e.g. 25 aliases returns "Aliases limit of 20 exceeded". See Errors and limits for the codes and how an agent should branch on them.


Next steps

  • GraphQL API — the overview: the one endpoint, the request envelope, and your first query.
  • Schema overview — the full type model (Doc, Heading, Section, ApiMeta) and the root Query fields.
  • Queries — every read operation with variables and sample responses.
  • Errors and limits — the error-code contract plus the complexity guards above.