WesndAll
Wesendall
  • Product
  • Pricing
  • Docs
  • AI Integration
  • Sandbox
  • FAQs
Log inGet started

Documentation

Introduction
Quickstart
Documentation

Wesendall API docs.

Send your first SMS in five minutes. REST + JSON, HTTP Basic Auth, JavaScript fetch examples, Mobile Money top-ups, and an interactive sandbox to try every endpoint live.

QuickstartOpen sandboxAI integration

Quickstart

Send your first SMS in five minutes.

Authentication

HTTP Basic Auth, key + secret, wallet scoping.

Endpoints

Every endpoint with JS fetch + Node + Python.

Wallet & top-ups

Mobile Money flow, charges, refunds, idempotency.

Error handling

Codes, retries, what's transient vs fatal.

LLM reference

Plain-text doc designed for AI coding agents.

Quickstart

Send your first SMS.

  1. 1. Create a free account and verify your phone.
  2. 2. Top up with Mobile Money (UGX 500 minimum).
  3. 3. Mint an API key at Dashboard → API.
  4. 4. Copy the walletId, the key prefix and the secret. The secret is shown once — save it now.
  5. 5. Send →
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/sms/send",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "message": "Hello from Wesendall",
    "recipient": [
      "+256700000000",
      "+256701234567"
    ]
  })
  }
);
const data = await res.json();
console.log(data);

Authentication

HTTP Basic Auth.

Every request includes an Authorization header with Basic base64(API_KEY:API_SECRET). Most HTTP clients (fetch, axios, requests, Guzzle) build it for you when you pass a basic-auth tuple.

Every endpoint requires walletId: this is your wallet's stable ID, shown next to your API keys. Wesendall verifies that the key's owner controls the wallet — keys cannot operate across wallets they don't own.

// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/account/balance",
  {
  method: "GET",
  headers: {,
    "Authorization": `Basic ${credentials}`,
  }
  }
);
const data = await res.json();
console.log(data);

You need three things to call the API

The API key and API secret prove who you are. The walletId says which wallet to charge. All three are shown on the Dashboard → API page after you sign in.

API key

username in Basic Auth

sk_live_a1b2c3d4e5f6

A public-ish identifier for one of your keys. Safe to log.

Where to find it: Visible at Dashboard → API in the table of your keys (the column labeled “Key”).

How to use it: Goes in the username slot of the HTTP Basic Auth header: Basic base64(API_KEY:API_SECRET). Most HTTP clients build this header for you when you pass the key+secret as a basic-auth tuple.

API secret

password in Basic Auth

ws_secret_••••••••••••••

The matching secret for the key. Treat it like a password — anyone with it can spend your wallet.

Where to find it: Shown only once, at the moment you create the key in the dashboard's “Create API key” dialog. If you lose it, revoke the key and create a new one — Wesendall stores only a bcrypt hash and cannot show you the raw value again.

How to use it: Goes in the password slot of the Basic Auth header. Never put it in a frontend bundle, never commit it to git — always read it from an environment variable like WESENDALL_SECRET on your server.

walletId

your wallet's stable ID

wal_kx8s12abcd9...

Identifies the wallet to charge for this request. Every API call needs it.

Where to find it: Shown next to your balance on the Dashboard → API page. It does not rotate when you create or revoke keys — it's tied to your account.

How to use it: For POST endpoints, include it in the JSON body: { "walletId": "...", ... }.
For GET endpoints, include it as a query string: ?walletId=....
The API verifies that the key's owner controls this wallet — you can't call endpoints against a wallet you don't own.

Authorization header: Authorization: Basic base64(API_KEY:API_SECRET)

Go to Dashboard → API

Endpoints

Eight endpoints. Zero surprises.

Base URL https://www.wesendall.com. Try any of them live in the sandbox.

GET/api/v1/account/balanceCheck account balance

Returns the wallet balance, currency and per-SMS cost.

NameInTypeRequiredDescription
walletIdquerystringYesYour wallet's stable ID. Shown on the API page.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/account/balance",
  {
  method: "GET",
  headers: {,
    "Authorization": `Basic ${credentials}`,
  }
  }
);
const data = await res.json();
console.log(data);
POST/api/v1/account/topupTop up wallet (Mobile Money)

Initiates a Mobile Money charge against the supplied phone number. Wallet credits when the user approves on their phone.

NameInTypeRequiredDescription
walletIdbodystringYesYour wallet's stable ID.
amountbodynumberYesAmount in UGX (500 – 10,000,000).
phone_numberbodystringYesPayer phone — +256, 256, or 0XXXXXXXXX.
descriptionbodystringNoOptional payment description.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/account/topup",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "amount": 5000,
    "phone_number": "+256700000000",
    "description": "Wallet top-up from API"
  })
  }
);
const data = await res.json();
console.log(data);
POST/api/v1/sms/sendSend SMS (single or bulk)

Send to a single phone or many. `recipient` accepts a string, an array of strings, or a comma/space separated list.

NameInTypeRequiredDescription
walletIdbodystringYesYour wallet's stable ID.
messagebodystringYesMessage text (up to 1600 chars, billed per 160-char unit).
recipientbodystringYesSingle phone, array, or comma/space separated.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/sms/send",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "message": "Hello from Wesendall",
    "recipient": [
      "+256700000000",
      "+256701234567"
    ]
  })
  }
);
const data = await res.json();
console.log(data);
POST/api/v1/sms/groupSend SMS to a saved group

Sends the same message to every contact in a saved group.

NameInTypeRequiredDescription
walletIdbodystringYesYour wallet's stable ID.
groupIdbodystringYesSaved group ID.
messagebodystringYesMessage text.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/sms/group",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "groupId": "{GROUP_ID}",
    "message": "Q2 promo: 20% off this Friday"
  })
  }
);
const data = await res.json();
console.log(data);
GET/api/v1/sms/historyList SMS history

Paginated SMS send history scoped to the wallet.

NameInTypeRequiredDescription
walletIdquerystringYesYour wallet's stable ID.
pagequerynumberNoPage number (default 1).
per_pagequerynumberNoResults per page (1-100, default 50).
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/sms/history",
  {
  method: "GET",
  headers: {,
    "Authorization": `Basic ${credentials}`,
  }
  }
);
const data = await res.json();
console.log(data);
GET/api/v1/groupsList contact groups

All saved contact groups on the wallet, with contact counts.

NameInTypeRequiredDescription
walletIdquerystringYesYour wallet's stable ID.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/groups",
  {
  method: "GET",
  headers: {,
    "Authorization": `Basic ${credentials}`,
  }
  }
);
const data = await res.json();
console.log(data);
POST/api/v1/groupsCreate a contact group

Creates an empty group with the given title.

NameInTypeRequiredDescription
walletIdbodystringYesYour wallet's stable ID.
titlebodystringYesGroup title (1-120 chars).
descriptionbodystringNoOptional description.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/groups",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "title": "Q2 Promo",
    "description": "April–June promo list"
  })
  }
);
const data = await res.json();
console.log(data);
POST/api/v1/groups/{groupId}/contactsAdd contacts to a group

Bulk-adds contacts to a group. Duplicates are skipped silently.

NameInTypeRequiredDescription
groupIdpathstringYesGroup ID in the URL.
walletIdbodystringYesYour wallet's stable ID.
contactsbodyarrayYesArray of { phone, name? } objects.
// JavaScript (browser or Node 18+)
const credentials = btoa("API_KEY:API_SECRET");

const res = await fetch(
  "https://www.wesendall.com/api/v1/groups/{groupId}/contacts",
  {
  method: "POST",
  headers: {,
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "walletId": "{WALLET_ID}",
    "contacts": [
      {
        "phone": "+256700000000",
        "name": "Sarah"
      },
      {
        "phone": "+256701234567"
      }
    ]
  })
  }
);
const data = await res.json();
console.log(data);

Wallet & top-ups

UGX wallet, MoMo-funded.

Top up via Mobile Money

POST /account/topup with { walletId, amount, phone_number }. Wesendall kicks off an MTN MoMo or Airtel Money charge — the user approves on their phone, wallet credits in seconds.

Charges are atomic

Every send uses wallet.updateMany with a `balance >= cost` guard. Concurrent sends can't double-debit.

Refunds on failure

If the SMS gateway rejects a send, the wallet is incremented back within the same request. You never pay for messages that didn't go out.

Idempotent credits

Deposit status transitions are gated on `pending`. Retry storms can't double-credit.

Error handling

Error codes you can retry against.

Every error response includes a stable error code so your retry logic can switch on machine-readable values.

HTTPCodeMeaningRetry?
401invalid_credentialsBad API key or secretNo
401key_revokedKey has been revokedNo
403wallet_forbiddenWallet doesn't belong to this keyNo
402insufficient_balanceWallet < total costAfter top-up
422validation_failedBody or query failed Zod validationAfter fix
502send_failedProvider rejected; wallet refundedOnce with backoff
500internal_errorTransient server errorOnce with backoff

For LLMs & AI agents

Building with Cursor, Claude Code or Bolt?

Use our LLM reference doc to give your AI agent everything it needs to integrate Wesendall in one paste.

LLM referenceGenerate an integration prompt