Send an SMS
POSThttps://api.9bits.net/ng/v1/sendsms
Queues a message for one or more recipients.
Request
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
from | string | ✅ | Your approved sender ID — the name recipients see. Unapproved IDs fail with sender_id_invalid. See Sender IDs. |
to | array | ✅ | Destinations in international format. Strings or integers both accepted; strings preferred. See Phone numbers. |
content | string | ✅ | The message text. |
delivery_report | boolean | — | Request delivery receipts. Defaults to false. |
callback_url | string | — | HTTPS endpoint for receipts. Overrides the account webhook for this batch. |
Headers
Section titled “Headers”| Header | Required | Description |
|---|---|---|
Authorization | ✅ | Bearer YOUR_API_TOKEN |
Content-Type | ✅ | application/json |
Idempotency-Key | — | Makes the request safely retryable. Strongly recommended. |
Examples
Section titled “Examples”curl -X POST https://api.9bits.net/ng/v1/sendsms \ -H "Authorization: Bearer $NINEBITS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "from": "9bits", "to": ["2348020000000", "2348030000000"], "content": "Flash sale: 20% off everything today only.", "delivery_report": true, "callback_url": "https://example.com/webhooks/9bits/dlr" }'import { randomUUID } from 'node:crypto';
async function sendSMS({ to, content, from = '9bits' }) { const res = await fetch('https://api.9bits.net/ng/v1/sendsms', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NINEBITS_TOKEN}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ from, to, content, delivery_report: true, callback_url: 'https://example.com/webhooks/9bits/dlr', }), });
const body = await res.json(); if (!res.ok) { throw new Error(`${body.error.code}: ${body.error.message}`); } return body;}
const batch = await sendSMS({ to: ['2348020000000', '2348030000000'], content: 'Flash sale: 20% off everything today only.',});
console.log(batch.reference_id, batch.cost_estimate.total_cost);import os, uuid, requests
def send_sms(to: list[str], content: str, sender: str = "9bits") -> dict: res = requests.post( "https://api.9bits.net/ng/v1/sendsms", headers={ "Authorization": f"Bearer {os.environ['NINEBITS_TOKEN']}", "Idempotency-Key": str(uuid.uuid4()), }, json={ "from": sender, "to": to, "content": content, "delivery_report": True, "callback_url": "https://example.com/webhooks/9bits/dlr", }, timeout=10, )
if not res.ok: err = res.json()["error"] raise RuntimeError(f"{err['code']}: {err['message']}")
return res.json()
batch = send_sms(["2348020000000"], "Flash sale: 20% off today only.")print(batch["reference_id"], batch["cost_estimate"]["total_cost"])<?phpfunction sendSms(array $to, string $content, string $from = '9bits'): array{ $ch = curl_init('https://api.9bits.net/ng/v1/sendsms'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_TIMEOUT => 10, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('NINEBITS_TOKEN'), 'Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16)), ], CURLOPT_POSTFIELDS => json_encode([ 'from' => $from, 'to' => $to, 'content' => $content, 'delivery_report' => true, ]), ]);
$res = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $body = json_decode($res, true);
if ($code >= 400) { throw new RuntimeException("{$body['error']['code']}: {$body['error']['message']}"); } return $body;}
$batch = sendSms(['2348020000000'], 'Flash sale: 20% off today only.');echo $batch['reference_id'];type sendReq struct { From string `json:"from"` To []string `json:"to"` Content string `json:"content"` DeliveryReport bool `json:"delivery_report"`}
type sendRes struct { ReferenceID string `json:"reference_id"` Status string `json:"status"` RecipientsAccepted int `json:"recipients_accepted"`}
func SendSMS(ctx context.Context, to []string, content string) (*sendRes, error) { payload, _ := json.Marshal(sendReq{ From: "9bits", To: to, Content: content, DeliveryReport: true, })
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.9bits.net/ng/v1/sendsms", bytes.NewReader(payload)) req.Header.Set("Authorization", "Bearer "+os.Getenv("NINEBITS_TOKEN")) req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer res.Body.Close()
if res.StatusCode >= 400 { var e struct { Error struct{ Code, Message string } `json:"error"` } json.NewDecoder(res.Body).Decode(&e) return nil, fmt.Errorf("%s: %s", e.Error.Code, e.Error.Message) }
var out sendRes return &out, json.NewDecoder(res.Body).Decode(&out)}var payload = """ { "from": "9bits", "to": ["2348020000000"], "content": "Flash sale: 20%% off today only.", "delivery_report": true } """;
var request = HttpRequest.newBuilder() .uri(URI.create("https://api.9bits.net/ng/v1/sendsms")) .header("Authorization", "Bearer " + System.getenv("NINEBITS_TOKEN")) .header("Content-Type", "application/json") .header("Idempotency-Key", UUID.randomUUID().toString()) .timeout(Duration.ofSeconds(10)) .POST(HttpRequest.BodyPublishers.ofString(payload)) .build();
var response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) { throw new IllegalStateException(response.body());}System.out.println(response.body());Response
Section titled “Response”{ "reference_id": "fdab8f46df049e58b0af7c2d7", "status": "accepted", "accepted_at": "2026-07-14T18:48:14+01:00", "recipients_accepted": 2, "status_url": "/ng/v1/sendsms/status/fdab8f46df049e58b0af7c2d7", "cost_estimate": { "total_cost": 6.4, "currency": "NGN", "pages": 1, "accepted_recipients": 2, "unknown_operator_count": 0, "sufficient": true, "per_operator": { "mtn": { "recipients": 1, "cost": 3.2 }, "airtel": { "recipients": 1, "cost": 3.2 } } }}| Field | Meaning |
|---|---|
reference_id | Store this. It’s how you track the batch. |
status | Always accepted here. Queued, not delivered. |
accepted_at | RFC-3339, Africa/Lagos. |
recipients_accepted | Count after normalisation and de-duplication — may be lower than what you sent. |
status_url | Relative path to poll. |
cost_estimate | What this batch costs. See below. |
Reading cost_estimate
Section titled “Reading cost_estimate”| Field | Meaning |
|---|---|
total_cost | Total for the batch — all recipients × all pages. |
currency | Account currency, e.g. NGN. |
pages | Pages one message consumes. 160 chars (GSM-7) or 70 (UCS-2). |
accepted_recipients | Recipients priced into total_cost. |
unknown_operator_count | Unroutable prefixes. Rejected, not charged — but never delivered. |
sufficient | Whether your balance covers total_cost. |
per_operator | Breakdown by network. |
Errors
Section titled “Errors”{ "error": { "type": "validation_error", "code": "sender_id_invalid", "message": "sender ID '9bits' is not approved for this account" }}Branch on code. Full list in the Error reference.
Now that you can send, learn how to find out whether it actually arrived.