Every action in the Chirp UI is also a plain HTTP endpoint. Log in once, reuse the session cookie (or plug in your own bearer auth), and drive Chirp from any stack.
https://your-host:11009 in every example with wherever your Chirp server lives. All requests and responses are application/json unless noted.
Log in with phone + password. Chirp sets an HttpOnly session cookie; pass it back on every subsequent call with -b cookies.txt.
curl -s -c cookies.txt -X POST https://your-host:11009/auth/login \
-H 'content-type: application/json' \
-d '{"phone":"919xxxxxxxxx","password":"your-password"}'
{ "ok": true, "user": { "id": 1, "phone": "919xxxxxxxxx", "role": "admin" } }
Returns the current user and daily quota status. Useful for a UI badge.
curl -s -b cookies.txt https://your-host:11009/auth/me
# { "user": {...}, "quota": { "unlimited": false, "limit": 100, "used": 37, "remaining": 63, "date": "2026-05-09" } }
curl -s -b cookies.txt -X POST https://your-host:11009/auth/logout
Self-serve signup — phone → OTP on WhatsApp → password. If the phone is already registered, the same flow becomes a password-reset (response includes reset: true).
curl -s -X POST https://your-host:11009/auth/register/request \
-H 'content-type: application/json' \
-d '{"phone":"14155551234"}'
# → { "ok": true, "phone": "14155551234", "expiresInSec": 900, "reset": false }
curl -s -c cookies.txt -X POST https://your-host:11009/auth/register/verify \
-H 'content-type: application/json' \
-d '{"phone":"14155551234","otp":"123456","password":"min8chars"}'
# → { "ok": true, "user": {...}, "reset": false }
Each logged-in user has their own Baileys socket. Start it, check status, and fetch the pairing QR when needed.
Idempotent. Kicks off the Baileys socket for your user. You'll need to call this once after login on a fresh device.
curl -s -b cookies.txt -X POST https://your-host:11009/session/start
# → { "status": "pairing" }
curl -s -b cookies.txt https://your-host:11009/session/status
# → { "status": "connected", "hasQR": false }
States: disconnected, pairing (a QR is available), connected.
Returns the current pairing QR as a PNG image, or 204 if no QR is staged. Add ?format=text to get the raw QR string instead (handy for rendering in a terminal).
curl -s -b cookies.txt https://your-host:11009/qr -o qr.png
curl -s -b cookies.txt 'https://your-host:11009/qr?format=text'
Logs out from WhatsApp on the server side and clears auth state — next /session/start returns a fresh QR.
Chirp persists every text message it sees. Read the full stream, filter by chat, by read-state, or since a timestamp.
| Query param | Type | Description |
|---|---|---|
limit | int (≤500) | Page size, default 50. |
offset | int | Pagination offset. |
chat | string | Full JID to filter to one chat (e.g. 14155551234@s.whatsapp.net). |
unread | 1 | Only rows where is_read = 0. |
since | epoch ms | Only rows newer than this timestamp. |
# latest 50 messages across all chats
curl -s -b cookies.txt 'https://your-host:11009/messages?limit=50'
# unread only, since a given point in time
curl -s -b cookies.txt 'https://your-host:11009/messages?unread=1&since=1730000000000'
# one chat
curl -s -b cookies.txt 'https://your-host:11009/messages?chat=14155551234@s.whatsapp.net'
{
"total": 1284,
"limit": 50,
"offset": 0,
"messages": [
{
"id": "3EB0...",
"chat_jid": "14155551234@s.whatsapp.net",
"from_jid": "14155551234@s.whatsapp.net",
"from_me": 0,
"body": "hello",
"timestamp": 1736020800000,
"push_name": "Alice",
"quoted_id": null,
"quoted_body": null,
"is_read": 0,
"is_deleted": 0
}
]
}
Chat list with last-message preview, unread count, and group subject (for groups).
curl -s -b cookies.txt https://your-host:11009/chats
Pass a phone number (digits + country code, no +) or a full JID. Chirp pre-warms the Signal session on first contact so the recipient's device decrypts immediately instead of showing "Waiting for this message".
# to a phone number
curl -s -b cookies.txt -X POST https://your-host:11009/chats/14155551234/send \
-H 'content-type: application/json' \
-d '{"body":"Hello from Chirp"}'
# to a group JID
curl -s -b cookies.txt -X POST \
'https://your-host:11009/chats/120363025012345678@g.us/send' \
-H 'content-type: application/json' \
-d '{"body":"Hello team"}'
{ "id": "3EB0A8F2...", "quota": { "unlimited": false, "limit": 100, "used": 38, "remaining": 62 } }
Sends a WhatsApp-native quoted reply. The recipient sees the original message quoted above yours.
curl -s -b cookies.txt -X POST \
https://your-host:11009/messages/3EB0A8F2.../reply \
-H 'content-type: application/json' \
-d '{"body":"sure, on it"}'
React with a single emoji. Pass an empty string to remove the reaction.
curl -s -b cookies.txt -X POST \
https://your-host:11009/messages/3EB0A8F2.../react \
-H 'content-type: application/json' \
-d '{"emoji":"👍"}'
Mark as read, soft-hide locally, or revoke on WhatsApp ("deleted for everyone").
curl -s -b cookies.txt -X POST https://your-host:11009/messages/3EB0.../read
Apply an action to many messages at once. action must be read, hide, or delete-remote.
curl -s -b cookies.txt -X POST \
https://your-host:11009/messages/bulk-action \
-H 'content-type: application/json' \
-d '{"ids":["id1","id2","id3"],"action":"read"}'
Without query string, soft-deletes locally. Add ?remote=1 to also revoke on WhatsApp for the recipient.
curl -s -b cookies.txt -X DELETE \
'https://your-host:11009/messages/3EB0.../?remote=1'
Enqueue a broadcast. Chirp paces sends with jittered delays (2–6 seconds by default) so your number doesn't trip anti-spam heuristics. The queue survives restarts and auto-retries failures up to 3x.
curl -s -b cookies.txt -X POST https://your-host:11009/send/bulk \
-H 'content-type: application/json' \
-d '{
"recipients": ["919xxxxxxxxx", "14155551234"],
"body": "Maintenance at 10pm tonight.",
"delayMs": [2000, 6000]
}'
# → { "jobId": "a1b2c3...", "count": 2, "quota": {...} }
curl -s -b cookies.txt https://your-host:11009/send/jobs/a1b2c3...
# → {
# "jobId": "a1b2c3...",
# "counts": { "pending": 0, "sent": 2 },
# "items": [
# { "id": 1, "recipient": "919...", "status": "sent", "attempts": 1,
# "last_error": null, "sent_at": 1736020900, "wa_message_id": "3EB0..." }
# ]
# }
quota.remaining or wait for tomorrow.Non-admin users get 100 outbound messages per day. Admins are unlimited. Reset happens at server-local midnight.
Counted against quota: /chats/:jid/send, /messages/:id/reply, /send/bulk (N = recipients.length).
Not counted: reactions, read-marks, local hides, remote deletes, OTP dispatches.
Query quota at any time via /auth/me. When a send is rejected for quota, you'll get an HTTP 429 with a quota object in the body.
Errors are returned with an appropriate HTTP status and a body of { "error": "human-readable message" }. Common codes:
| Status | Meaning |
|---|---|
400 | Validation failure. Also used when a recipient isn't on WhatsApp. |
401 | Not authenticated, bad credentials, or invalid/expired OTP. |
403 | Authenticated but not permitted (e.g. admin-only endpoint). |
404 | Message/resource not found or not owned by you. |
429 | Daily quota exceeded, or OTP resend cooldown (60s). Body includes useful state. |
502 | Upstream (Baileys/WhatsApp) error during send. |
503 | Your WhatsApp session is not connected yet. |