{"openapi": "3.0.3", "info": {"title": "MultiMessage API", "version": "1.2.0", "description": "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.", "contact": {"email": "todd@innersitesolutions.com"}}, "servers": [{"url": "https://multimessage.me"}], "components": {"securitySchemes": {"apiKey": {"type": "http", "scheme": "bearer", "description": "Account API key from the web app (mm_live_\u2026), or an operator-issued platform key (mm_platform_\u2026). Platform keys work only on GET /usage, POST /plan (with X-MM-Account) and GET /accounts; everything else returns 403 platform_key_scope. Rate limit: 120 requests/minute per key."}}, "schemas": {"Usage": {"type": "object", "properties": {"plan": {"type": "string", "nullable": true}, "subscription_status": {"type": "string", "nullable": true}, "can_send": {"type": "boolean"}, "reason": {"type": "string"}, "texts_used": {"type": "integer"}, "texts_cap": {"type": "integer", "nullable": true}, "texts_remaining": {"type": "integer", "nullable": true}, "unlimited": {"type": "boolean"}, "period": {"type": "string"}, "resets_at": {"type": "string", "format": "date-time"}}}, "Message": {"type": "object", "properties": {"id": {"type": "integer"}, "seq": {"type": "integer", "description": "change cursor \u2014 poll GET /api/v1/messages?since=<seq>"}, "direction": {"type": "string", "enum": ["out", "in"]}, "address": {"type": "string"}, "address_norm": {"type": "string"}, "body": {"type": "string"}, "status": {"type": "string", "enum": ["queued", "sending", "sent", "delivered", "received", "failed"]}, "route": {"type": "string"}, "error": {"type": "string", "nullable": true}, "created_at": {"type": "string", "format": "date-time"}, "sent_at": {"type": "string", "format": "date-time", "nullable": true}, "delivered_at": {"type": "string", "format": "date-time", "nullable": true}}}, "Account": {"type": "object", "properties": {"id": {"type": "integer", "description": "send this as X-MM-Account (platform keys)"}, "name": {"type": "string"}, "plan": {"type": "string", "nullable": true}, "subscription_status": {"type": "string", "nullable": true}, "linked_at": {"type": "string", "format": "date-time", "nullable": true}}}, "PlanResult": {"type": "object", "properties": {"ok": {"type": "boolean"}, "plan": {"type": "string"}, "applied": {"type": "boolean", "description": "true \u2192 the requested plan is in effect NOW (prorated in-place switch, or already on it); false \u2192 nothing has changed yet \u2014 complete payment at checkout_url first"}, "changed": {"type": "boolean", "description": "true only when a live subscription was switched"}, "method": {"type": "string", "enum": ["subscription_updated", "checkout"]}, "checkout_url": {"type": "string", "description": "present only when applied is false"}, "detail": {"type": "string"}, "usage": {"$ref": "#/components/schemas/Usage"}}}, "Error": {"type": "object", "properties": {"error": {"type": "string", "description": "human-readable reason"}, "code": {"type": "string", "description": "stable machine-readable code", "enum": ["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"]}}}}}, "paths": {"/api/v1/me": {"get": {"operationId": "getAccount", "summary": "Account summary + usage + tiers", "security": [{"apiKey": []}], "responses": {"200": {"description": "OK", "content": {"application/json": {}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/usage": {"get": {"operationId": "getUsage", "summary": "Texts used/remaining this month", "security": [{"apiKey": []}], "parameters": [{"name": "X-MM-Account", "in": "header", "required": false, "schema": {"type": "integer"}, "description": "Platform keys (mm_platform_\u2026) only, and REQUIRED for them: the target account id. Unknown or unlinked ids return 404. Account keys omit this."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Usage"}}}}, "400": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/accounts": {"get": {"operationId": "listAccounts", "summary": "Accounts this key may act for", "description": "Platform keys: every account an owner/admin has linked to the key \u2014 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.", "security": [{"apiKey": []}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "properties": {"accounts": {"type": "array", "items": {"$ref": "#/components/schemas/Account"}}}}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/tiers": {"get": {"operationId": "listTiers", "summary": "Available pricing tiers", "responses": {"200": {"description": "OK", "content": {"application/json": {}}}}}}, "/api/v1/plan": {"post": {"operationId": "changePlan", "summary": "Change subscription tier on demand", "description": "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.", "security": [{"apiKey": []}], "parameters": [{"name": "X-MM-Account", "in": "header", "required": false, "schema": {"type": "integer"}, "description": "Platform keys (mm_platform_\u2026) only, and REQUIRED for them: the target account id. Unknown or unlinked ids return 404. Account keys omit this."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["plan"], "properties": {"plan": {"type": "string", "enum": ["starter", "growth", "pro"]}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PlanResult"}}}}, "400": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "502": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "503": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/messages": {"post": {"operationId": "sendMessage", "summary": "Send a text", "description": "Idempotent on client_ref \u2014 retry with the same value and the original message is returned. Response includes updated usage.", "security": [{"apiKey": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["address", "body"], "properties": {"address": {"type": "string", "description": "E.164 preferred; national formats accepted"}, "body": {"type": "string", "maxLength": 1600}, "client_ref": {"type": "string", "maxLength": 64}, "route": {"type": "string", "enum": ["android", "twilio"]}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {}}}, "400": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "402": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}, "get": {"operationId": "pollMessages", "summary": "Message changes since a cursor", "description": "Returns messages whose seq advanced past `since` (new inbound + delivery-status updates). Set `wait` to long-poll until something arrives.", "security": [{"apiKey": []}], "parameters": [{"name": "since", "in": "query", "schema": {"type": "integer"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "maximum": 500}}, {"name": "wait", "in": "query", "schema": {"type": "integer", "maximum": 25}, "description": "seconds to hold the request open waiting for news"}], "responses": {"200": {"description": "OK", "content": {"application/json": {}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/messages/{id}": {"get": {"operationId": "getMessage", "summary": "One message (delivery status)", "security": [{"apiKey": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "properties": {"message": {"$ref": "#/components/schemas/Message"}}}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/threads": {"get": {"operationId": "listThreads", "summary": "Conversation list", "security": [{"apiKey": []}], "responses": {"200": {"description": "OK", "content": {"application/json": {}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/api/v1/threads/{address}/messages": {"get": {"operationId": "getThread", "summary": "One conversation's messages", "security": [{"apiKey": []}], "parameters": [{"name": "address", "in": "path", "required": true, "schema": {"type": "string"}, "description": "any reasonable format \u2014 normalized server-side (e.g. 706-555-1212 \u2192 +17065551212)"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "maximum": 500}}], "responses": {"200": {"description": "OK", "content": {"application/json": {}}}, "400": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}}}