Agent API
Let an agent create models and entries with an API key on /v2/agent, with no person signed in.
An agent can stand up a content model, fill it with entries and hand the result to a frontend, using an API key and nothing else. It does that on /v2/agent/*, the same routes the Capa admin uses, mounted a second time behind key authentication.
To read content from an agent, or to let an assistant like Claude Code or Cursor query it, use the MCP server instead. This page is for writes.
What a key can reach
| Surface | Prefix | Authentication | Host | For |
|---|---|---|---|---|
| Content reads | /v2/api, /v3/api, /api/ | API key | https://cdn.capacms.com | your site |
| Agent writes | /v2/agent/* | API key | https://api.capacms.com | this page |
| Admin | /v2/models, /v2/model-instances and the rest | a signed-in person | https://api.capacms.com | the Capa admin |
The admin routes refuse a key with 401. Send key requests to /v2/agent/..., not to the admin path of the same name.
The key
Every request carries one header:
x-api-key: pk_...A key belongs to one project, and the project comes from the key. No path or body below names a project, so nothing you send can reach outside it.
/v2/agent/* takes a legacy key: pk_ for production, sk_ for other environments, or an older key with no prefix. A cap_ key is refused with 401 {"error":"Invalid API key"}. See Keys and scopes.
A legacy key holds one of four permissions:
| Permission | Can |
|---|---|
read | read models, entries, media, types, categories, workspaces and search |
write | everything read can, plus create, update and publish entries, upload media, and arrange workspaces |
delete | everything write can, plus delete entries and media |
agent | everything write can, plus create and update models |
You want agent. It is the only permission that can create a model. It deletes nothing, on purpose: an agent building a project never needs to remove a model or an entry, and a key that cannot delete cannot destroy content by misreading an instruction. No legacy key can publish a model version or delete a model.
A person on the project mints the key once, with their own session (see Management API):
curl -X POST https://api.capacms.com/v2/tenants/api-keys \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H 'content-type: application/json' \
-d '{"environment":"production","permission":"agent"}'A permission that is not one of the four is refused with 400, rather than becoming a read key.
Create a model
curl -X POST https://api.capacms.com/v2/agent/models/ \
-H "x-api-key: $CAPA_AGENT_KEY" \
-H 'content-type: application/json' \
-d '{
"modelName": "Author",
"namespace": "guide_author",
"fields": [{ "name": "Name", "namespace": "name", "type": "string", "sortOrder": 0 }]
}'The answer is 201 with the model, including its id.
Every model gets a required title field, whether you ask for one or not. The model above comes back with two fields, name and title. So every entry you create must carry a title, or it is refused with 400 {"error":"Field title is required"}.
Field types: string, markdown, html, code, color, number, true_false, date, enum, image, video, file, relation, array and mixed. Anything else is refused with 400 and the list of valid types.
Relations
A relation field names the model it points at by namespace:
{ "name": "Author", "namespace": "author", "type": "relation", "relationRef": "guide_author", "sortOrder": 1 }Use the namespace. A model id is refused with 400 and Model with namespace <id> does not exist. For a list of related entries, use an array of relations:
{ "name": "Co-authors", "namespace": "coauthors", "type": "array", "arrayType": "relation", "relationRef": "guide_author", "sortOrder": 2 }Create and publish entries
curl -X POST https://api.capacms.com/v2/agent/model-instances/ \
-H "x-api-key: $CAPA_AGENT_KEY" \
-H 'content-type: application/json' \
-d '{
"modelId": "<model id>",
"publish": true,
"data": { "title": "Notes on the Analytical Engine", "author": "<author entry id>" }
}'Three things to know:
datavalues are flat. Send"title": "...", not"title": { "value": "..." }. The read API returns the wrapped form, and the write routes do not accept it.- A relation value is the id of the entry it points at. Anything else is refused with
400 {"error":"Field author must be a valid UUID"}. publishisfalseby default, which saves a draft. A production key on the read API sees published entries only.
Read what you made
Your site reads with a separate read key, never the agent key. Reads go to the CDN:
curl -H "x-api-key: $CAPA_KEY" \
'https://cdn.capacms.com/v2/api/guide_post?limit=5&depth=1'{
"data": [{
"title": { "type": "string", "value": "Notes on the Analytical Engine" },
"author": { "type": "relation", "value": "f3e288a2-…" }
}],
"relations": { "f3e288a2-…": { "…": "the author entry" } },
"meta": { "total": 1, "limit": 5, "environment": "production" }
}On /v2/api, values come back wrapped as type and value, and a relation's value is the target's id. The target itself is in the top-level relations map. depth sets how far relations expand. The legacy API reference has the whole format.
For new code, read the same entries from /api/entries. The same legacy key works there.
Keep the agent key out of your site. A NEXT_PUBLIC_ variable is compiled into the browser bundle, and a leaked agent key can rewrite your models.
End to end
KEY=pk_... # permission: agent
READ=pk_... # permission: read, for the site
API=https://api.capacms.com
H="content-type: application/json"
# 1. The models
AUTHOR=$(curl -s -X POST $API/v2/agent/models/ -H "x-api-key: $KEY" -H "$H" \
-d '{"modelName":"Author","namespace":"guide_author",
"fields":[{"name":"Name","namespace":"name","type":"string","sortOrder":0}]}' \
| jq -r .id)
POST=$(curl -s -X POST $API/v2/agent/models/ -H "x-api-key: $KEY" -H "$H" \
-d '{"modelName":"Post","namespace":"guide_post",
"fields":[{"name":"Title","namespace":"title","type":"string","sortOrder":0},
{"name":"Author","namespace":"author","type":"relation",
"relationRef":"guide_author","sortOrder":1}]}' \
| jq -r .id)
# 2. The entries. Both models have a required title.
ADA=$(curl -s -X POST $API/v2/agent/model-instances/ -H "x-api-key: $KEY" -H "$H" \
-d "{\"modelId\":\"$AUTHOR\",\"publish\":true,
\"data\":{\"title\":\"Ada Lovelace\",\"name\":\"Ada Lovelace\"}}" | jq -r .id)
curl -s -X POST $API/v2/agent/model-instances/ -H "x-api-key: $KEY" -H "$H" \
-d "{\"modelId\":\"$POST\",\"publish\":true,
\"data\":{\"title\":\"Notes on the Analytical Engine\",\"author\":\"$ADA\"}}"
# 3. What the site reads
curl -s -H "x-api-key: $READ" "https://cdn.capacms.com/v2/api/guide_post?limit=5&depth=1"More an agent key can do
The same key reaches the other routes mounted under /v2/agent:
| Prefix | What | Permission it needs |
|---|---|---|
/v2/agent/scheduled-actions | schedule entries to publish or unpublish later | write, delete or agent |
/v2/agent/workspaces | arrange the admin's left rail | write, delete or agent |
/v2/agent/models/:id/layout | arrange the entry editor | agent |
/v2/agent/models/:id/field-changes | change a field's type on a model with entries | agent |
Each works as its session twin does. See Management API.
Errors
| Status | Body | Cause |
|---|---|---|
| 400 | Field title is required | every model has a required title |
| 400 | Field <name> must be a valid UUID | a relation value must be the id of an entry |
| 400 | Field <name> must be a string | data values are flat, not { "value": … } |
| 401 | API key required | no x-api-key header |
| 401 | Invalid API key | an unknown or inactive key, or a cap_ key |
| 403 | Insufficient permissions | the key's permission does not allow this |
A 403 does not say which permission was missing. If the call is right, check that the key's permission is agent.
What this surface does not do
- Delete models. No legacy key can, and an
agentkey deletes nothing at all. - Name an author. A write made with a key has no person behind it, so its
createdByisnull. - Show who edited. Where a record names the person who saved it, a key gets
{ "id": "…" }and no name, email or avatar. - Create projects. A key acts inside one project. Creating a project is done in the Capa admin.