Skip to content
Chesly API

Docs

The Chesly API: the CLI, the MCP server, REST, events, keys, limits and billing.

View as Markdown

Overview

The Chesly API gives your code and your agents the chats you've connected in the Chesly app: WhatsApp, iMessage, Instagram, Messenger, Telegram, Signal, Discord, Slack, LinkedIn, X, Google Messages, Google Chat, Reddit and email. It's hosted and always on, so there's nothing to run on your side.

There are four ways in. All of them use the same key and see the same chats.

Way in Where Best for
CLI npm install -g chesly Agents that run commands: Claude Code, Codex, Cursor, OpenClaw, Grok Bot, OpenAI Dots
MCP https://api.chesly.app/mcp Clients that speak MCP: the OpenAI API, n8n, Cursor, Claude Code
REST https://api.chesly.app/v1 Your own code
Events Webhooks, a live stream and a feed Acting on new messages as they arrive

What the API doesn't do:

  • Start new conversations. Sends go to chats you already have.
  • Answer message requests. You can read them, but a reply would accept the request, so the API won't send one.
  • Connect accounts. You do that once, in the Chesly app on iPhone, Android or Mac.

Quickstart

  1. Connect your chats in the Chesly app.
  2. Start the 7-day trial at web.chesly.app/api.
  3. Install the CLI and sign in. chesly login prints a link, and opens it in your browser when it can. Approve it, and the key goes to your keychain.
npm install -g chesly
chesly login
  1. See what's new across your chats:
chesly catchup

Or call the API yourself, with a key from the dashboard:

curl https://api.chesly.app/v1/catch_up \
  -H "Authorization: Bearer $CHESLY_API_KEY"

To hand all of this to an agent, copy its prompt from the agent setup page.

Keys

  • A key is ck_live_ followed by 48 characters. You see it once, when you make it.
  • Full access keys can read and send. Read-only keys can read, search and watch, and can't send or change anything.
  • Make keys in the dashboard at web.chesly.app/api, or with chesly login. You can have up to 100, on either plan. chesly keys list lists them, and chesly keys revoke revokes one.
  • Send a key as a bearer token: Authorization: Bearer ck_live_.... The CLI also reads CHESLY_API_KEY.
  • A revoked key stops working on its next request. The dashboard shows when each key was last used.

CLI

The CLI is built for agents first. Every command takes --json (JSON Lines for streams), nothing waits for input when there's no terminal, and exit codes are stable. chesly --help and each command's help are written so an agent can learn the tool from them.

You can name chats instead of passing IDs: chesly read "Mom". When a name fits more than one chat, the CLI lists the matches. With --json, that's an error that carries the candidates.

Command What it does
chesly login Sign in. It prints a link, opens it in your browser when it can, and waits while you approve. An agent whose tool shows a command's output only once it ends signs in in two steps: --no-wait prints the link and the code, then --wait waits for the approval. --key - stores a key you already have, read from standard input; never put a key on the command line. On servers and in CI, set CHESLY_API_KEY instead.
chesly logout Revokes the key it stored, so it stops working everywhere, and removes it from this computer. When it can't revoke it, it still signs out, and names the key that's still active.
chesly whoami The key in use: its name, scope, API version, limits and usage
chesly key The key in use, masked. --reveal prints all of it, for MCP clients.
chesly accounts Your connected accounts and their status
chesly chats Your chats, most recent first. --unread for unread ones, --account for one account.
chesly read <chat> A chat's latest messages
chesly search <query> Search your chats, or one with --chat
chesly catchup Unread chats with their latest messages, in one call
chesly send <chat> <text> Send a message in a chat you already have, its text on standard input (below). --reply-to answers one message, --file attaches a file, and --wait waits for its delivery status.
chesly watch Stream new events as they happen. --chat, --account, --types and --networks narrow it; --after resumes after an event.
chesly webhooks list your webhooks, create <url> one, delete <id>, rotate <id> its secret, test <id> it, and see its deliveries <id>
chesly keys list your keys, or revoke <key_id> one. New keys come from chesly login or the dashboard.
chesly usage Your plan, this period's calls and transcription minutes, and each account's send limits

Pass a message's text as - and a quoted heredoc. Inside double quotes the shell still expands $ and backticks, so "It costs $5" would arrive as "It costs "; the quotes around EOF keep the text exactly as written:

chesly send "Mom" - <<'EOF'
Landed safe! Calling you tonight
EOF

chats, catchup and search take --network <name> to keep to one network, and read and send take it to look a chat name up on that network. Email isn't in the default lists: add --network email, as in chesly catchup --network email.

Exit code Meaning
0 Done
1 An error
2 A usage error: a missing argument or an unknown option, or a request the API refused as invalid_request (400)
3 Not signed in: no key, a malformed one, or one the API refused (401)
4 Not found (404), or a chat name that fits more than one chat
5 Not final, come back to it: a limit (429), the API or a gateway briefly away (502, 503 or 504), a first attempt still running (409 idempotency_in_progress), or a send the network didn't confirm (delivery_unconfirmed: check the chat before you send it again). retry_after, when there is one, says when.
6 Payment required (402): no API plan, or one of its limits reached

In JSON mode, errors go to standard error as one line of problem details, with the same type links as the API's errors.

MCP

The MCP server is at https://api.chesly.app/mcp and speaks Streamable HTTP. Authenticate with your key as a bearer token. It works with Claude Code, the Claude Agent SDK, Codex, Cursor, OpenClaw, n8n and OpenAI's Responses and Agents APIs. Setup for each is on the agent setup page.

Tool What it does
list_accounts Your connected accounts and their status
list_chats Your chats, filtered by unread, a name or a network
catch_up Unread chats with their latest messages, on every network or one
read_chat The messages in a chat
search_messages Search every chat, one chat or one network
get_message One message, with its delivery status
send_message Send a message in a chat you already have, once per idempotency_key
mark_read Mark a chat as read
find_contact Find the people of your direct chats by name
transcribe_voice_note Turn a voice note into text

A read-only key sees 7 of them: no send_message, mark_read or transcribe_voice_note.

Tools answer with the REST API's JSON, with chat and sender names filled in. What people wrote stays inside each message's text, so your agent can tell a message apart from the tool's own words. Email stays out of the lists unless a tool's network is email. A refused call is a tool error whose text starts with its code, such as chat_not_found: …; the codes are on the errors page.

During each Chesly deploy, MCP calls can answer 502 for about 15 to 25 seconds. Retry a call that fails that way after a few seconds.

REST API

The base URL is https://api.chesly.app/v1. The OpenAPI 3.1 document is at api.chesly.app/v1/openapi.json, generated from the code that runs the API, and the REST reference lists every operation from it, with its parameters, answers and errors.

Resource Endpoints
You GET /v1/me: your key, plan, usage this period and limits
Accounts GET /v1/accounts
Chats GET /v1/chats, GET /v1/chats/{chat_id}, POST /v1/chats/{chat_id}/read
Messages GET /v1/chats/{chat_id}/messages, GET /v1/messages/{message_id}, GET /v1/messages/search?q=, GET /v1/messages?since=
Catch-up GET /v1/catch_up
Sending POST /v1/chats/{chat_id}/messages, and POST /v1/media for a file
Media GET /v1/media/{media_id}
Contacts GET /v1/contacts?q=: the people of your direct chats
Voice notes POST /v1/messages/{message_id}/transcript
Events GET /v1/events?after=, GET /v1/events/stream
Webhooks GET /v1/webhooks, POST /v1/webhooks, GET /v1/webhooks/{id}, PATCH /v1/webhooks/{id}, DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/rotate_secret, POST /v1/webhooks/{id}/test, GET /v1/webhooks/{id}/deliveries
Keys GET /v1/keys, DELETE /v1/keys/{key_id}

How it behaves:

  • IDs are opaque and prefixed: acct_, chat_, msg_, media_, evt_, wh_, dlv_ and contact_. Use them as they come: a message ID is long, because it carries its chat.
  • Lists page with cursors. Pass limit (1 to 100, 50 by default) and the next_cursor of the page before. GET /v1/messages?since= takes up to 200 a page.
  • Filters. GET /v1/chats takes unread, archived, account_id, network, q, sort and updated_since, and a chat's messages take before and after. Times are ISO 8601 with an offset, such as 2026-10-27T09:30:00Z. Unknown query parameters are refused, so a misspelt filter can't widen the answer.
  • Networks are named whatsapp, imessage, instagram, messenger, telegram, signal, discord, slack, linkedin, twitter (X), gmessages (Google Messages), googlechat, reddit and email.
  • Email stays out of chat lists, search, the feed, catch-up and events unless you ask for it with network=email (for events, networks with email in it), or name an email account's account_id. An email chat or message is always reachable by its ID. An email reaches the since feed (GET /v1/messages?since=) up to 10 seconds after it arrives.
  • Idempotency. Every POST takes an Idempotency-Key header: 1 to 255 characters from A-Za-z0-9._:-, such as a UUID (the quoted form "…" works too), kept for 24 hours. POST /v1/media ignores it, as an upload changes nothing on its own. Repeat a request with the same key and you get the first answer back, with Idempotent-Replayed: true. The same key with a different request is a 422, and while the first request is still running it's a 409 with Retry-After: 1. An answer of 429 or 5xx isn't kept, so a retry with the same key runs again, and a send still goes out only once.
  • Versions. Every answer carries Chesly-Version, the API version your key was made on: 2026-11-01. Changes within a version only add fields and endpoints.
  • Rate limits come back in the RateLimit and RateLimit-Policy headers.

Sending

A send goes to a chat you already have, with text, a reply_to message ID from the same chat, a media_id from POST /v1/media, or text and a file together. A message takes one file, up to 50 MB (50,000,000 bytes). An upload has 60 seconds, and must keep up 50 KB a second once its first 10 are over; a slower one is 408 upload_timeout. In a Google Messages RCS chat, which takes no captions, the text follows the file as a message of its own. It has no side effects beyond the message: no typing, no presence and no read receipt.

curl https://api.chesly.app/v1/chats/chat_x1R7KTsNtH88aWCJvUyo/messages \
  -H "Authorization: Bearer $CHESLY_API_KEY" \
  -H "Idempotency-Key: 8d4faeef-90bb-4b3a-8760-de2fde14c267" \
  -H "Content-Type: application/json" \
  -d '{"text": "Landed safe! Calling you tonight"}'

The answer, 201, is the message, with its delivery status queued:

{
  "id": "msg_x1R7KTsNtH88aWCJvUyo.JFp5TUlnekJnT3RVZ2RMdHgtbTZwLWNWWGtyazNSamdfRXVPOUNuR3dJdDg",
  "chat_id": "chat_x1R7KTsNtH88aWCJvUyo",
  "sender": { "name": "Sam Reyes", "is_me": true },
  "text": "Landed safe! Calling you tonight",
  "truncated": false,
  "type": "text",
  "media": null,
  "sent_at": "2026-10-27T09:52:31.204Z",
  "edited": false,
  "deleted": false,
  "reply_to": null,
  "delivery": {
    "status": "queued",
    "error": null,
    "updated_at": "2026-10-27T09:52:31.236Z"
  }
}

delivery.status moves on to sent, and to delivered only where the network reports it (iMessage does; elsewhere a send ends at sent), or to failed with a delivery.error whose code says why. network_rejected, not_logged_in, rate_limited_by_network and unsupported are certain (certain: true): the message didn't go out. timeout and unknown aren't (certain: false): the message may have gone out anyway, so check the chat before you send it again. If such a failure turns out certain later, a second message.delivery_failed for the same message follows, with certain: true. GET /v1/messages/{message_id} shows where a message is, and each failure is also a message.delivery_failed event. Messages you sent through the API in the last 30 days carry their delivery; every other message has delivery: null.

Events

Every change lands in one log, kept for 7 days on Personal and 30 on Pro, and you can take it three ways:

  • Webhooks: Chesly posts each event to your URL, signed under the Standard Webhooks spec and retried for about 28 hours. See Webhooks.
  • A live stream at GET /v1/events/stream, as server-sent events. Resume with Last-Event-ID; a ping every 15 seconds keeps it open. You can keep 10 open at once on Personal and 25 on Pro. chesly watch reads it.
  • A feed at GET /v1/events?after=, oldest first, for polling.

None of them count as calls. An event is { id, type, timestamp, data }, and the same event can arrive twice, so skip an id you've seen. Email events come only when you ask for them, with networks including email. Each type's data is on the events page.

A cursor older than your plan keeps events (after, or the stream's Last-Event-ID) answers 410 events_expired: start again without one, and catch up with GET /v1/catch_up. chesly watch does that by itself, and starts from new events with a warning.

Event When
message.received Someone sends you a message
message.sent You send a message, from anywhere
message.edited A message is edited
message.deleted A message is deleted
message.delivery_failed A message you sent through the API didn't go through
chat.created, chat.updated, chat.read A chat starts, changes or is read
account.connected, account.disconnected, account.needs_attention An account changes state
reaction.added, reaction.removed Someone reacts to a message

Limits

Each plan has its own:

Limit Personal Pro
Requests, per key and for all your keys together 15 a second, 900 a minute 30 a second, 1,800 a minute
Webhooks 25 100
Transcription 600 minutes a month 3,000 minutes a month
Live event streams 10 open at once 25 open at once
Event history 7 days 30 days

Both plans share the rest:

Limit Value
API keys Up to 100
Searches, catch-ups and since-feed pages 30 a minute each, per user
Sends on WhatsApp 60 an hour and 250 a day, per account
Sends on every other network, email included 30 an hour and 150 a day, per account
Text in one message Instagram 1,000, Discord 2,000, Telegram 4,096, email 100,000 and every other network 10,000 UTF-16 code units
Files One per message, up to 50 MB (50,000,000 bytes)
Transcripts 30 a minute, one at a time per user

Until billing starts, every key has Personal's limits, and transcription is capped at 60 minutes of audio a day per user instead, until midnight UTC.

Past a limit, you get a 429 with a Retry-After header. GET /v1/me shows where you stand, account by account. More keys keep agents apart; they never add requests.

Send limits exist because messages leave through shared servers: one script's burst can get a network to flag those servers, and every account on them. Automation can also get a LinkedIn account restricted. The limits sit above what a busy person sends by hand. If you need more, tell us.

Billing

  • Personal, $49 a month: 600 transcription minutes a month, 15 requests a second and 900 a minute, 25 webhooks, 10 live event streams, 7 days of event history and email support.
  • Pro, $199 a month: 3,000 transcription minutes a month, 30 requests a second and 1,800 a minute, 100 webhooks, 25 live event streams, 30 days of event history and priority email support.
  • Both have everything else: reads, sends, events, the live stream, webhooks, the CLI, MCP, every account you've connected and up to 100 API keys.
  • No meter. Calls are counted for the dashboard and GET /v1/me, never billed.
  • Transcription uses the plan's minutes. Past them, transcripts answer 402 transcription_limit_reached until the next month, and everything else keeps working. A note is checked before it's transcribed, so one note can run past the line.
  • The trial lasts 7 days on the plan you choose, with its limits and minutes, and needs a card. There's one trial.
  • Switching between Personal and Pro happens in the dashboard. An upgrade applies at once, prorated; a downgrade applies at the end of your billing month, with no credit. Until then, GET /v1/me shows it as plan.scheduled_change, and you can call it off in the dashboard. Switching down deletes nothing: past the new plan's limit, new webhooks are refused.
  • You buy a plan on the web at web.chesly.app/api, never in the Chesly apps. It's separate from Chesly Plus.

Errors

Every error is an RFC 9457 problem, with a stable code you can branch on:

{
  "type": "https://agents.chesly.app/docs/errors#rate_limited",
  "title": "Too many requests.",
  "status": 429,
  "detail": "This API key made more than 600 requests in one minute. Retry in 4 s.",
  "code": "rate_limited",
  "request_id": "571d003e-ad60-4a08-858a-b7b5353f39db",
  "retry_after": 4
}

type links to the code on the errors page, which lists every code the API and the CLI answer with, and what to do about each. Include the request_id when you write to us.

Give your agents
your messages.

Connect your chats in the Chesly app, paste one prompt into your agent, and ask what you missed.

7 days free, then from $49 a month.