Sending

POST /v1/messages

The body is to plus exactly one content key. The content key names the type. There is no type field.

Every content key

{ "to": "254712345678", "text": "Hello" }
{ "to": "254712345678", "image": "<url|med_id|data uri>", "caption": "..." }
{ "to": "254712345678", "video": "<...>", "caption": "..." }
{ "to": "254712345678", "audio": "<...>", "voice": true }
{ "to": "254712345678", "document": "<...>", "filename": "Invoice-4417.pdf",
  "caption": "Your invoice" }
{ "to": "254712345678", "location": { "lat": -1.29, "lng": 36.82,
  "name": "UPEO.AI", "address": "..." } }
{ "to": "254712345678", "contact": { "name": "...", "phone": "..." } }

Optional on any send

fromPick the sending number by msisdn when you have several.
instance_idThe same choice, by id.
reply_toA message id to quote.
preview_urlWhether a link in a text message gets a preview.
mentionsPeople to @-mention: numbers, or jids from GET /v1/chats/{jid}/members (a group that uses LIDs needs the @lid jid). Put @ and the same digits in the text where the name should appear. Text and captions only.
mention_alltrue for @all in a group. Write @all in the text. WhatsApp only notifies everybody when the sending number is an admin.

If your account has exactly one number, from and instance_id are optional everywhere.

Waiting for the result

?wait=true blocks up to 15 seconds until the message reaches sent or failed, then returns the terminal state. Without it you get 202 and status: queued immediately.

Idempotency

Send an Idempotency-Key header on anything that must not happen twice. Honoured for 24 hours. The same key with the same body replays the original response; the same key with a different body is a 409.

curl -X POST https://njiwa.upeo.ai/v1/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: invoice-4417" \
  -H "Content-Type: application/json" \
  -d '{"to": "254712345678", "document": "https://example.com/invoice-4417.pdf"}'
Without this, a client that retries on a timeout sends the same invoice to the same real person twice.

Pacing

Njiwa paces sends per number, by default one every three seconds. Sending as fast as the socket allows is how a number gets banned.

What 202 promises

A 202 means the message is stored and queued. From that point it is Njiwa's problem, not yours: you do not need to poll it, hold it, or send it again.

The number is offlineThe message waits. It is sent when the number reconnects, not failed for being early.
A send failsRetried up to five times with a widening gap, from a few seconds out to five minutes.
Njiwa restarts mid sendThe message is put back and sent. Nothing in flight is dropped by a deploy.
It truly cannot be sentIt goes to failed with an error saying why. It is never left sitting at queued for ever.
At least once, not exactly once. If a send succeeds but the confirmation is lost on the way back, Njiwa retries and the recipient can get the message twice. That trade is deliberate: for an invoice or a receipt, a customer who gets two is a smaller harm than a customer who gets none. Where a duplicate would be worse than a miss, send an Idempotency-Key.