API Reference
A REST API over HTTPS with JSON bodies and Bearer authentication. Buy a virtual number for an exact price tier, poll it for the verification code, then finish. Everything the dashboard does is available here.
Overview
The API is versioned under /api/v2. Responses are JSON. Money is a decimal USD string with variable precision ("0.5", "0.425") — parse it, don't assume two places. Timestamps are ISO 8601 UTC.
Typical integration: list products for a service, create an order against one, then poll the order until otp_code is set.
Authentication
Every request needs a Bearer token. Mint one from the dashboard (Settings → API keys) or via POST /api/v1/api-keys with a session cookie. Keys look like smsg_live_… and are shown once.
curl https://api.smsgecko.com/api/v2/orders/active \
-H "Authorization: Bearer smsg_live_xxxxxxxxxxxx"A missing or invalid key returns 401 unauthorized. A valid key for a suspended account returns 403 forbidden.
Base URL
| Field | Type | Notes |
|---|---|---|
| Production | string | https://api.smsgecko.com/api/v2 |
| Local dev | string | http://localhost:4000/api/v2 |
Idempotency
POST /orders is retry-safe. Send an Idempotency-Key header (8–128 chars) — replaying it returns the original order instead of buying twice. Reuse the same key across every retry of one logical purchase.
Errors
Failures use HTTP status codes and a consistent body: { "error": { "code": string, "message": string, "details"?: unknown } }.
| Field | Type | Notes |
|---|---|---|
| 400 bad_request | error | Malformed body / query. `details` carries the field messages. |
| 400 invalid_json | error | Request body is not valid JSON. |
| 400 invalid id | error | The `:id` in the path is not a 24-hex order id. |
| 401 unauthorized | error | Missing or invalid Bearer token. |
| 402 insufficient_balance | error | Wallet balance is below the order price. |
| 403 forbidden | error | API key belongs to a suspended account. |
| 404 not_found | error | No order with that id under your account. |
| 409 conflict | error | No stock, order already resolved, provider can’t resend/reactivate, … |
| 422 unprocessable | error | Price moved above `max_price`, or the tier is gone. |
| 429 rate_limited | error | Too many requests — back off and retry. |
Rate limits
300 requests per minute per IP. Poll a single order no faster than every ~3 seconds; the code rarely lands sooner than that.
The OTP lifecycle
- Find a product.
GET /catalog/products?service=returns one row per price tier for each country. Each row'sidis a stable tier slot. - Create the order.
POST /orderswithcatalog_product_idand amax_pricecap. The wallet is charged now; the order iswaitingwith aphone_number. - Poll for the code.
GET /orders/:iduntilstatusiscompletedandotp_codeis set. If nothing arrives beforeexpires_at, the order auto-refunds. - Finish (
POST /orders/:id/finish) to signal you're done. Or cancel a still-waiting order for a full refund, resend to ask for another SMS, or reactivate a completed order to buy another code on the same number.
TOKEN="smsg_live_xxxxxxxxxxxx"
# 1. a WhatsApp product in the US
PID=$(curl -s "https://api.smsgecko.com/api/v2/catalog/products?service=wa&country=us" \
-H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')
# 2. buy it, capped at $0.50
ORDER=$(curl -s -X POST https://api.smsgecko.com/api/v2/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "{\"catalog_product_id\":\"$PID\",\"max_price\":\"0.50\"}")
ID=$(echo "$ORDER" | jq -r '.id')
# 3. poll until otp_code is present
until curl -s https://api.smsgecko.com/api/v2/orders/$ID \
-H "Authorization: Bearer $TOKEN" | jq -e '.otp_code' >/dev/null; do sleep 3; done
# 4. finish
curl -s -X POST https://api.smsgecko.com/api/v2/orders/$ID/finish -H "Authorization: Bearer $TOKEN"List products
One product per price tier of a service×country, cheapest first — pass a row's id to POST /orders. Without service you instead get a bare service list for discovery (price "0", stock 0, id is just the service code — not orderable). Query with ?service= to get real, buyable rows.
| Field | Type | Notes |
|---|---|---|
| service | string? | Provider service code (e.g. "wa"). Required for prices. |
| country | string? | Provider country code — narrows the results. |
| limit | int? | 1–500, default 200. |
{
"data": [
{
"id": "wa::us::0",
"service": "wa",
"service_slug": "wa",
"country": "United States",
"country_code": "us",
"operator": null,
"price": "0.47",
"stock": 212412
}
]
}Create an order
Buys the chosen tier, charges the wallet, rents a number. Returns 201 with a waiting order.
| Field | Type | Notes |
|---|---|---|
| catalog_product_id | string | A product `id` from /catalog/products. (`product_id` is accepted too.) |
| max_price | string? | Decimal USD cap — 422 if the live price exceeds it. |
| operator_id | string? | Pin a carrier: buys the cheapest in-stock tier for it, overriding any tier index in `catalog_product_id`. |
| Idempotency-Key | header? | 8–128 chars — replay-safe create. |
curl -X POST https://api.smsgecko.com/api/v2/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 7b1e0c9c-3d21-4b2f-9a10-8f4e2c1d0abc" \
-H "Content-Type: application/json" \
-d '{"catalog_product_id":"wa::us::0","max_price":"0.50"}'Active orders
Every still-waiting order on your account, newest first: { "data": Order[] }.
Get an order
Poll this for the code. sms[] holds each received message; otp_code is the parsed code once it lands.
Finish an order
Marks a completed order finished (finished_at is set) and tells the upstream you're done with the number. No-op if already finished.
Cancel an order
Cancels a waiting order and refunds it in full. A short post-purchase lock applies — 409 until it elapses, and once a code has arrived.
Resend a code
Asks the upstream for another SMS on a still-waiting order. Free — no new rental, no charge. 409 if the order isn't waiting or the provider can't resend.
Reactivate an order
Buys another code on a completed order's number. Charges the current tier price and reopens the order as waiting, keeping its message history. 402 on low balance; 409 if the order isn't completed or the provider can't reactivate.
The order object
| Field | Type | Notes |
|---|---|---|
| id | string | 24-hex order id. |
| status | enum | "waiting" | "completed" | "canceled" | "expired" |
| product | object | { service, country } — display names. |
| phone_number | string | E.164, e.g. "+15551234567". |
| price | string | Decimal USD charged. |
| otp_code | string | null | Parsed code once delivered. |
| sms | array | [{ sender, text, received_at }] |
| created_at | string | ISO 8601. |
| expires_at | string | Auto-refund deadline while waiting. |
| finished_at | string | null | Set by /finish. |
The product object
| Field | Type | Notes |
|---|---|---|
| id | string | "<service>::<country>[::<tier>]" — pass to /orders. |
| service | string | Provider service code. |
| service_slug | string | Same code (kept for compatibility). |
| country | string | Display name. |
| country_code | string | Provider country code. |
| operator | string | null | Upstream operator, when distinguished. |
| price | string | Decimal USD, markup applied. Variable precision — "0.5", "0.425". |
| stock | number | Reported availability (0 = unknown / none). |