Tutorial · SMS
Send your first SMS with the REST API
Create an API key, find your template ID and send a DLT-compliant SMS with one cURL call, then check its delivery status.
Pixabits team · · 8 min read

On this page
This tutorial sends one SMS through POST /api/v1/sms/send and then reads back its delivery status. It takes about ten minutes once your DLT details are in the console.
Before you start
- A Pixabits account with SMS enabled and wallet balance. Each send reserves the per-SMS rate from your wallet.
- A PE ID, a sender ID (header) and a content template registered on your operator's DLT portal, and added in the console under SMS → Assets. DLT setup in the console walks through it.
- The template must be linked to the same sender ID you will send from.
1. Create an API key
In the console, open SMS → APIs & SDK and fill in Generate API Key: an API name, a narration (what the key is for), the message category (Transactional or Promotional) and the header the key may send from. Choose Any Sender ID if one key should send from all your headers. Start and end dates are optional; leave the end date blank for no expiry.
The full key is shown once, when it is created, so copy it then. New keys can both send and read status. After creating a key you can edit its expiry, rate limit and allowed IPs from the key list.
Keep the key on your server
Anyone with the key can send SMS at your cost. Never put it in a browser, a mobile app or a public repository.
2. Find your template ID
The API's template_id is the Pixabits template ID (a UUID), not the 19-digit DLT template ID. Pixabits looks up the DLT template ID and PE ID from the stored template when it submits the message. List your approved templates to get the UUID:
curl "https://pixabits.ai/api/v1/sms/templates?api_key=YOUR_API_KEY&category=transactional"{
"success": true,
"data": [
{
"id": "3f6c2a9e-5b1d-4c7a-9e2f-8a1b0c4d6e7f",
"name": "login_otp",
"body": "Your OTP is {#var#}. It is valid for 10 minutes.",
"category": "transactional",
"dlt_template_id": "1707170000000000000",
"sender_header": "MYBRND",
"status": "approved"
}
],
"pagination": { "total": 1, "page": 1, "limit": 50, "pages": 1 }
}Optional filters: category (transactional, promotional, otp, service; comma-separated), status (default approved), search, page and limit (1–100).
3. Send the SMS
The API key goes in the JSON body as api_key. No Authorization header is used.
curl -X POST https://pixabits.ai/api/v1/sms/send \
-H "Content-Type: application/json" \
-d '{
"api_key": "YOUR_API_KEY",
"to": "919876543210",
"sender_id": "MYBRND",
"template_id": "3f6c2a9e-5b1d-4c7a-9e2f-8a1b0c4d6e7f",
"text": "Your OTP is {#var#}. It is valid for 10 minutes.",
"variables": { "var1": "482916" }
}'| Field | Required | What it is |
|---|---|---|
to | Yes | Recipient number with country code, for example 919876543210. A leading + and separators are removed; the result must be 7–15 digits. |
sender_id | Yes | Your registered, active header (up to 11 characters). |
template_id | Yes | The Pixabits template UUID from step 2. The template must be approved and linked to sender_id. |
text | Yes | The message. It must match your DLT template, with the variable parts filled in. message is accepted as an alias. |
variables | No | Values for placeholders in text. DLT {#var#} slots are filled in order from var1, var2, …; named {{name}} placeholders from the matching key. |
shorten_urls | No | Map of variable name → full URL. Each URL becomes a tracked short link and is put into that variable. |
filter_dnd | No | true skips numbers registered on DND (not sent, not charged). Default false. |
priority | No | 0 (default) or 1. |
pe_id | No | Only needed if neither the template nor the header's entity already carries a PE ID. |
A successful call returns 202 Accepted with a job_id. Store it: it is the handle for the status call and for delivery webhooks.
HTTP/1.1 202 Accepted
{
"success": true,
"job_id": "sms_job_3f9a1c2b7d",
"message": "SMS accepted for delivery"
}Two other results return 200 without sending: "status": "opted_out_excluded" when the recipient has replied STOP or UNSUB to you (always skipped), and "status": "dnd_excluded" when filter_dnd is true and the number is on DND. Both have "job_id": null.
Retrying safely
Send an Idempotency-Key header with a unique value per message. If you retry with the same key, you get the first response back instead of a second SMS.
4. Check delivery status
curl "https://pixabits.ai/api/v1/sms/status/sms_job_3f9a1c2b7d?api_key=YOUR_API_KEY"{
"success": true,
"job_id": "sms_job_3f9a1c2b7d",
"status": "DELIVERED",
"operator_message_id": "…",
"to": "919876543210",
"sender_id": "MYBRND",
"submitted_at": "2026-10-23T09:41:07.000Z",
"delivered_at": "2026-10-23T09:41:09.000Z",
"failed_at": null,
"operator": "…"
}| Status | Meaning |
|---|---|
PENDING | Accepted by Pixabits, not yet handed to the operator. |
SUBMITTED | Accepted by the operator; waiting for the delivery report. |
DELIVERED | The operator reported delivery to the handset. |
FAILED | The operator rejected the message or reported a failure. |
EXPIRED | No delivery report arrived in time. A late DELIVERED report still updates the job. |
Polling works for a single message. For production traffic, register a webhook instead: see Track SMS delivery.
Errors and fixes
Errors return { "success": false, "error": "<CODE>", "message": "…" }.
| HTTP | Code | What to check |
|---|---|---|
| 401 | MISSING_TOKEN, INVALID_TOKEN, TOKEN_EXPIRED, TOKEN_NOT_ACTIVE, TOKEN_REVOKED | api_key is in the JSON body, is copied in full and is within its start and end dates. |
| 403 | IP_NOT_ALLOWED, PERMISSION_DENIED | The key's allowed IPs and permissions. |
| 403 | SENDER_NOT_ALLOWED_FOR_KEY, TEMPLATE_NOT_ALLOWED_FOR_KEY | The key is bound to another header, or to a set of templates that excludes this one. |
| 400 | INVALID_NUMBER | to has 7–15 digits after cleaning. |
| 400 | INVALID_TEMPLATE, INVALID_SENDER, TEMPLATE_SENDER_MISMATCH | The template is approved and belongs to your account, the header is active, and the template is registered for that header. |
| 402 | INSUFFICIENT_BALANCE and related | Recharge the wallet, or ask your admin to assign credits. |
| 409 | REQUEST_IN_PROGRESS | A request with the same Idempotency-Key is still running. Retry shortly. |
| 429 | RATE_LIMITED | Too many requests for the key, or to one number with one template. Wait for Retry-After. |
| 500 | INTERNAL_ERROR | Also returned today when the body is not valid JSON, a required field is missing or template_id is not a UUID. Check those first. |


