Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

iMessage API for Search, Replies, and Agent Actions

Apple has no public iMessage API. Figment routes around that gap with a user-approved connection to a signed-in Mac, then exposes a REST interface your app calls like any other HTTP service. The user grants access through Figment's hosted consent screen; your app receives a scoped bearer token—not an Apple password. Reading history, searching conversations, and sending replies live behind exactly two scopes: messages:read and messages:send.

Why a Mac Is Required

iMessage operates on a device-local basis: message history is accessible on a Mac signed in to your Apple Account. Figment runs a signed-in isolated Mac that hosts the native Messages app; your application calls a REST API, and the Mac executes the Apple-native operations against its local message store.

A service can be in any of these states: connecting, needs_approval, syncing, ready, offline, reconnect_required, or revoked. ready is required for the service to accept read and send operations; non-ready services reject requests outright. A ready state does not prove Automation permission, guaranteed delivery, complete history, or continuous uptime. The status endpoint enforces authorization as well—it may return a non-2xx response for a revoked or non-ready connection rather than a diagnostic state object. Use figment.so/apple-connect/account to monitor and manage connection setup.

Register and Connect

Register your application at figment.so/apple-connect/developers to receive a client_id. Authorization uses PKCE with S256; generate a fresh verifier and state for each attempt:

GET https://figment.so/apple-connect/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.example.com/callback
  &response_type=code
  &scope=messages%3Aread%20messages%3Asend
  &state=OPAQUE_RANDOM_VALUE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

The redirect_uri is the exact HTTPS URL you registered; its path is not fixed by Figment. When redirected back, validate that the returned state matches the value you sent before proceeding to the token exchange.

Figment's consent screen lets the user select an existing Mac connection and approve the requested scopes. When messages:send is in scope, the user selects which existing conversations your app may send into—that selection is recorded in the grant and enforced on every send. Figment issues the grant and token; this is not an Apple-issued authorization redirect.

Exchange the authorization code for tokens:

POST https://figment.so/api/apple-connect/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fyour-app.example.com%2Fcallback&code=AUTHORIZATION_CODE&code_verifier=YOUR_CODE_VERIFIER

The response includes access_token, refresh_token, expires_in (900 s for the access token), scope, and connection_id. Tokens are opaque strings, not JWTs. Store connection_id alongside your tokens; refresh using grant_type=refresh_token, client_id, and refresh_token as form fields, and store the new refresh token returned by each successful refresh—refresh tokens rotate per grant; serialize refresh calls per grant.

All paths below are relative to BASE_URL = https://figment.so/api/apple-connect.

Reading Conversations

Both history and search require messages:read. Confirm the connection is ready before issuing either.

Check connection status:

GET /v1/connections/{connection_id}
Authorization: Bearer ACCESS_TOKEN
{
  "connection": {
    "id": "con_...",
    "mode": "live",
    "services": {
      "messages": { "state": "ready", "reason": null, "observedAt": 1794398400000 }
    }
  }
}

Each service entry is an object with state, reason, and observedAt (epoch milliseconds). Read connection.services.messages.state; only granted services appear. A ready state confirms the connection is accepted, but does not guarantee complete history—older messages may still be arriving from iCloud.

List open chats:

GET /v1/connections/{connection_id}/chats?limit=20&offset=0
Authorization: Bearer ACCESS_TOKEN

Responses use {data, mode, offset, nextOffset, hasMore, historyComplete, scope}. Follow hasMore and nextOffset to paginate; nextOffset is null when exhausted. Use each chat item's guid as chatGuid in history and search calls; displayName may be absent.

Pull messages from a thread:

URL-encode chatGuid from the chats response before placing it in a query string—a raw + decodes as a space:

GET /v1/connections/{connection_id}/messages?chatGuid=iMessage%3B-%3B%2B15551234567&limit=20
Authorization: Bearer ACCESS_TOKEN

Message items include guid, text, isFromMe, and chatGuid. Check historyComplete and hasMore rather than treating an empty page as a definitive absence of history.

Searching Message History

Full-text search runs against the locally available message store and shares messages:read—there is no separate search scope:

GET /v1/connections/{connection_id}/messages/search?query=invoice&chatGuid=iMessage%3B-%3B%2B15551234567&limit=20
Authorization: Bearer ACCESS_TOKEN

Coverage is bounded by history present on the Mac at query time. Accepted parameters: query, chatGuid, limit (default 20, max 100), offset (max 10 000). When the grant covers selected conversations, chatGuid is required. Encode chatGuid with a URL builder.

A chat appearing in search results is not automatically authorized for sends. The messages:send grant must already include that exact existing conversation from the user's original approval; a search result alone does not confer send access.

The Send Contract: Idempotency and Delivery States

The contract is two steps: submit the message, then poll the receipt with the same key.

Step 1 — Send:

POST /v1/connections/{connection_id}/messages/send
Authorization: Bearer ACCESS_TOKEN
Idempotency-Key: order-8821-shipped-notify
Content-Type: application/json

{"chatGuid": "iMessage;-;+15551234567", "text": "Your order has shipped. Tracking: 1Z999AA1."}

Generate and persist the Idempotency-Key before the first request; derive it from a stable intent identifier like an order ID plus action label. Do not use a body hash alone—two separately intended identical messages would share that hash and wrongly merge. A repeated request with the same key and body returns the same recorded action; a changed body is rejected.

Step 2 — Poll the receipt:

GET /v1/connections/{connection_id}/messages/receipt
Authorization: Bearer ACCESS_TOKEN
Idempotency-Key: order-8821-shipped-notify

Fresh send result (sandbox):

{
  "data": {
    "idempotencyKey": "order-8821-shipped-notify",
    "state": "simulated",
    "delivery": "simulated",
    "retrySend": false
  },
  "mode": "sandbox",
  "replayed": false
}

Re-polling the same receipt returns replayed: true. Receipt states and their meanings:

StateMeaning
submittedBridge accepted; delivery in progress
deliveredConfirmed delivered to recipient's device
readRecipient opened the message
failedSend failed; same key returns the recorded failure, not a new attempt
unknownOutcome could not be determined
simulatedSandbox result; no real recipient contacted

A submitted state is not proof of delivery. A failed receipt is preserved as-is; decide independently whether to initiate a fresh action under a new key. An unknown outcome means the system could not confirm whether the message was sent: hold the original key, wait, and re-poll. Never resend an unknown outcome under a new key.

Agents and Automation

An AI agent uses the same REST endpoints with a stored bearer token. Surface context with GET /messages/search, produce a reply, and submit with POST /messages/send. Idempotency rules apply—unknown outcomes must not trigger a blind retry. Confirm the chatGuid is in the granted conversation set before sending.

To use these operations in an MCP-compatible workflow, build a local adapter over the Figment REST API; there is no published remote MCP endpoint.

SMS, MMS, and RCS

Apple's forwarding guide explains that Messages in iCloud includes SMS, MMS, RCS, and iMessage—forwarding is built in when Messages in iCloud is enabled on iPhone. Previously synced carrier history remains available on the Mac when the iPhone is offline; phone availability affects only new carrier messages sent or received. If your use case covers mixed threads, confirm the iPhone is set up with Messages in iCloud before building the read path.

Pricing

Sandbox access is free. Developer-paid live connections cost $29 per month per active Apple connection. The initial checkout covers one connection; access starts after setup. Mac rental is a separate product line.


Start in sandbox first at figment.so/apple-connect/developers. Set up a live connection when you are ready to work with your own messages.

Manage an existing grant at figment.so/apple-connect/account.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms