GET /v1/response_stream
The single server-sent events stream carrying command settlements and inbound messages.
GET /v1/response_streamOne long-lived server-sent events connection carries everything that happens to your account: how each command settled, and every message people send you.
Anatomy of a connection
curl -N $MAPIER_BASE_URL/v1/response_stream \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "Accept: text/event-stream"Response headers:
HTTP/1.1 200 OK
content-type: text/event-stream
cache-control: no-cache
connection: keep-aliveThe first bytes on the wire are a comment confirming the stream is live:
: connectedThen, every 25 seconds while idle, a heartbeat:
: pingBoth are SSE comments, not events. A client that only reads data: lines
ignores them — which is correct; they exist to keep intermediaries from closing
an idle socket.
Event: command
A command reached a terminal state.
event: command
data: {"commandId":"cmd-1","logicalActionId":"order-2481","status":"succeeded","errorCode":null}Prop
Type
Only terminal statuses are emitted. The intermediate lifecycle —
claimed, executing, accepted — is deliberately suppressed, so you get
exactly one command event per command, not a progress feed.
logicalActionId echoes your Idempotency-Key only for commands on the agent-turn path. For
group.* and recipient-target sends it equals commandId, so you cannot use it to correlate with
a key you chose. Correlate on commandId instead — it is always reliable.
Event: message
Someone sent you a message.
event: message
data: {"conversationId":"a0000000-0000-4000-8000-000000000001","from":"+14155550100","text":"is it here yet?","externalId":"guid-1","replyToExternalId":null,"occurredAt":"2026-08-24T18:03:11.000Z","attachmentIds":[]}Prop
Type
Only inbound messages appear. Your own outbound sends do not echo back here —
their outcome arrives as a command event instead.
What you don't get
Documenting this precisely matters more than the happy path, because every one of these is a wrong assumption a client can silently make.
- No
id:field, so noLast-Event-ID. SSE's built-in resume mechanism is not implemented. - No replay and no backfill. Anything that happens while you are disconnected is gone. There is no cursor, no sequence number, and no endpoint to fetch missed events.
- At-most-once delivery. An event can be lost; it is never redelivered.
- No ordering guarantee. Events arrive in roughly the order they occur, but nothing enforces it and nothing lets you detect a gap.
- No delivery receipts.
succeededmeans iMessage accepted the send, not that it appeared on the recipient's device. - No typing or read events. These exist internally and are deliberately not exposed.
Unconfirmed
At-most-once with no resume is the behaviour today, not a committed contract. If your application cannot tolerate a lost settlement, keep your own record of commands you queued and reconcile on a timer rather than relying on the stream alone.
Forward compatibility
Fields are added to both event shapes without a version change. Two rules keep a client working:
- Ignore unknown fields. Do not use exhaustive destructuring that throws.
- Tolerate unknown enum values. New
errorCodevalues and newstatusvalues can appear. Write adefaultbranch:
switch (event.status) {
case "succeeded":
markDelivered(event.commandId);
break;
case "failed":
case "expired":
markFailed(event.commandId, event.errorCode);
break;
default:
// ambiguous, cancelled, no_effect, or something added later.
markNeedsReview(event.commandId, event.status);
}Reconnecting
The stream drops periodically even when healthy. Reconnect immediately and unconditionally — there is no resume token to manage, and no penalty for reconnecting often.
for (;;) {
try {
const res = await fetch(`${BASE}/v1/response_stream`, {
headers: {
Authorization: `Bearer ${process.env.MAPIER_API_KEY}`,
Accept: "text/event-stream",
},
});
if (!res.ok || !res.body) throw new Error(`stream status ${res.status}`);
await consume(res.body);
} catch (err) {
console.warn("stream dropped, reconnecting", err);
}
await new Promise((r) => setTimeout(r, 1000));
}A worked parser is in Reconnection and delivery guarantees.
Multiple connections
You may hold several streams open at once. Every connection receives every
event — they are a broadcast, not a work queue. Two workers on one account both
see the same settlement, so deduplicate on commandId if that matters.
Errors
| Status | Code | Cause |
|---|---|---|
401 | unauthenticated | No credential |
404 | no_connector | Your account has no Mac connector provisioned |
405 | method_not_allowed | Not a GET |
429 | rate_limited | 30 connections per minute |
503 | stream_unavailable | The stream service is not configured |
Quick reference
| Events | command, message |
| Heartbeat | : ping every 25s |
| Resume | none |
| Delivery | at-most-once |
| Ordering | not guaranteed |
| Concurrent streams | allowed, all receive everything |
| Rate limit | 30 per minute |