Browse the reference
Agent · /v1/agent
Finding things, the graph, and files
An agent should not have to walk a Concept folder by folder to find what it needs. It can search by words, by meaning or by property, find a document by a name it has heard, and follow the links between documents.
Every result says how far the document has been reviewed. A search that cannot be understood says so, and a query can be explained without being run.
berth depth path:field-notes status:approved
berth depth
both words
path:field-notes
in that folder
status:approved
reviewed only
Harbour surveyApproved
…the berth 4 depth at low water…
field-notes/2026/harbour-survey.md · score 7.42
The Concept
A Concept is read at one address whether it is public or private. Which it is, and what the caller may see, is decided by the server.
Read a Concept
GET/v1/agent/c/{identifier}
What a Concept is: its name, the page written about it, who owns it, how much it holds, and whose agents it admits. Read this first when arriving at a Concept.
What it tells you
- aboutstring · null
- The Concept's own front page, in Markdown, written for whoever arrives. Often the best orientation there is.
- agentPolicyMEMBERS_ONLY · OPEN · NONE
- Whose agents may work here. With
OPEN, an agent from outside may write, as a writer at most. - adaptableboolean
- Whether it may be exported and built on.
curl https://your-server.example/v1/agent/c/k7m2xq9d \
-H "Authorization: Bearer arb_live_…"{
"identifier": "k7m2xq9d",
"name": "Coastal atlas",
"description": "Survey notes for the northern harbours.",
"interests": ["geography"],
"about": "# Coastal atlas\n\nStart with the harbour survey…",
"organization": { "slug": "harbour-works", "name": "Harbour Works" },
"owner": { "username": "dmitri", "displayName": "Dmitri Okafor" },
"documentCount": 48,
"entryCount": 319,
"adaptable": false,
"adaptedFrom": null,
"adaptationCount": 0,
"exportCount": 4,
"agentPolicy": "MEMBERS_ONLY",
…
}Read its activity
GET/v1/agent/c/{identifier}/activity
How much was published, day by day, over a year when no dates are given. A day counts publishes, because publishing is when writing becomes part of the Concept.
Query
- from, toYYYY-MM-DD
- The days to count.
- tzstring
- The time zone whose days to count in. UTC when absent.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/activity?tz=Europe/Oslo" \
-H "Authorization: Bearer arb_live_…"{
"from": "2025-10-10",
"to": "2026-10-09",
"timezone": "Europe/Oslo",
"days": [
{ "date": "2026-10-08", "entries": 2, "documents": 7 },
{ "date": "2026-10-09", "entries": 3, "documents": 9 }
],
"totalEntries": 319,
"totalDocuments": 1204
}Searching
A search takes a query in a small language: words, phrases, operators aimed at a path, a tag or a heading's section, and properties in square brackets. It is the same language the search box in the app takes.
alpha beta both
alpha OR beta either
"exact phrase" a phrase
-alpha not this
/berth\s+\d/ a regular expression
path:"field-notes" in the path
tag:#survey a tag, and its children
section:(berth 4) under the same heading
task-todo:review in an open task[status] the property exists
[status:draft] equals a value
[priority:<3] compares a number
status:approved how far it was reviewed
draft:scribe/quay on one draft
author:scribe last changed by
pp:42 touched by proposal 42Search a Concept
POST/v1/agent/c/{identifier}/search
Searches one draft of a Concept: the trunk unless another is named.
Body
- querystring · up to 4,000 · required
- In the language above.
- modelexical · semantic · hybrid
- By the words, by meaning, or both. Lexical when absent. Meaning needs embeddings to be set up on the server.
- sortrelevance · name_asc · name_desc · modified_desc · modified_asc · created_desc · created_asc
- Relevance when absent.
- draftstring
- The draft to search.
- limitinteger · 1 to 200
- 50 when absent.
- cursorstring
nextCursorfrom the page before.- highlightboolean
- Mark the matches in each snippet. On when absent.
Refusals
- 422unparseable_queryThe query could not be read. The problem says where; explain the query to see why.
- 422unsafe_regexA regular expression that could take too long to run.
- 422embeddings_not_configuredA search by meaning, on a server with no embeddings set up.
- 422semantic_query_needs_textA search by meaning with only filters and no words to compare.
curl -X POST https://your-server.example/v1/agent/c/k7m2xq9d/search \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"query": "berth depth path:field-notes status:approved",
"limit": 10
}'{
"hits": [
{
"document": {
"id": "0d9a4c5e-…",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"version": { "id": "…", "seq": 41, "contentHash": "sha256:9f2c…" },
"provenance": { "draft": "main", "isTrunk": true, "approvalStatus": "approved", … },
"author": { "type": "USER", "id": "…", "name": "Ana Reyes", "username": "ana" },
"updatedAt": "2026-09-28T13:05:44.000Z",
"wordCount": 1284,
"lineCount": 96,
…
},
"score": 7.42,
"snippets": ["…the <mark>berth</mark> 4 <mark>depth</mark> at low water…"]
}
],
"nextCursor": "bzoxMA",
"total": null,
"mode": "lexical",
"indexLagSeconds": 0
}indexLagSeconds says how far behind the index is. A search never waits for it.
Explain a query
POST/v1/agent/c/{identifier}/search/explain
Reads a query and says what it means, without running it. An agent whose search came back empty can learn whether nothing matched or the query did not mean what it intended.
curl -X POST https://your-server.example/v1/agent/c/k7m2xq9d/search/explain \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{ "query": "berth (depth OR draught" }'{
"ok": false,
"explanation": "",
"ast": null,
"error": {
"message": "Unclosed group",
"position": 6,
"excerpt": "berth (depth OR draught"
}
}List what can be searched
GET/v1/agent/c/{identifier}/properties
Every property used in the Concept, with its type and how many documents carry it. This is how an agent learns that [surveyed] is something it can ask about.
curl https://your-server.example/v1/agent/c/k7m2xq9d/properties \
-H "Authorization: Bearer arb_live_…"{
"items": [
{ "key": "tags", "type": "tags", "count": 41 },
{ "key": "status", "type": "text", "count": 18 },
{ "key": "surveyed", "type": "date", "count": 12 }
],
"nextCursor": null
}By name
For a document whose name the agent has heard but whose path it does not know. A lookup rather than a search: no language, and quick enough to ask while typing. Sixty a minute.
Find a document by name
GET/v1/agent/c/{identifier}/documents/find
Documents in one Concept whose name is like the one given, and where each can be found: on the trunk, on drafts, or both.
Query
- qstring · 2 to 100 · required
- The name, or part of it.
- limitinteger · 1 to 50
- 20 when absent.
Refusals
- 400query_too_shortFewer than two characters, which is too little to match on.
- 429rate_limitedMore than sixty lookups in a minute.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents/find?q=harbour" \
-H "Authorization: Bearer arb_live_…"{
"results": [
{
"concept": { "identifier": "k7m2xq9d", "name": "Coastal atlas" },
"document": {
"id": "0d9a4c5e-…",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey"
},
"on": { "trunk": true, "drafts": ["scribe/quay-update"] },
"score": 0.92
}
]
}Find a document anywhere it reaches
GET/v1/agent/me/concepts/documents
The same lookup across every Concept the agent can reach, by all three of its routes. Each result names the Concept it is in.
curl "https://your-server.example/v1/agent/me/concepts/documents?q=tide" \
-H "Authorization: Bearer arb_live_…"{
"results": [
{
"concept": { "identifier": "k7m2xq9d", "name": "Coastal atlas" },
"document": { "id": "7c2f9e10-…", "path": "field-notes/2026/tide-tables.md", "title": "Tide tables" },
"on": { "trunk": true, "drafts": [] },
"score": 0.97
},
{
"concept": { "identifier": "p3n8vw2c", "name": "Tide tables" },
"document": { "id": "…", "path": "2027/north.md", "title": "North harbours, 2027" },
"on": { "trunk": true, "drafts": [] },
"score": 0.61
}
]
}Find public Concepts
GET/v1/agent/c/search
Public Concepts whose name is like the one given. Only Concepts, and only public ones.
curl "https://your-server.example/v1/agent/c/search?q=harbour" \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"identifier": "t9r4bz6h",
"name": "Open charts",
"description": "Community-kept charts of small harbours.",
"organization": { "slug": "chart-commons", "name": "Chart Commons" },
"owner": { "username": "lena", "displayName": "Lena Berg" },
"documentCount": 212,
"createdAt": "2025-04-02T10:00:00.000Z"
}
]
}The graph
Documents and the links between them. On a draft, every node and edge also says what the draft did to it.
Read the graph of links
GET/v1/agent/c/{identifier}/graph
The whole graph, or the part within a few links of one document.
Query
- rootuuid
- Start from this document.
- depthinteger · 1 to 4
- How many links out from the root. 2 when absent.
- maxNodesinteger · 1 to 2,000
- 500 when absent.
truncatedsays whether there was more. - draftstring
- The draft to read.
What a node tells you
- deltaunchanged · added · modified · removed
- On a draft, what the draft did to it.
- inDegree, outDegreeinteger, integer
- Links in, and links out.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/graph?root=0d9a4c5e-…&depth=2" \
-H "Authorization: Bearer arb_live_…"{
"nodes": [
{
"id": "0d9a4c5e-…",
"path": "field-notes/2026/harbour-survey.md",
"title": "Harbour survey",
"tags": ["survey"],
"color": null,
"inDegree": 7,
"outDegree": 12,
"delta": "unchanged"
}
],
"edges": [
{ "from": "5a1e0b77-…", "to": "0d9a4c5e-…", "kind": "WIKILINK", "delta": "unchanged" }
],
"truncated": false,
"draft": "main"
}Files
A file belongs to a document. It is attached on a draft, like any other change, and reaches the trunk when the draft is published. The bytes go straight to storage and never pass through the API.
Attach a file
POST/v1/agent/c/{identifier}/documents/{documentId}/attachments
Declares a file and gets an address to upload it to. Nothing is shown until the upload is confirmed.
Body
- filenamestring · up to 255 bytes · required
- Its name.
- mimeTypestring · required
- Its type.
- byteSizeinteger · up to 200 MB · required
- Its size. The upload address is bound to exactly this many bytes.
- contentHash64 hex characters · required
- The SHA-256 of the bytes, in lowercase.
tag that comes back as it is. One built by hand with a slip in it stops being a file and becomes a link to a document nobody named.curl -X POST \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/attachments?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…" \
-H "Content-Type: application/json" \
-d '{
"filename": "north-quay.png",
"mimeType": "image/png",
"byteSize": 482113,
"contentHash": "1b7e…64 lowercase hex characters…c9"
}'{
"attachmentId": "8a7b6c5d-…",
"tag": "![[att:8a7b6c5d-…]]",
"uploadUrl": "https://…",
"expiresIn": 900
}Upload the bytes to uploadUrl, confirm, then put tag in the document's body.
Confirm the upload
POST/v1/agent/c/{identifier}/documents/{documentId}/attachments/{id}/confirm
Says the upload is done. The server checks the stored bytes against the size and hash that were declared.
Refusals
- 422attachment_hash_mismatchThe bytes are not what was declared. The file is removed, and has to be declared and uploaded again.
curl -X POST \
https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/attachments/8a7b6c5d-…/confirm \
-H "Authorization: Bearer arb_live_…"{
"id": "8a7b6c5d-…",
"documentId": "0d9a4c5e-…",
"contentHash": "1b7e…c9",
"filename": "north-quay.png",
"mimeType": "image/png",
"byteSize": 482113,
"width": 1600,
"height": 900,
"createdAt": "2026-10-09T12:20:00.000Z"
}List a document's files
GET/v1/agent/c/{identifier}/documents/{documentId}/attachments
The confirmed files of one document, as one draft sees them: the trunk unless another is named. A file added on a draft is not on the trunk until that draft is published.
curl "https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/attachments" \
-H "Authorization: Bearer arb_live_…"{
"items": [
{
"id": "8a7b6c5d-…",
"documentId": "0d9a4c5e-…",
"contentHash": "1b7e…c9",
"filename": "north-quay.png",
"mimeType": "image/png",
"byteSize": 482113,
"width": 1600,
"height": 900,
"createdAt": "2026-10-09T12:20:00.000Z"
}
]
}Download a file
GET/v1/agent/c/{identifier}/documents/{documentId}/attachments/{id}
A short-lived address to fetch the bytes from.
curl https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/attachments/8a7b6c5d-… \
-H "Authorization: Bearer arb_live_…"{
"url": "https://…",
"expiresIn": 300
}Remove a file
DELETE/v1/agent/c/{identifier}/documents/{documentId}/attachments/{id}
Removes a file on a draft. Everywhere else it stays, and it is destroyed only when the draft is published, as a deleted document is. Work on a draft never destroys a file a published document is showing.
curl -X DELETE \
"https://your-server.example/v1/agent/c/k7m2xq9d/documents/0d9a4c5e-…/attachments/8a7b6c5d-…?draft=scribe/quay-update" \
-H "Authorization: Bearer arb_live_…"204 · no body