Developers

SMS API reference

Send single, bulk, and template SMS from your own stack. Authenticate with a bearer token, post JSON, and receive delivery status through webhooks.

Quick Start

Base URL

https://rest.api.fortwave.io/v1/sms

All API requests must be made over HTTPS.

Send your first SMS

Set your API key in the FORTWAVE_API_KEY environment variable, then run one of these snippets. Pick a language once and every example on this page follows.

curl -X POST https://rest.api.fortwave.io/v1/sms \
  -H "Authorization: Bearer $FORTWAVE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "phone_number": "+25761000000",
    "sender": "MyApp",
    "message": "Your verification code is 123456."
  }'

Prerequisites

Before you can send an SMS, ensure the following:

  1. API Token — Generate an API token for your user account.
  2. Company — Your user account must be the creator of a company.
  3. Company Approval — Your company must have been approved by an administrator.
  4. Company Verification — Your company must be verified.
  5. Sender ID — Register a sender name under your company (1–11 alphanumeric characters).
  6. Sender ID Approval — The sender ID must be approved before it can be used to send messages.

Authentication

All requests must include a valid Bearer token in the Authorization header. Unauthenticated requests will receive a 401 Unauthorized response.

Getting Your API Key

  1. Sign in to your FortWave dashboard
  2. Navigate to Settings → API Keys
  3. Click "Generate New API Key"
  4. Copy and securely store your API key
Authorization Header
Authorization: Bearer {your_api_token}

Keep the token on your server and out of source control. The examples on this page read it from the FORTWAVE_API_KEY environment variable.

Endpoints

Three endpoints send SMS. Each section below is self-contained: headers, parameters, code samples, and every response you can get back.

EndpointUse it when
POST/One message to one recipient, such as an OTP or alert. Also the only endpoint that accepts the MiraiTest test sender.
POST/bulkUp to 500 recipients, each with their own message text.
POST/templateUp to 500 recipients sharing one template, with per-recipient values filled into placeholders.

Send SMS

POSThttps://rest.api.fortwave.io/v1/sms500 req/min

Send one SMS to one recipient. Use it for transactional messages such as OTPs and alerts, and for test traffic with the MiraiTest sender.

Good to know

  • Phone numbers are accepted in local or international format (61000000 or +25761000000) and normalized to E.164. The to field in the response shows the normalized number.
  • Credits are deducted when the message is queued. One credit covers 160 standard (GSM-7) characters, or 153 per part for longer messages. With emoji or other Unicode characters, it covers 70 characters, or 67 per part. item_count tells you how many were used.
  • To try the API without spending credits, send with the MiraiTest sender to your verified test number. See Test environment below.

Headers

HeaderValueDescription
AuthorizationBearer {token}Your API token.
Content-Typeapplication/jsonThe body is sent as JSON.
Acceptapplication/jsonEnsures errors are returned as JSON.

Body parameters

ParameterTypeRequiredDescription
phone_numberstringYesRecipient number in local or international format. Must be a valid number for your company's country. With MiraiTest, it must be your verified test number.
senderstringYesName of an approved sender ID on your company, 1–11 letters or digits. Use MiraiTest for test sends.
messagestringYesMessage text, up to 600 characters.

Test environment

Send real test messages to your own handset through this same endpoint, without spending company credits.

  • Test sender name. Use the fixed sender MiraiTest in the sender field. It is separate from your registered production sender IDs and only works on this endpoint.
  • Verified test phone number. Register one test number under SMS Configurations, then confirm it with the 6-digit code we text you. Only that number can receive test messages. The verification SMS does not count against your test quota.
  • No company needed. Test sends are tied to your user account, so you can try the API before your company is created or approved.
  • Quotas. Each account gets 5 test credits (counted like regular SMS parts). After verifying a number, you can replace it twice; each change requires verifying the new number again.

Manage your test number and verification in the dashboard: SMS Configurations

Request

Set FORTWAVE_API_KEY in your environment, then run:

curl -X POST https://rest.api.fortwave.io/v1/sms \
  -H "Authorization: Bearer $FORTWAVE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "phone_number": "+25761000000",
    "sender": "MyApp",
    "message": "Your verification code is 123456."
  }'

Responses

Every response is JSON. Select a status code to see when it happens and what the body contains.

The message was accepted and queued for delivery.

200 OK
{
  "id": "01JQ8Z5N2W4K7V3T9R6B1M0XHC",
  "item_count": 1,
  "remaining_items": 9999,
  "status": "queued",
  "to": "+25761000000",
  "sender": "MyApp",
  "message": "Message queued for sending"
}

Response fields

FieldTypeDescription
idstringMessage ID (ULID).
item_countintegerCredits (SMS parts) charged for this message.
remaining_itemsintegerYour company's credit balance after this message. With MiraiTest, your remaining test credits instead.
statusstringAlways queued.
tostringRecipient number in E.164 format.
senderstringThe sender from your request.
messagestringHuman-readable confirmation, localized.
A 200 means the request was accepted and queued, not delivered. Delivery results arrive later: Message Lifecycle · Webhooks

Send bulk SMS

POSThttps://rest.api.fortwave.io/v1/sms/bulk500 req/min

Send different messages to up to 500 recipients in one request, all from the same sender. Use it when every recipient gets their own text.

Good to know

  • All or nothing: if any item fails validation or a business rule, nothing is queued and no credits are used. Fix the request and send the whole batch again.
  • Every recipient must be in the same country as the carrier of your sender ID.
  • Each message is charged by its own length. item_count is the total for the batch.
  • The MiraiTest sender is not accepted here. Use Send SMS for test traffic.

Headers

HeaderValueDescription
AuthorizationBearer {token}Your API token.
Content-Typeapplication/jsonThe body is sent as JSON.
Acceptapplication/jsonEnsures errors are returned as JSON.

Body parameters

ParameterTypeRequiredDescription
senderstringYesName of an approved sender ID on your company, 1–11 letters or digits.
messagesarrayYesList of 1 to 500 messages.
messages[].phone_numberstringYesRecipient number in local or international format. Must be a valid number for your company's country.
messages[].messagestringYesText for this recipient, up to 600 characters.

Request

Set FORTWAVE_API_KEY in your environment, then run:

curl -X POST https://rest.api.fortwave.io/v1/sms/bulk \
  -H "Authorization: Bearer $FORTWAVE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "sender": "MyApp",
    "messages": [
      {
        "phone_number": "+25761000000",
        "message": "Hi Alice, your order has shipped."
      },
      {
        "phone_number": "+25762000000",
        "message": "Hi Bob, your order is ready for pickup."
      }
    ]
  }'

Responses

Every response is JSON. Select a status code to see when it happens and what the body contains.

Every message was accepted and queued as one batch.

200 OK
{
  "batch_id": "01JV5P0Y7B0V3P8NQ2TV6QG5V4",
  "status": "queued",
  "sender": "MyApp",
  "recipients_count": 2,
  "item_count": 2,
  "remaining_items": 9997
}

Response fields

FieldTypeDescription
batch_idstringBatch ID (ULID).
statusstringAlways queued.
senderstringSender used for every message in the batch.
recipients_countintegerNumber of messages queued.
item_countintegerTotal credits charged for the batch.
remaining_itemsintegerYour company's credit balance after the batch.
A 200 means the request was accepted and queued, not delivered. Delivery results arrive later: Message Lifecycle · Webhooks

Send template SMS

POSThttps://rest.api.fortwave.io/v1/sms/template500 req/min

Send one template to up to 500 recipients and fill in each recipient's own values, such as their name or a code.

Good to know

  • All or nothing: if any item fails validation or a business rule, nothing is queued and no credits are used. Fix the request and send the whole batch again.
  • Each recipient's variables must include every placeholder used in the template, or the whole request fails with 400. Extra keys are ignored.
  • Variable values must be strings of up to 255 characters, so send numbers as text ("1234"). null renders as empty text.
  • Credits are calculated on each recipient's rendered message, so longer values can cost more parts.
  • Every recipient must be in the same country as the carrier of your sender ID.
  • The MiraiTest sender is not accepted here. Use Send SMS for test traffic.

Headers

HeaderValueDescription
AuthorizationBearer {token}Your API token.
Content-Typeapplication/jsonThe body is sent as JSON.
Acceptapplication/jsonEnsures errors are returned as JSON.

Body parameters

ParameterTypeRequiredDescription
senderstringYesName of an approved sender ID on your company, 1–11 letters or digits.
templatestringYesMessage text with placeholders, up to 600 characters.
recipientsarrayYesList of 1 to 500 recipients.
recipients[].phone_numberstringYesRecipient number in local or international format. Must be a valid number for your company's country.
recipients[].variablesobjectYesPlaceholder values for this recipient, keyed by placeholder name.
recipients[].variables.<name>string | nullYesValue for one placeholder, up to 255 characters.

Placeholder syntax

  • Wrap a variable name in double curly braces: {{name}}
  • Names start with a letter or underscore, followed by letters, digits, or underscores.
  • Spaces inside the braces are allowed: {{ name }}

Request

Set FORTWAVE_API_KEY in your environment, then run:

curl -X POST https://rest.api.fortwave.io/v1/sms/template \
  -H "Authorization: Bearer $FORTWAVE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "sender": "MyApp",
    "template": "Hello {{name}}, your code is {{code}}.",
    "recipients": [
      {
        "phone_number": "+25761000000",
        "variables": {
          "name": "Alice",
          "code": "1234"
        }
      },
      {
        "phone_number": "+25762000000",
        "variables": {
          "name": "Bob",
          "code": "5678"
        }
      }
    ]
  }'

Responses

Every response is JSON. Select a status code to see when it happens and what the body contains.

Every rendered message was accepted and queued as one batch.

200 OK
{
  "batch_id": "01JS9M8AN9T25FE0J8QWWHE6BV",
  "status": "queued",
  "sender": "MyApp",
  "recipients_count": 2,
  "item_count": 2,
  "remaining_items": 9997
}

Response fields

FieldTypeDescription
batch_idstringBatch ID (ULID).
statusstringAlways queued.
senderstringSender used for every message in the batch.
recipients_countintegerNumber of messages queued.
item_countintegerTotal credits charged for the batch.
remaining_itemsintegerYour company's credit balance after the batch.
A 200 means the request was accepted and queued, not delivered. Delivery results arrive later: Message Lifecycle · Webhooks

Delivery status

Every message queued by any of the endpoints above follows the same lifecycle and reports its outcome through the same webhooks.

Message Lifecycle

Once a message is accepted, it progresses through the following statuses:

StatusDescription
pendingMessage has been created and queued for delivery to the carrier.
sentCarrier has accepted and sent the message.
failedMessage delivery failed at the carrier level.

The phone number is stored in E.164 format (e.g. +25761000000) regardless of the input format provided.

Webhooks

If your company has an active webhook endpoint, FortWave sends a POST request to it each time a message changes status.

Request headers

HeaderDescription
X-Webhook-EventThe event name (e.g. sms.sent, sms.failed).
X-Webhook-SignatureHex-encoded HMAC-SHA256 of the raw request body, signed with your webhook endpoint's secret. Recompute it on your side to confirm the request came from FortWave.

Supported Events

EventTriggered When
sms.sentThe carrier confirms the message was sent.
sms.failedThe carrier reports a delivery failure.

Webhook Payload

The body is JSON (Content-Type: application/json):

Webhook Payload
{
  "event": "sms.sent",
  "timestamp": "2026-03-26T12:00:00+00:00",
  "data": {
    "message_id": 42,
    "phone_number": "+25761000000",
    "status": "sms.sent",
    "sent_at": "2026-03-26T12:00:00+00:00",
    "failed_at": null
  }
}

Deliveries Retry Policy

Failed webhook deliveries are retried up to 5 times with exponential backoff. After all retries are exhausted, the delivery is marked as permanently failed and no further retries are attempted.

AttemptDelay
110 seconds
230 seconds
360 seconds
45 minutes
515 minutes