Skip to content

Docs

REST API reference

Send and read iMessages programmatically. Connect any app, CRM, or agent.

Base URL

https://automessage-api-tfsfyvwypq-uc.a.run.app

All endpoints below are relative to this host. HTTPS only.

Authentication

Every /v1/* request must carry your key in the X-API-KEY header (Authorization: Bearer <key> also works).

X-API-KEY: <your-api-key>

Keep it secret — anyone holding this key can read and send your iMessages. Manage or regenerate it in Settings inside the dashboard.

Errors, limits & conventions

  • Every success response is wrapped in a data envelope; every failure in an error envelope.
  • Identifiers: conversations, drafts, and contacts are addressed by their server-generated id (collision-proof). Messages are addressed by messageGUID — the stable iMessage UUID — always scoped to your account.
  • Case & naming: input is snake_case (phone_number, display_name, photo_url); responses are camelCase. API-created contacts are marked source=manual so the Mac connector never overwrites your edits.
  • Phone numbers are stored E.164 (+14155551234). The stored field spelled refrence is returned as reference by the API.

Error envelope example

{ "error": { "code": "unauthorized", "message": "Invalid API key." } }

Quickstart — send your first iMessage

curl -X POST \
  https://automessage-api-tfsfyvwypq-uc.a.run.app/v1/drafts \
  -H "X-API-KEY: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"to":"+14155551234","body":"Hello from the autoMessage API"}'

Sending is asynchronous: you create a draft, your Mac connector delivers it over iMessage, then the draft records the outcome.

Response

{
  "data": {
    "id": "aB3dE5…",
    "to": "+14155551234",
    "body": "Hello from the autoMessage API",
    "sent": false,
    "sendError": false,
    "sentMessage": null
  }
}

Prefer AI tools over raw HTTP?

The autoMessage MCP server gives Claude, Cursor, Codex, and any MCP client these same capabilities as native tools — install it in 30 seconds.

Conversations

Read-only. Threads are created by real iMessage activity.

GET/v1/conversationsList or search your conversations, newest activity first.

QueryPass at most one primary filter — participant (phone/email in the thread; matches 1:1 AND groups; loose formats like "(502) 544-2686" are normalized), name (case-insensitive display-name substring), handle (exact 1:1 recipient), group (true/false), or unread (true). All also accept activeSince / activeBefore (ISO-8601), limit (default 50, max 200), and cursor (from pagination.nextCursor). Combining primaries returns 400.

Response

{
  "data": [ {
  "id": "zjueBDrNP2490J6JPWnJ",
  "displayName": "+14155551234",
  "groupName": "",
  "participants": ["+14155551234"],
  "previewMessage": "See you at 2pm!",
  "unreadCount": 1,
  "lastActive": "2026-07-16T17:13:29.497Z",
  "contacts": [],
  "owner": "users/<your-uid>"
} ]
}
GET/v1/conversations/{id}Fetch a single conversation by its id.

Response

{ "data": {
  "id": "zjueBDrNP2490J6JPWnJ",
  "displayName": "+14155551234",
  "groupName": "",
  "participants": ["+14155551234"],
  "previewMessage": "See you at 2pm!",
  "unreadCount": 1,
  "lastActive": "2026-07-16T17:13:29.497Z",
  "contacts": [],
  "owner": "users/<your-uid>"
} }
GET/v1/conversations/{id}/messagesPaginated messages in a thread, with sender names resolved from your contacts.

Querylimit (default 50) · cursor = the sentDate of the last item from the previous page (ISO 8601). Response includes nextCursor when more pages exist.

Response

{
  "data": [ {
  "id": "0NQXTM0s4ZgJGqrv4qbK",
  "messageGUID": "8328F671-7D1D-B351-EF5E-B0963FD80FF1",
  "conversationGUID": "zjueBDrNP2490J6JPWnJ",
  "sender": "+14155551234",
  "senderName": "Jane Appleseed",
  "body": "See you at 2pm!",
  "type": "iMessage",
  "outbound": false,
  "read": true,
  "sentDate": "2026-07-16T17:13:29.497Z",
  "dateRead": null,
  "dateDelivered": null,
  "cacheHasAttachments": false,
  "attachments": [],
  "tapbacks": [],
  "replyToGUID": "",
  "unsent": false,
  "reference": ""
} ],
  "nextCursor": "2026-07-16T17:13:29.497Z"
}

Messages

Read-only. Individual messages are addressed by their iMessage GUID.

GET/v1/messages/{messageGUID}Look up any single message you own by its iMessage GUID.

Response

{ "data": {
  "id": "0NQXTM0s4ZgJGqrv4qbK",
  "messageGUID": "8328F671-7D1D-B351-EF5E-B0963FD80FF1",
  "conversationGUID": "zjueBDrNP2490J6JPWnJ",
  "sender": "+14155551234",
  "senderName": "Jane Appleseed",
  "body": "See you at 2pm!",
  "type": "iMessage",
  "outbound": false,
  "read": true,
  "sentDate": "2026-07-16T17:13:29.497Z",
  "dateRead": null,
  "dateDelivered": null,
  "cacheHasAttachments": false,
  "attachments": [],
  "tapbacks": [],
  "replyToGUID": "",
  "unsent": false,
  "reference": ""
} }

Drafts — sending iMessages

Create, list, poll, edit, and cancel outbound drafts.

POST/v1/draftsQueue an iMessage for delivery.

Request body

{
  "to": "+14155551234",
  "body": "Hello from the autoMessage API"
}

Response

{ "data": {
  "id": "aB3dE5fGh7…",
  "to": "+14155551234",
  "body": "Hello from the autoMessage API",
  "sent": false,
  "sendError": null,
  "draftDate": "2026-07-16T18:00:00.000Z",
  "sentDate": null,
  "sentMessage": null,
  "responseWebhook": null,
  "owner": "users/<your-uid>"
} }
GET/v1/draftsList your drafts, newest first.

Querylimit (default 200).

Response

{
  "data": [ {
  "id": "aB3dE5fGh7…",
  "to": "+14155551234",
  "body": "Hello from the autoMessage API",
  "sent": true,
  "sendError": null,
  "draftDate": "2026-07-16T18:00:00.000Z",
  "sentDate": "2026-07-16T18:00:07.412Z",
  "sentMessage": "conversations/zjueBD…/messages/0NQXTM…",
  "responseWebhook": null,
  "owner": "users/<your-uid>"
} ]
}
GET/v1/drafts/{id}Poll delivery status.

Response

{ "data": {
  "id": "aB3dE5fGh7…",
  "to": "+14155551234",
  "body": "Hello from the autoMessage API",
  "sent": true,
  "sendError": null,
  "draftDate": "2026-07-16T18:00:00.000Z",
  "sentDate": "2026-07-16T18:00:07.412Z",
  "sentMessage": "conversations/zjueBD…/messages/0NQXTM…",
  "responseWebhook": null,
  "owner": "users/<your-uid>"
} }
PATCH/v1/drafts/{id}Edit a draft that has not sent yet.

Request body

{
  "body": "Updated message text"
}

Response

{ "data": {
  "id": "aB3dE5fGh7…",
  "to": "+14155551234",
  "body": "Hello from the autoMessage API",
  "sent": false,
  "sendError": null,
  "draftDate": "2026-07-16T18:00:00.000Z",
  "sentDate": null,
  "sentMessage": null,
  "responseWebhook": null,
  "owner": "users/<your-uid>"
} }
DELETE/v1/drafts/{id}Cancel an unsent draft (soft-cancel — the record is kept, sendError becomes "cancelled").

Response

{
  "data": {
    "id": "aB3dE5fGh7…",
    "sent": false,
    "sendError": "cancelled",
    "cancelledAt": "2026-07-16T18:02:11.000Z"
  }
}

Contacts

Manage your address book from code. API-created rows are marked source=manual.

GET/v1/contactsList your contacts.

Querylimit (default 200).

Response

{
  "data": [ {
  "id": "<your-uid>__+14155551234",
  "displayName": "Jane Appleseed",
  "phoneNumber": "+14155551234",
  "email": "jane@example.com",
  "photoURL": "",
  "userName": "",
  "url1": "",
  "source": "manual",
  "wouldSyncBack": false,
  "createdTime": "2026-07-16T18:00:00.000Z",
  "editedTime": null,
  "owner": "users/<your-uid>"
} ]
}
POST/v1/contactsCreate or update a contact (idempotent upsert per handle).

Request body

{
  "phone_number": "+14155551234",
  "display_name": "Jane Appleseed",
  "email": "jane@example.com",
  "photo_url": "https://example.com/jane.jpg"
}

Response

{ "data": {
  "id": "<your-uid>__+14155551234",
  "displayName": "Jane Appleseed",
  "phoneNumber": "+14155551234",
  "email": "jane@example.com",
  "photoURL": "",
  "userName": "",
  "url1": "",
  "source": "manual",
  "wouldSyncBack": false,
  "createdTime": "2026-07-16T18:00:00.000Z",
  "editedTime": null,
  "owner": "users/<your-uid>"
} }
GET/v1/contacts/{id}Fetch a single contact.

Response

{ "data": {
  "id": "<your-uid>__+14155551234",
  "displayName": "Jane Appleseed",
  "phoneNumber": "+14155551234",
  "email": "jane@example.com",
  "photoURL": "",
  "userName": "",
  "url1": "",
  "source": "manual",
  "wouldSyncBack": false,
  "createdTime": "2026-07-16T18:00:00.000Z",
  "editedTime": null,
  "owner": "users/<your-uid>"
} }
PATCH/v1/contacts/{id}Update fields on a contact (also forces source=manual).

Request body

{
  "display_name": "Jane A."
}

Response

{ "data": {
  "id": "<your-uid>__+14155551234",
  "displayName": "Jane Appleseed",
  "phoneNumber": "+14155551234",
  "email": "jane@example.com",
  "photoURL": "",
  "userName": "",
  "url1": "",
  "source": "manual",
  "wouldSyncBack": false,
  "createdTime": "2026-07-16T18:00:00.000Z",
  "editedTime": null,
  "owner": "users/<your-uid>"
} }
DELETE/v1/contacts/{id}Delete a contact.

Response

{ "data": { "id": "<your-uid>__+14155551234", "deleted": true } }

Response webhooks

Attach a responseWebhook to a draft and, after that draft sends, the subscription is registered on the matched conversation. Any future inbound message in that conversation posts a JSON payload to your URL. The URL must be a public HTTPS endpoint — private and internal addresses are rejected.

Verifying signatures

When you register a webhook, the response returns a meta.webhookSecret (prefixed whsec_) shown only once — store it. Every delivery carries an X-AutoMessage-Signature header of the form t=<timestamp>,v1=<hmac>. Recompute it as HMAC-SHA256(secret, "<timestamp>.<raw-body>") and compare in constant time; reject old timestamps to prevent replays.

import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const [t, v1] = header.split(',').map(p => p.split('=')[1]);
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
  const fresh = Math.abs(Date.now()/1000 - Number(t)) < 300; // 5 min
  return ok && fresh;
}
POST/v1/draftsDraft body with an attached responseWebhook.

Request body

{
  "to": "+14155551234",
  "body": "Hello from the autoMessage API",
  "responseWebhook": {
    "url": "https://your.app/hooks/imessage"
  }
}

Status

API health, your account info, and whether your Mac connector is online (the agent must be running to deliver messages).

GET/v1/statusHealth snapshot for API, account, and connector.

Response

{
  "data": {
    "api": { "service": "automessage-api", "version": "0.1.0", "healthy": true },
    "user": { "uid": "<your-uid>", "email": "you@example.com", "phone_number": "14155551234" },
    "connector": { "online": true }
  }
}

Schemas & machine-readable spec

Full OpenAPI 3.1 spec: /openapi.yaml. MCP install guide: /docs/mcp/.

Note the legacy POST /v1/messages endpoint is a deprecated alias for POST /v1/drafts. Still accepted; behaves identically. New integrations should use /v1/drafts.