wapi.
Guides

Sending messages

One endpoint sends every message type. Which field you set decides what goes out.

POST /api/send-message handles everything. There is no separate route for images, and none for groups. Which content field you set decides what gets sent — and setting two is an error rather than a silent preference for one of them.

The minimal call

curl -X POST https://api.wapi.crafter.run/api/send-message \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"to":"+51999888777","text":"hello"}'
{ "success": true,
  "data": { "msgId": 100024, "jid": "+51999888777", "status": "in_progress" } }

Every content type

{ "to": "+51999888777", "text": "hello" }

Media types not shown above follow the same pattern: videoUrl, audioUrl, stickerUrl.

Recipients

to accepts any of these, and sending to a group is the same call with a group JID:

FormExample
Phone number, any readable form+51999888777, 51999888777
WhatsApp JID51999888777@s.whatsapp.net
Group JID120363000000000000@g.us
Channel JID120363000000000000@newsletter

Use GET /api/groups to find a group's JID.

Reactions

A wapi extension. WasenderAPI reports reactions over webhooks but has no endpoint to send one, so their SDK never calls this.

curl -X POST https://api.wapi.crafter.run/api/messages/react \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"key":{"id":"3EB0...","remoteJid":"51999888777@s.whatsapp.net","fromMe":false},
       "emoji":"👍"}'

Addressed by WhatsApp key, not msgId — you mostly react to messages somebody else sent, and those have no msgId because wapi never assigned one. Take the key from the webhook payload.

An empty emoji removes the reaction. That is WhatsApp's convention rather than a second endpoint.

Read receipts

{ "keys": [{ "id": "3EB0...", "remoteJid": "51999888777@s.whatsapp.net", "fromMe": false }] }

Also keyed by WhatsApp key, for the same reason. Marking read is visible to the sender if they have read receipts enabled, and silently does nothing if they don't.

Editing and deleting

PUT /api/messages/{msgId} and DELETE /api/messages/{msgId}, both addressed by the integer msgId a send returned.

WhatsApp allows each only for a short window afterwards and gives no way to ask how long is left, so a refusal here is ordinary rather than a bug. An edit is a new message that supersedes the old one, so it comes back with a fresh key while keeping the original msgId.

Resending

POST /api/messages/{msgId}/resend retries, and only for messages whose status is failed.

That restriction is worth understanding rather than working around. A send that timed out is recorded as in_progress precisely because nobody knows whether it arrived — resending one of those is how somebody receives the same message twice. It also needs log_messages to have been on, since otherwise there is no stored content to send again.

If a send times out, reconcile — never re-send blindly

The request failing is not the same as the message not arriving. Call GET /api/messages/{msgId}/info and look at status: 0 error, 1 pending, 2 sent, 3 delivered, 4 read.

What goes wrong

StatusMeaning
422Two content fields set, or none. Body carries errors keyed by field.
403You sent a Personal Access Token; this needs a session key.
503The session is not connected. Check GET /api/status.
429Rate limited. This body has no success key — see Errors.

Pacing

Sessions with account_protection on wait about five seconds between sends. This is real protection rather than throttling for its own sake: WhatsApp bans accounts that behave like software, and a burst of instant messages is the clearest possible signal. A sandbox ignores the pacing, which is why you should never tune retry or timing logic against one.

On this page