Errors and limits
This docs site is powered by GraphQL: every page you are reading is a Doc you can
query through a live API. That API is read-only and open — no API key, no
Authorization header, no mutations. So the error surface is small and entirely about
request shape and complexity.
Errors come back in the standard GraphQL envelope, and most carry a stable machine code
at errors[].extensions.code. Branch on that code — not the human message — and your
agent can recover from a malformed query without regex-parsing prose.
The error envelope
A successful request returns data; a failed one returns an errors array. Each entry
has a message, usually a locations array, and — for parse and validation failures —
an extensions.code:
{
"errors": [
{
"message": "Cannot query field \"nope\" on type \"ApiMeta\". Did you mean \"name\"?",
"locations": [{ "line": 1, "column": 10 }],
"extensions": { "code": "GRAPHQL_VALIDATION_FAILED" }
}
]
}
That response is exactly what the live server returns for:
{ meta { nope } }
Human message text can change. The extensions.code values are the stable contract —
pin your branching logic to them.
Field suggestions are enabled (blockFieldSuggestion: false in the server config), so
a misspelled field comes back with a Did you mean …? hint that helps an agent
self-correct.
Error-code reference
Because the endpoint is read-only and open, there are no auth, scope, or write-conflict codes — only request-shape and complexity failures.
extensions.code | When it happens | Suggested agent action |
|---|---|---|
GRAPHQL_PARSE_FAILED | The query string is not valid GraphQL — a syntax error, or it tripped the token limit (see below). | Fix the syntax / shrink the query. Do not retry the same string unchanged. |
GRAPHQL_VALIDATION_FAILED | The query parses but references something the schema doesn't have — an unknown field, wrong argument, or bad type. | Correct the selection set against the Schema overview. The message often names the right field. |
BAD_REQUEST | The HTTP body is malformed for GraphQL — most commonly an array body (request batching is disabled). | Send a single, well-formed JSON operation: {"query":"…","variables":{…}}. |
A GRAPHQL_PARSE_FAILED looks like this — note the <EOF> from an unterminated
selection set:
{
"errors": [
{
"message": "Syntax Error: Expected Name, found <EOF>.",
"locations": [{ "line": 1, "column": 15 }],
"extensions": { "code": "GRAPHQL_PARSE_FAILED" }
}
]
}
Some guardrails are enforced before a code is attached and so arrive as a plain
Syntax Error message with no extensions.code — notably the alias limit. Match
on the message text for those, or just treat any errors payload without data as
"my query was rejected, shrink and retry."
This endpoint is read-only
There are no Authorization headers, no API keys, and no write operations — so there are
no auth or mutation errors to handle. The schema has no mutation type at all, so a
mutation is rejected flatly:
{
"errors": [
{ "message": "Schema is not configured to execute mutation operation.",
"locations": [{ "line": 1, "column": 1 }] }
],
"data": null
}
Point your agent at the five Query fields (docs, doc, sections, search, meta)
documented in Queries.
Transport-level rejections (HTTP status)
A request can fail before it reaches the GraphQL layer:
- Non-JSON content type → HTTP
415 Unsupported Media Type. Always sendcontent-type: application/jsonon POST. - Both
POSTandGETare accepted for these reads. AGETwith a?query=…parameter works:
curl -sS 'http://localhost:4000/graphql?query=%7Bmeta%7Bname%7D%7D' \
-H 'accept: application/json'
# {"data":{"meta":{"name":"Keen Docs GraphQL API"}}}
Limits
The server wraps GraphQL Yoga with EnvelopArmorPlugin from
@escape.tech/graphql-armor.
These are the configured values in graphql-server/, stated exactly. Each is a complexity
bound checked before execution, so an oversized query is rejected without ever
touching a resolver.
| Guardrail | Limit | What it caps |
|---|---|---|
maxDepth | 10 | Maximum selection-set nesting depth. |
maxAliases | 20 | Maximum number of field aliases in one operation. |
maxTokens | 1500 | Maximum lexical tokens in the query document. |
costLimit | 8000 | Overall query cost budget (depth- and breadth-weighted). |
blockFieldSuggestion | false | Field "Did you mean …?" hints stay enabled. |
These bounds are generous for honest queries. The schema is deliberately shallow — the
deepest natural path is sections → docs → headings → anchor (four levels) — so a real
selection set lands far under depth 10 and cost 8000. The limits exist to stop abusive or
accidental fan-out, not to constrain normal use.
Token limit (maxTokens: 1500)
A query document with more than 1500 lexical tokens is rejected at parse time:
{
"errors": [
{ "message": "Syntax Error: Token limit of 1500 exceeded.",
"extensions": { "code": "GRAPHQL_PARSE_FAILED" } }
]
}
Alias limit (maxAliases: 20)
More than 20 aliases in a single operation is rejected. This one arrives as a plain syntax
error with no extensions.code. Sending 25 aliased copies of meta:
{
"errors": [
{ "message": "Syntax Error: Aliases limit of 20 exceeded, found 25." }
]
}
Batching is disabled
Send one operation per request. An array body is rejected:
{
"errors": [
{ "message": "Batching is not supported.",
"extensions": { "code": "BAD_REQUEST" } }
]
}
Practical guidance for agents
- Ask for exactly the fields you need. Precise selection sets stay far under the cost and token bounds and return a predictable shape.
- Split, don't nest. If you need many things, send several small requests rather than one deep, alias-heavy query.
- Treat a missing
datakey as "reshape and retry." On anyerrorspayload, readerrors[0].extensions.codewhen present, fix the query, and resend — never loop on the same rejected string.
Introspection is on
Introspection is enabled and GraphiQL is served at
http://localhost:4000/graphql. Your agent can discover
the full type system live with a standard introspection query, or just read the
Schema overview. The landing page is off — the endpoint is an API
surface (and an explorer), not a marketing page.
A clean request, for contrast
When the query is well-formed and within bounds, you get back exactly the shape you asked for — no envelope surprises:
curl -sS http://localhost:4000/graphql \
-H 'content-type: application/json' \
--data '{"query":"{ search(query: \"idempotent\") { id title slug } }"}'
{
"data": {
"search": [
{ "id": "graphql/errors-and-limits", "title": "Errors and limits", "slug": "/docs/graphql/errors-and-limits" },
{ "id": "graphql/queries", "title": "Queries", "slug": "/docs/graphql/queries" }
]
}
}
See also
- Queries — the five read fields, arguments, and sample responses.
- Schema overview — the full type system you are validating against.