
Authenticate with an API key. Send a transactional message to one Nigerian number. Check its status, list your sender IDs, check your wallet balance. That's the whole surface — small and predictable, five endpoints, nothing hidden.
Created, rotated, and revoked from the dashboard — never over the API. Authorization: Bearer is rejected.
Charged per page (segment) on a successful send — a longer message spanning more pages costs proportionally more.
One fixed-window limit, shared across every /v1 endpoint combined — not a per-endpoint allowance.
Transactional is the only kind usable through this API. Both types are registered from the dashboard, not by API call.
5 events, HMAC-signed, dashboard-configured. Not built for per-message delivery — poll GET /v1/status for that.
Auto-generated Swagger docs at /docs, kept in sync with what's actually deployed — the canonical schema.
Every request in this reference is made to:
https://api.uselumaapp.com
Each endpoint below is relative to this host, under /v1. For the complete, interactive, always-current schema — every field, every error — see the live Swagger reference at /docs. It's generated directly from the API, so it can't drift from what's actually deployed; this page is the fast overview, not a replacement for it.
Every request needs:
X-API-Key: luma_live_xxxxxxxxxxxxxxxxxxxxxxxx
Keys are 42 characters: the luma_live_ prefix followed by 32 random base64url characters. There's no test or sandbox key — every key is live, and every send it makes is real and billed.
Authorization: Bearer is rejected outright — that header is reserved for internal service tokens. Use X-API-Key.Keys are created, rotated, and revoked from the dashboard (Developer → API Keys) — there's no endpoint for any of that. The full secret is shown exactly once, at creation or rotation; store it then, because you won't see it again.
This is the entire list — no bulk send, no contacts API, no sender ID submission through this API.
{ "status": "success" | "failed", "message": string, "data": ... | null }An error looks like this:
{
"status": "failed",
"message": "\"123\" is not a recognizable Nigerian phone number.",
"data": { "code": "INVALID_MSISDN" }
}60 requests per minute per API key, shared across every /v1 endpoint combined — not a per-endpoint allowance. It's a fixed window, and the response carries no Retry-After or X-RateLimit-* header. On a 429, back off — there's no header telling you exactly how long.
₦7 per SMS page, charged only when a send is actually accepted for delivery — a failed send (invalid number, insufficient funds, provider rejection) isn't billed. A page is one SMS segment; a message that spans more pages costs proportionally more — a 2-page message costs ₦14, not ₦7.
GET /v1/status returns two genuinely different things. status reflects whether the message was accepted for sending — that updates quickly and is reliable. delivered and undelivered reflect carrier-level delivery confirmation, and whether that's available depends on the route a given send was carried on.
delivered: 0, undelivered: 0 forever — that's not a bug in your integration, it's the current state of delivery confirmation on this API. Build success criteria around status, not around delivered ever reaching recipientCount.Configured from the dashboard (Developer → Webhooks) — there's no API for creating or managing them. Five events, and this is the complete list:
GET /v1/status.Payload shape:
{ "event": "wallet.funded", "occurredAt": "2026-09-01T09:00:00Z", "data": { ... } }Every request is signed — HMAC-SHA256 over the raw request body, sent as:
X-Luma-Signature: sha256=<hex>
Hash the raw bytes you received — not a re-serialized copy of the parsed JSON. Re-encoding the payload (different key order, different whitespace) produces a different hash, and the signature will fail to verify even though the payload is "the same" data.
Retried up to 5 times on failure, with backoff of 1 minute, 5 minutes, 30 minutes, 2 hours, then 6 hours. After 20 consecutive failures, the webhook is disabled automatically. The endpoint URL must be HTTPS.
Two traffic types: transactional and promotional. Only transactional sender IDs can be used through this API — GET /v1/senderIds lists both if you have them, but promotional traffic isn't sent through this API. Registering a new sender ID happens in the dashboard, not here; approval touches all four Nigerian mobile operators (MTN, Airtel, Glo, 9mobile) and is manual on the operators' side, typically taking 4–6 weeks. If you don't specify a senderId on a send and don't have one approved, the message goes out under a platform default (LUMA or N-Alert).
One API key. No sandbox required.