Getting started
Quickstart: send your first SMS
Create an account, take a sk_test_ key, POST to /v1/sms/send with a sender, a +234 recipient, a message and an sms_type, then read the success envelope. Sandbox runs the same validation without calling a provider or debiting the wallet.
Before you start
- A Sendozi account. Email and phone verification are part of signup.
- An API key from the Console. Sandbox keys start sk_test_, production keys sk_live_.
- A recipient number in +234 format. The API does not accept local 0-prefixed numbers.
- For production only: KYC approval and an approved sender ID.
1. Get an API key
Create a key in the Console under API keys. The secret is shown once and stored as a SHA-256 hash - a lost key is replaced, not recovered. Put it in an environment variable; never in source control.
export SENDOZI_API_KEY="sk_test_your_sandbox_key"
export SENDOZI_SENDER_ID="Sendozi"2. Send your first message
curl -X POST https://api.sendozi.com/v1/sms/send \
-H "Authorization: Bearer $SENDOZI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-001" \
-d '{
"sender": "Sendozi",
"recipient": "+2348012345678",
"message": "Your Acme code is 492811. It expires in 10 minutes.",
"sms_type": "transactional"
}'const response = await fetch("https://api.sendozi.com/v1/sms/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDOZI_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "quickstart-001",
},
body: JSON.stringify({
sender: process.env.SENDOZI_SENDER_ID,
recipient: "+2348012345678",
message: "Your Acme code is 492811. It expires in 10 minutes.",
sms_type: "transactional",
}),
});
const body = await response.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data);import os
import httpx
response = httpx.post(
"https://api.sendozi.com/v1/sms/send",
headers={
"Authorization": f"Bearer {os.environ['SENDOZI_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": "quickstart-001",
},
json={
"sender": os.environ["SENDOZI_SENDER_ID"],
"recipient": "+2348012345678",
"message": "Your Acme code is 492811. It expires in 10 minutes.",
"sms_type": "transactional",
},
timeout=15.0,
)
body = response.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])3. Read the response
Every response, success or failure, uses the same envelope. Write your client against the envelope once and every endpoint behaves the same way.
{
"success": true,
"data": {
"message_id": "msg_1f4c0a2b",
"channel": "sms",
"mode": "sandbox",
"status": "accepted",
"recipient": "+2348012345678",
"customer_price_kobo": 700
},
"request_id": "req_8ecdcb4ce2ae4290"
}{
"success": false,
"error": {
"code": "sender_id_not_approved",
"message": "The sender ID is not approved for this workspace.",
"resolution": "Use an approved sender ID, or submit this one for registration."
},
"request_id": "req_8ecdcb4ce2ae4290"
}- status accepted means the request was validated and queued. It is not delivery.
- request_id is also returned as the x-request-id header. Log it - it is how support traces a send.
- Every error carries a resolution written for a developer to act on, not for display to an end user.
- customer_price_kobo is what the send will cost. Sandbox reports it without debiting.
4. Find out what happened to it
Accepted is the start of the story. Register a webhook endpoint so Sendozi pushes delivery events to you rather than making you poll.
curl -X POST https://api.sendozi.com/v1/webhooks/endpoints \
-H "Authorization: Bearer $SENDOZI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/hooks/sendozi",
"events": ["message.delivered", "message.failed"]
}'You can also read status directly: GET /v1/messages/{id} for one message, GET /v1/messages for a paginated list. See delivery webhooks for the receiver pattern.
5. Move to production
- 1
Complete KYC in the Console
Production access is gated on it. Until it is approved, live sends return kyc_required.
- 2
Register a sender ID
A live send with an unapproved sender returns sender_id_not_approved. Registration depends on the networks, so start early.
- 3
Fund the wallet
Sends are debited per page. An empty wallet returns insufficient_balance before anything is sent.
- 4
Swap the key
Change sk_test_ to sk_live_ in your environment. Nothing else in your code changes.
- 5
Send one real message to your own phone
Then check the delivery report and confirm your webhook fired.
A production send is checked through thirteen gates in a fixed order, and the first failure is returned without charging you. The error reference lists the order and what each code means.
Frequently asked questions
- Do I need an SDK to use the Sendozi API?
- No, and there is no official SDK to install. Every endpoint is a plain HTTPS request with a JSON body, so the HTTP client already in your project is enough.
- Does a sandbox send cost anything?
- No. A sk_test_ key records the send, calls no provider and debits nothing. It still runs recipient validation, content policy checks and suppression checks, so a request that fails in sandbox would have failed live.
- Why is my first live send rejected?
- Usually one of three gates: KYC not approved (kyc_required), the sender ID not registered (sender_id_not_approved), or an empty wallet (insufficient_balance). The resolution field on the error names the fix.
- What is the base URL?
- https://api.sendozi.com. GET /v1 returns a live index of every available route.
Related reading
Getting started
Authentication and API keys
How to authenticate Sendozi API requests, the difference between sandbox and production keys, how keys are stored, and how to rotate a key without downtime.
Channels
SMS API reference
Endpoints, request fields, routing, batching, sender ID management and page-based cost for sending SMS to Nigerian numbers through the Sendozi API.
Reliability
Errors and the response envelope
The Sendozi response envelope, every error code with its status and meaning, which errors are worth retrying, and how request_id is used to trace a failure.
Reliability
Idempotency
How Idempotency-Key works on Sendozi send endpoints, what happens on a repeat with the same or a different body, how long keys are retained, and how to choose a key.
Reliability
Delivery webhooks
Register a webhook endpoint, receive message.delivered and message.failed events, verify the signature over the raw body, and build a receiver that survives retries and reordering.