API reference
Send WhatsApp messages with a single HTTP request. Messages are accepted immediately, queued, and delivered as soon as capacity allows โ your request never blocks waiting on delivery.
Base URL
https://smsgateway.swiftcoder.in/api/v1
All paths below are relative to this. Requests and responses are JSON;
send Content-Type: application/json on anything with a body.
Authentication
Every request carries an API key. Pass it as a bearer token:
Authorization: Bearer sk_a1b2c3d4_your_key_here
Or, if a bearer header is inconvenient, as a dedicated header:
X-API-Key: sk_a1b2c3d4_your_key_here
messages:send to send, messages:read to read
back. A key without the right scope gets 403.
A key is a bearer credential: anyone holding it can send messages at your expense. Keep it server-side โ never in a browser, mobile binary, or public repository.
The 24-hour window
WhatsApp does not let a business open a conversation with free text. Which of the two shapes you need depends on one thing:
| Situation | What to send |
|---|---|
| The recipient messaged you in the last 24 hours | Free text โ a body |
| Anything else: a notification, an OTP, an order update | An approved template |
body will be rejected. Send a template and the same call
works whether the window is open or not.
Templates are written and approved in your WhatsApp Business account before you can use them. This API sends one by name; it does not create or submit them.
Send a message
POST /messages
Parameters
| Field | Type | Description |
|---|---|---|
| to | string, required | Recipient number in E.164 (+919140327455), country
code included โ spaces, dashes and brackets are accepted and
stripped. |
| body | string | Free message text. Only reaches someone inside the
24-hour window. Required unless you send a
template. |
| template | object | An approved template to send instead of body โ see
below. |
| clientRef | string, optional | Your own reference for this message. Makes the request safe to retry โ see Idempotency. |
| senderId | string, optional | Send from one specific number of yours. Omit and any of them may
carry it; list them with GET /senders. |
Request
curl -X POST https://smsgateway.swiftcoder.in/api/v1/messages \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919140327455",
"body": "Thanks โ your driver is two minutes away.",
"clientRef": "trip-8842"
}'
const res = await fetch("https://smsgateway.swiftcoder.in/api/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+919140327455",
body: "Thanks โ your driver is two minutes away.",
clientRef: "trip-8842",
}),
});
if (!res.ok) throw new Error(`send failed: ${res.status}`);
const message = await res.json();
console.log(message.id, message.status);
import os, requests
res = requests.post(
"https://smsgateway.swiftcoder.in/api/v1/messages",
headers={"Authorization": f"Bearer {os.environ['WA_API_KEY']}"},
json={
"to": "+919140327455",
"body": "Thanks โ your driver is two minutes away.",
"clientRef": "trip-8842",
},
timeout=10,
)
res.raise_for_status()
message = res.json()
print(message["id"], message["status"])
$ch = curl_init("https://smsgateway.swiftcoder.in/api/v1/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("WA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "+919140327455",
"body" => "Thanks โ your driver is two minutes away.",
"clientRef" => "trip-8842",
]),
]);
$message = json_decode(curl_exec($ch), true);
echo $message["id"], " ", $message["status"];
Response 202 Accepted
{
"id": "6a9720e14e2f3b1b37d311d6",
"to": "+919140327455",
"body": "Thanks โ your driver is two minutes away.",
"template": null,
"channel": "whatsapp",
"status": "queued",
"clientRef": "trip-8842",
"senderId": null,
"providerMessageId": null,
"attempts": 0,
"lastError": null,
"lastErrorCode": null,
"createdAt": "2026-09-01T19:01:02.627Z",
"sentAt": null,
"deliveredAt": null,
"readAt": null,
"failedAt": null
}
202 means accepted for delivery, not delivered. Keep the
id and poll it if you need the outcome.
Send a template
Replace body with a template object. Send one or
the other โ both together is a 400.
| Field | Type | Description |
|---|---|---|
| template.name | string, required | The approved template's name, exactly as it appears in your WhatsApp Business account. |
| template.language | string | Language code of the approved copy, e.g. en_US.
Defaults to en_US. |
| template.components | array, optional | Values for the template's placeholders, in WhatsApp's component format. Omit for a template that takes none. |
curl -X POST https://smsgateway.swiftcoder.in/api/v1/messages \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919140327455",
"clientRef": "signup-8842",
"template": {
"name": "verification_code",
"language": "en_US",
"components": [
{
"type": "body",
"parameters": [{ "type": "text", "text": "123456" }]
}
]
}
}'
The message text comes from the approved template, so the response's
body is null and template names
what was sent. A template that is not approved, or whose parameters do
not match the approved copy, fails with the reason in
lastError.
Send in bulk
POST /messages/bulk
Up to 500 messages per request. Each entry takes the same fields as a single send and is validated on its own, so one bad number does not reject the batch.
curl -X POST https://smsgateway.swiftcoder.in/api/v1/messages/bulk \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "to": "+919140327455", "template": { "name": "order_shipped" } },
{ "to": "not-a-number", "template": { "name": "order_shipped" } }
]
}'
Response 207 Multi-Status
{
"results": [
{ "index": 0, "status": "accepted", "message": { "id": "6a97โฆ", "status": "queued" } },
{ "index": 1, "status": "rejected", "error": "invalid_recipient",
"message": "`to` must be a phone number in E.164 form (+15551234567), including the country code." }
]
}
Always 207, even if every entry succeeded. Read each result
by its index, which matches the order you sent.
Check a message
GET /messages/:id
Returns the message with its current state and timestamps.
curl https://smsgateway.swiftcoder.in/api/v1/messages/6a9720e14e2f3b1b37d311d6 \ -H "Authorization: Bearer $WA_API_KEY"
{
"id": "6a9720e14e2f3b1b37d311d6",
"to": "+919140327455",
"status": "read",
"attempts": 1,
"lastError": null,
"lastErrorCode": null,
"sentAt": "2026-09-01T19:01:02.627Z",
"deliveredAt": "2026-09-01T19:01:04.133Z",
"readAt": "2026-09-01T19:02:11.980Z",
"failedAt": null
}
A message you did not send with this key returns 404.
List messages
GET /messages
Your key's messages, newest first.
| Query | Description |
|---|---|
| status | Filter by state, e.g. failed |
| limit | 1โ200, default 50 |
| before | Pass the previous response's nextBefore to page back |
curl "https://smsgateway.swiftcoder.in/api/v1/messages?status=failed&limit=20" \ -H "Authorization: Bearer $WA_API_KEY"
{
"data": [ { "id": "6a97โฆ", "to": "+919140327455", "status": "failed" } ],
"nextBefore": "6a97โฆ"
}
nextBefore is null on the last page. Paging is
by cursor, so new messages arriving mid-scan will not shift your results.
Your numbers
GET /senders
The numbers your messages can go out from. Use an id from
here as senderId to pin a message to one of them.
{
"data": [
{ "id": "6a97โฆ", "name": "Support line", "phoneNumber": "+15551234567",
"channels": ["whatsapp"], "lastUsedAt": "2026-09-01T19:01:02.627Z" }
]
}
Delivery states
A message moves forward through these states and never backwards.
| State | Meaning |
|---|---|
| queued | Accepted and waiting for capacity. |
| dispatched | Being sent right now. |
| sent | WhatsApp has taken the message. |
| delivered | It reached the recipient's phone. |
| read | They opened it. Only reported if they have read receipts on. |
| failed | Terminal. lastError says why, and
lastErrorCode is WhatsApp's own numeric code. |
delivered as success. read depends
on a setting the recipient controls, so plenty of messages that were read
never report it. A message resting at sent usually means the
recipient's phone has not been online since.
Transient failures โ a rate limit, a moment when WhatsApp is unreachable โ are retried automatically up to 3 times, spaced out so each attempt gets a fresh chance. A permanent refusal, such as a number that is not on WhatsApp, fails immediately rather than burning retries on an answer that will not change. You do not need to implement retries yourself.
Idempotency
Network timeouts leave you unsure whether a message was accepted.
Resending blindly risks a duplicate. Send a clientRef โ any
string unique to that message in your own system โ and a repeat of the
same request returns the original message instead of sending a second one.
{ "to": "+919140327455", "template": { "name": "verification_code" }, "clientRef": "signup-8842" }
Scoped to your API key, so your references cannot collide with anyone else's. The response is the first message โ including its original content โ so changed content under a reused reference is not sent.
Errors
Failures return the matching HTTP status and a JSON body with a stable
error code and a human-readable message. Branch
on the code, not the text.
{ "error": "invalid_recipient", "message": "`to` must be a phone number in E.164 form (+15551234567), including the country code." }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_recipient | Missing or unparseable to |
| 400 | invalid_body | Neither body nor template, or both at once |
| 400 | invalid_template | Malformed template name, language or components |
| 400 | invalid_sender | Unknown senderId |
| 400 | invalid_channel | Unknown channel requested |
| 400 | too_many_messages | More than 500 in one bulk request |
| 401 | missing_api_key | No key on the request |
| 401 | invalid_api_key | Unknown or revoked key |
| 403 | insufficient_scope | Key lacks the required scope |
| 404 | not_found | No such message under this key |
| 410 | channel_unavailable | The retired sms channel |
A 4xx will not succeed on retry โ fix the request. A
5xx is safe to retry, and safest with a
clientRef.
Health
GET /health
Unauthenticated liveness check, suitable for a monitor.
curl https://smsgateway.swiftcoder.in/api/v1/health
{ "ok": true, "uptime": 1284.51 }