Automation & API

REST API

Integrate Zender into your own software: send SMS and WhatsApp, check message status, and read back your subscription's usage from outside the dashboard.

API keys, created per project with their own permissions
API keys, created per project with their own permissions
The interactive API reference built into the dashboard
The interactive API reference built into the dashboard

Every example below uses yourdomain.com as a placeholder for your own site domain — prefixed with whichever scheme (http or https) your site actually runs under — and zk_9f2a...c81b as a placeholder API secret; replace both before running anything. The dashboard also renders this same reference interactively under Docs → API, served from a locally vendored copy of Redoc rather than a CDN, so it works even without internet access.

Getting your API key

From Tools → API Keys (visible when your subscription includes the API), Add API key takes a name and a multi-select list of permissions — every endpoint below is gated on one, and a key with none checked is rejected at creation. The secret itself is a 40-character random string generated for you, and is never shown in the table as plain text — click the copy button on the key's row to copy it to your clipboard. How many keys you're allowed depends on your subscription's key limit.

Authentication

There's no Authorization header. Every call takes your secret as a request parameter named secret — so it works equally as a query string parameter on a GET or a form field on a POST. A missing or unrecognized secret gets a 400 or 401 before your call reaches any endpoint logic.

Endpoints are grouped under six URL prefixes: /api/send/{service}, /api/get/{service}, /api/create/{service}, /api/delete/{service}, /api/validate/{service}, and /api/remote/{service}. This page covers the ones named in its headings; the interactive reference under Docs → API covers the rest (contacts, groups, devices, WhatsApp account management, scheduled-campaign start/stop, and more).

Zender's API responses are always transport-level HTTP 200, regardless of whether the call succeeded. curl -i against any endpoint below, including a rejected one, shows HTTP/1.1 200 OK. You must branch on the JSON body's status field, not the HTTP status line:

{"status": 403, "message": "This API key doesn't have permission to use this endpoint!", "data": false}

Sending SMS

POST /api/send/sms — requires the sms_send permission and the sms subscription service. mode is either devices (send from one of your Android gateway devices — needs device, and optionally sim: 1 or 2) or credits (send through a gateway or a shared device, deducted from your balance — needs gateway, either a gateway ID or a global device ID). message must clear the site's minimum length; an optional shortener ID link-shortens any URLs in the message first.

curl -X POST "yourdomain.com/api/send/sms" \
  -d "secret=zk_9f2a...c81b" \
  -d "mode=devices" \
  -d "phone=+15551234567" \
  -d "device=00000000-0000-0000-d57d-f30cb6a89289" \
  -d "sim=1" \
  -d "priority=2" \
  -d "message=Hello from the API!"

Success: {"status":200,"message":"Message has been queued for sending!","data":{"messageId":123}}.

POST /api/send/sms.bulk (permission sms_send_bulk) is the same idea for many recipients at once: instead of phone it takes campaign (a name for the send) plus numbers (comma-separated phone numbers) and/or groups (comma-separated contact group IDs) — at least one of the two is required.

Sending WhatsApp

POST /api/send/whatsapp — requires the wa_send permission and the whatsapp subscription service. Note the recipient parameter is recipient, not phone. account is the unique ID of one of your linked WhatsApp accounts (visible from WhatsApp → Queue in the dashboard, or /api/get/wa.accounts). recipient is either a phone number or a full …@g.us group ID, following the same identity contract described on the Webhooks page. type defaults to text; an unrecognized value is silently treated as text rather than rejected.

curl -X POST "yourdomain.com/api/send/whatsapp" \
  -d "secret=zk_9f2a...c81b" \
  -d "account=6f1a9c...unique" \
  -d "recipient=+15551234567" \
  -d "type=text" \
  -d "message=Hello from the API!"

Success (default priority, queued rather than sent immediately): {"status":200,"message":"WhatsApp chat has been queued for sending!","data":{"messageId":456}}. Pass priority=1 to send immediately instead of queuing (message: "WhatsApp chat has been sent!").

For type=media, send either a media_url (with media_type: image, audio, or video) or a multipart media_file upload (jpg/jpeg/png/gif/mp4/mp3/ogg). For type=document, send either a document_url (with document_type: pdf/xml/xls/xlsx/doc/docx, and optionally document_name) or a multipart document_file upload.

POST /api/send/whatsapp.bulk (permission wa_send_bulk) takes account, campaign, message, and recipients (comma-separated numbers and/or …@g.us IDs) and/or groups, the same pattern as sms.bulk.

Checking status

GET /api/get/sms.message and GET /api/get/wa.message look up one message by ID: pass id and type (sent or received). Looking up a sent message needs get_sms_sent / get_wa_sent; a received one needs get_sms_received / get_wa_received — sms.message additionally always requires a base get_message permission.

curl -G "yourdomain.com/api/get/sms.message" \
  -d "secret=zk_9f2a...c81b" \
  -d "id=123" \
  -d "type=sent"

Response data for a sent SMS includes status as one of queued, pending, sent, or failed (see the Sending SMS page for what each means), plus sender, sender_type, recipient, message, and created (Unix timestamp). A WhatsApp chat's status is one of pending, queued, sent, or failed, plus an attachment download link when there was media.

For an account-wide picture rather than one message, GET /api/get/credits (permission get_credits) returns your remaining credit balance, and GET /api/get/subscription (permission get_subscription) returns your whole package as used/limit pairs — e.g. usage.sms_send, usage.wa_send, usage.apikeys — which doubles as the definitive way to check how close you are to any of the limits in the next section.

curl -G "yourdomain.com/api/get/subscription" \
  -d "secret=zk_9f2a...c81b"

Sending a USSD request

POST /api/send/ussd — requires the ussd permission and the Android USSD subscription service, and needs a device running Android 8 or later. Pass device (one of your Android device IDs), sim (1 or 2), and code (the USSD string to dial, e.g. a balance-check code from your carrier). The request is queued against the device, not answered inline — the device's response comes back asynchronously as a ussd webhook event (see the Webhooks page), so set one up before you rely on this.

GET /api/get/ussd (permission get_ussd) lists your past requests and their status (pending, queued, or completed), each with its device, SIM, code, and response once one has come back. DELETE /api/delete/ussd (permission delete_ussd) removes a request record.

Remote administration (SaaS API)

Separate from the per-account API keys above, Zender has a second, admin-level API for platforms that want to provision or manage the installation itself from their own software rather than through the dashboard — creating users, granting packages, generating vouchers, and so on, without a human clicking through Admin.

It's authenticated with its own Admin API token, separate from both your regular API key secrets and the site's system token, and it's off by default. A super admin turns it on and chooses exactly which actions are allowed — get/create/edit/delete for users, roles, packages, vouchers, subscriptions, transactions, languages, and API keys — from the Settings screen under Admin → Overview (see the System settings page), where the token itself can also be regenerated. Anything not explicitly enabled there is rejected, even with a valid token.

Rate limits and errors

There's no per-request throttle — what looks like a rate limit is really your subscription package's usage quota. Each send-type endpoint checks your usage against counters like send_limit / wa_send_limit; a limit of 0 or less means unlimited. Once you hit it, sends fail with:

{"status":403,"message":"Maximum allowed number of sent messages has been reached!","data":false}

Quotas reset on whichever cadence your package uses (daily or monthly — see the Cron jobs page's note on the quota job) rather than on a rolling window, so there's no "retry after N seconds" — check /api/get/subscription to see when you're close.

StatusMeaning
200Success.
400Missing/invalid parameters — e.g. an unparsable phone number, a message shorter than the site minimum, or an unrecognized media type.
401The secret doesn't match any API key.
403Recognized secret, but not allowed to do this — a missing permission on the key itself, a subscription that doesn't include the service, or a quota/limit reached.
404A referenced device, gateway, WhatsApp account, or message ID doesn't belong to this key's account.
500Something failed on Zender's or a downstream service's side — a disconnected WhatsApp server, a gateway that rejected the send, etc. Usually safe to retry later; not a problem with your request shape.

If the site has AI moderation turned on (from the Settings screen under Admin → Overview), every SMS and WhatsApp text send is screened before it's queued; flagged content is rejected with 400 and logged to Tools → Logger rather than being sent.