Developers
SMS API reference
Send SMS from your own stack with a single REST endpoint. Authenticate with a bearer token, post JSON, and receive delivery status through webhooks.
Quick Start
Base URL
https://rest.api.fortwave.io/v1All API requests must be made over HTTPS.
Code Examples
Ready-to-use code snippets for sending SMS in popular programming languages.
const axios = require('axios');
async function sendSMS() {
const response = await axios.post(
'https://rest.api.fortwave.io/v1/sms',
{
phone_number: '+25761000000',
sender: 'MyApp',
message: 'Hello! Your verification code is 123456.'
},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
);
console.log(response.data);
}
sendSMS();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.
- 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}Send SMS
Send a single SMS message to a phone number.
/smsHeaders
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Content-Type | application/json | Yes |
Accept | application/json | Yes |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone_number | string | Yes | Recipient's phone number. Must be a valid Burundi (+257) number. Accepted in local or international format (e.g. +25761000000 or 61000000). |
| sender | string | Yes | Sender name (1β11 alphanumeric characters). Must correspond to an approved Sender ID belonging to your company. |
| message | string | Yes | Message body. Maximum 600 characters. |
Example Request
curl -X POST https://rest.api.fortwave.io/v1/sms \
-H "Authorization: Bearer {your_api_token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"phone_number": "+25761000000",
"sender": "MyApp",
"message": "Hello! Your verification code is 123456."
}'Example Response
{
"id": "01AN4Z07BY79KA1307SR9X4MV3",
"item_count": 1,
"remaining_items": 9999,
"status": "queued",
"to": "+25769803000",
"sender": "Fortwave",
"message": "Message queued for sending"
}Send Bulk SMS
Queue multiple SMS messages in one request using a single sender and a per-recipient message body. Bulk requests support up to 500 recipients and follow fail-fast behavior: if any business rule fails, no messages are queued and no credits are deducted.
/sms/bulkHeaders
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Content-Type | application/json | Yes |
Accept | application/json | Yes |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sender | string | Yes | Sender ID (1-11 alphanumeric characters). Must match an approved sender under your company. |
| messages | array | Yes | Array of recipient payloads. Minimum 1 item, maximum 500 items. |
| messages[].phone_number | string | Yes | Recipient number for this item. Must be a valid Burundi (+257) number. |
| messages[].message | string | Yes | Message body for this recipient. Maximum 600 characters. |
Bulk endpoint notes
- Maximum 500 recipients per request.
- Bulk send does not support the MiraiTest sender. Use the single-message endpoint for test traffic.
Example Request
curl -X POST https://rest.api.fortwave.io/v1/sms/bulk \
-H "Authorization: Bearer {your_api_token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"sender": "MyApp",
"messages": [
{ "phone_number": "+25761000000", "message": "Hi Alice" },
{ "phone_number": "+25762000000", "message": "Hi Bob" }
]
}'Example Response
{
"batch_id": "01JV5P0Y7B0V3P8NQ2TV6QG5V4",
"status": "queued",
"sender": "MyApp",
"recipients_count": 2,
"item_count": 2,
"remaining_items": 498
}Response Fields
| Field | Value |
|---|---|
batch_id | ULID of the created batch record. |
status | Always queued when the request succeeds. |
sender | Sender used for the full batch. |
recipients_count | Number of recipients in this batch. |
item_count | Total SMS credits consumed by the batch. |
remaining_items | Company SMS credits remaining after queuing. |
Validation Error β 422 (Bulk)
Returned for payload shape or field validation errors in sender/messages.
{
"message": "The given data was invalid.",
"errors": {
"messages.0.phone_number": [
"The messages.0.phone_number field must be a valid Burundi (+257) number."
],
"messages.0.message": [
"The messages.0.message field must not exceed 600 characters."
]
}
}| Field | Message |
|---|---|
| sender | The sender must be 1-11 alphanumeric characters. |
| messages | The messages field must contain at least one item. |
| messages | The messages field must not contain more than 500 items. |
| messages[0].phone_number | The messages.0.phone_number field is required. |
| messages[0].phone_number | The messages.0.phone_number field must be a valid Burundi (+257) number. |
| messages[0].message | The messages.0.message field must not exceed 600 characters. |
Bad Request β 400 (Bulk)
Returned for business-rule failures while creating a bulk batch.
| Scenario | Error Message |
|---|---|
| User has no company | You must create a company before sending messages. |
| MiraiTest sender used on bulk endpoint | Bulk send does not support the MiraiTest sender; use the single-message endpoint for test traffic. |
| Company is not approved | Your company must be approved before sending messages. |
| Company is not verified | Your company must be verified before sending messages. |
| Sender name not found on company | Sender not found. |
| Sender ID exists but is not approved | The sender must be approved before sending messages. |
| Recipient country does not match sender telco country | The phone number country (:phoneCountry) does not match the sender ID's telco country (:telcoCountry). |
| Company does not have enough SMS credits | Insufficient SMS credits. This batch requires :credits :unit. |
Credits and Segments
item_count represents the total SMS segments required by all messages in the batch. Longer or Unicode messages can consume multiple credits.
Rate limit: throttle:developers-sms-send (500 requests per minute per authenticated user).
Bulk Code Examples
Ready-to-use snippets for sending a bulk SMS batch in popular languages.
const axios = require('axios');
async function sendBulkSms() {
const response = await axios.post(
'https://rest.api.fortwave.io/v1/sms/bulk',
{
sender: 'MyApp',
messages: [
{ phone_number: '+25761000000', message: 'Hi Alice' },
{ phone_number: '+25762000000', message: 'Hi Bob' }
]
},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
);
console.log(response.data);
}
sendBulkSms();Send Template SMS
Queue one template message to multiple recipients. Each recipient provides values for template placeholders in a variables object.
/sms/templateHeaders
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Content-Type | application/json | Yes |
Accept | application/json | Yes |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sender | string | Yes | Sender ID (1-11 alphanumeric characters). Must match an approved sender under your company. |
| template | string | Yes | Template text with placeholders. Maximum 600 characters. |
| recipients | array | Yes | Array of recipient payloads. Minimum 1 item, maximum 500 items. |
| recipients[].phone_number | string | Yes | Recipient number for this item. Must be a valid Burundi (+257) number. |
| recipients[].variables | object | Yes | Key/value object for placeholder replacements for this recipient. |
| recipients[].variables.* | string | null | Yes | Variable value for each placeholder key. Nullable string, maximum 255 characters. |
Template endpoint notes
- Maximum 500 recipients per request.
- If any recipient misses a required placeholder variable, the entire request fails.
- When placeholder validation fails, no messages are queued and no credits are deducted.
- Extra keys in recipients[].variables are ignored.
- If a variable value is null, it is rendered as an empty string.
- Template bulk send does not support the MiraiTest sender. Use the single-message endpoint for test traffic.
Placeholder Rules
- Use double curly braces in the template for named variables such as name and code.
- Variable names must start with a letter or underscore, then contain letters, numbers, or underscores.
- Whitespace inside placeholder braces is supported.
Example Request
curl -X POST https://rest.api.fortwave.io/v1/sms/template \
-H "Authorization: Bearer {your_api_token}" \
-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" }
}
]
}'Example Response
{
"batch_id": "01JS9M8AN9T25FE0J8QWWHE6BV",
"status": "queued",
"sender": "MyApp",
"recipients_count": 2,
"item_count": 2,
"remaining_items": 498
}Response Fields
| Field | Value |
|---|---|
batch_id | ULID of the created batch record. |
status | Always queued when the request succeeds. |
sender | Sender used for the full batch. |
recipients_count | Number of recipients in this batch. |
item_count | Total SMS credits consumed by the batch. |
remaining_items | Company SMS credits remaining after queuing. |
Validation Error β 422 (Template)
Returned for payload shape or field validation errors in sender/template/recipients.
{
"message": "The given data was invalid.",
"errors": {
"template": [
"The template field is required."
],
"recipients.0.variables.code": [
"The recipients.0.variables.code field must not exceed 255 characters."
]
}
}| Field | Message |
|---|---|
| sender | The sender must be 1-11 alphanumeric characters. |
| template | The template field is required. |
| template | The template field must not exceed 600 characters. |
| recipients | The recipients field must contain at least one item. |
| recipients | The recipients field must not contain more than 500 items. |
| recipients[0].phone_number | The recipients.0.phone_number field is required. |
| recipients[0].phone_number | The recipients.0.phone_number field must be a valid Burundi (+257) number. |
| recipients[0].variables | The recipients.0.variables field is required. |
| recipients[0].variables.code | The recipients.0.variables.code field must not exceed 255 characters. |
Bad Request β 400 (Template)
Returned for business-rule failures while creating a template batch.
| Scenario | Error Message |
|---|---|
| User has no company | You must create a company before sending messages. |
| MiraiTest sender used on template endpoint | Bulk send does not support the MiraiTest sender; use the single-message endpoint for test traffic. |
| Company is not approved | Your company must be approved before sending messages. |
| Company is not verified | Your company must be verified before sending messages. |
| Sender name not found on company | Sender not found. |
| Sender ID exists but is not approved | The sender must be approved before sending messages. |
| Recipient country does not match sender telco country | The phone number country (:phoneCountry) does not match the sender ID's telco country (:telcoCountry). |
| Company does not have enough SMS credits | Insufficient SMS credits. This batch requires :credits :unit. |
| A recipient is missing required template variables | Recipient #1 is missing required template variables: code. |
Credits and Segments
item_count represents the total SMS segments required by all rendered messages in the batch. Longer or Unicode messages can consume multiple credits.
Rate limit: throttle:developers-sms-send (500 requests per minute per authenticated user).
Template SMS Code Examples
Ready-to-use snippets for sending a template SMS batch in popular languages.
const axios = require('axios');
async function sendTemplateSms() {
const response = await axios.post(
'https://rest.api.fortwave.io/v1/sms/template',
{
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' }
}
]
},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
);
console.log(response.data);
}
sendTemplateSms();Test environment
FortWave provides a controlled way to exercise the same SMS API against a dedicated test sender and your own verified test handset. Test sends follow the same POST /sms contract as production, with the rules below.
- Company. You must create a company before you can send test messages through the API. Company verification by an administrator is not required for test sends.
- Verified test phone number. Register one Burundi test number under SMS Configurations, then complete SMS verification (6-digit OTP). Only that verified line is eligible to receive test API messages. Verification SMS for setup does not consume your test message quota.
- Test sender name. Use the fixed sender
MiraiTestin the sender field for test sends. It is separate from your registered production sender IDs. - Quotas and changes. Test traffic draws from a small per-account pool of SMS segments (not your standard company SMS balance). After your number is verified, you may only replace it a limited number of timesβeach change clears verification until you confirm the new number again.
Typical limits (subject to platform policy)
| Limit | Details |
|---|---|
| Test SMS segments | Small fixed pool per account (e.g. five segments); counts test API deliveries, not OTP setup messages. |
| Verified number updates | Limited number of times you may save a different test number after the previous one was verified. |
Manage your test number and OTP flow in the dashboard: SMS Configurations
Error Responses
Validation Error β 422
Returned when one or more fields fail validation.
{
"message": "The phone number must be a valid Burundi (+257) number.",
"errors": {
"phone_number": [
"The phone number must be a valid Burundi (+257) number."
]
}
}| Field | Message |
|---|---|
| phone_number | The phone number is required. |
| phone_number | The phone number must be a valid Burundi (+257) number. |
| sender | The sender is required. |
| sender | The sender must be 1β11 alphanumeric characters. |
| message | The message content is required. |
| message | The message must not exceed 600 characters. |
Bad Request β 400
Returned when a business rule prevents message sending.
| Scenario | Error Message |
|---|---|
| User has no company | You must create a company before sending messages. |
| Company is not approved | Your company must be approved before sending messages. |
| Sender name not found on company | Sender not found. |
| Sender ID exists but is not approved | The sender must be approved before sending messages. |
{
"message": "Your company must be approved before sending messages."
}Unauthorized β 401
{
"message": "Unauthenticated."
}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 configured, FortWave will send real-time status notifications as your message progresses. Each webhook request includes the following custom header:
| Header | Description |
|---|---|
X-Webhook-Event | The event name (e.g. sms.sent, sms.failed). |
Supported Events
| Event | Triggered When |
|---|---|
sms.sent | The carrier confirms the message was sent. |
sms.failed | The carrier reports a delivery failure. |
Webhook Payload
Webhook payloads are sent as POST requests with a Content-Type: application/json body:
{
"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 |