Marsal WhatsApp API

A simple REST API to send WhatsApp messages and templates, run OTP verification, and manage your conversations, contacts and campaigns — from any programming language.

Base URL
https://usemarsal.cloud/api/v1
Get your API key →
All examples in:

Authentication

Send your API key as a Bearer token on every request. Create keys in Developers · API. A key is scoped to your workspace and grants full access — keep it secret.

Authorization: Bearer msk_live_xxxxxxxxxxxxxxxxxxxx

Phone numbers

Always send recipient numbers in international format, digits only (no +, spaces or dashes). Example: a Saudi number becomes 9665xxxxxxxx, an Egyptian number 2010xxxxxxxx.

Response format

Every response is JSON. Success returns "ok": true; a business error returns "ok": false with an error string and a matching HTTP status.

{ "ok": true, "message_id": 4210, "wamid": "wamid.HBg...", "to": "9665xxxxxxxx" }

Send a text message POST

POST https://usemarsal.cloud/api/v1/messages

Free text can only be sent inside the 24‑hour customer‑service window. Outside it, use a template.

FieldTypeDescription
tostringRecipient, international digits only. Required.
typestringtext (default) or template.
bodystringMessage text.
curl -X POST "https://usemarsal.cloud/api/v1/messages" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "to": "9665xxxxxxxx", "type": "text", "body": "أهلاً بيك" }'
const r = await fetch("https://usemarsal.cloud/api/v1/messages", {
  method: "POST",
  headers: { Authorization: "Bearer msk_live_xxxx", "Content-Type": "application/json" },
  body: JSON.stringify({ to: "9665xxxxxxxx", type: "text", body: "أهلاً بيك" })
});
console.log(await r.json());
import requests
r = requests.post("https://usemarsal.cloud/api/v1/messages",
  headers={"Authorization": "Bearer msk_live_xxxx"},
  json={"to": "9665xxxxxxxx", "type": "text", "body": "أهلاً بيك"})
print(r.json())
$ch = curl_init("https://usemarsal.cloud/api/v1/messages");
curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true,
  CURLOPT_HTTPHEADER=>["Authorization: Bearer msk_live_xxxx","Content-Type: application/json"],
  CURLOPT_POSTFIELDS=>json_encode(["to"=>"9665xxxxxxxx","type"=>"text","body"=>"أهلاً بيك"])]);
$res = json_decode(curl_exec($ch), true);

Send a template POST

POST https://usemarsal.cloud/api/v1/messages

Approved templates can be sent any time. Pass the name, language, and body variables in order.

FieldTypeDescription
typestringMust be template.
templatestringApproved template name.
languagestringe.g. ar.
paramsarrayBody variables, in order.
curl -X POST "https://usemarsal.cloud/api/v1/messages" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "to": "9665xxxxxxxx", "type": "template", "template": "order_confirmation", "language": "ar", "params": ["Ahmed", "#9402"] }'
await fetch("https://usemarsal.cloud/api/v1/messages", { method:"POST",
  headers:{ Authorization:"Bearer msk_live_xxxx","Content-Type":"application/json" },
  body: JSON.stringify({ to:"9665xxxxxxxx", type:"template",
    template:"order_confirmation", language:"ar", params:["Ahmed","#9402"] }) });
requests.post("https://usemarsal.cloud/api/v1/messages",
  headers={"Authorization":"Bearer msk_live_xxxx"},
  json={"to":"9665xxxxxxxx","type":"template",
        "template":"order_confirmation","language":"ar","params":["Ahmed","#9402"]})
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  "to"=>"9665xxxxxxxx","type"=>"template",
  "template"=>"order_confirmation","language"=>"ar","params"=>["Ahmed","#9402"]]));

List conversations GET

GET https://usemarsal.cloud/api/v1/conversations?status=open&limit=50

Optional status = all|open|pending|closed. Returns each chat with the contact, unread count, and whether the 24h window is open.

curl -X GET "https://usemarsal.cloud/api/v1/conversations?limit=50" \
  -H "Authorization: Bearer msk_live_xxxx"
const r = await fetch("https://usemarsal.cloud/api/v1/conversations?limit=50",
  { headers: { Authorization: "Bearer msk_live_xxxx" } });
requests.get("https://usemarsal.cloud/api/v1/conversations?limit=50",
  headers={"Authorization":"Bearer msk_live_xxxx"})
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer msk_live_xxxx"]);

Get a conversation's messages GET

GET https://usemarsal.cloud/api/v1/conversations/{id}/messages?after_id=0

Returns messages (in/out) for a conversation. Pass after_id to fetch only newer ones.

curl -X GET "https://usemarsal.cloud/api/v1/conversations/123/messages" \
  -H "Authorization: Bearer msk_live_xxxx"

Reply inside a chat POST

POST https://usemarsal.cloud/api/v1/conversations/{id}/messages

Sends a free‑text reply in an existing conversation (inside the 24h window). Pauses the AI bot for that chat (human takeover), same as the inbox.

curl -X POST "https://usemarsal.cloud/api/v1/conversations/123/messages" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "body": "تمام هجهزلك الطلب" }'
await fetch("https://usemarsal.cloud/api/v1/conversations/123/messages", { method:"POST",
  headers:{ Authorization:"Bearer msk_live_xxxx","Content-Type":"application/json" },
  body: JSON.stringify({ body:"تمام هجهزلك الطلب" }) });
requests.post("https://usemarsal.cloud/api/v1/conversations/123/messages",
  headers={"Authorization":"Bearer msk_live_xxxx"}, json={"body":"تمام هجهزلك الطلب"})
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["body"=>"تمام هجهزلك الطلب"]));

Mark read & toggle AI POST

POST https://usemarsal.cloud/api/v1/conversations/{id}/read
POST https://usemarsal.cloud/api/v1/conversations/{id}/ai   { "enabled": 0|1 }
curl -X POST "https://usemarsal.cloud/api/v1/conversations/123/ai" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": 0 }'

List contacts GET

GET https://usemarsal.cloud/api/v1/contacts?q=ahmed&limit=100
curl -X GET "https://usemarsal.cloud/api/v1/contacts?limit=100" \
  -H "Authorization: Bearer msk_live_xxxx"

Add a contact POST

POST https://usemarsal.cloud/api/v1/contacts
curl -X POST "https://usemarsal.cloud/api/v1/contacts" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "wa_id": "9665xxxxxxxx", "name": "Ahmed", "email": "a@x.com" }'
await fetch("https://usemarsal.cloud/api/v1/contacts", { method:"POST",
  headers:{ Authorization:"Bearer msk_live_xxxx","Content-Type":"application/json" },
  body: JSON.stringify({ wa_id:"9665xxxxxxxx", name:"Ahmed", email:"a@x.com" }) });
requests.post("https://usemarsal.cloud/api/v1/contacts",
  headers={"Authorization":"Bearer msk_live_xxxx"},
  json={"wa_id":"9665xxxxxxxx","name":"Ahmed","email":"a@x.com"})
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["wa_id"=>"9665xxxxxxxx","name"=>"Ahmed"]));

Send an OTP POST

POST https://usemarsal.cloud/api/v1/otp/send

Marsal generates a 6‑digit code, sends it, and stores only its hash. Pass a template to deliver any time; omit it for plain text inside the 24h window.

curl -X POST "https://usemarsal.cloud/api/v1/otp/send" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "to": "9665xxxxxxxx", "template": "otp_code", "ttl": 300 }'
await fetch("https://usemarsal.cloud/api/v1/otp/send", { method:"POST",
  headers:{ Authorization:"Bearer msk_live_xxxx","Content-Type":"application/json" },
  body: JSON.stringify({ to:"9665xxxxxxxx", template:"otp_code", ttl:300 }) });
requests.post("https://usemarsal.cloud/api/v1/otp/send",
  headers={"Authorization":"Bearer msk_live_xxxx"},
  json={"to":"9665xxxxxxxx","template":"otp_code","ttl":300})
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["to"=>"9665xxxxxxxx","template"=>"otp_code","ttl"=>300]));

Verify an OTP POST

POST https://usemarsal.cloud/api/v1/otp/verify

Returns { "ok": true, "verified": true|false }. Codes expire after the TTL and lock after 5 wrong attempts.

curl -X POST "https://usemarsal.cloud/api/v1/otp/verify" \
  -H "Authorization: Bearer msk_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{ "to": "9665xxxxxxxx", "code": "123456" }'
const { verified } = await (await fetch("https://usemarsal.cloud/api/v1/otp/verify", { method:"POST",
  headers:{ Authorization:"Bearer msk_live_xxxx","Content-Type":"application/json" },
  body: JSON.stringify({ to:"9665xxxxxxxx", code:"123456" }) })).json();
r = requests.post("https://usemarsal.cloud/api/v1/otp/verify",
  headers={"Authorization":"Bearer msk_live_xxxx"}, json={"to":"9665xxxxxxxx","code":"123456"})
print(r.json()["verified"])
$verified = json_decode(curl_exec($ch), true)["verified"];

Templates GET

GET https://usemarsal.cloud/api/v1/templates

Your approved message templates (name, language, category, status).

curl -X GET "https://usemarsal.cloud/api/v1/templates" \
  -H "Authorization: Bearer msk_live_xxxx"

Numbers GET

GET https://usemarsal.cloud/api/v1/numbers

Your connected WhatsApp numbers, quality rating and status.

curl -X GET "https://usemarsal.cloud/api/v1/numbers" \
  -H "Authorization: Bearer msk_live_xxxx"

Campaigns GET

GET https://usemarsal.cloud/api/v1/campaigns
curl -X GET "https://usemarsal.cloud/api/v1/campaigns" \
  -H "Authorization: Bearer msk_live_xxxx"

Dashboard stats GET

GET https://usemarsal.cloud/api/v1/dashboard

Counts for contacts, open conversations, unread, messages today, and campaigns.

curl -X GET "https://usemarsal.cloud/api/v1/dashboard" \
  -H "Authorization: Bearer msk_live_xxxx"

Poll for new messages GET

GET https://usemarsal.cloud/api/v1/updates?since_id=0

Returns inbound messages newer than since_id, plus the latest id — poll it every few seconds to build a live inbox or trigger your own automations.

curl -X GET "https://usemarsal.cloud/api/v1/updates?since_id=4200" \
  -H "Authorization: Bearer msk_live_xxxx"
let since = 0;
setInterval(async () => {
  const r = await fetch(`https://usemarsal.cloud/api/v1/updates?since_id=${since}`,
    { headers: { Authorization: "Bearer msk_live_xxxx" } });
  const data = await r.json();
  since = data.last_id;
  data.messages.forEach(m => console.log(m.from, m.body));
}, 5000);
import time, requests
since = 0
while True:
    d = requests.get(f"https://usemarsal.cloud/api/v1/updates?since_id={since}",
        headers={"Authorization":"Bearer msk_live_xxxx"}).json()
    since = d["last_id"]
    for m in d["messages"]: print(m["from"], m["body"])
    time.sleep(5)
$d = json_decode(file_get_contents("https://usemarsal.cloud/api/v1/updates?since_id=$since"), true);

Endpoint index

MethodPathDescription
POST/messagesSend text or template
POST/otp/sendSend an OTP
POST/otp/verifyVerify an OTP
GET/conversationsList conversations
GET/conversations/{id}/messagesGet messages
POST/conversations/{id}/messagesReply in a chat
POST/conversations/{id}/readMark read
POST/conversations/{id}/aiToggle AI bot
GET/contactsList contacts
POST/contactsAdd a contact
GET/templatesList templates
GET/numbersList numbers
GET/campaignsList campaigns
GET/dashboardWorkspace stats
GET/updatesPoll new inbound

Errors

HTTPerrorMeaning
401unauthenticatedMissing or invalid API key.
403no_workspaceKey not linked to a workspace.
404conversation_not_foundNo such conversation in your workspace.
409no_whatsapp_number / session_closedNo connected number, or the 24h window is closed.
422to_required / body_requiredA required field is missing.
502send_failedWhatsApp rejected the message (see detail).

Ready to build?

Grab your API key and send your first WhatsApp message in minutes.

Get your API key →