---
title: For AI agents
description: 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

```http
GET /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_shape` each one serves;
- every built-in module, with its endpoints, its shapes, how much is in it,
  and the notes that matter;
- the `core_endpoints` for 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.

> **INFO: The rule of thumb**
>
> Read `/v1/introspection` to learn what to send. Read these pages to learn
> *why*: the reasoning, the trade-offs, and the mistakes the shape is built to
> prevent. If the two ever disagree, the API is right and this is a bug worth
> reporting.

## Machine-readable copies of this site

- **`/llms.txt`**: a curated index of every page, per the
  [llms.txt convention](https://llmstxt.org).
- **`/llms-full.txt`**: every page concatenated, for one-shot context.
- **`<any page>.md`**: the clean markdown body of a single page. Append `.md`
  to any URL here, such as `/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, such as 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:

```json
{
  "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](/api/errors).

## Writing content

An agent can set a project up end to end with a **secret** key:

- `PUT /v1/schema/content-types/{type}` defines a type and its fields.
- `POST /v1/content/{type}/entries` creates an entry, optionally publishing it
  in the same call with `"publish": true`.
- `PUT /v1/content/{type}` saves a singleton.

Rich text is a document, not an HTML string. `/v1/introspection` returns the
whole node schema under `rich_text`; [Rich text](/concepts/rich-text) explains
the parts worth understanding before you generate one.

> **WARNING: Drafts are the default**
>
> Omitting `publish` creates a draft, which is deliberate: content generated by
> an agent should be reviewed by a person before it is public. Passing
> `"publish": true` skips that review. Do it only when the person you are
> working for has asked you to.