Overview

Relaydeck is one service for AI agents and for the Apple app. An agent reports its work as tasks and asks the person questions. The app shows each change live, and sends answers, changes, and messages back to the agent.

These files are the contract between the server and its clients:

  1. Overview: the rules that all surfaces share.
  2. Quickstart: connect an agent.
  3. MCP: the tools of an agent.
  4. REST API: the objects and the routes.
  5. Events: the live event stream.
  6. Inbox: what the person and other agents send to an agent.
  7. Push: notifications, Live Activities, and widget reloads.
  8. App integrity: App Attest and the app code.
  9. Self-hosting: run your own server.

Surfaces#

Surface Path Client
MCP endpoint POST /mcp AI agents
REST API /v1/... The Apple app, and scripts
Event stream GET /v1/events The Apple app
Health GET /health Monitors

A change through one surface shows on all the others.

Concepts#

Entity ID prefix Meaning
Account acc_ One person. The first launch of the app makes it. It has no email and no password.
Device dev_ One iPhone, iPad, or Apple Watch of an account. It holds the push tokens. The client makes this ID.
Key key_ One API key that an agent uses. Each key has a label, for example Claude Code.
Task tsk_ One unit of work. Each task belongs to one key.
Question qst_ One question from an agent to the person. Each question belongs to one key.
Inbox item inb_ One thing that the person or a different agent sends to a key. See Inbox.
Activity entry act_ One request of a key, in the activity log of that key.

An agent sees only the tasks and the questions of its own key. The account sees those of all its keys.

Conventions#

  • Requests and responses are JSON with snake_case fields.
  • A timestamp is Unix seconds as a number with a fraction, for example 1760112000.412.
  • An ID is an opaque string. Do not parse it.
  • An unknown field in a request gives 422. Ignore unknown fields in a response, because new fields can come.

Authentication#

Send one credential in the Authorization header.

Credential Format Who has it Scope
Account token Bearer mca_<secret> The Apple app The full account
API key Bearer mck_<secret> Agents and scripts The tasks and the questions of that key
  • An API key is also accepted in the header X-API-Key: mck_<secret>.
  • The MCP endpoint also accepts ?key=mck_<secret>, for a client that cannot send headers. A URL can appear in logs, so use a header if you can.
  • The server keeps only the SHA-256 hash of a secret, and does not log it. It returns a secret one time, in the response that makes it.
  • A server can also ask the app for an app code. See App integrity.

Host name#

An agent can send the short host name of its machine with each request of an API key, on the MCP endpoint and on the REST API. The app then shows where each agent runs.

HTTP
X-Relaydeck-Host: studio-mac
  • The value is 1 to 64 printable ASCII characters, after the server removes the space at its two ends.
  • The server ignores a value that is not valid. Such a header never gives an error.
  • The server keeps the most recent host name as last_host of the Key, and puts the host name on each Task that the request makes and on each entry of the activity log.
  • A request with no header changes nothing: the key keeps its last_host.

Errors#

Each error has this body and a matching HTTP status.

JSON
{ "error": { "code": "not_found", "message": "Task not found." } }
Status code Meaning
400 bad_request The body is not valid JSON.
401 unauthorized The credential is missing or wrong.
401 app_code_invalid, attestation_required, attestation_invalid See App integrity.
403 forbidden The credential type cannot use this route.
404 not_found The resource does not exist, or it belongs to a different key or account. A deleted key or account gives this at once, also while the server still removes its data.
405 method_not_allowed
409 conflict A count limit is full, or the state of the resource does not permit the change.
413 bad_request The body is larger than 1 MB.
422 validation_failed error.fields maps each field name to a message.
429 rate_limited Wait for the seconds in the Retry-After header.
500 internal A defect in the server.
503 unavailable The server cannot reach its store. Wait for the seconds in Retry-After, then send the request again.

Rate limits#

Limit Default
Requests for each credential 600 in one minute
POST /v1/accounts for each IP address 20 in one hour
POST /v1/attest/challenge for each IP address 20 in one hour
notify_user calls for each key 30 in one hour
Messages from one sender 60 in one hour
Calls that wait at the same time, for each key 32 on one server process. See Wait for the answer.

The operator of a server can change the first three values.

Limits#

Item Limit
Keys for each account 20
Devices for each account 20
Open tasks (todo or in_progress) for each key 1,000
Closed tasks (done or cancelled) for each key 5,000. When one more closes, the oldest closed task is removed, and the event stream sends task.deleted.
Pending questions for each key 20
Task title 1 to 200 characters
Task description 0 to 20,000 characters, Markdown
Task status_note 0 to 140 characters
Key label 1 to 40 characters
notify_user title 1 to 100 characters
notify_user body 1 to 1,000 characters
Question prompt 1 to 300 characters
One question option 1 to 30 characters
Typed answer 1 to 1,000 characters
Question expires_in 60 to 86,400 seconds
Message text 1 to 2,000 characters
Unread inbox items for each key 200. The oldest item is removed first.
Activity log entries for each key 200. The oldest entry is removed first.
Host name 1 to 64 printable ASCII characters

A request above a count limit gets 409.

The server makes title, label, status_note, prompt, and each option one line: it removes the space at the two ends and changes each run of white space to one space. An empty status_note is stored as null. A typed answer loses only the space at its two ends.

Retention#

Data Deleted
A task with the status done or cancelled 90 days after its last update, or when the key has more than 5,000 closed tasks. The event stream sends task.deleted.
A question that is not pending 90 days after it closed
An inbox item 7 days if it is unread, 24 hours after it was read
An entry of the activity log 7 days after the request
All other data When the person deletes it

Health#

GET /health needs no credential. It returns 200 {"status":"ok","store":"ok"}, or 503 {"status":"unavailable","store":"unavailable"} if the store does not answer.

This page as Markdown