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.

Header
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

FieldTypeNotes
Productionstringhttps://api.smsgecko.com/api/v2
Local devstringhttp://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 } }.

FieldTypeNotes
400 bad_requesterrorMalformed body / query. `details` carries the field messages.
400 invalid_jsonerrorRequest body is not valid JSON.
400 invalid iderrorThe `:id` in the path is not a 24-hex order id.
401 unauthorizederrorMissing or invalid Bearer token.
402 insufficient_balanceerrorWallet balance is below the order price.
403 forbiddenerrorAPI key belongs to a suspended account.
404 not_founderrorNo order with that id under your account.
409 conflicterrorNo stock, order already resolved, provider can’t resend/reactivate, …
422 unprocessableerrorPrice moved above `max_price`, or the tier is gone.
429 rate_limitederrorToo 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

  1. Find a product. GET /catalog/products?service= returns one row per price tier for each country. Each row's id is a stable tier slot.
  2. Create the order. POST /orders with catalog_product_id and a max_price cap. The wallet is charged now; the order is waiting with a phone_number.
  3. Poll for the code. GET /orders/:id until status is completed and otp_code is set. If nothing arrives before expires_at, the order auto-refunds.
  4. 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.
End to end
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

GET/api/v2/catalog/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.

FieldTypeNotes
servicestring?Provider service code (e.g. "wa"). Required for prices.
countrystring?Provider country code — narrows the results.
limitint?1–500, default 200.
Response
{
  "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

POST/api/v2/orders

Buys the chosen tier, charges the wallet, rents a number. Returns 201 with a waiting order.

FieldTypeNotes
catalog_product_idstringA product `id` from /catalog/products. (`product_id` is accepted too.)
max_pricestring?Decimal USD cap — 422 if the live price exceeds it.
operator_idstring?Pin a carrier: buys the cheapest in-stock tier for it, overriding any tier index in `catalog_product_id`.
Idempotency-Keyheader?8–128 chars — replay-safe create.
Request
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

GET/api/v2/orders/active

Every still-waiting order on your account, newest first: { "data": Order[] }.

Get an order

GET/api/v2/orders/:id

Poll this for the code. sms[] holds each received message; otp_code is the parsed code once it lands.

Finish an order

POST/api/v2/orders/:id/finish

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

POST/api/v2/orders/:id/cancel

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

POST/api/v2/orders/:id/resend

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

POST/api/v2/orders/:id/reactivate

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

FieldTypeNotes
idstring24-hex order id.
statusenum"waiting" | "completed" | "canceled" | "expired"
productobject{ service, country } — display names.
phone_numberstringE.164, e.g. "+15551234567".
pricestringDecimal USD charged.
otp_codestring | nullParsed code once delivered.
smsarray[{ sender, text, received_at }]
created_atstringISO 8601.
expires_atstringAuto-refund deadline while waiting.
finished_atstring | nullSet by /finish.

The product object

FieldTypeNotes
idstring"<service>::<country>[::<tier>]" — pass to /orders.
servicestringProvider service code.
service_slugstringSame code (kept for compatibility).
countrystringDisplay name.
country_codestringProvider country code.
operatorstring | nullUpstream operator, when distinguished.
pricestringDecimal USD, markup applied. Variable precision — "0.5", "0.425".
stocknumberReported availability (0 = unknown / none).