mapier docs
Response stream

The response stream

Why every asynchronous outcome arrives on one SSE connection, and how to start consuming it.

A command request never tells you what happened. POST /v1/commands/:type returns 202 the moment the command is durably queued — before the Mac connector has claimed it, before iMessage has seen it, and long before anything appears on anyone's phone.

202 Accepted
{ "commandId": "cmd-1", "status": "queued", "disposition": "queued" }

That is a receipt, not a result. GET /v1/response_stream is where the result arrives, and it is the only push channel this API has. There are no webhooks, and there is no endpoint to poll for a command's status. If nothing in your system is reading the stream, nothing in your system knows whether your message was sent.

One connection, two kinds of event

A single stream carries both directions of your account's activity.

EventFires when
commandone of your commands reaches a terminal state
messagesomeone sends an inbound message to your account

You do not subscribe to one or the other, and there are no filters — you open the connection and receive everything for your account.

Opening it

The stream is ordinary server-sent events over HTTP, so you can watch it with curl before writing any code. The -N flag disables output buffering, which you need to see events as they land.

curl -N $MAPIER_BASE_URL/v1/response_stream \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "Accept: text/event-stream"

The raw bytes look like this — a comment on connect, then frames as things happen, then a heartbeat when the account is idle:

: connected

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":[]}

event: command
data: {"commandId":"cmd-1","logicalActionId":"order-2481","status":"succeeded","errorCode":null}

: ping

Comments and the heartbeat

Two of those lines are not events. Anything starting with : is an SSE comment, and both of the ones you will see are infrastructure rather than data:

  • : connected is written immediately on a successful connection. Seeing it confirms the socket is open and authenticated.
  • : ping is written every 25 seconds. It exists to stop proxies and load balancers from closing a stream that has been quiet.

A parser that acts only on data: lines ignores both without any special handling, which is the behaviour you want. Going the other way is also useful: if you have received no bytes at all for well over 25 seconds, the connection is probably dead even though it has not reported an error, and you should drop it and reconnect.

A minimal consumer

This is enough to see events arrive in Node. It reads the response body, accumulates bytes into a buffer, and keeps the trailing partial line back for the next read — a chunk boundary can fall anywhere, including the middle of a JSON payload.

const BASE = process.env.MAPIER_BASE_URL!;

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}`);

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  const lines = buffer.split("\n");
  buffer = lines.pop() ?? ""; // the last piece may be half a line

  for (const line of lines) {
    if (!line.startsWith("data:")) continue; // skips ": connected" and ": ping"
    console.log(JSON.parse(line.slice(5)));
  }
}

This prints payloads but discards the event: line, so it tells you what arrived without telling you which kind it was. It also stops at the first disconnect. Both are fine for a first look and wrong for production — the full parser and the reconnect loop are on Reconnection and delivery guarantees.

The stream drops periodically even when everything is healthy, and there is no resume token or backfill. Anything that happens while you are disconnected is gone. Treat reconnection as the normal case, not an error path.

On this page