Check your balance
Sending WhatsApp messages uses credits. How many credits a message uses depends on its
category (marketing, utility, authentication or service). GET /balance tells you how many
credits you have left and how many you used this month.
Any API key can call it: it needs only the read scope.
Read the balance
Section titled “Read the balance”curl https://api.sanadwa.com/v1/balance \ -H "Authorization: Bearer $SANADWA_API_KEY"const res = await fetch("https://api.sanadwa.com/v1/balance", { headers: { Authorization: `Bearer ${process.env.SANADWA_API_KEY}` },});const { data } = await res.json();console.log(data.billing_type, data.available_credits, data.usage_this_month.credits_used);import osimport requests
res = requests.get( "https://api.sanadwa.com/v1/balance", headers={"Authorization": f"Bearer {os.environ['SANADWA_API_KEY']}"}, timeout=30,)data = res.json()["data"]print(data["billing_type"], data["available_credits"], data["usage_this_month"]["credits_used"])<?php$ch = curl_init("https://api.sanadwa.com/v1/balance");curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SANADWA_API_KEY")], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30,]);$data = json_decode(curl_exec($ch), true)["data"];echo $data["billing_type"], " ", var_export($data["available_credits"], true), PHP_EOL;{ "success": true, "data": { "billing_type": "prepaid", "available_credits": 1450, "paid_credits_remaining": 1200, "free_credits_remaining": 250, "unlimited": false, "unlimited_until": null, "usage_this_month": { "period_start": "2026-11-01", "credits_used": 830, "marketing": 700, "utility": 100, "authentication": 30, "service": 0 }, "as_of": "2026-11-11T08:00:00.000Z" }}What the fields mean
Section titled “What the fields mean”| Field | Meaning |
|---|---|
billing_type |
prepaid: you buy credits in advance. postpaid: your usage is invoiced. |
available_credits |
Credits you can spend now: paid plus free credits, minus credits held for messages that are being sent right now. null on a postpaid account. |
paid_credits_remaining |
Credits you bought and have not used yet. null on a postpaid account. |
free_credits_remaining |
Free credits left this month. |
unlimited |
true while a subscription covers your sending. Messages then use no credits. |
unlimited_until |
When that subscription period ends. null when unlimited is false. |
usage_this_month |
Credits used since the first day of this month (UTC): the total in credits_used, and the part for each message category. period_start is that first day. |
as_of |
When the balance was computed. |
On a prepaid account, sending stops when available_credits is not enough:
- A single message answers
402 PREPAID_CREDIT_CAP_REACHED. - Creating a campaign answers
402 insufficient_credits, and nothing is created. - A campaign that is already sending pauses with
pause_reasoninsufficient_credits. See When credits run out. If the balance cannot be checked at that moment, it pauses withbalance_check_failedinstead.
Warn before you run out
Section titled “Warn before you run out”Call GET /balance on a schedule, for example once an hour, and alert your team when
available_credits falls below a level you choose. Skip the check when unlimited is true or
billing_type is postpaid.
const LOW_CREDITS = 500; // choose your own level
async function checkCredits() { const res = await fetch("https://api.sanadwa.com/v1/balance", { headers: { Authorization: `Bearer ${process.env.SANADWA_API_KEY}` }, }); if (!res.ok) throw new Error(`GET /balance failed with HTTP ${res.status}`); const { data } = await res.json(); if (data.billing_type === "postpaid" || data.unlimited) return; if (data.available_credits < LOW_CREDITS) { await notifyTeam(`Only ${data.available_credits} SanadWA credits left. Buy credits in the console.`); }}LOW_CREDITS = 500 # choose your own level
def check_credits(): res = requests.get( "https://api.sanadwa.com/v1/balance", headers={"Authorization": f"Bearer {os.environ['SANADWA_API_KEY']}"}, timeout=30, ) res.raise_for_status() data = res.json()["data"] if data["billing_type"] == "postpaid" or data["unlimited"]: return if data["available_credits"] < LOW_CREDITS: notify_team(f"Only {data['available_credits']} SanadWA credits left. Buy credits in the console.")<?phpconst LOW_CREDITS = 500; // choose your own level
function checkCredits(): void { $ch = curl_init("https://api.sanadwa.com/v1/balance"); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SANADWA_API_KEY")], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, ]); $raw = curl_exec($ch); if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) { throw new RuntimeException("GET /balance failed"); } $data = json_decode($raw, true)["data"]; if ($data["billing_type"] === "postpaid" || $data["unlimited"]) return; if ($data["available_credits"] < LOW_CREDITS) { notifyTeam("Only {$data['available_credits']} SanadWA credits left. Buy credits in the console."); }}# Print only the number of available credits (needs jq).curl -s https://api.sanadwa.com/v1/balance \ -H "Authorization: Bearer $SANADWA_API_KEY" | jq '.data.available_credits'You do not need to check the balance before each send or each campaign. The API checks it
itself and refuses the request with 402 if your credits are not enough; nothing is sent then.
Errors
Section titled “Errors”| Code | HTTP | What to do |
|---|---|---|
balance_lookup_failed |
500 | The balance could not be read. Retry after a few seconds. The API never reports a balance it could not read. |
The usual authentication and rate-limit errors also apply; see Errors.