POST /v1/replay
Catch up on command settlements and inbound messages that the stream delivered while you were disconnected.
POST /v1/replayGET /v1/response_stream is at-most-once with no resume — anything it delivered
while nothing was listening is gone from the stream. /v1/replay is the
backfill: ask for past command settlements and past inbound messages by the
boundary you supply.
Like handle-check, this is a synchronous read. The answer is in the HTTP response; nothing arrives on the stream. The server keeps no per-client cursor — it is stateless, and you tell it where to resume.
Two independent lenses, either or both per call:
commandIds— re-fetch how specific commands settled.conversationId(+ optionalsinceExternalId) — re-fetch inbound messages in one conversation.
Request
Prop
Type
# Re-settle specific commands you queued.
curl $MAPIER_BASE_URL/v1/replay \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"commandIds": ["cmd-1", "cmd-2"]
}'sinceExternalId is a message GUID, not a conversation id
sinceExternalId takes an externalId — the iMessage message GUID (a UUID like
88AF7A6D-1234-4ABC-9DEF-0123456789AB), the exact value from a message event's externalId
field. It is not the conversationId and not a timestamp. It anchors to that one
message's row; replay returns everything stored after it.
Response
{
"commands": [
{
"commandId": "cmd-1",
"logicalActionId": "order-2481",
"status": "succeeded",
"errorCode": null
}
],
"messages": [
{
"conversationId": "a0000000-0000-4000-8000-000000000001",
"from": "+14155550100",
"text": "did it go through?",
"externalId": "6F1B2C3D-0000-4000-8000-000000000002",
"replyToExternalId": null,
"occurredAt": "2026-08-24T18:03:11.000Z",
"attachmentIds": []
}
]
}commands is present only if you sent commandIds; messages only if
you sent conversationId. Each array holds the exact same object shape as the
matching response_stream event:
commands[]— one entry per command that was found, identical to acommandevent (commandId,logicalActionId,status,errorCode, plusconversationIdon a successfulgroup.create). The count can be smaller than the ids you asked for; missing ones were unknown or not yours.messages[]— inbound messages only, identical to amessageevent, oldest first.
How messages are ordered and anchored
- Order is arrival order — the sequence the server received the messages in,
not
occurredAt.occurredAtis what iMessage stamped and can be backdated, so it is not the cursor. sinceExternalIdis exclusive — the anchor message itself is never returned, only messages after it. Pass the newestexternalIdyou have and you get strictly what you are missing.- No
sinceExternalIdreturns the most recentlimitmessages, still oldest-first within the page. - No dedup. Replay does not know what you have already applied. Re-apply by
externalId— seeing a message you already have must be idempotent. This is the same discipline the stream asks for, so a client that keys onexternalIdneeds no extra logic.
Replay covers inbound messages and command settlements — the two things the stream carries.
Your own outbound sends are not messages here; their outcome is a command, so re-fetch those with
commandIds.
Errors
| Status | Code | Cause |
|---|---|---|
400 | commandIds must be 1..256 uuids | commandIds was present but not 1–256 valid UUIDs |
400 | invalid_conversationId | conversationId was not a UUID |
400 | sinceExternalId must be a message externalId | sinceExternalId was not a string |
400 | (body message) | The body was not valid JSON |
401 | unauthenticated | No credential |
404 | no_connector | Your account has no Mac connector provisioned |
404 | conversation_not_found | That conversationId is not one of your account's conversations |
404 | sinceExternalId_not_found | That externalId is not a message in the given conversation |
405 | method_not_allowed | Not a POST |
500 | internal | Unexpected server error |
A call with neither commandIds nor conversationId is valid and returns {}.