API documentation
One authenticated call sends a notification across in-app, email, SMS and WhatsApp. Notimba picks the provider, handles failover and reports delivery back onto the message.
Quickstart
Create an account and copy your API key (shown once). Then send your first message:
curl -X POST https://notimba.informationvillagelimited.com/api/notify \
-H "Authorization: Bearer ntb_yourkey..." \
-H "Content-Type: application/json" \
-d '{
"event": "order.confirmed",
"recipient": { "user_ref": "cust_8812", "phone": "+256772123456" },
"body": "Payment of UGX 45,000 received."
}'A 202 comes back with one message id per resolved channel. That is the whole integration.
Authentication
Every request carries your key as a bearer token:
Authorization: Bearer ntb_<8 chars>_<32 chars>Keys are shown once at signup and stored only as a hash - a lost key is replaced, never recovered. Failed authentication is always a uniform 401, and repeated failures from one address are rate limited.
Send a notification
POST/api/notify
| Field | Type | Notes |
|---|---|---|
| event | string, required | Your name for what happened, e.g. order.confirmed. |
| recipient.user_ref | string, required | Your stable id for the person. Keys the in-app feed and opt-outs. |
| recipient.email | string, optional | Enables the email channel for this send. |
| recipient.phone | string, optional | Enables SMS (and WhatsApp when requested). Use +256... format. |
| channels | string[], optional | Subset of in_app, email, sms, whatsapp. Omitted = in_app + email + sms where contact exists. |
| reply_to | string, optional | Reply-To address on the email channel, so replies reach a human instead of the sender identity. |
| subject | string, optional | Email subject and in-app title. |
| body | string, optional | The message text, all channels. |
Response, always one id per channel that resolved:
HTTP 202
{ "message_ids": ["82d42c8f-...", "2fd71914-..."] }Channels
| Channel | Fires when | Notes |
|---|---|---|
| in_app | Always | Written to the feed your product pulls. Never billed. |
| recipient.email present | Warmed sending path, no SMTP setup. Free tier: a fair daily share of the shared pool (at least 10 a day, 10 a day for your first week), then402 quota_exhausted until the UTC day resets. | |
| sms | recipient.phone present | Network-aware routing across Ugandan providers with automatic failover. Prepaid only: without credits every SMS is 402 wallet_required. Uganda (+256) numbers only. |
| Explicitly requested + phone present + enabled for your account | Meta Cloud API with delivery and read receipts. On request: call POST /api/channels/whatsapp/request and we configure your sender, until then it is 403 channel_not_enabled. |
channels array names it, so adding a phone number to a request never triggers an unexpected WhatsApp send.{ "channels": ["whatsapp"], ... } // WhatsApp only
{ "channels": ["sms", "whatsapp"], ... } // bothcurl -X POST https://notimba.informationvillagelimited.com/api/channels/whatsapp/request \
-H "Authorization: Bearer $NOTIMBA_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"OTP and order updates, ~2k/month"}'
# 202 { "status": "requested", "request_id": "..." }Message status
GET/api/messages/:id
Look up any message id from a send. Statuses move queued → sent → delivered as provider receipts arrive; failures land on bounced (rejected) or undelivered (retries exhausted). Messages belonging to another tenant are a 404, indistinguishable from nonexistent ones.
In-app feed
GET/api/notifications?user_ref=cust_8812
Pull a user's notifications to render an inbox or a bell badge. Delivery is asynchronous: a notification appears in the feed within a few seconds of the 202, not in the same instant. Mark one read with:
POST/api/notifications/:id/read
Idempotency
Send an Idempotency-Key header to make retries safe: the same key, tenant and channel never creates a second message, you get the original ids back. Keys are limited to 255 characters.
curl -X POST https://notimba.informationvillagelimited.com/api/notify \
-H "Authorization: Bearer ntb_yourkey..." \
-H "Idempotency-Key: order-8812-confirmed" \
...Errors and limits
| Status | Shape | Meaning |
|---|---|---|
| 401 | { "error": "unauthorized" } | Missing, malformed or wrong key. |
| 422 | { "error": "validation_error", "fields": { "recipient.user_ref": [...] } } | Invalid body. Fields are keyed by full dotted path. |
| 429 | { "error": "rate_limited" } | Per-tenant burst limit or repeated auth failures. Back off and retry. |
| 404 | { "error": "not_found" } | Unknown id, or a resource that is not yours. |
| 402 | { "error": "wallet_required", "channel": "sms" } | SMS is prepaid. Top up before sending. |
| 402 | { "error": "quota_exhausted", "channel": "email", "reason": "share", "share": 60, "used": 60 } | Your free email share for this UTC day is spent. reason is share, ramp (first week) or pool_full. |
| 403 | { "error": "email_unverified" } | Click the verification link we emailed at signup before sending. |
| 403 | { "error": "channel_not_enabled", "channel": "whatsapp" } | WhatsApp is not switched on for your account yet. Request it above. |
| 403 | { "error": "channel_disabled", "channel": "email" } | Sending on this channel was paused for your account after bounce or complaint rates crossed the limit. We email you when this happens. |
| 202 | { "message_ids": [...], "skipped": [{ "channel": "email", "reason": "suppressed" }] } | The recipient previously bounced or complained, so that channel was skipped. Other channels in the request still went out. |
Need a higher limit or a channel configured for your account? ivilleinc@gmail.com