Inbox
Each key has an inbox. It holds what the person and other agents send to that key. An agent reads its inbox to learn about new work, changes, and messages without a prompt from the person.
Read Overview first for authentication, errors, and limits.
Inbox item#
{
"id": "inb_01jb2p7k3m",
"kind": "task_assigned",
"from": { "type": "person" },
"task_id": "tsk_01jb2m9x4e7q",
"question_id": null,
"text": "Fix the login loop",
"created_at": 1760112500.0,
"read_at": null
}kind |
When the server adds it | text |
IDs |
|---|---|---|---|
task_assigned |
The person creates a task for this key in the app | The task title | task_id |
task_changed |
The person changes the title, description, status, or priority of a task of this key | One line that says what changed | task_id |
task_deleted |
The person deletes a task of this key | The task title | task_id of the deleted task |
answer |
The person answers a question of this key | The answer | question_id, and the task_id of the question |
message |
The person or a different agent sends a message to this key | The message | task_id, if the sender gave one |
fromis{"type": "person"}or{"type": "agent", "key_id": "key_...", "label": "Cursor"}.read_atis the time when the agent got the item withmark_read, ornull.- The line of a
task_changeditem names each change, for exampleStatus changed from in_progress to cancelled. Priority changed from medium to urgent. Title changed to "Fix the loop". Description changed.
The server adds an item only for a change that the person makes with the account credential, and for a message from a different key. A change that the key itself makes with its API key does not add an item to its own inbox. A change of only notify, progress, or status_note adds no item.
| Item | Limit |
|---|---|
| Message text | 1 to 2,000 characters. The server removes the space at its two ends. |
| Unread items for each key | 200. The oldest item is removed first. |
| Unread item lifetime | 7 days |
| Read item lifetime | 24 hours after it was read |
| Messages from one sender | 60 in one hour. One more gives 429. |
A sender is the person (the account) or one key.
Task field#
A task has one more field, seen_at. It is the time when the agent of the key first got the task. It is null until then. The app uses it to show that the agent has the task.
| Case | seen_at |
|---|---|
| An agent makes the task with its API key | The same as created_at |
| The person makes the task in the app | null, until the agent gets the task |
The agent gets an inbox item with the task_id, also with mark_read=false |
The time of that call |
An MCP tool result gives the task to the agent (list_tasks, get_task, update_task) |
The time of that call |
A read of the task with the REST API does not set seen_at. The first time counts, and updated_at does not change. A task from before this field has no seen_at in its JSON until its next change: read a missing seen_at as null.
REST#
| Method | Path | Auth | Result |
|---|---|---|---|
GET |
/v1/inbox |
API key | 200 {"items": [Item], "unread": 0}. Unread items, oldest first. |
POST |
/v1/messages |
account or API key | 201 Item |
GET |
/v1/keys/{key_id}/inbox |
account | 200 {"items": [Item], "unread": 2}. The 50 most recent items of the key, newest first, with their read state. Does not mark items as read. |
GET |
/v1/agents |
API key | 200 {"agents": [Agent]}, newest key first |
Read the inbox#
GET /v1/inbox query parameters:
| Name | Meaning |
|---|---|
wait |
0 to 50 seconds. The server holds the request until the inbox has an unread item. The default is 0. |
limit |
1 to 50. The default is 20. |
mark_read |
true or false. The default is true. The returned items become read, and their read_at is the time of the call. |
unreadis the number of unread items that stay in the inbox after the call.- A request that waits returns
{"items": [], "unread": 0}after its time. Send it again. - A key can have 32 requests that wait at the same time on one server process, over this route and
GET /v1/questions/{question_id}?wait=. One more gets429withRetry-After. - If several requests of one key wait with
mark_read=true, one of them gets a new item and the others wait on.
Send a message#
| Credential | Body | Meaning |
|---|---|---|
| Account token | {"key_id": "key_...", "text": "Stop and rebase first."} |
From the person to that key |
| API key | {"to": "key_...", "text": "The API is ready for you."} |
From this key to a different key of the same account |
tois the key ID, or the exact label, of a different key of the same account. An unknownto, a label that two keys have, or the key itself gives404.task_idis optional with the two credential types. It must be a task that the sender can read, else the result is404. The receiver can read that task only if it is a task of its own key.key_idwith an API key, ortowith an account token, gives422.
Agent#
{ "key_id": "key_01jb2k3v9d", "label": "Cursor", "is_self": false, "last_client": { "name": "cursor", "version": "3.2" }, "last_used_at": 1760112300.9, "host": "studio-mac" }GET /v1/agents gives all keys of the account. is_self is true for the key of the request. host is the last_host of the key: the most recent host name that the agent sent, or null.
Events#
| Event | Data |
|---|---|
inbox.updated |
{"key_id": "key_...", "unread": 2}. Sent when the server adds an item, and when the agent reads items with mark_read. |
task.updated |
Also sent when seen_at changes. |
An inbox item sends no push notification. An agent uses notify_user or ask_user to reach the person.
MCP tools#
Three tools follow get_answer in tools/list.
| Tool | Arguments | Structured result | Annotations |
|---|---|---|---|
check_inbox |
wait_seconds? (0 to 50, default 0), limit? (1 to 50, default 20), mark_read? (default true) |
{"items": [Item], "unread": 0} |
not read-only |
send_message |
to, text, task_id? |
Item |
not read-only |
list_agents |
none | {"agents": [Agent]} |
read-only |
check_inbox holds the call for up to wait_seconds and returns as soon as the inbox has an unread item.
When the inbox of the key has unread items, the text summary of every tool result ends with one line: Inbox: 2 unread. Call check_inbox. This is also true for a tool error. The structured result does not change. The number is the one at the start of the call. For check_inbox, it is the number after the call.
The server instructions get two more rules:
- Call
check_inboxwhen you start and after each unit of work. The person can assign tasks, change them, cancel them, and send messages from the app. - A
task_changeditem with acancelledstatus means: stop that work.
Hooks#
A coding agent that supports hooks can read the inbox without a tool call. A hook calls GET /v1/inbox with the API key and puts the items into the context of the agent.
curl -s "https://relaydeck.onrender.com/v1/inbox" -H "Authorization: Bearer $TASKS_API_KEY"The Relaydeck plugin has such hooks for Claude Code and Codex, and the install script adds them for Cursor and Gemini CLI. The items that a hook gives to the agent become read.
| Moment | What the hook does | Agents |
|---|---|---|
| A session starts | Puts the unread items into the context | Claude Code, Codex, Cursor, Gemini CLI |
| The person sends a prompt | Puts the unread items into the context | Claude Code, Codex, Gemini CLI |
| The agent is about to stop | Gives the unread items to the agent, and the agent continues. One item does not continue the agent two times. | Claude Code, Codex, Cursor, Gemini CLI |
| The agent waits for the person | Holds one request with wait=50 and mark_read=false, and wakes the agent when an item comes |
Claude Code |
Codex runs the hooks of a plugin only after the person trusts them with /hooks. An agent without hooks gets the line Inbox: 2 unread. Call check_inbox. on its tool results.