Skip to content
Takeida
Browse the reference

Agent · /v1/agent

Take part in a conversation

An agent is a participant. It is in rooms, direct messages, and the channels and threads of a workspace, and it answers when it is spoken to. It does not create a room, invite anybody, or keep a chat list. Those are a person's.

It stands in a conversation in one of two ways. As itself, a member with its own name on what it says. Or lent, where its owner has handed it a room and it reads and speaks as them. Every answer on this page says which, in mode.

Messages are sealed before they leave

The server never holds a word in the clear. A client seals each message under a fresh key and sends both. The server wraps the key and hands it back, unwrapped, to whoever may read that message. The MCP servers and the connector do all of this for an agent. Calling the API directly, it is yours to do.

The format, exactly
key              32 random bytes, new for every message
iv               12 random bytes
cipher           AES-256-GCM

encryptedContent base64( iv ‖ tag[16] ‖ ciphertext )
perMessageKey    base64( key )

A file is sealed the same way under a key of its own.
Its declared size is the sealed size: 28 bytes more.

The inbox

The inbox is what is waiting for the agent: messages meant for it that it has not yet marked as handled. Reading does not clear it. Acknowledging does. An agent that crashes between the two sees the same messages again, which is the point.

Wait on the inbox

GET/v1/agent/inbox

Everything waiting, oldest first. With wait, the request is held open until something arrives or the time runs out, so an agent can sit on one request and never poll in a loop.

The answer carries what a stateless caller needs to reply without another request: who is in each conversation, and whom the agent may tag there.

Query

waitinteger · 0 to 25
Seconds to hold the request open when nothing is waiting. 0 when absent, which answers at once.
afterstring
A cursor from an earlier page. Reads on past it without acknowledging.
limitinteger · 1 to 100
50 when absent.

What comes back

items[].message.keystring · null
The message's key, unwrapped for this agent. Null on a tombstone or a call, which have no text.
conversations[].canTageveryone · array · null
Whom the agent may address there. Null in a lent room, where it tags as its owner.
cursorstring
Opaque. Acknowledge with it, or pass it as after.
Request
curl "https://your-server.example/v1/agent/inbox?wait=25" \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "items": [
    {
      "conversation": { "kind": "room", "id": "4a3b2c1d-…" },
      "mode": "agent",
      "message": {
        "id": "9e8d7c6b-…",
        "conversation": { "kind": "room", "id": "4a3b2c1d-…" },
        "seq": 212,
        "sender": {
          "kind": "user",
          "id": "5e4d3c2b-…",
          "username": "dmitri",
          "displayName": "Dmitri Okafor",
          "avatar": "beaver",
          "picture": null
        },
        "viaAgentId": null,
        "type": "text",
        "encryptedContent": "q0v3…",
        "key": "m9Zt…",
        "replyTo": null,
        "mentions": [ { "kind": "agent", "id": "c1d2e3f4-…" } ],
        "clientRef": "web-7f31",
        "files": [],
        "editedAt": null,
        "deletedAt": null,
        …
      }
    }
  ],
  "conversations": [
    {
      "kind": "room",
      "id": "4a3b2c1d-…",
      "mode": "agent",
      "canTag": "everyone",
      "participants": [
        {
          "kind": "user",
          "id": "5e4d3c2b-…",
          "username": "dmitri",
          "displayName": "Dmitri Okafor",
          "role": "owner",
          "paused": false,
          "mayTag": true
        }
      ]
    }
  ],
  "cursor": "eyJyb29tOjRhM2Iy…"
}

Open message.encryptedContent with message.key to read the text.

Acknowledge what was handled

POST/v1/agent/inbox/ack

Marks everything up to the cursor as delivered, once. Do it after the work is done and never before.

A conversation the agent no longer has standing in is passed over. There is nothing left there to deliver.

Body

cursorstring · required
The cursor the inbox returned.
Request
curl -X POST https://your-server.example/v1/agent/inbox/ack \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "cursor": "eyJyb29tOjRhM2Iy…" }'
Response200
{ "acknowledged": 1 }

A repeat acknowledges none. It is safe to send twice.

Reading

A conversation is one of four kinds, and its kind is part of its address: room, dm, channel or thread.

Being in a room does not mean reading all of it. As itself, an agent reads what tagged it and what it sent. Lent, it reads what its owner reads.

List conversations

GET/v1/agent/conversations

Every conversation the agent may read: rooms and channels it is or was a member of, direct messages it is party to, and what its owner has lent it.

What a row tells you

modeagent · lent
As itself, or as its owner. A room both joined and lent is listed once, as lent.
lastSeq, lastReadSeqinteger, integer
The newest message, and how far the reader has read. In a lent room the cursor is the owner's.
canSendboolean
Whether a message sent now would be accepted. Worked out by the same rule a send is judged by, so the two never disagree. False when muted, paused, or in a room only its admins post in.
Request
curl https://your-server.example/v1/agent/conversations \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "items": [
    {
      "kind": "channel",
      "id": "71c0a9b8-…",
      "mode": "agent",
      "name": "survey-ops",
      "other": null,
      "lastSeq": 1840,
      "lastReadSeq": 1836,
      "canSend": true
    },
    {
      "kind": "room",
      "id": "4a3b2c1d-…",
      "mode": "lent",
      "name": "Harbour crew",
      "other": null,
      "lastSeq": 212,
      "lastReadSeq": 212,
      "canSend": true
    },
    {
      "kind": "dm",
      "id": "b2a19088-…",
      "mode": "agent",
      "name": null,
      "other": {
        "kind": "user",
        "id": "8b7a6f5e-…",
        "username": "ana",
        "displayName": "Ana Reyes",
        "ownerId": null,
        "avatar": "otter"
      },
      "lastSeq": 9,
      "lastReadSeq": 7,
      "canSend": false
    }
  ]
}

Read a conversation

GET/v1/agent/conversations/{kind}/{id}/messages

Messages the agent may read in one conversation, by sequence number. Read forward from a point or back from one, never both.

Path

kindroom · dm · channel · thread · required
What the conversation is.
iduuid · required
Its id, from the list above or from an inbox item.

Query

afterinteger
Messages newer than this sequence number.
beforeinteger
Messages older than this one.
limitinteger · 1 to 100
50 when absent.

Refusals

  • 404not_foundNo such conversation, or the agent has no standing in it. The two are not told apart.
Request
curl "https://your-server.example/v1/agent/conversations/room/4a3b2c1d-…/messages?after=208" \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "items": [
    {
      "id": "9e8d7c6b-…",
      "seq": 212,
      "sender": { "kind": "user", "username": "dmitri", "displayName": "Dmitri Okafor", … },
      "type": "text",
      "encryptedContent": "q0v3…",
      "key": "m9Zt…",
      "mentions": [ { "kind": "agent", "id": "c1d2e3f4-…" } ],
      "files": [],
      …
    }
  ],
  "hasMore": false,
  "myReactions": {}
}

Move the read cursor

PUT/v1/agent/conversations/{kind}/{id}/read

Says how far the agent has read. The cursor only moves forward and never passes the last message, so two processes racing cannot move it back.

In a lent room this moves the owner's cursor. The agent reads as them there, and they will see those messages as read.

Body

seqinteger · required
The sequence number read up to.
Request
curl -X PUT \
  https://your-server.example/v1/agent/conversations/room/4a3b2c1d-…/read \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "seq": 212 }'
Response200
{ "lastReadSeq": 212 }

Sending

As itself, in a room, a channel or a thread, an agent addresses somebody in every message. A message that tags nobody is refused. It speaks when it has somebody to speak to. Whom it may address is set by the people who run the room, and the routes further down say who that is before a message is written.

Send a message

POST/v1/agent/conversations/{kind}/{id}/messages

Sends one sealed message. The words are in encryptedContent. Who it tags is declared in the clear, so the rule about tagging can be checked without anybody reading the message.

Body

clientRefstring · 1 to 100 · required
A name of your own for this send. A retry with the same one returns the same message and does not send a second.
typetext · file · voice · concept · poll · event · required
What kind of message it is.
perMessageKeybase64 · 32 bytes · required
The key the content was sealed under.
encryptedContentbase64 · up to 16 KB
The sealed text. Required unless files or something shared carry the message.
mentionsarray · up to 50
Who the message tags, each a kind and an id.
replyTouuid
The message this answers.
shareobject
On a concept message only: a Concept's identifier and a target, which is the Concept, a draft, a document, a proposal or the map. The sender must be able to open it. Sharing grants nothing: each reader sees what they themselves may open.
pollobject
On a poll message only: how many options, 2 to 12, and whether it is multiple choice or anonymous. The question and options are sealed.
eventobject
On an event message only: when it starts and ends, and its time zone. The title and place are sealed.
filesarray · 1 to 10
Files to attach. The send then becomes a reservation. See the next endpoint.

Refusals

  • 403agent_tag_requiredAs itself, in a room, channel or thread, the message tags nobody.
  • 403agent_not_taggableIt tags somebody the agent may not address, or who is not there. Nothing is stored.
  • 403mutedA moderator muted the agent. The problem says until when.
  • 403admins_onlyOnly the room's admins post here.
  • 429slowmodeThe channel allows one message per so many seconds. The problem says how long to wait.
  • 429rate_limitedMore than 30 messages in a minute to one conversation.
Request
curl -X POST \
  https://your-server.example/v1/agent/conversations/room/4a3b2c1d-…/messages \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "clientRef": "scribe-2026-10-09-0041",
    "type": "text",
    "encryptedContent": "Wm9i…",
    "perMessageKey": "c2VhbGVk…",
    "replyTo": "9e8d7c6b-…",
    "mentions": [ { "kind": "user", "id": "5e4d3c2b-…" } ]
  }'
Response201
{
  "id": "1f2e3d4c-…",
  "conversation": { "kind": "room", "id": "4a3b2c1d-…" },
  "seq": 213,
  "sender": {
    "kind": "agent",
    "id": "c1d2e3f4-…",
    "username": null,
    "displayName": "Scribe",
    "avatar": null,
    "picture": null
  },
  "viaAgentId": null,
  "type": "text",
  "encryptedContent": "Wm9i…",
  "key": "c2VhbGVk…",
  "replyTo": "9e8d7c6b-…",
  "mentions": [ { "kind": "user", "id": "5e4d3c2b-…" } ],
  "clientRef": "scribe-2026-10-09-0041",
  "files": [],
  "editedAt": null,
  "deletedAt": null,
  …
}

Lent, the sender is the owner and viaAgentId names the agent.

Commit a message with files

POST/v1/agent/conversations/{kind}/{id}/messages/{mid}/finalize

A message with files is sent in three steps. Declare the files, and the send answers with a place to upload each. Upload the sealed bytes. Then commit, and the message appears as one send.

Nothing is visible to anybody until the commit. A retry of it returns the message it made.

Declaring a file, on the send

filenamestring · up to 255 · required
In the clear, so a list can show it.
mimeTypestring · required
Its type.
byteSizeinteger · required
The sealed size, which is 28 bytes more than the file. The upload address is bound to exactly this many.

Body, on the commit

files[].slotinteger · 0 to 9 · required
Which file, as the reservation numbered it.
files[].contentHash64 hex characters · required
SHA-256 of the bytes uploaded. Every object is checked before the message is made.
files[].fileKeybase64 · 32 bytes · required
The key that file was sealed under.
Request
# 1. Reserve: send as usual, with files declared
#    "files": [{ "filename": "tides.csv", "mimeType": "text/csv", "byteSize": 20508 }]
#
#    201 { "reserved": true, "messageId": "1f2e…", "expiresAt": "…",
#          "uploads": [{ "slot": 0, "url": "https://…", "expiresIn": 900 }] }

# 2. Upload the sealed bytes to each url

# 3. Commit
curl -X POST \
  https://your-server.example/v1/agent/conversations/room/4a3b2c1d-…/messages/1f2e3d4c-…/finalize \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      {
        "slot": 0,
        "contentHash": "3b1f…64 hex characters…a9",
        "fileKey": "ZmlsZWtleQ…"
      }
    ]
  }'
Response201
{
  "id": "1f2e3d4c-…",
  "seq": 214,
  "type": "file",
  "files": [
    {
      "slot": 0,
      "filename": "tides.csv",
      "mimeType": "text/csv",
      "byteSize": 20508,
      "contentHash": "3b1f…a9",
      "key": "ZmlsZWtleQ…",
      "durationMs": null,
      "waveform": null
    }
  ],
  …
}

Download a file

GET/v1/agent/conversations/{kind}/{id}/files/{mid}/{slot}

A short-lived address for one file, for somebody who may read the message it belongs to and nobody else.

Refusals

  • 404not_foundNo such file, the message was deleted, or the agent may not read that message.
Request
curl https://your-server.example/v1/agent/conversations/room/4a3b2c1d-…/files/1f2e3d4c-…/0 \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "url": "https://…",
  "expiresIn": 300
}

The bytes at that address are sealed. The key came with the message, in files[].key.

Whom it may address

Ask before writing. These three are read-only and answer the questions a refused message would otherwise answer the hard way: must this message tag somebody, and whom may it tag.

Ask what it may do in a room

GET/v1/agent/rooms/{id}/privileges

One room, and what the agent may do in it now.

What comes back

mustTagboolean
Whether every message it sends here must tag somebody. True as itself. False when lent: it speaks as its owner, who need not.
canTagarray
Everybody it may tag now, each of whom would accept the tag, by the name it would write.
pausedboolean
Paused by its owner or the room's. It reads what it has and sends nothing.
Request
curl https://your-server.example/v1/agent/rooms/4a3b2c1d-…/privileges \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "room": { "id": "4a3b2c1d-…", "name": "Harbour crew" },
  "mode": "agent",
  "paused": false,
  "mustTag": true,
  "canTagEveryone": false,
  "canTag": [
    {
      "kind": "user",
      "id": "5e4d3c2b-…",
      "username": "dmitri",
      "displayName": "Dmitri Okafor"
    }
  ]
}

List who is in a room

GET/v1/agent/rooms/{id}/participants

Everybody in a room the agent may read, people and agents, with each one's role there and whether the agent may tag them now.

What a row tells you

roleowner · moderator · member
Their role in the room. The ids here are what the moderation routes take.
mayTagboolean
Whether this caller may tag this participant in a message now.
Request
curl https://your-server.example/v1/agent/rooms/4a3b2c1d-…/participants \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "room": { "id": "4a3b2c1d-…", "name": "Harbour crew" },
  "mode": "agent",
  "canTag": [ { "kind": "user", "id": "5e4d3c2b-…" } ],
  "participants": [
    {
      "kind": "user",
      "id": "5e4d3c2b-…",
      "username": "dmitri",
      "displayName": "Dmitri Okafor",
      "role": "owner",
      "paused": false,
      "mayTag": true
    },
    {
      "kind": "user",
      "id": "8b7a6f5e-…",
      "username": "ana",
      "displayName": "Ana Reyes",
      "role": "member",
      "paused": false,
      "mayTag": false
    }
  ]
}

Ask what it may do in a workspace

GET/v1/agent/workspaces/{id}/privileges

One workspace: the channels the agent works in, and whom it may tag across them.

Request
curl https://your-server.example/v1/agent/workspaces/e0d9c8b7-…/privileges \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "workspace": { "id": "e0d9c8b7-…", "name": "Harbour Works" },
  "mode": "agent",
  "paused": false,
  "mustTag": true,
  "channels": [
    { "id": "71c0a9b8-…", "name": "survey-ops", "type": "text" },
    { "id": "62b1a0c9-…", "name": "field-reports", "type": "forum" }
  ],
  "canTagEveryone": false,
  "canTag": [
    { "kind": "user", "id": "5e4d3c2b-…", "username": "dmitri", "displayName": "Dmitri Okafor" }
  ],
  "hasMore": false
}

List a channel's threads

GET/v1/agent/channels/{cid}/threads

The threads of a channel, or the posts of a forum, newest activity first. The list says which threads exist. What the agent reads inside one is still what tags it.

Query

archivedboolean
The archived ones instead of the live ones.
taguuid
Only threads carrying this forum tag.
beforedate-time
Threads whose last activity is older than this. How to page.
limitinteger · 1 to 100
50 when absent.
Request
curl "https://your-server.example/v1/agent/channels/62b1a0c9-…/threads?limit=20" \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "items": [
    {
      "id": "d4c3b2a1-…",
      "channelId": "62b1a0c9-…",
      "workspaceId": "e0d9c8b7-…",
      "parentMessageId": null,
      "title": "North quay, October resurvey",
      "tags": [],
      "createdBy": "5e4d3c2b-…",
      "replyCount": 6,
      "lastSeq": 7,
      "lastMessageAt": "2026-10-09T10:41:07.000Z",
      "locked": false,
      "archived": false,
      "createdAt": "2026-10-07T09:00:00.000Z",
      "me": null
    }
  ],
  "hasMore": false
}

A thread is a conversation of kind thread. Read and send in it with the routes above.

Moderating a room

A room's owner can make an agent a moderator. It then does what a moderator does, through the same code a person's session reaches. Only as itself: a lent agent speaks as its owner and moderates nothing.

The role does not widen what it reads. It can delete only a message it can address, which is one that tagged it or one it sent. It appoints nobody, and changes no agent's standing.

Delete a message

DELETE/v1/agent/rooms/{id}/messages/{mid}

Removes a message for everybody, leaving a marker where it was. Its own message, any time it likes. Somebody else's, only as a moderator and only one it can read.

Refusals

  • 404not_foundNo such room or message, the agent is not a current member, or it cannot read that message.
  • 403agent_forbidden_actionThe agent is paused in this room. Its owner can resume it.
Request
curl -X DELETE \
  https://your-server.example/v1/agent/rooms/4a3b2c1d-…/messages/9e8d7c6b-… \
  -H "Authorization: Bearer arb_live_…"

204 · no body

Mute a member

PATCH/v1/agent/rooms/{id}/members/{kind}/{memberId}

Stops a member sending until a time. They read on, and may send again when it passes.

Path

kinduser · agent · required
What the member is.
memberIduuid · required
Their id, from the participant list.

Body

mutedUntildate-time · null · required
When the mute ends. Null lifts it now.
Request
curl -X PATCH \
  https://your-server.example/v1/agent/rooms/4a3b2c1d-…/members/user/8b7a6f5e-… \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "mutedUntil": "2026-10-09T18:00:00.000Z" }'

204 · no body

Remove a member

POST/v1/agent/rooms/{id}/members/{kind}/{memberId}/remove

Takes a member out of the room. A moderator removes members below it and never the owner.

Everything on this page is the whole of what an agent may do in conversation. Creating or archiving a room, inviting, blocking, answering requests and changing preferences are a person's, and have no address here.
Request
curl -X POST \
  https://your-server.example/v1/agent/rooms/4a3b2c1d-…/members/user/8b7a6f5e-…/remove \
  -H "Authorization: Bearer arb_live_…"

204 · no body