API · v1 · REST + JSON

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.

Base URL · api.massivesms.com·Auth · Bearer key·Format · JSON
01

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.

Sandbox & Production
Every account ships with a sandbox key and a live key. Sandbox messages are routed to a virtual carrier (no real delivery, no credits charged) so you can test integration end-to-end. Switch by changing the API key.
02

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"
Never expose your live key
API keys grant full workspace access. Never commit them to source control, never paste into client-side JavaScript, never log them. If a key leaks, revoke it immediately from Settings.
03

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.

CodeMeaning
200 OKRequest succeeded.
201 CreatedResource created.
400 Bad RequestValidation failed. See response body for details.
401 UnauthorizedInvalid or missing API key.
402 Payment RequiredInsufficient credits to complete the request.
404 Not FoundResource does not exist or was deleted.
409 ConflictResource state conflict (e.g. opt-in already revoked).
422 UnprocessableCompliance check failed (e.g. phone in blocked region).
429 Too Many RequestsRate limit hit. Honor Retry-After header.
500–504Server 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..."
  }
}
04

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: 12
05

Versioning

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.

06

Messages

The atomic resource. A message is one SMS sent to one phone number. For bulk sends, use campaigns which dispatch many messages efficiently.

POST/v1/messagesSend a single SMS

Sends a single SMS immediately. Charged 1 credit per 160-char segment. Returns the queued message resource.

ParamTypeRequiredDescription
tostringyesRecipient phone in E.164 format (e.g. +14155550123).
bodystringyesMessage body. Max 1600 chars. Supports {variables}.
fromstringnoSender ID (alphanumeric) or phone. Defaults to workspace sender.
variablesobjectnoMap of {key: value} substituted into {body}.
metadataobjectnoFree-form keys (max 20). Stored, returned in webhooks.
scheduled_atstringnoISO-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"
}
GET/v1/messages/{id}Retrieve a message

Get the current state of a sent or scheduled message.

curl https://api.massivesms.com/v1/messages/msg_01HYZ8K5VQAE... \
  -H "Authorization: Bearer msvs_live_..."
GET/v1/messagesList messages

List recent messages, optionally filtered by status, campaign, or contact. Paginated with cursor-based starting_after / ending_before.

Query paramTypeDescription
statusstringqueued · sending · delivered · failed · replied
campaign_idstringFilter to a specific campaign.
contact_idstringFilter to a specific contact.
limitnumber1–100, default 20.
starting_afterstringCursor: message ID to paginate after.
POST/v1/messages/{id}/cancelCancel a scheduled message

Cancels a queued/scheduled message before it dispatches. No-op if already sent.

07

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.

POST/v1/campaignsCreate a campaign
ParamTypeRequiredDescription
namestringyesInternal name (your team only).
bodystringyesSMS body. Same {variable} support as messages.
audienceobjectyes{ list_ids?: string[], segment_id?: string, contact_ids?: string[] }
fromstringnoSender ID.
scheduled_atstringnoISO-8601. Omit to send immediately.
metadataobjectnoFree-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"
  }'
GET/v1/campaigns/{id}Retrieve a campaign
GET/v1/campaignsList campaigns
POST/v1/campaigns/{id}/cancelCancel a scheduled campaign
08

Contacts

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.

POST/v1/contactsCreate a contact
curl 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" }
  }'
GET/v1/contacts/{id}Retrieve a contact
POST/v1/contacts/{id}Update a contact
DELETE/v1/contacts/{id}Delete a contact
GET/v1/contactsList/search contacts
POST/v1/contacts/{id}/opt_outOpt a contact out
09

Lists

A list is a collection of contacts you send to as a group. Lists can be static (manual) or synced (Kit, Kajabi, Skool, etc.).

POST/v1/listsCreate a list
curl https://api.massivesms.com/v1/lists \
  -H "Authorization: Bearer msvs_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Cohort 06 — open cart" }'
POST/v1/lists/{id}/contactsAdd contacts to a list
{
  "contact_ids": ["con_01...", "con_02...", "con_03..."]
}
DELETE/v1/lists/{id}/contacts/{contact_id}Remove a contact from a list
GET/v1/listsList all lists
10

Credits

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.

GET/v1/creditsGet current balance
{
  "object": "credits",
  "balance": 8420,
  "total_lifetime": 27000,
  "low_balance_threshold": 1000,
  "estimated_runway_days": 14
}
11

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.

POST/v1/webhooksCreate a webhook subscription
curl 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)
  );
}
Reject events older than 5 minutes
Treat any webhook with timestamp drift > 5 min as a replay attack and respond with 400. Always respond 2xx within 10 seconds; otherwise we retry with exponential backoff for up to 24 hours.
12

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" }
  }
}
EventTriggered when
message.queuedMessage accepted into the dispatch queue.
message.sentMessage handed off to carrier.
message.deliveredCarrier confirms delivery to handset.
message.failedCarrier returns a permanent failure (e.g. invalid number).
message.repliedRecipient replied. Body in data.reply.body.
contact.opted_inContact confirmed opt-in (double opt-in flow).
contact.opted_outContact replied STOP/UNSUBSCRIBE/CANCELAR.
campaign.dispatchedAll messages in campaign queued.
campaign.completedAll messages in campaign reached terminal status.
credits.lowBalance dropped below low_balance_threshold.
13

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
14

OpenAPI spec

Full machine-readable spec for codegen, mocking, or import into Insomnia/Bruno:

curl -O https://api.massivesms.com/openapi.yaml
15

Postman 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.