A REST API built for automated verification
Generate a scoped key from your dashboard, authenticate with a bearer token, and integrate in minutes.
Authentication
Every request must include your secret key as a bearer token. Keys are hashed at rest, scoped to your account with read, order and wallet permissions, and can be revoked or regenerated at any time. Never embed a key in browser or mobile code — call the API from your server.
curl https://canonsms.com/api/public/v1/balance \
-H "Authorization: Bearer csms_live_••••••••"All responses are JSON. Successful calls return { "success": true, "data": { … } }; failures return { "success": false, "error": { "code", "message" } } with a matching HTTP status.
Endpoints
/api/public/v1/balancereadYour Canon SMS wallet balance in Naira.
{
"data": { "balance": 12500, "currency": "NGN", "frozen": false }
}/api/public/v1/countriesreadCountries currently enabled and visible for ordering.
{
"data": {
"countries": [
{ "id": "0f2c…", "name": "United States", "iso_code": "US", "dial_code": "+1", "flag": "🇺🇸" }
]
}
}/api/public/v1/servicesreadAll enabled services. Add ?country_id=<uuid> to get purchasable products with your Naira price and stock.
{
"data": {
"products": [
{ "country_id": "0f2c…", "service_id": "8b91…", "service": "WhatsApp",
"price": 375, "currency": "NGN", "stock": 214 }
]
}
}/api/public/v1/ordersorderReserve a number for a country + service pair. Send an Idempotency-Key header to make retries safe — a replay returns the original order instead of buying twice. Your wallet is only debited after the provider assigns a number.
{
"data": {
"order": {
"id": "b41f…", "status": "waiting_sms", "phone_number": "+14155550182",
"code": null, "price": 375, "country": "United States", "service": "WhatsApp",
"expires_at": "2026-03-04T12:20:00Z"
},
"replayed": false
}
}/api/public/v1/ordersreadList your orders. Supports ?status=, ?limit= (max 100) and ?offset=.
{
"data": {
"orders": [ { "id": "b41f…", "status": "completed", "code": "481902" } ],
"pagination": { "limit": 25, "offset": 0, "total": 148 }
}
}/api/public/v1/orders/activereadOnly orders still awaiting a code or completion.
{ "data": { "orders": [ { "id": "b41f…", "status": "waiting_sms" } ] } }/api/public/v1/orders/:idreadDetails of a single order you own.
{
"data": { "order": { "id": "b41f…", "status": "otp_received", "code": "481902" } }
}/api/public/v1/orders/:id/otpreadPolls the provider and returns live status. `code` stays null until the SMS actually arrives — Canon SMS never fabricates a code. Poll every 3–5 seconds.
{
"data": { "status": "otp_received", "code": "481902", "order": { "id": "b41f…" } }
}/api/public/v1/orders/:id/cancelorderCancel an unused order. The wallet refund is automatic and idempotent.
{ "data": { "order": { "id": "b41f…", "status": "refunded" } } }/api/public/v1/orders/:id/finishorderMark an activation as finished once you have used the code.
{ "data": { "order": { "id": "b41f…", "status": "completed" } } }/api/public/v1/transactionswalletWallet credits and debits with references and running balance.
{
"data": {
"transactions": [
{ "id": "9c1a…", "type": "debit", "amount": 375, "balance_after": 12125,
"reference": "ord_b41f…", "created_at": "2026-03-04T12:10:00Z" }
],
"pagination": { "limit": 25, "offset": 0, "total": 92 }
}
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | 401 | Missing, malformed, revoked or unknown API key. |
| forbidden | 403 | The key lacks the permission the endpoint requires. |
| account_suspended | 403 | The account owning the key is not active. |
| not_found | 404 | The order does not exist or does not belong to you. |
| invalid_request | 422 | Body or query parameters failed validation. |
| insufficient_funds | 402 | Wallet balance is lower than the selling price. |
| unavailable | 409 | The provider has no number for that country/service right now. |
| rate_limited | 429 | Per-minute limit for the key exceeded. Retry after 60s. |
| maintenance | 503 | Canon SMS is temporarily in maintenance mode. |
| provider_error | 502 | The upstream number provider failed. Safe to retry. |
A complete purchase, end to end
const KEY = process.env.CANON_SMS_KEY;
const api = (path, init) =>
fetch("https://canonsms.com/api/public/v1" + path, {
...init,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", ...init?.headers },
}).then(async (r) => {
const body = await r.json();
if (!r.ok) throw new Error(body.error?.message ?? "Request failed");
return body.data;
});
// 1. pick a product
const { countries } = await api("/countries");
const usa = countries.find((c) => c.iso_code === "US");
const { products } = await api(`/services?country_id=${usa.id}`);
const whatsapp = products.find((p) => p.service === "WhatsApp");
// 2. order it (retry-safe)
const { order } = await api("/orders", {
method: "POST",
headers: { "Idempotency-Key": "signup-1042" },
body: JSON.stringify({ country_id: usa.id, service_id: whatsapp.service_id }),
});
console.log("Use this number:", order.phone_number);
// 3. poll until the code lands
let code = null;
while (!code) {
await new Promise((r) => setTimeout(r, 4000));
({ code } = await api(`/orders/${order.id}/otp`));
}
// 4. release the activation
await api(`/orders/${order.id}/finish`, { method: "POST" });Rate limits & logging
Each key has a per-minute request limit; responses carry X-RateLimit-Limit and X-RateLimit-Remaining, and a 429 includes Retry-After. Every call is written to your request log with endpoint, status code and latency, visible under Developer/API in your dashboard.
