BulkieMsg API Reference
bulkiemsg.com/api/v1 Dashboard →
REST API · v1

API Reference

Send SMS and WhatsApp messages, check your credit balance, and manage sender identities from any application.

HTTPS only JSON responses API key auth

All requests go to the base URL:

Base URL
https://bulkiemsg.com/api/v1
ℹ️ Every response is JSON. Every request body must be JSON with Content-Type: application/json. Successful responses include "success": true. Errors include "error".

Authentication

Every request must include your API key. Generate one from your dashboard under Settings → API Keys.

Authorization header (preferred)
Authorization: Bearer bm_live_your_key_here
X-API-Key header (alternative)
X-API-Key: bm_live_your_key_here
⚠️ Keep your API key secret. Don't commit it to source control or expose it in client-side code. The API does not allow requests from web pages (no CORS) on purpose: call it from your server.

Keys look like bm_live_ followed by 64 characters. You can have up to 3 keys, each with its own name, access level and optional IP allowlist, so every app gets its own key and one leak never forces you to change them all.

Security

The API is built so that a mistake on one side does not become a loss on the other.

Keys are shown once

We store only a scrambled (SHA-256) copy of your key, so even we cannot read it back. If you lose it, create a new one and revoke the old one.

IP allowlist

Limit a key to your server's IP address (or a range like 203.0.113.0/24). Requests from anywhere else get 403, even with the right key.

Read-only keys

Give apps that only check balance, delivery status or Sender IDs a Read-only key. It cannot send messages or spend credits.

Rate limits and lockout

Every key is limited per minute, and an address that sends many wrong keys is locked out for a few minutes. See Rate Limits.

Activity log

Your dashboard (API Settings) lists the last 30 requests made with your keys, with time, result and IP address. Revoke any key you do not recognise.

Safe retries

Send an Idempotency-Key so a retried request can never send, or charge, twice. See Safe Retries.

Checklist

  • Keep the key in an environment variable or secret store, never in code you commit.
  • Use one key per app and name it, so you know which one to revoke.
  • Add your server's IP address to the allowlist.
  • Use Read-only keys wherever sending is not needed.
  • Always call the API over HTTPS, so your key is never sent in the clear.

Quick Start

Get your API key

Go to Settings → API Keys in your dashboard and click Generate Key. Save it — it's shown once.

Get a Sender ID approved

Go to SMS → Sender IDs and request your brand name as a Sender ID. You can't send without one.

Send your first message

bash
curl -X POST https://bulkiemsg.com/api/v1/sms/send \
  -H "Authorization: Bearer bm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "YourBrand",
    "message":   "Hello from BulkieMsg!",
    "to":        ["233201234567"]
  }'
php
$ch = curl_init('https://bulkiemsg.com/api/v1/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer bm_live_YOUR_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'sender_id' => 'YourBrand',
        'message'   => 'Hello from BulkieMsg!',
        'to'        => ['233201234567'],
    ]),
]);
$result = json_decode(curl_exec($ch), true);
python
import requests

r = requests.post(
    "https://bulkiemsg.com/api/v1/sms/send",
    headers={"Authorization": "Bearer bm_live_YOUR_KEY"},
    json={
        "sender_id": "YourBrand",
        "message":   "Hello from BulkieMsg!",
        "to":        ["233201234567"],
    }
)
print(r.json())
javascript
const res = await fetch('https://bulkiemsg.com/api/v1/sms/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer bm_live_YOUR_KEY',
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    sender_id: 'YourBrand',
    message:   'Hello from BulkieMsg!',
    to:        ['233201234567'],
  }),
});
console.log(await res.json());

Send SMS

POST /api/v1/sms/send Send single or bulk SMS ▼

Send a message to one number or thousands. Credits are deducted at 1 per SMS page per recipient (160 chars = 1 page).

Headers

HeaderTypeDescription
AuthorizationstringrequiredBearer bm_live_… with a Full access key.
Idempotency-KeystringrecommendedA unique value per message you intend to send (8 to 80 characters). A retry with the same value is answered with the first result and is not sent or charged again. Details

Parameters

ParameterTypeDescription
sender_idstringrequiredYour approved Sender ID (e.g. MyBrand).
messagestringrequiredThe SMS body. Over 160 chars splits into multiple pages.
tostring | arrayrequiredOne phone number or an array. Accepts 233XXXXXXXXX or local 0XXXXXXXXX — normalised automatically.
bash
curl -X POST https://bulkiemsg.com/api/v1/sms/send \
  -H "Authorization: Bearer bm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "MyBrand",
    "message":   "Your OTP is 482910. Valid 5 minutes.",
    "to":        ["233201234567", "233271234567"]
  }'
php
$ch = curl_init('https://bulkiemsg.com/api/v1/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer bm_live_YOUR_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'sender_id' => 'MyBrand',
        'message'   => 'Your OTP is 482910.',
        'to'        => ['233201234567', '233271234567'],
    ]),
]);
$res = json_decode(curl_exec($ch), true);
python
import requests

r = requests.post(
    "https://bulkiemsg.com/api/v1/sms/send",
    headers={"Authorization": "Bearer bm_live_YOUR_KEY"},
    json={
        "sender_id": "MyBrand",
        "message":   "Your OTP is 482910.",
        "to":        ["233201234567", "233271234567"],
    }
)
print(r.json())
javascript
const res = await fetch('https://bulkiemsg.com/api/v1/sms/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer bm_live_YOUR_KEY',
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    sender_id: 'MyBrand',
    message:   'Your OTP is 482910.',
    to:        ['233201234567', '233271234567'],
  }),
});
console.log(await res.json());

Responses

200 Success
json
{ "success": true, "message": "Queued 2 recipient(s) for delivery.", "campaign_id": 1042, "recipients": 2, "status": "queued" }
402 Insufficient credits
json
{ "success": false, "error": "Insufficient credits. Need 5, have 2." }

Check Balance

GET /api/v1/sms/balance Get SMS credit balance ▼

Returns the current SMS credit balance. No request body needed.

bash
curl https://bulkiemsg.com/api/v1/sms/balance \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
php
$ch = curl_init('https://bulkiemsg.com/api/v1/sms/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer bm_live_YOUR_KEY'],
]);
$res = json_decode(curl_exec($ch), true);
python
import requests
r = requests.get(
    "https://bulkiemsg.com/api/v1/sms/balance",
    headers={"Authorization": "Bearer bm_live_YOUR_KEY"}
)
print(r.json())
javascript
const res = await fetch('https://bulkiemsg.com/api/v1/sms/balance', {
  headers: { 'Authorization': 'Bearer bm_live_YOUR_KEY' }
});
console.log(await res.json());
200 Success
json
{ "success": true, "sms_credits": 500 }

Sender IDs

All SMS must be sent with an approved Sender ID. Submit one for review — approval typically takes up to 48 hours.

POST /api/v1/sms/sender_ids Request new Sender ID ▼
ParameterTypeDescription
sender_idstringrequired3–11 alphanumeric characters, no spaces. e.g. MyBrand.
bash
curl -X POST https://bulkiemsg.com/api/v1/sms/sender_ids \
  -H "Authorization: Bearer bm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sender_id": "MyBrand" }'
201 Created
json
{ "success": true, "message": "Sender ID submitted for review." }
GET /api/v1/sms/sender_ids List Sender IDs ▼

Returns all your Sender IDs and their status: pending, approved, or rejected.

bash
curl https://bulkiemsg.com/api/v1/sms/sender_ids \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 Success
json
{
  "success": true,
  "sender_ids": [
    { "id": 42, "sender_id": "MyBrand", "status": "approved" }
  ]
}

WhatsApp — Send Message

ℹ️ Requires a linked device. See Link Device to connect your phone first.
POST /api/v1/whatsapp/send Send WhatsApp message ▼

Sends a plain text WhatsApp message via your connected device. Costs 1 credit per recipient.

ParameterTypeDescription
tostring | arrayrequiredPhone number(s). Use international format: 233XXXXXXXXX.
messagestringrequiredMessage text. Max 4096 characters.
bash
curl -X POST https://bulkiemsg.com/api/v1/whatsapp/send \
  -H "Authorization: Bearer bm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to":      "233201234567",
    "message": "Your appointment is tomorrow at 10am."
  }'
200 Success
json
{ "success": true, "message": "Message sent to 1 recipient(s)." }

WhatsApp — Link Device

POST /api/v1/whatsapp/session Get QR code to link phone ▼

Returns a QR code you scan with WhatsApp on your phone to link it. Expires in 60 seconds.

⚠️ One active session per account. Calling this again resets the existing session.
bash
curl -X POST https://bulkiemsg.com/api/v1/whatsapp/session \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 Success
json
{
  "success":    true,
  "qr_code":    "data:image/png;base64,...",
  "expires_in": 60
}

Safe Retries

If your request times out you cannot tell whether the messages were sent. Retrying blindly could send them twice. Add an Idempotency-Key header to POST /sms/send and a retry returns the first answer instead of sending again.

example
curl -X POST https://bulkiemsg.com/api/v1/sms/send \
  -H "Authorization: Bearer bm_live_your_key_here" \
  -H "Idempotency-Key: order-1001-confirmation" \
  -H "Content-Type: application/json" \
  -d '{"sender_id":"MyBrand","to":"233201234567","message":"Your order is confirmed"}'
  • Use one new value per message you mean to send, such as an order number or a UUID. Letters, numbers and . _ : -, 8 to 80 characters.
  • A repeat with the same key and the same body returns the original response with the header Idempotent-Replay: true. Nothing is sent or charged.
  • The same key with a different body returns 422. A repeat while the first request is still running returns 409.
  • A request that failed (for example 402 not enough credits) frees its key, so you can retry it after fixing the problem.
  • Keys are remembered for 24 hours.

Rate Limits

Limits are counted per API key, per minute:

Type of requestLimit
Sending (/sms/send, /whatsapp/send, device linking)30 per minute
Reading (/sms/balance, /sms/status, /sms/sender_ids)120 per minute
Wrong or revoked keys, from one IP address20 per 10 minutes, then locked out for the rest of the window

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. When you go over, you get 429 with a Retry-After header (seconds). Wait that long, then retry.

✅ Bulk campaigns count as a single request regardless of recipient count. Sending to 10,000 numbers uses just 1 of your 30 requests.
429 response
{ "success": false, "error": "Rate limit exceeded (30 requests per minute). Retry in 23s." }

Errors & Codes

All errors return "success": false and an error string. Use the HTTP status code to categorise the failure.

CodeMeaningCommon cause
200OKRequest succeeded.
400Bad RequestMissing or invalid parameter.
401UnauthorizedAPI key missing, invalid, or revoked.
402Payment RequiredNot enough SMS credits.
403ForbiddenRead-only key used to send, your IP address is not on the key's allowlist, or the account is suspended.
404Not FoundResource doesn't exist.
405Method Not AllowedWrong HTTP verb.
422UnprocessableValidation failed, or an Idempotency-Key was reused with a different request.
409ConflictA request with the same Idempotency-Key is still being processed.
429Rate LimitedRate limit reached, or too many wrong keys from your IP address. Wait for Retry-After seconds.
500Server ErrorInternal error. Contact support.
503UnavailableSending is temporarily paused. Nothing was charged; retry later.
502Gateway ErrorSMS gateway unreachable. Credits not deducted.

Common error messages

examples
// Missing key
{ "success": false, "error": "API key is required." }

// Not enough credits
{ "success": false, "error": "Insufficient credits. Need 10, have 3." }

// Sender ID not approved
{ "success": false, "error": "Sender ID 'MyBrand' is not approved." }