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
| Param | In | Type | Notes |
|---|---|---|---|
X-MM-Account | header | integer | Platform 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.
| Param | In | Type | Notes |
|---|---|---|---|
X-MM-Account | header | integer | Platform 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):
| Field | Type | Notes |
|---|---|---|
plan | starter | growth | pro | required |
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):
| Field | Type | Notes |
|---|---|---|
address | string | required — E.164 preferred; national formats accepted |
body | string | required |
client_ref | string | |
route | android | 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.
| Param | In | Type | Notes |
|---|---|---|---|
since | query | integer | |
limit | query | integer | |
wait | query | integer | seconds to hold the request open waiting for news |
Responses: 200 · 401
GET/api/v1/messages/{id}API key
One message (delivery status)
| Param | In | Type | Notes |
|---|---|---|---|
id | path | integer | required |
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
| Param | In | Type | Notes |
|---|---|---|---|
address | path | string | required any reasonable format — normalized server-side (e.g. 706-555-1212 → +17065551212) |
limit | query | integer |
Responses: 200 · 400 · 401
Schemas
Usage
| Field | Type | Notes |
|---|---|---|
plan | string (nullable) | |
subscription_status | string (nullable) | |
can_send | boolean | |
reason | string | |
texts_used | integer | |
texts_cap | integer (nullable) | |
texts_remaining | integer (nullable) | |
unlimited | boolean | |
period | string | |
resets_at | string |
Message
| Field | Type | Notes |
|---|---|---|
id | integer | |
seq | integer | change cursor — poll GET /api/v1/messages?since=<seq> |
direction | out | in | |
address | string | |
address_norm | string | |
body | string | |
status | queued | sending | sent | delivered | received | failed | |
route | string | |
error | string (nullable) | |
created_at | string | |
sent_at | string (nullable) | |
delivered_at | string (nullable) |
Account
| Field | Type | Notes |
|---|---|---|
id | integer | send this as X-MM-Account (platform keys) |
name | string | |
plan | string (nullable) | |
subscription_status | string (nullable) | |
linked_at | string (nullable) |
PlanResult
| Field | Type | Notes |
|---|---|---|
ok | boolean | |
plan | string | |
applied | boolean | true → 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 |
changed | boolean | true only when a live subscription was switched |
method | subscription_updated | checkout | |
checkout_url | string | present only when applied is false |
detail | string | |
usage | object |
Error
| Field | Type | Notes |
|---|---|---|
error | string | human-readable reason |
code | invalid_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_scope | stable machine-readable code |
Questions or higher volume? [email protected]