Developers

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

GET/api/public/v1/balanceread

Your Canon SMS wallet balance in Naira.

{
  "data": { "balance": 12500, "currency": "NGN", "frozen": false }
}
GET/api/public/v1/countriesread

Countries currently enabled and visible for ordering.

{
  "data": {
    "countries": [
      { "id": "0f2c…", "name": "United States", "iso_code": "US", "dial_code": "+1", "flag": "🇺🇸" }
    ]
  }
}
GET/api/public/v1/servicesread

All 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 }
    ]
  }
}
POST/api/public/v1/ordersorder

Reserve 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
  }
}
GET/api/public/v1/ordersread

List your orders. Supports ?status=, ?limit= (max 100) and ?offset=.

{
  "data": {
    "orders": [ { "id": "b41f…", "status": "completed", "code": "481902" } ],
    "pagination": { "limit": 25, "offset": 0, "total": 148 }
  }
}
GET/api/public/v1/orders/activeread

Only orders still awaiting a code or completion.

{ "data": { "orders": [ { "id": "b41f…", "status": "waiting_sms" } ] } }
GET/api/public/v1/orders/:idread

Details of a single order you own.

{
  "data": { "order": { "id": "b41f…", "status": "otp_received", "code": "481902" } }
}
GET/api/public/v1/orders/:id/otpread

Polls 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…" } }
}
POST/api/public/v1/orders/:id/cancelorder

Cancel an unused order. The wallet refund is automatic and idempotent.

{ "data": { "order": { "id": "b41f…", "status": "refunded" } } }
POST/api/public/v1/orders/:id/finishorder

Mark an activation as finished once you have used the code.

{ "data": { "order": { "id": "b41f…", "status": "completed" } } }
GET/api/public/v1/transactionswallet

Wallet 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

CodeHTTPMeaning
unauthorized401Missing, malformed, revoked or unknown API key.
forbidden403The key lacks the permission the endpoint requires.
account_suspended403The account owning the key is not active.
not_found404The order does not exist or does not belong to you.
invalid_request422Body or query parameters failed validation.
insufficient_funds402Wallet balance is lower than the selling price.
unavailable409The provider has no number for that country/service right now.
rate_limited429Per-minute limit for the key exceeded. Retry after 60s.
maintenance503Canon SMS is temporarily in maintenance mode.
provider_error502The 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.

Get your API key