Reference
Conversations
A conversation is what a person sees: one thread with one handle, or a group. Distinct from the message list, which is a log.
- GET/v1/conversations/sync
- POST/v1/conversations/sync
- GET/v1/threads
- PUT/v1/threads/{chat_id}/pin
- GET/v1/threads/lines
- GET/v1/threads/unread
- POST/v1/read-receipts
- POST/v1/typing
- POST/v1/typing-indicators
Read conversation sync availability and the latest result.
Limited to Macs behind numbers this credential can access. The cooldown is shared per Mac, not per caller. Optional last_started_at, last_completed_at, last_count, and last_error describe the latest observed work.
curl -X GET https://api.missblue.dev/v1/conversations/sync \
-H "Authorization: Bearer $MISS_BLUE_KEY"Response
{ "limit_per_agent": 100, "cooldown_seconds": 300, "connected_agents": 1, "eligible_agents": 1, "syncing_agents": 0 }Queue a fresh conversation history sync from eligible Macs.
Requests up to 100 recent conversations per eligible Mac. A 202 means queued, not complete; poll GET /v1/conversations/sync for progress. Offline Macs, active syncs, and Macs in the five-minute cooldown may be skipped.
curl -X POST https://api.missblue.dev/v1/conversations/sync \
-H "Authorization: Bearer $MISS_BLUE_KEY"Response
{ "queued_agents": 1, "skipped_agents": 0 }Every conversation on your project's numbers, most recent first.
contact_is_typing is transient presence reported by the Mac and expires automatically. unread is always 0 for a key. A key is not a person and has no place in a conversation it has read up to, so reporting every inbound message ever would have read as a backlog nobody has. number_id and search are applied in the query, before the page is cut — filtering the returned page client-side would search the newest few hundred conversations and report that as the whole answer. link_code and url are each conversation's short link (url is https://missblue.dev/c/ and the code). It opens only for somebody who may read the conversation, so it is safe to pass on.
| sort | string | newest (default), oldest, or unreplied. |
| number_id | uuid | One of your lines, to see only what arrived on it. Use GET /v1/threads/lines for the ids and what each is carrying. |
| search | string | A handle, part of one, or a saved contact's name. Matches both, so somebody found by name does not have to be found again by number. |
curl -X GET https://api.missblue.dev/v1/threads \
-H "Authorization: Bearer $MISS_BLUE_KEY"Response
{
"total": 2,
"data": [
{
"chat_id": "iMessage;-;+15555550100",
"handle": "+15555550100",
"is_group": false,
"last_text": "Perfect, see you then",
"last_at": "2026-08-23T09:12:04Z",
"last_direction": "inbound",
"message_count": 14,
"unread": 0,
"contact_is_typing": true,
"last_replied_by": "Sam Okafor",
"link_code": "k7Qm3xT9pa",
"url": "https://missblue.dev/c/k7Qm3xT9pa"
}
]
}Pin or unpin a conversation for the signed-in member.
Member sessions only. Pins stay ahead of unpinned conversations under every sort and do not affect other members. Current number and organization access is required; a pin never grants access.
| agent_id* | string | The agent_id returned by the conversation list. |
| pinned* | boolean | true to pin; false to unpin. |
curl -X PUT https://api.missblue.dev/v1/threads/iMessage%3B-%3B%2B15555550100/pin \
-H "Authorization: Bearer $MISS_BLUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "d8f25b47-dda6-41ec-a303-4bdfb9d6953e",
"pinned": true
}'Each of your numbers, and what it is carrying right now.
The counts behind a by-number filter, asked as its own question rather than derived from the conversation list. The list is a page: counting what came back would report the lines present in the newest few hundred conversations and call it the total. unread is 0 for a key, for the same reason it is 0 in the conversation list.
| project_id | uuid | Narrow to one project. Omit for every project this credential reaches. |
curl -X GET https://api.missblue.dev/v1/threads/lines \
-H "Authorization: Bearer $MISS_BLUE_KEY"Response
{
"total": 2,
"data": [
{ "number_id": "9c1e4f0a-3d7b-4a21-9f38-7c2d5e6b8a14", "unread": 0, "conversations": 214 },
{ "number_id": "b47d2a90-5e13-4c8f-a6b2-19d3f7c04e85", "unread": 0, "conversations": 37 }
]
}Unread totals across everything this credential can see.
Every visible unread message, not the unread on one page of conversations. notification_unread counts only what a person has asked to be notified about, so a badge and an inbox count can differ honestly. Both are 0 for an API key, which has read nothing and has no place to have read up to.
| project_id | uuid | Narrow to one project. Omit for every project this credential reaches. |
curl -X GET https://api.missblue.dev/v1/threads/unread \
-H "Authorization: Bearer $MISS_BLUE_KEY"Response
{
"unread": 0,
"latest_unread_at": null,
"notification_unread": 0,
"latest_notification_at": null
}Tell the customer their message was seen.
The "Read" that appears under their message on their phone. Distinct from POST /v1/threads/{chat_id}/read, which records where a person has read to so their own unread count is right and which nobody outside this system sees. Nothing sends this automatically: it is a claim that a human looked, and a receipt that fired because a dashboard was open is a lie told at scale.
| chat_id* | string | The conversation to acknowledge. |
| read | boolean | Defaults to true. False marks it unread again. |
curl -X POST https://api.missblue.dev/v1/read-receipts \
-H "Authorization: Bearer $MISS_BLUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "iMessage;-;+15555550100",
"read": true
}'- 404No such conversation on a number your key holds.
- 409The number is temporarily unavailable (agent_offline). Nothing was acknowledged; retry shortly.
Show or hide the typing bubble in a conversation.
Do not block a reply on this. A bubble that arrives after the message it was meant to precede is worse than no bubble — send it, ignore the outcome, and reply. There is no guaranteed stop either: Messages.app clears it on its own after a few seconds of silence, which is the behaviour to rely on.
| chat_id* | string | The conversation. Not a bare handle — guessing which of several threads was meant would show a stranger that somebody is typing. |
| active | boolean | true to start, false to stop. |
curl -X POST https://api.missblue.dev/v1/typing \
-H "Authorization: Bearer $MISS_BLUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "iMessage;-;+15555550100",
"active": true
}'- 404No such conversation on a number your key holds.
- 409The number is temporarily unavailable (agent_offline). Retry shortly, or skip it — a late typing bubble is worse than none.
Show a bounded typing bubble to a contact.
Use this form when your integration knows the contact rather than the chat_id. The one-to-one iMessage conversation must already exist. Start defaults to 60 seconds and is always cleared by five minutes; another start extends the active timer, while stop is a safe no-op when nothing is active.
| number* | E.164 string | The contact who sees the bubble. |
| number_id | uuid | Which of your numbers sends it. Preferred over a mutable handle. |
| from_number | E.164 string | Handle alternative to number_id. Omit both only when your key holds one number. |
| state | start | stop | Defaults to start. |
| max_duration_ms | integer | 1–300000. Defaults to 60000. |
curl -X POST https://api.missblue.dev/v1/typing-indicators \
-H "Authorization: Bearer $MISS_BLUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"number": "+15555550100",
"from_number": "+16465550142",
"state": "start",
"max_duration_ms": 60000
}'Response
{
"status": "ACTIVE",
"number": "+15555550100",
"from_number": "+16465550142",
"chat_id": "iMessage;-;+15555550100",
"max_duration_ms": 60000
}- 400The number or duration is invalid, the source is ambiguous, or no prior iMessage conversation exists.
- 404The source number is not held by this credential.
- 409The source number is temporarily unavailable (agent_offline). Retry shortly.