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/smsAll 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:
- API Token — Generate an API token for your user account.
- Company — Your user account must be the creator of a company.
- Company Approval — Your company must have been approved by an administrator.
- Company Verification — Your company must be verified.
- Sender ID — Register a sender name under your company (1–11 alphanumeric characters).
- 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
- Sign in to your FortWave dashboard
- Navigate to Settings → API Keys
- Click "Generate New API Key"
- Copy and securely store your API key
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.
| Endpoint | Use 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/bulk | Up to 500 recipients, each with their own message text. |
POST/template | Up to 500 recipients sharing one template, with per-recipient values filled into placeholders. |
Send SMS
https://rest.api.fortwave.io/v1/sms500 req/minSend 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
| Header | Value | Description |
|---|---|---|
Authorization | Bearer {token} | Your API token. |
Content-Type | application/json | The body is sent as JSON. |
Accept | application/json | Ensures errors are returned as JSON. |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | Recipient 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. |
sender | string | Yes | Name of an approved sender ID on your company, 1–11 letters or digits. Use MiraiTest for test sends. |
message | string | Yes | Message 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
MiraiTestin 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.
{
"id": "01JQ8Z5N2W4K7V3T9R6B1M0XHC",
"item_count": 1,
"remaining_items": 9999,
"status": "queued",
"to": "+25761000000",
"sender": "MyApp",
"message": "Message queued for sending"
}Response fields
| Field | Type | Description |
|---|---|---|
id | string | Message ID (ULID). |
item_count | integer | Credits (SMS parts) charged for this message. |
remaining_items | integer | Your company's credit balance after this message. With MiraiTest, your remaining test credits instead. |
status | string | Always queued. |
to | string | Recipient number in E.164 format. |
sender | string | The sender from your request. |
message | string | Human-readable confirmation, localized. |
Send bulk SMS
https://rest.api.fortwave.io/v1/sms/bulk500 req/minSend 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
| Header | Value | Description |
|---|---|---|
Authorization | Bearer {token} | Your API token. |
Content-Type | application/json | The body is sent as JSON. |
Accept | application/json | Ensures errors are returned as JSON. |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sender | string | Yes | Name of an approved sender ID on your company, 1–11 letters or digits. |
messages | array | Yes | List of 1 to 500 messages. |
messages[].phone_number | string | Yes | Recipient number in local or international format. Must be a valid number for your company's country. |
messages[].message | string | Yes | Text 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.
{
"batch_id": "01JV5P0Y7B0V3P8NQ2TV6QG5V4",
"status": "queued",
"sender": "MyApp",
"recipients_count": 2,
"item_count": 2,
"remaining_items": 9997
}Response fields
| Field | Type | Description |
|---|---|---|
batch_id | string | Batch ID (ULID). |
status | string | Always queued. |
sender | string | Sender used for every message in the batch. |
recipients_count | integer | Number of messages queued. |
item_count | integer | Total credits charged for the batch. |
remaining_items | integer | Your company's credit balance after the batch. |
Send template SMS
https://rest.api.fortwave.io/v1/sms/template500 req/minSend 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
| Header | Value | Description |
|---|---|---|
Authorization | Bearer {token} | Your API token. |
Content-Type | application/json | The body is sent as JSON. |
Accept | application/json | Ensures errors are returned as JSON. |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sender | string | Yes | Name of an approved sender ID on your company, 1–11 letters or digits. |
template | string | Yes | Message text with placeholders, up to 600 characters. |
recipients | array | Yes | List of 1 to 500 recipients. |
recipients[].phone_number | string | Yes | Recipient number in local or international format. Must be a valid number for your company's country. |
recipients[].variables | object | Yes | Placeholder values for this recipient, keyed by placeholder name. |
recipients[].variables.<name> | string | null | Yes | Value 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.
{
"batch_id": "01JS9M8AN9T25FE0J8QWWHE6BV",
"status": "queued",
"sender": "MyApp",
"recipients_count": 2,
"item_count": 2,
"remaining_items": 9997
}Response fields
| Field | Type | Description |
|---|---|---|
batch_id | string | Batch ID (ULID). |
status | string | Always queued. |
sender | string | Sender used for every message in the batch. |
recipients_count | integer | Number of messages queued. |
item_count | integer | Total credits charged for the batch. |
remaining_items | integer | Your company's credit balance after the batch. |
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:
| Status | Description |
|---|---|
| pending | Message has been created and queued for delivery to the carrier. |
| sent | Carrier has accepted and sent the message. |
| failed | Message 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
| Header | Description |
|---|---|
X-Webhook-Event | The event name (e.g. sms.sent, sms.failed). |
X-Webhook-Signature | Hex-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
| Event | Triggered When |
|---|---|
sms.sent | The carrier confirms the message was sent. |
sms.failed | The carrier reports a delivery failure. |
Webhook Payload
The body is JSON (Content-Type: application/json):
{
"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.
| Attempt | Delay |
|---|---|
| 1 | 10 seconds |
| 2 | 30 seconds |
| 3 | 60 seconds |
| 4 | 5 minutes |
| 5 | 15 minutes |