POST /v1/commands/:type
The shared envelope every command uses — target, payload, idempotency and the two routing paths.
Every action you take on iMessage is a command. They all share one endpoint,
one request envelope and one response shape; the :type path segment selects
the verb.
POST /v1/commands/{type}Request
Headers
Prop
Type
Body
Prop
Type
The body must be a JSON object no larger than 65,536 bytes, with both keys present and both objects. See Addressing a conversation for the three target shapes.
Command types
| Type | Purpose | Reference |
|---|---|---|
message.send | Send text, rich text, a link, a poll or media | message.send |
reaction.tapback | React to any message, in a DM or a group | reaction.tapback |
reaction.apply | React to the newest incoming DM message | reaction.apply |
name_photo.share | Offer your contact card in a DM | name_photo.share |
group.create | Start a group chat | group.create |
group.update | Rename a group or change its members | group.update |
group.leave | Leave a group | group.leave |
An unrecognised type returns 404:
{ "error": "unknown_command", "command": "message.explode" }Response
202 Accepted:
{
"commandId": "3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71",
"status": "queued",
"disposition": "queued"
}disposition is "adopted" when an identical idempotency key already existed,
meaning no new work was created and commandId refers to the original command.
In that case status reflects that command's current state, so an adopted
duplicate can come back already succeeded.
The response tells you the command was queued. It does not tell you the message was delivered.
Watch the response stream for the command event that settles it.
The two routing paths
This is the behaviour most likely to surprise you, and it is invisible in the request. Internally a command takes one of two paths, decided by its type and target, and they differ in ways you can observe.
Prop
Type
On the admin path an Idempotency-Key header is accepted and silently ignored. Retrying a
group.create or a cold message.send creates a second group or sends a second message. See
Idempotency and retries.
Payloads are not validated here
The endpoint checks the envelope — that target and payload are objects, that
a conversation id is a UUID, that a recipient send has text. It does not
validate the payload against the command's schema. That happens on the Mac at
execution time.
A payload with a misspelled field, an out-of-range value or a bad enum returns
202, then settles failed on the stream seconds later. When you are
developing against a new payload shape, keep the stream open.
Errors
| Status | Code | Cause |
|---|---|---|
400 | body_too_large | Body over 65,536 bytes |
400 | body_not_object | Body is not a JSON object |
400 | target_and_payload_required | Missing or non-object target/payload |
400 | invalid_conversationId | Not a UUID |
401 | unauthenticated | No credential |
404 | unknown_command | Unknown :type |
404 | conversation_not_found | Unknown conversation, or another account's |
405 | method_not_allowed | Not a POST |
422 | (varies) | Address validation failed |
429 | rate_limited | 60 requests per minute |
500 | internal | Persistence failure |
503 | no_live_connector | No Mac holds a live lease |
503 | outbound_paused | Sending is paused |
Full descriptions and retry guidance: Error codes.