MassiveSMS API.
Send SMS, manage contacts, fire scheduled campaigns, and receive delivery events. Designed for course creators, coaches, and operators building SMS into their own funnels and CRMs.
Introduction
The MassiveSMS API is organized around REST. Predictable resource-oriented URLs, standard HTTP verbs, JSON-encoded request and response bodies, and standard HTTP response codes. All requests are made over HTTPS. Plain HTTP is not supported.
The API exposes everything you can do in the dashboard: send a single SMS to a phone number, dispatch a launch campaign to thousands of contacts, import/export contact lists, query credit balance, and subscribe to delivery and reply events through webhooks.
Authentication
Authenticate by setting the Authorization header to Bearer {API_KEY}. API keys are scoped to a workspace and start with msvs_live_ (production) or msvs_test_ (sandbox). Manage keys at Settings → API & webhooks.
curl https://api.massivesms.com/v1/credits \
-H "Authorization: Bearer msvs_live_4f2a..." \
-H "Accept: application/json"Errors
MassiveSMS uses conventional HTTP status codes. 2xx indicates success. 4xx indicates a client error (missing parameter, invalid phone, insufficient credits). 5xx indicates a problem on our end and should be retried with exponential backoff.
| Code | Meaning |
|---|---|
| 200 OK | Request succeeded. |
| 201 Created | Resource created. |
| 400 Bad Request | Validation failed. See response body for details. |
| 401 Unauthorized | Invalid or missing API key. |
| 402 Payment Required | Insufficient credits to complete the request. |
| 404 Not Found | Resource does not exist or was deleted. |
| 409 Conflict | Resource state conflict (e.g. opt-in already revoked). |
| 422 Unprocessable | Compliance check failed (e.g. phone in blocked region). |
| 429 Too Many Requests | Rate limit hit. Honor Retry-After header. |
| 500–504 | Server error. Retry with exponential backoff. |
Every error response follows this shape:
{
"error": {
"type": "invalid_request_error",
"code": "phone_number_invalid",
"message": "The phone number '+1abc' could not be parsed as E.164.",
"param": "to",
"request_id": "req_01HYZ8K5VQAE..."
}
}Rate limits
The API rate limits per workspace, not per key. The default limit is 100 requests per second with bursts up to 200. Bulk campaign endpoints have higher limits because they queue internally.
Rate limit metadata is exposed in response headers. Honor Retry-After when present.
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1714579200
Retry-After: 12Versioning
The API is versioned via the URL path: /v1/. Breaking changes ship as a new version (/v2/). Backwards-compatible additions go to the existing version. Deprecated fields stay alive for at least 12 months after announcement.
Subscribe to the changelog to track API updates.
Messages
The atomic resource. A message is one SMS sent to one phone number. For bulk sends, use campaigns which dispatch many messages efficiently.
/v1/messagesSend a single SMSSends a single SMS immediately. Charged 1 credit per 160-char segment. Returns the queued message resource.
| Param | Type | Required | Description |
|---|---|---|---|
| to | string | yes | Recipient phone in E.164 format (e.g. +14155550123). |
| body | string | yes | Message body. Max 1600 chars. Supports {variables}. |
| from | string | no | Sender ID (alphanumeric) or phone. Defaults to workspace sender. |
| variables | object | no | Map of {key: value} substituted into {body}. |
| metadata | object | no | Free-form keys (max 20). Stored, returned in webhooks. |
| scheduled_at | string | no | ISO-8601 to schedule. Omit to send now. |
curl https://api.massivesms.com/v1/messages \
-H "Authorization: Bearer msvs_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155550123",
"body": "Hey {firstName}, cart closes tonight at 8pm ET. Reply STOP to opt out.",
"variables": { "firstName": "Maria" },
"metadata": { "campaign": "cohort_06_close" }
}'{
"id": "msg_01HYZ8K5VQAE2N0PWQ6DC9V8MP",
"object": "message",
"to": "+14155550123",
"from": "MASSIVESMS",
"body": "Hey Maria, cart closes tonight at 8pm ET. Reply STOP to opt out.",
"status": "queued",
"segments": 1,
"credits_used": 1,
"metadata": { "campaign": "cohort_06_close" },
"created_at": "2026-05-01T20:00:00Z"
}/v1/messages/{id}Retrieve a messageGet the current state of a sent or scheduled message.
curl https://api.massivesms.com/v1/messages/msg_01HYZ8K5VQAE... \
-H "Authorization: Bearer msvs_live_..."/v1/messagesList messagesList recent messages, optionally filtered by status, campaign, or contact. Paginated with cursor-based starting_after / ending_before.
| Query param | Type | Description |
|---|---|---|
| status | string | queued · sending · delivered · failed · replied |
| campaign_id | string | Filter to a specific campaign. |
| contact_id | string | Filter to a specific contact. |
| limit | number | 1–100, default 20. |
| starting_after | string | Cursor: message ID to paginate after. |
/v1/messages/{id}/cancelCancel a scheduled messageCancels a queued/scheduled message before it dispatches. No-op if already sent.
Campaigns
A campaign is a launch: one message body sent to many contacts (a list, segment, or ad-hoc set). Use this for cart-open blasts, webinar reminders, challenge sequences. Credits are debited at send time, not schedule time.
/v1/campaignsCreate a campaign| Param | Type | Required | Description |
|---|---|---|---|
| name | string | yes | Internal name (your team only). |
| body | string | yes | SMS body. Same {variable} support as messages. |
| audience | object | yes | { list_ids?: string[], segment_id?: string, contact_ids?: string[] } |
| from | string | no | Sender ID. |
| scheduled_at | string | no | ISO-8601. Omit to send immediately. |
| metadata | object | no | Free-form metadata. |
curl https://api.massivesms.com/v1/campaigns \
-H "Authorization: Bearer msvs_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Cohort 06 — cart close 4h",
"body": "Hey {firstName}, Cohort 06 closes tonight at 8pm ET.",
"audience": { "list_ids": ["lst_cohort06_open"] },
"scheduled_at": "2026-05-01T20:00:00-04:00"
}'/v1/campaigns/{id}Retrieve a campaign/v1/campaignsList campaigns/v1/campaigns/{id}/cancelCancel a scheduled campaignContacts
Contacts are the people you send SMS to. Each contact has a phone number, optional first/last name, opt-in status, and arbitrary attributes for personalization.
/v1/contactsCreate a contactcurl https://api.massivesms.com/v1/contacts \
-H "Authorization: Bearer msvs_live_..." \
-H "Content-Type: application/json" \
-d '{
"phone": "+14155550123",
"first_name": "Maria",
"last_name": "Silva",
"email": "maria@example.com",
"opt_in": { "source": "checkout", "ip": "203.0.113.4" },
"attributes": { "tier": "vip", "joined_at": "2026-04-22" }
}'/v1/contacts/{id}Retrieve a contact/v1/contacts/{id}Update a contact/v1/contacts/{id}Delete a contact/v1/contactsList/search contacts/v1/contacts/{id}/opt_outOpt a contact outLists
A list is a collection of contacts you send to as a group. Lists can be static (manual) or synced (Kit, Kajabi, Skool, etc.).
/v1/listsCreate a listcurl https://api.massivesms.com/v1/lists \
-H "Authorization: Bearer msvs_live_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Cohort 06 — open cart" }'/v1/lists/{id}/contactsAdd contacts to a list{
"contact_ids": ["con_01...", "con_02...", "con_03..."]
}/v1/lists/{id}/contacts/{contact_id}Remove a contact from a list/v1/listsList all listsCredits
Read your current credit balance and recent purchases. To purchase additional credit packs, redirect users to /dashboard/credits (Stripe checkout). Programmatic purchase via API is on the roadmap.
/v1/creditsGet current balance{
"object": "credits",
"balance": 8420,
"total_lifetime": 27000,
"low_balance_threshold": 1000,
"estimated_runway_days": 14
}Webhooks
Webhooks push events to your URL as they happen. Subscribe at Settings → Webhooks or via API. Each event includes a type, the resource data, and a signed timestamp for verification.
/v1/webhooksCreate a webhook subscriptioncurl https://api.massivesms.com/v1/webhooks \
-H "Authorization: Bearer msvs_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.cohortlabs.co/webhooks/sms",
"events": ["message.delivered", "message.failed", "message.replied", "contact.opted_out"],
"secret": "whsec_..."
}'Verifying signatures
Every webhook request includes MSVS-Signature with the format t={timestamp},v1={hmac}. Compute HMAC-SHA256 of {timestamp}.{raw_body} with your endpoint secret and compare in constant time.
import crypto from "crypto";
function verify(rawBody, header, secret) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(v1)
);
}Event types
Every webhook payload follows this shape:
{
"id": "evt_01HYZ...",
"type": "message.delivered",
"created_at": "2026-05-01T20:00:14Z",
"data": {
"object": "message",
"id": "msg_01HY...",
"to": "+14155550123",
"status": "delivered",
"campaign_id": "cmp_01HY...",
"metadata": { "campaign": "cohort_06_close" }
}
}| Event | Triggered when |
|---|---|
| message.queued | Message accepted into the dispatch queue. |
| message.sent | Message handed off to carrier. |
| message.delivered | Carrier confirms delivery to handset. |
| message.failed | Carrier returns a permanent failure (e.g. invalid number). |
| message.replied | Recipient replied. Body in data.reply.body. |
| contact.opted_in | Contact confirmed opt-in (double opt-in flow). |
| contact.opted_out | Contact replied STOP/UNSUBSCRIBE/CANCELAR. |
| campaign.dispatched | All messages in campaign queued. |
| campaign.completed | All messages in campaign reached terminal status. |
| credits.low | Balance dropped below low_balance_threshold. |
SDKs
Official client libraries with typed methods, retries, and idempotency built-in:
- Node.js ·
npm install @massivesms/node - Python ·
pip install massivesms - Ruby ·
gem install massivesms - PHP ·
composer require massivesms/massivesms-php - Go ·
go get github.com/massivesms/massivesms-go
OpenAPI spec
Full machine-readable spec for codegen, mocking, or import into Insomnia/Bruno:
curl -O https://api.massivesms.com/openapi.yamlPostman collection
Import the official collection (auto-synced with the latest endpoints): postman.com/massivesms. The collection includes example requests, environment templates for sandbox/production, and pre-built test scripts for signature verification.
