How-to guide

Send an SMS

Send a text message from your app to any phone number in about 5 minutes.

What you'll build

A request that texts a phone number from your workspace's number, safe to retry, and a way to know when it was delivered.

Prerequisites

  • A secret API key with the messages:send scope. Start with a test key. See Authentication and API keys.
  • A phone number in your workspace, assigned to an agent (dashboard → Phone numbers). It is your sender.
  • To text real US numbers with a live key, a registered texting program for your number (dashboard → Channels).
  • curl, Node.js 18 or later with @voicelayer/api, or Python 3.9 or later with voicelayer.

Step 1: Send the message

POST /v1/messages takes the channel, the recipient in E.164 format (+ and the country code), and the content. With a test key, use a reserved number such as +15550123.

curl -X POST https://api.vlayers.ai/v1/messages \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "to": "+15550123",
    "content": {
      "type": "text",
      "text": "Your table for two is booked for 7pm."
    }
  }'
Response · 202
{
  "message": {
    "id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
    "conversationId": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
    "channel": "sms",
    "direction": "outbound",
    "status": "queued",
    "text": "Your table for two is booked for 7pm.",
    "media": [],
    "from": "+14155550100",
    "to": "+15550123",
    "bindingId": null,
    "metadata": {},
    "error": null,
    "createdAt": "2026-10-20T15:04:05.000Z",
    "parts": [
      {
        "id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
        "status": "queued"
      }
    ]
  }
}

The answer is 202 Accepted: the message is queued, not delivered yet. Keep message.id to match later events.

Step 2: Make it safe to retry

Add an Idempotency-Key header with a fresh UUID. If your request times out and you retry with the same key, VoiceLayer returns the first result instead of sending twice. Generate one key per message you mean to send, and reuse it only for retries. See Errors, retries and idempotency.

curl -X POST https://api.vlayers.ai/v1/messages \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channel": "sms",
    "to": "+15550123",
    "content": {
      "type": "text",
      "text": "Your table for two is booked for 7pm."
    }
  }'

Step 3: Choose the sender (when you have several numbers)

Leave from out and the API uses your workspace's one number. With several numbers, name one:

{
  "channel": "sms",
  "from": { "phoneNumberId": "<your number's id>" },
  "to": "+15550123",
  "content": { "type": "text", "text": "Your table for two is booked for 7pm." }
}

Without from, several numbers give 409 sender_ambiguous; no number at all gives 409 no_sender.

Step 4: Know when it is delivered

Delivery is reported as events: message.sent when it leaves through the carrier, message.delivered when the carrier confirms it, and message.failed with data.error when it cannot be delivered. Receive them with webhooks or the event stream.

Long texts and pictures

  • A text longer than one SMS goes out as several parts (split: "auto", the default). Each part is billed, and all are listed in parts. Send split: "refuse" to get 422 content_too_long instead.
  • To send a picture, use the mms channel with media content. The image must be at a public https:// URL:
curl -X POST https://api.vlayers.ai/v1/messages \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "mms",
    "to": "+15550123",
    "content": {
      "type": "media",
      "media": [
        {
          "url": "https://vlayers.ai/og.png"
        }
      ],
      "caption": "Here is the menu."
    }
  }'

Test it

With a test key, send to any number from +15550100 to +15550199. The message goes to a simulated carrier and costs nothing. A test key that names a real number is refused with 422 test_address_required. See Test mode.

Common errors

ErrorStatusHow to fix
no_sender409Connect a sender for the channel in the dashboard (for email, create a mailbox), or name one in from.
sender_ambiguous409Name one of details.senders in from.
sms_refused403, 429For a 403, change what is sent (another number, a destination the workspace may text, consent on file, the recipient's allowed hours). For a 429, retry after the delay, or ask for bulk sending to be enabled.
recipient_opted_out409Do not message them on this channel. They opt back in by writing to you again (for SMS, START).
content_too_long422Shorten the text, or send it with split: "auto" where it fits the part limit.
wallet_refused402Top up the wallet (or turn on auto top-up) in the dashboard, then send again.
idempotency_conflict409Use a new key for a new message; repeat the original body exactly to replay the original request.
test_address_required422Use a reserved test address, or a live key to reach a real person.

Next steps

On this page