API reference

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.

Base URL — replace https://your-host:11009 in every example with wherever your Chirp server lives. All requests and responses are application/json unless noted.

On this page

  1. Authentication
  2. WhatsApp session
  3. Reading messages & chats
  4. Sending a message
  5. Replies & reactions
  6. Mark, hide, delete
  7. Bulk send
  8. Quota
  9. Errors

1. Authentication

Log in with phone + password. Chirp sets an HttpOnly session cookie; pass it back on every subsequent call with -b cookies.txt.

POST/auth/login

curl -s -c cookies.txt -X POST https://your-host:11009/auth/login \
  -H 'content-type: application/json' \
  -d '{"phone":"919xxxxxxxxx","password":"your-password"}'

Response

{ "ok": true, "user": { "id": 1, "phone": "919xxxxxxxxx", "role": "admin" } }

GET/auth/me

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" } }

POST/auth/logout

curl -s -b cookies.txt -X POST https://your-host:11009/auth/logout

POST/auth/register/request   POST/auth/register/verify

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 }

2. WhatsApp session

Each logged-in user has their own Baileys socket. Start it, check status, and fetch the pairing QR when needed.

POST/session/start

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" }

GET/session/status

curl -s -b cookies.txt https://your-host:11009/session/status
# → { "status": "connected", "hasQR": false }

States: disconnected, pairing (a QR is available), connected.

GET/qr

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'

POST/session/reset

Logs out from WhatsApp on the server side and clears auth state — next /session/start returns a fresh QR.

3. Reading messages & chats

Chirp persists every text message it sees. Read the full stream, filter by chat, by read-state, or since a timestamp.

GET/messages

Query paramTypeDescription
limitint (≤500)Page size, default 50.
offsetintPagination offset.
chatstringFull JID to filter to one chat (e.g. 14155551234@s.whatsapp.net).
unread1Only rows where is_read = 0.
sinceepoch msOnly 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'

Response shape

{
  "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
    }
  ]
}

GET/chats

Chat list with last-message preview, unread count, and group subject (for groups).

curl -s -b cookies.txt https://your-host:11009/chats

4. Sending a message

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".

POST/chats/:jid/send

# 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"}'

Response

{ "id": "3EB0A8F2...", "quota": { "unlimited": false, "limit": 100, "used": 38, "remaining": 62 } }
Counts as 1 against your daily quota. Admin users are unlimited.

5. Replies & reactions

POST/messages/:id/reply

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"}'

POST/messages/:id/react

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":"👍"}'
Replies count as 1 against quota. Reactions don't count — they're tiny protocol messages, not outbound content.

6. Mark, hide, delete

Mark as read, soft-hide locally, or revoke on WhatsApp ("deleted for everyone").

POST/messages/:id/read

curl -s -b cookies.txt -X POST https://your-host:11009/messages/3EB0.../read

POST/messages/bulk-action

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"}'

DELETE/messages/:id

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'
None of these count against quota.

7. Bulk send

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.

POST/send/bulk

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": {...} }

GET/send/jobs/:id

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..." }
#   ]
# }
A bulk submit is rejected whole-or-nothing if it would exceed your daily quota. Trim your recipient list to quota.remaining or wait for tomorrow.

8. Quota

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.

9. Errors

Errors are returned with an appropriate HTTP status and a body of { "error": "human-readable message" }. Common codes:

StatusMeaning
400Validation failure. Also used when a recipient isn't on WhatsApp.
401Not authenticated, bad credentials, or invalid/expired OTP.
403Authenticated but not permitted (e.g. admin-only endpoint).
404Message/resource not found or not owned by you.
429Daily quota exceeded, or OTP resend cooldown (60s). Body includes useful state.
502Upstream (Baileys/WhatsApp) error during send.
503Your WhatsApp session is not connected yet.