Skip to content
Miss Blue

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.

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.

sortstringnewest (default), oldest, or unreplied.
number_iduuidOne of your lines, to see only what arrived on it. Use GET /v1/threads/lines for the ids and what each is carrying.
searchstringA 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*stringThe agent_id returned by the conversation list.
pinned*booleantrue 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_iduuidNarrow 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_iduuidNarrow 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*stringThe conversation to acknowledge.
readbooleanDefaults 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*stringThe conversation. Not a bare handle — guessing which of several threads was meant would show a stranger that somebody is typing.
activebooleantrue 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 stringThe contact who sees the bubble.
number_iduuidWhich of your numbers sends it. Preferred over a mutable handle.
from_numberE.164 stringHandle alternative to number_id. Omit both only when your key holds one number.
statestart | stopDefaults to start.
max_duration_msinteger1–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.
NextGroup conversationsCreate groups, pull metadata, and manage names, photos, and people.