Browse the reference
Agent · /v1/agent
Read and write documents
A document is Markdown at a path. It is read from a draft, and the trunk is the draft read when none is named. Whatever is read comes back with its provenance: which draft it was read from, and whether that makes it approved, proposed or unapproved.
Reads are built so an agent spends in proportion to what it needs. A read returns no body unless it is asked for. The outline says where a section starts, and a range of lines fetches only that.
field-notes/2026/harbour-survey.md · v41
The outlineheadings only
…/map
One section44 lines
…/lines?from=14&to=57
The whole document96 lines
…?include=body
Name the draft on every write
?draft=dmitri/field-notes. Without it a write is aimed at the trunk, and is refused.
Say what you read
Send expectedVersion, the version.seq you read. If the document has changed since, the write is refused instead of overwriting.
At most 500 KB of text
A body is measured as the editor measures it, and compared with stats.size.max.
Reading
Every route here takes ?draft= to read from a draft, and reads the trunk without it. A document the agent has been excluded from answers as though it were not there.
List documents
GET/v1/agent/c/{identifier}/documents
One page of a draft's documents in path order, without bodies. lineCount is in every row because it is what decides the next call: read the whole thing, or a range of it.
Query
- draftstring
- The draft to list. The trunk when absent.
- pathstring
- A path prefix.
field-notes/lists that folder and everything under it. - limitinteger · 1 to 200
- 50 when absent.
- cursorstring
nextCursorfrom the page before.
id, path and excluded: true: a document the agent has been shut out of. It keeps its place so that paging stays even.curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents?path=field-notes/&limit=50" \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"id": "0d9a4c5e-7b1f-4c7a-9a10-3f6b2f1c8e21",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"frontmatter": {},
"version": { "id": "…", "seq": 41, "contentHash": "sha256:9f2c…" },
"provenance": {
"concept": "coastal-atlas",
"draft": "main",
"isTrunk": true,
"approvalStatus": "approved",
"proposal": null,
"stack": null
},
"author": { "type": "USER", "id": "…", "name": "Ana Reyes", "username": "ana" },
"updatedAt": "2026-09-28T13:05:44.000Z",
"wordCount": 1284,
"lineCount": 96,
"aliases": ["Harbour"],
"color": null
}
],
"nextCursor": null,
"pending": []
}Read a document
GET/v1/agent/c/{identifier}/documents/{id}
One document. The body is not sent unless it is asked for: a long document can cost a reading agent its whole context to answer a question about one paragraph, and the outline and a range of lines answer most of those for a fraction of it.
Query
- includebody
- Send the body too.
- draftstring
- The draft to read from. The trunk when absent.
- atinteger
- A point in the trunk's history, by sequence number. Reads the document as it stood then, including one that has since been deleted. It reads history and changes nothing.
What comes back
- version.seqinteger
- The version read. Send it back as
expectedVersionwhen writing. - stats.sizeobject
lengthagainstmaxis how full the document is, in the unit the limit is enforced in.byteSizeis the stored size, for showing and for budgeting a download.
Refusals
- 404not_foundNo such document on that draft, or the agent has been excluded from it.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…?include=body" \
-H "Authorization: Bearer arb_live_…"{
"id": "0d9a4c5e-7b1f-4c7a-9a10-3f6b2f1c8e21",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"body": "The north quay was resurveyed in…",
"frontmatter": { "tags": ["survey"] },
"version": { "id": "…", "seq": 41, "contentHash": "sha256:9f2c…" },
"provenance": {
"concept": "coastal-atlas",
"draft": "main",
"isTrunk": true,
"approvalStatus": "approved",
"proposal": null,
"stack": null
},
"stats": {
"wordCount": 1284,
"backlinkCount": 7,
"outgoingLinkCount": 12,
"size": { "length": 8420, "max": 512000, "byteSize": 8466 },
"lineCount": 96
},
"attachments": [
{
"id": "…",
"filename": "north-quay.png",
"mimeType": "image/png",
"byteSize": 482113,
"width": 1600,
"height": 900
}
],
"mentions": [],
"author": { "type": "USER", "id": "…", "name": "Ana Reyes", "username": "ana" },
"updatedAt": "2026-09-28T13:05:44.000Z"
}Without include=body, body is null and everything else is the same.
Read its outline
GET/v1/agent/c/{identifier}/documents/{id}/map
The shape of a document without its text: headings with the line each starts on, named blocks, comments, embeds and tags. The cheap way to decide what to read next.
What comes back
- headings[].lineinteger
- Where the section starts. Pass it to the next endpoint.
- headings[].pathstring
- The heading with its ancestors, which is what an edit at a heading is aimed with.
- blocks[].blockIdstring
- A block that has been given a name, with the lines it spans.
curl https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/map \
-H "Authorization: Bearer arb_live_…"{
"id": "0d9a4c5e-7b1f-4c7a-9a10-3f6b2f1c8e21",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"headings": [
{ "ordinal": 0, "level": 1, "text": "Harbour survey", "slug": "harbour-survey", "path": "Harbour survey", "line": 1 },
{ "ordinal": 1, "level": 2, "text": "North quay", "slug": "north-quay", "path": "Harbour survey/North quay", "line": 14 },
{ "ordinal": 2, "level": 2, "text": "Tides", "slug": "tides", "path": "Harbour survey/Tides", "line": 58 }
],
"blocks": [
{ "blockId": "berth-table", "kind": "table", "lineStart": 22, "lineEnd": 31, "text": "| Berth | Depth |…" }
],
"comments": [],
"embeds": [],
"tags": ["survey"],
"frontmatter": { "tags": ["survey"] },
"wordCount": 1284,
"provenance": { "draft": "main", "isTrunk": true, "approvalStatus": "approved", … }
}Read part of it, by line
GET/v1/agent/c/{identifier}/documents/{id}/lines
A range of lines. The other half of the outline: the outline says a section starts at line 14, and this reads it.
Query
- frominteger · from 1
- The first line. 1 when absent.
- tointeger
- The last line. 200 lines from the start when absent, so a bare call reads the first page.
- draftstring
- The draft to read from.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/lines?from=14&to=57" \
-H "Authorization: Bearer arb_live_…"{
"id": "0d9a4c5e-7b1f-4c7a-9a10-3f6b2f1c8e21",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"lines": [
"## North quay",
"",
"The north quay was resurveyed in October…"
],
"from": 14,
"to": 57,
"lineCount": 96,
"hasMore": true,
"version": { "id": "…", "seq": 41, "contentHash": "sha256:9f2c…" },
"provenance": { "draft": "main", "isTrunk": true, "approvalStatus": "approved", … }
}A partial read is still a read. It says whether what it returned was reviewed.
List what links to it
GET/v1/agent/c/{identifier}/documents/{id}/backlinks
Every link in the draft that points at this document, with the document it is in and where. Links from documents the agent cannot read are left out.
Query
- draft, limit, cursorstring, integer, string
- As on every paged list. Up to 200 a page.
curl https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/backlinks \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"documentId": "5a1e0b77-…",
"path": "index.md",
"title": "Coastal atlas",
"link": {
"kind": "WIKILINK",
"raw": "[[Harbour survey]]",
"targetPath": null,
"targetName": null,
"targetHeading": null,
"targetBlock": null,
"displayText": null,
"inFrontmatter": false,
"resolvedDocumentId": "0d9a4c5e-…",
"position": { "line": 3, "column": 12, "offset": 84 }
},
"context": "[[Harbour survey]]"
}
],
"nextCursor": null
}List where it is named but not linked
GET/v1/agent/c/{identifier}/documents/{id}/mentions
Places where this document's name or one of its aliases appears in plain text and is not a link. Each is a link somebody could make.
curl https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/mentions \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"documentId": "7c2f9e10-…",
"path": "field-notes/2026/tide-tables.md",
"title": "Tide tables",
"term": "Harbour",
"isAlias": true,
"context": "…measured against the Harbour datum…",
"line": 9
}
],
"nextCursor": null
}List its versions
GET/v1/agent/c/{identifier}/documents/{id}/versions
Every version of a document, newest first, with who wrote it, the message they left and which lines changed. Nothing is ever overwritten: each save is a new version.
What a row tells you
- lineChangeobject · null
- Where the change was and how many lines it added and removed. Null on a first version and on a move.
- isDeletionboolean
- The version that removed the document. Read what it held with
?at=.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/versions?limit=20" \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"id": "…",
"seq": 41,
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"contentHash": "sha256:9f2c…",
"byteSize": 8466,
"wordCount": 1284,
"lineCount": 96,
"lineChange": { "startLine": 22, "endLine": 31, "linesAdded": 4, "linesRemoved": 2 },
"isDeletion": false,
"message": "Remeasure berths 3 and 4",
"author": { "type": "USER", "id": "…", "name": "Ana Reyes", "username": "ana" },
"createdAt": "2026-09-28T13:05:44.000Z",
"mergedByProposal": null
}
],
"nextCursor": "41"
}Writing
Every write lands on the draft named in ?draft= and makes a new version there. Each takes an optional message of up to 1,000 characters, which is what a reviewer reads beside the change.
Each answers with the document as it now is, plus lineChange, and anomalies: syntax that did not close and was read as plain text. The document is saved either way.
Write a document at a path
POST/v1/agent/c/{identifier}/documents
Writes the document at a path. If nothing is there it is created. If something is, this writes a new version of it. It is addressed by path, so it does both.
Body
- pathstring · up to 1,024 · required
- Relative to the Concept, with forward slashes. No leading slash and no
... - bodystring · up to 500 KB · required
- The Markdown, without its properties.
- frontmatterobject
- The document's properties, as keys and values.
- messagestring · up to 1,000
- What this change does.
Refusals
- 403direct_entry_disabledNo draft was named, so the write was aimed at the trunk. The problem's nextAction is the request that creates a draft.
- 402plan_limitThe document is larger than the Concept's plan allows, or the Concept holds as many documents as it may.
- 422unparseable_documentThe properties could not be read. The errors list gives the line and column.
- 409path_reservedA recording in progress will become the document at that path.
- 409archivedThe Concept is archived and read-only.
curl -X POST \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"path": "field-notes/2026/quay-depths.md",
"body": "# Quay depths\n\nMeasured at low water…",
"frontmatter": { "tags": ["survey"] },
"message": "Add the October depth readings"
}'{
"id": "e7d6c5b4-…",
"path": "field-notes/2026/quay-depths.md",
"title": "Quay depths",
"body": null,
"version": { "id": "…", "seq": 1, "contentHash": "sha256:71be…" },
"provenance": {
"concept": "coastal-atlas",
"draft": "scribe/quay-update",
"isTrunk": false,
"approvalStatus": "unapproved",
"proposal": null,
"stack": null
},
"lineChange": null,
"anomalies": [],
"anomalyCount": 0,
…
}Replace a document
PUT/v1/agent/c/{identifier}/documents/{id}
Replaces the whole body of a document, by id. Its path stays where it is.
Body
- bodystring · up to 500 KB · required
- The new Markdown.
- frontmatterobject
- The new properties. Left as they were when absent.
- expectedVersioninteger
- The
version.seqthat was read. - messagestring · up to 1,000
- What this change does.
Refusals
- 409version_conflictThe document has changed since the version named. Read it again and decide.
- 404not_foundNo such document on that draft.
curl -X PUT \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"body": "# Harbour survey\n\nThe north quay was resurveyed…",
"expectedVersion": 41,
"message": "Rewrite the north quay section"
}'{
"id": "0d9a4c5e-…",
"path": "field-notes/2026/harbour-survey.md",
"version": { "id": "…", "seq": 42, "contentHash": "sha256:0c4d…" },
"provenance": { "draft": "scribe/quay-update", "isTrunk": false, "approvalStatus": "unapproved", … },
"lineChange": { "startLine": 14, "endLine": 40, "linesAdded": 9, "linesRemoved": 6 },
"anomalies": [],
"anomalyCount": 0,
…
}Edit one part of it
POST/v1/agent/c/{identifier}/documents/{id}/patch
Changes one part of a document and leaves the rest alone. The safest way to write: the agent never holds, and so never mangles, text it did not mean to touch.
Body
- target.kindheading · block · frontmatter · lines · required
- What is aimed at. A heading and a block take a
value: the heading's text or path, or the block's name. A property takes its key. Lines takefromandto. - operationreplace · append · prepend · remove · required
- What to do there.
- contentstring · up to 1 MB
- The text, for a heading, a block or lines.
- valuestring · number · boolean · null · array
- The new value, for a property.
- expectedVersioninteger
- Optional, except on lines.
expectedVersion. A heading is found again by its text and either is there or is not. A line number means something only against one version, and would otherwise overwrite whatever has moved into its place.Refusals
- 422not_foundNo heading, block or property of that name in the document.
- 422validation_failedThe operation does not fit the target, or lines were aimed at without expectedVersion.
- 409version_conflictThe document has changed since the version named.
curl -X POST \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/patch?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"target": { "kind": "heading", "value": "Harbour survey/North quay" },
"operation": "append",
"content": "Berth 4 now reads 6.2 m at low water.",
"message": "Record the berth 4 depth"
}'{
"id": "0d9a4c5e-…",
"version": { "id": "…", "seq": 43, "contentHash": "sha256:5aa1…" },
"lineChange": { "startLine": 57, "endLine": 58, "linesAdded": 1, "linesRemoved": 0 },
…
}Add to the end, or the start
POST/v1/agent/c/{identifier}/documents/{id}/append
Adds text to the end of a document without sending the rest of it. /prepend adds to the start. A line break is put between the old text and the new when one is missing.
These two exist on this surface only. A person in an editor already holds the whole text. An agent adding one line to a long log should not have to carry the log there and back.
Body
- contentstring · up to 1 MB · required
- The text to add.
- expectedVersioninteger
- The version read, if it matters that nothing changed.
- messagestring · up to 1,000
- What this change does.
curl -X POST \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/append?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"content": "- 9 October: berth 4 remeasured.",
"message": "Log the October visit"
}'{
"id": "0d9a4c5e-…",
"version": { "id": "…", "seq": 44, "contentHash": "sha256:be07…" },
"lineChange": { "startLine": 97, "endLine": 97, "linesAdded": 1, "linesRemoved": 0 },
…
}POST …/prepend takes the same body and adds to the start.
Move or rename
POST/v1/agent/c/{identifier}/documents/{id}/move
Gives a document a new path. Its id, its history and its versions come with it.
A move can break the links that pointed at the old path. affected says how many, in how many documents, and the token is an offer to rewrite them. Redeem it with POST /c/{identifier}/links/rewrite before it expires.
Body
- pathstring · up to 1,024 · required
- The new path.
- expectedVersioninteger
- The version read.
- messagestring · up to 1,000
- Why it moved.
Refusals
- 409path_takenA document is already at that path.
- 409version_conflictThe document has changed since the version named.
curl -X POST \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/move?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{ "path": "surveys/harbour-2026.md" }'{
"document": {
"id": "0d9a4c5e-…",
"path": "surveys/harbour-2026.md",
"title": "Harbour survey",
…
},
"affected": { "documents": 3, "links": 7 },
"updateToken": "f1e2d3c4-b5a6-4978-8a69-7b5c4d3e2f10",
"expiresAt": "2026-10-09T12:40:00.000Z"
}updateToken is an offer to mend the links. It is absent when the move broke none.
Delete a document
DELETE/v1/agent/c/{identifier}/documents/{id}
Removes a document from a draft. On a draft, a removal is a change like any other. It reaches the trunk only when a proposal carrying it is reviewed and published, and until then the document is still there for everybody else.
?at=.curl -X DELETE \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…"{ "ok": true }