MultiMessage

API Reference

Send and read texts, check remaining monthly quota, and change the pricing tier on demand. Errors carry a stable machine-readable `code`. Platform integrators can manage many accounts with one mm_platform_ key (usage/plan only, via the X-MM-Account header). Also see /llms.txt.

Base URL: https://multimessage.me · OpenAPI 3.0 spec · llms.txt · quickstart guide

Authentication

Endpoints marked API key require Authorization: Bearer mm_live_… (create keys in the app — shown once). Rate limit: 120 requests/minute per key. Every error is JSON with a human error and a stable machine-readable code — see the Error schema below.

Integrating platforms managing many accounts can request an operator-issued platform key (mm_platform_…): add X-MM-Account: <account id> on GET /api/v1/usage and POST /api/v1/plan, and list authorized account ids via GET /api/v1/accounts. Platform keys cannot send or read messages (403 platform_key_scope). Contact [email protected] to get one.

Endpoints

GET/api/v1/meAPI key

Account summary + usage + tiers

Responses: 200 · 401

GET/api/v1/usageAPI key

Texts used/remaining this month

ParamInTypeNotes
X-MM-AccountheaderintegerPlatform keys (mm_platform_…) only, and REQUIRED for them: the target account id. Unknown or unlinked ids return 404. Account keys omit this.

Responses: 200 · 400 · 401 · 404 · 429

GET/api/v1/accountsAPI key

Accounts this key may act for — Platform keys: every account an owner/admin has linked to the key — store the ids and send one as X-MM-Account per request (never store per-account secrets). Account keys: a single-element list with the key's own account.

Responses: 200 · 401

GET/api/v1/tiersno auth

Available pricing tiers

Responses: 200

POST/api/v1/planAPI key

Change subscription tier on demand — Live subscription: prorated in-place price switch (new cap applies immediately). Otherwise returns checkout_url to complete payment. The response's `applied` flag says unambiguously whether the switch took effect: true = in effect now; false = nothing changed yet, finish at checkout_url.

ParamInTypeNotes
X-MM-AccountheaderintegerPlatform keys (mm_platform_…) only, and REQUIRED for them: the target account id. Unknown or unlinked ids return 404. Account keys omit this.

Request body (JSON):

FieldTypeNotes
planstarter | growth | prorequired
curl -X POST https://multimessage.me/api/v1/plan \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"plan": "pro"}'

Responses: 200 · 400 · 401 · 404 · 409 · 502 · 503

POST/api/v1/messagesAPI key

Send a text — Idempotent on client_ref — retry with the same value and the original message is returned. Response includes updated usage.

Request body (JSON):

FieldTypeNotes
addressstringrequired — E.164 preferred; national formats accepted
bodystringrequired
client_refstring
routeandroid | twilio
curl -X POST https://multimessage.me/api/v1/messages \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"address": "+17065551212", "body": "Hi! Just following up."}'

Responses: 200 · 400 · 401 · 402 · 409 · 429

GET/api/v1/messagesAPI key

Message changes since a cursor — Returns messages whose seq advanced past `since` (new inbound + delivery-status updates). Set `wait` to long-poll until something arrives.

ParamInTypeNotes
sincequeryinteger
limitqueryinteger
waitqueryintegerseconds to hold the request open waiting for news

Responses: 200 · 401

GET/api/v1/messages/{id}API key

One message (delivery status)

ParamInTypeNotes
idpathintegerrequired

Responses: 200 · 401 · 404

GET/api/v1/threadsAPI key

Conversation list

Responses: 200 · 401

GET/api/v1/threads/{address}/messagesAPI key

One conversation's messages

ParamInTypeNotes
addresspathstringrequired any reasonable format — normalized server-side (e.g. 706-555-1212 → +17065551212)
limitqueryinteger

Responses: 200 · 400 · 401

Schemas

Usage

FieldTypeNotes
planstring (nullable)
subscription_statusstring (nullable)
can_sendboolean
reasonstring
texts_usedinteger
texts_capinteger (nullable)
texts_remaininginteger (nullable)
unlimitedboolean
periodstring
resets_atstring

Message

FieldTypeNotes
idinteger
seqintegerchange cursor — poll GET /api/v1/messages?since=<seq>
directionout | in
addressstring
address_normstring
bodystring
statusqueued | sending | sent | delivered | received | failed
routestring
errorstring (nullable)
created_atstring
sent_atstring (nullable)
delivered_atstring (nullable)

Account

FieldTypeNotes
idintegersend this as X-MM-Account (platform keys)
namestring
planstring (nullable)
subscription_statusstring (nullable)
linked_atstring (nullable)

PlanResult

FieldTypeNotes
okboolean
planstring
appliedbooleantrue → the requested plan is in effect NOW (prorated in-place switch, or already on it); false → nothing has changed yet — complete payment at checkout_url first
changedbooleantrue only when a live subscription was switched
methodsubscription_updated | checkout
checkout_urlstringpresent only when applied is false
detailstring
usageobject

Error

FieldTypeNotes
errorstringhuman-readable reason
codeinvalid_api_key | rate_limited | invalid_request | invalid_address | unknown_plan | no_subscription | cap_reached | no_plan | no_route | exempt_account | not_found | billing_disabled | stripe_error | platform_key_scopestable machine-readable code

Questions or higher volume? [email protected]