Messages HTTP API

The endpoints your site calls to send a text, read one back, list its history, read its templates and check its texting status, with every error they return.

On this page

Your site's texting is a small REST API. Every endpoint is a POST with a JSON body and a key header. /send sends one text with one of the site's templates, /text reads one text back with every delivery update, /texts lists the history, /templates lists the templates with their fields and review state, /countries lists every country Sent texts with its price, and /status reports whether texting is on, test mode, the countries and every limit. See Messages for the tab, the templates and the prices.

The helper your site's code imports, @/lib/modulify/messages, makes exactly these calls. Keep the key on a server, never in a browser or an app.

Authentication

Send the key as an X-Messages-Key header on every request. The base URL is the value of MODULIFY_MESSAGES_URL, such as https://api.modulify.ai/site-messages/site, and every path below goes after it.

curl -X POST "$MODULIFY_MESSAGES_URL/status" \
  -H "X-Messages-Key: $MODULIFY_MESSAGES_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Keys start with mmk_. Your site reads its key from process.env.MODULIFY_MESSAGES_KEY and the base URL from process.env.MODULIFY_MESSAGES_URL, both server side only. They are managed secrets that Modulify creates on its own the next time the preview starts or the site is published, or at once when the key is shown or rotated. API access on the Settings tab of the Messages tab shows the two values. A missing, malformed or unknown key returns 401 with "A valid texting key is required!"

A key belongs to exactly one site, and each endpoint only ever sees that site's texts. Rotating the key under API access invalidates the old one immediately, and your published site keeps the old key until you publish again. A site that moves to another workspace gets a new key the same way.

Response envelope

Every response, success or failure, has the same shape:

{
    "success": true,
    "message": "Sent checked this test text. Nothing went out and nothing was charged.",
    "data": { "text": { "id": "66f7c2a14d2b8e0012ab3501", "template": "order_confirmed", "templateLabel": "Order confirmed", "to": "+14155551234", "country": "US", "channel": "sms", "status": "queued", "credits": 0, "segments": 1, "test": true, "error": null, "createdAt": "2026-11-06T14:43:05.163Z" }, "code": null, "duplicate": false },
    "code": 200
}

Treat a call as failed unless the HTTP status is ok and success is true. Read the payload from data, and when a send is refused, branch on data.code rather than on the message.

Endpoints

Method Path Body Returns
POST /send { to, template, variables, channel, idempotencyKey } { text, code, duplicate }
POST /text { id } One text in full
POST /texts { limit, cursor, status, phone } { items, nextCursor }
POST /templates {} Every template of the site
POST /countries { query, selectedOnly } { countries, selected, max, total }
POST /status {} { enabled, testMode, paused, isPaid, countries, limits, usage }

/send

Sends one text. to is one number in international format, such as +14155551234. template is the key of one of the site's templates, such as order_confirmed, and variables holds a value for each of its fields, such as { "name": "Ana", "order": "#1042", "total": "EUR 48.00" }. {{site}} is filled with the site's name on its own. channel is sms when left out, whatsapp once the site has a WhatsApp account of its own, or sent to let Sent choose.

While test mode is on, Sent checks the text but nothing goes out and nothing is charged, and text.test is true. Real texts need a paid plan and test mode turned off in the Messages tab. text.status is queued once Sent takes the text, and delivery arrives later: read it with /text.

Pass an idempotencyKey on every send that could be retried, in the body or as an Idempotency-Key header. Use letters, digits, dashes and underscores, up to 200 characters: other characters are dropped. Sending again with the same key never sends a second text. It answers the first text with duplicate: true.

A refused text answers with success: false, the reason as message and its code in data.code:

Status Code Why
400 template-unknown No template on the site has that key
400 variables-invalid A field is missing, unknown, longer than 120 characters, not an https link on this site for a link field, or not 4 to 10 letters or digits for a code field
400 phone-invalid The number is not in international format
400 channel-invalid The channel is not one Sent sends on
400 country-not-priced There is no price for texts to that country yet
402 no-credits The workspace does not have the credits the text costs
402 cap-reached The text would pass the workspace spending cap
403 country-not-allowed The site may not text that country. Pick countries on the Settings tab
403 plan-required Real texts need a paid plan
409 turned-off Texting is turned off for the site
409 paused Modulify paused texting on the site
409 template-not-ready The template is not on Sent yet, or Sent has not approved it, which a real text needs
409 opted-out The number replied STOP or was blocked
409 no-credentials Texting was never turned on, so the site has no account on Sent yet
429 rate-limited The site sent 30 texts in the last minute
429 number-limit That number already got 5 texts from the site today
429 daily-limit The text would take today's spend past 20 credits
502 provider Sent refused or failed the text. No credits were taken
503 charge-failed The credits could not be reserved, so nothing was sent

A text that fails, is filtered, is blocked or is cancelled gets its credits back.

/text

Reads one text the site sent, by the id /send or /texts returned. It carries everything /send returns, plus variables, body (the text that went out, with a verification code masked), messageId, templateName, reasonCode, refunded, cancellable, unicode, contactFee and events, every delivery update as { event, status, reason, at }. An id the site never sent answers 404 with "That text could not be found!"

A text is pending, queued, routed, scheduled, sent, delivered, read, cancelled, filtered, blocked or failed.

/texts

Lists the site's texts, newest first, 25 by default. limit asks for up to 100. Pass nextCursor back as cursor until it is null. status is all, in-progress, delivered, failed or cancelled, and phone keeps the texts to one number.

/templates

Lists every template of the site, the built-in ones and the site's own, as { key, label, text, state, approved, custom, fields }. state is initial, pending, approved, rejected, paused or disabled, and approved is true when the template can send real texts. Each field is { name, kind, example, auto }: kind is text, link or code, and auto is true for {{site}}, which Modulify fills in.

/countries

Lists every country Sent texts as { code, name, credits, selected }: the ISO code, the English name, the credits one SMS part to it costs and whether the site may text it. The countries the site may text come first, then the rest by name. query keeps the countries whose name or code holds every word of it, ignoring case and accents, and selectedOnly: true keeps only the ones the site may text, which suits a country picker on a phone field. selected is the codes the site may text, max how many its plan lets it pick and total how many Sent texts.

/status

Reports enabled, testMode, paused and isPaid, the countries the site may text, the limits (perMinute, perNumberPerDay, creditsPerDay, countriesMax, variableMaxLength) and the usage for today and this month, each with texts, tests, delivered, failed and credits, plus blockedNumbers.

Limits

Limit Value
Texts a minute, per site 30 on a paid plan
Texts to one number a day 5 on a paid plan
Credits of texts a day, per site 20 on a paid plan
Value of one field 120 characters
Texts on one /texts page 100
Idempotency key 200 characters
How long texts stay in the history 90 days

Next

  • Messages covers the tab, the templates, test mode and the prices.
  • Secrets explains the managed MODULIFY_MESSAGES_URL and MODULIFY_MESSAGES_KEY values.