Getting started
For AI agents
Read this first if you are an agent holding a key — the API describes itself, and that description is better than this site.
This page is written for an AI agent asked to integrate ZevContent, and for the person pointing one at it.
Ask the server, not the docs
/v1/introspection If you hold a key, start here rather than with this site. One request returns:
- every content type in the project, with its fields and the exact
response_shapeeach one serves; - every built-in module, with its endpoints, its shapes, how much is in it, and the notes that matter;
- the
core_endpointsfor custom content and schema; - the rich text document contract — every node, mark and variant you may send;
conventions— authentication, pagination, filtering, rate limits and the error envelope, with the real numbers.
It describes that project, not the product in general. These pages can go stale; that response is generated from the code that serves the request, so it cannot.
Machine-readable copies of this site
/llms.txt— a curated index of every page, per the llms.txt convention./llms-full.txt— every page concatenated, for one-shot context.<any page>.md— the clean markdown body of a single page. Append.mdto any URL here:/api/errors.md.
The four things agents get wrong
1. Fetching everything. There is no way to ask for all of something, and
?limit=5000 is refused rather than clamped. Page it: read both
meta.next_offset and meta.next_cursor, pass back whichever is set, and stop
when both are null. Do not loop on has_more alone.
2. Filtering in the client. Every listing filters server-side, on indexed
columns, and validates against the declared schema. ?where[role]=engineer is
one indexed query; fetching a page and filtering it is a bug that only shows up
when the project grows. An unknown field is answered with the fields that
exist, so a wrong guess teaches you the right answer.
3. Treating an empty list and a 404 as the same thing. They are
deliberately different. A filter that matches nothing is 200 with an empty
array. Naming something that does not exist — an unknown category, section or
field — is a 4xx that names the real ones and often carries did_you_mean.
If you get an empty list, your query was valid and there is nothing there.
4. Retrying a 429 immediately. Read Retry-After and wait exactly that
long. It is the moment the refusal lifts, not when the window rolls. While you
are being refused, requests are not counted — so hammering neither helps nor
hurts, it just wastes time. Better: read X-RateLimit-Remaining-read on every
response and slow down before you are refused.
Errors are written to be acted on
Every failure is the same envelope, and the message is a sentence saying what to do next rather than a status restated:
{
"error": {
"type": "not_found_error",
"code": "unknown_kb_article",
"message": "This knowledge base has no published article at \"instal-the-cli\". Did you mean \"install-the-cli\"? List the published articles with GET /v1/kb/articles.",
"request_id": "6e5c4029-…",
"param": "slug",
"did_you_mean": "install-the-cli"
}
}
Branch on code, which is stable. message is for a human reading a log, and
may be reworded. See Errors.
Writing content
An agent can set a project up end to end with a secret key:
PUT /v1/schema/content-types/{type}— define a type and its fields.POST /v1/content/{type}/entries— create an entry, optionally publishing it in the same call with"publish": true.PUT /v1/content/{type}— save a singleton.
Rich text is a document, not an HTML string. /v1/introspection returns the
whole node schema under rich_text; Rich text explains
the parts worth understanding before you generate one.
Updated at, Wednesday, September 2, 2026