Skip to content
For developers

smsape API

Build SMS-verification flows on top of smsape's catalog and delivery network. Sign in, generate a key from Settings → API, and call https://smsape.com/api/v1 from your own server. One wallet covers UI and API purchases; every failed or cancelled purchase is refunded to it automatically.

Quick start

The whole activation lifecycle in four calls:

# 1. Check the live price (and the worst-case ceiling)
curl "https://smsape.com/api/v1/catalog/prices?country=us&service=facebook" -H "Authorization: Bearer sk_..."

# 2. Buy a number with a hard price cap
curl -X POST https://smsape.com/api/v1/activations -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" -d '{"country":"us","service":"facebook","maxPrice":0.40}'
# → 201 { "activation": { "id": "act_abc", "number": "+1206...", "status": "PENDING", ... } }

# 3. Poll until the code arrives (or receive it on your webhook)
curl https://smsape.com/api/v1/activations/act_abc -H "Authorization: Bearer sk_..."
# → "status": "SUCCESS", "messages": [ { "text": "FB-84921 is your code", ... } ]

# 4. No SMS? Cancel after the hold window — full automatic refund
curl -X POST https://smsape.com/api/v1/activations/act_abc/cancel -H "Authorization: Bearer sk_..."

Authentication

Every request sends an API key in the Authorization header. Keys are scoped to your account; the same wallet covers UI buys and API buys. Keep keys server-side — they should never reach the browser. A revoked key returns 401 on every call.

Authorization: Bearer sk_<your-key>

Conventions

  • Money — Integer cents everywhere (priceCents, balanceCents); convenience *Usd / *Price fields carry the same value in dollars. USD only.
  • Timestamps — ISO 8601 UTC strings (2026-07-21T12:00:00.000Z).
  • Countries — Catalog slug ("united-states") and ISO code ("us") are interchangeable in every request.
  • Pagination — List endpoints take limit (1–200, default 50) + cursor, and return nextCursor (null on the last page).
  • Errors — Non-2xx responses always carry { "error": "human-readable reason" }; validation failures add an issues array.
  • Compatibility — Activation responses include sms-activate-style aliases (activationId, phoneNumber, activationCost, activationStatus) so existing integrations can migrate without renaming fields.

Webhooks

Register an HTTPS URL under Settings → API → SMS webhook. smsape POSTs a JSON payload whenever an SMS arrives on one of your numbers (activations + rent) — usually within seconds, so most integrations need no polling at all. Up to 4 delivery attempts with exponential backoff; answer with any 2xx to acknowledge.

Signature header: X-Smsape-Signature: t=<ts>,v1=<hex hmac>. HMAC body is ${ts}.${rawBody} signed with your stored secret. Verify by recomputing and constant-time comparing.

{
  "type": "activation.sms",
  "activation": {
    "id": "act_abc",
    "service": "facebook",
    "country": "us",
    "number": "+1206...",
    "status": "PENDING"
  },
  "message": {
    "id": "sms_xyz",
    "fromNumber": "GoogleVerify",
    "text": "G-123456 is your verification code",
    "createdAt": "2026-05-17T12:00:00Z"
  }
}

Account

GET/me

Returns the calling account's identity and wallet balance. Use it to verify a key works and to gate purchases when the balance is too low.

Example
curl https://smsape.com/api/v1/me \
  -H "Authorization: Bearer sk_..."
Response
{
  "user": { "id": "cku...", "email": "[email protected]", "name": "Acme", "createdAt": "2026-05-01T09:30:00.000Z" },
  "wallet": { "balanceCents": 1250, "holdCents": 0, "balanceUsd": 12.5 }
}

Catalog

Read-only reference data. Prices include smsape's markup — what you see is what your wallet is charged; there are no hidden fees on top.

GET/catalog/services

Every purchasable service, sorted by popularity. Use the slug as the service identifier in buys and price lookups.

Parameters
  • selector=true — optional — narrow to the curated, cabinet-promoted subset instead of the full catalog
Example
curl https://smsape.com/api/v1/catalog/services \
  -H "Authorization: Bearer sk_..."
Response
{
  "items": [ { "slug": "facebook", "name": "Facebook", "iconUrl": "https://...", "sortOrder": 10 }, ... ],
  "total": 4000
}
GET/catalog/countries

Every purchasable country. Both the slug ("united-states") and the ISO code ("us") are accepted anywhere a country is expected.

Parameters
  • selector=true — optional — narrow to the curated subset
Example
curl https://smsape.com/api/v1/catalog/countries \
  -H "Authorization: Bearer sk_..."
Response
{
  "items": [ { "slug": "united-states", "name": "United States", "code": "us", "flagUrl": "https://...", "sortOrder": 1 }, ... ],
  "total": 190
}
GET/catalog/prices?country=us&service=facebook

Live activation prices and stock for one country. priceCents is the cheapest in-stock source (best case); maxPriceCents is the priciest in-stock tier. A buy fills from cheapest to dearest, so the actual charge can rise toward maxPriceCents when the cheapest tier is sold out — send maxPrice on the buy to lock an absolute ceiling. lastUpdated tells you when this country's cache was last refreshed, so you know how long to trust a local copy.

Parameters
  • country — required — country slug or ISO code
  • service — optional — filter to a single service slug
Example
curl "https://smsape.com/api/v1/catalog/prices?country=us&service=facebook" \
  -H "Authorization: Bearer sk_..."
Response
{
  "country": "united-states",
  "lastUpdated": "2026-07-21T10:02:11.000Z",
  "items": [
    { "service": "facebook", "priceCents": 21, "priceUsd": 0.21,
      "maxPriceCents": 84, "maxPriceUsd": 0.84, "stockCount": 10342 }
  ],
  "total": 1
}

Activations

One activation = one temporary number for one service. Lifecycle: PENDING (waiting for SMS) → SUCCESS (code received) | CANCELLED (you cancelled — auto-refunded) | FAILED (no delivery — auto-refunded). Refunds are automatic and idempotent: a terminal CANCELLED/FAILED activation always returns its cost to your wallet, exactly once.

POST/activations

Buy a number. Your wallet is debited at the number's actual price. Send maxPrice (dollars) to cap it — we never charge above it, and if nothing fits the cap the buy is refused with 409 + suggestedPriceCents so you can decide to retry with a higher ceiling. Returns 201 with the number immediately; SMS arrives asynchronously (poll or webhook).

Parameters
  • country — required — country slug or ISO code
  • service — required — service slug from /catalog/services
  • operator — optional — default "any"
  • maxPrice / max_price — optional — absolute price ceiling in dollars (industry-standard alias)
  • slippageCents — optional, legacy — same ceiling in cents; wins if both are sent
  • externalRef / ref — optional — your own order id, max 120 chars
Body
{ "country": "us", "service": "facebook", "maxPrice": 0.40 }
Example
curl -X POST https://smsape.com/api/v1/activations \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"country":"us","service":"facebook","maxPrice":0.40}'
Response
201 {
  "activation": {
    "id": "act_abc", "service": "facebook", "country": "united-states",
    "number": "+1206...", "status": "PENDING", "priceCents": 21,
    "createdAt": "...", "updatedAt": "...",
    "activationId": "act_abc", "phoneNumber": "+1206...",          // sms-activate-style aliases
    "activationCost": 0.21, "activationStatus": "PENDING"
  }
}
409 { "error": "...", "suggestedPriceCents": 55, "suggestedPrice": 0.55 }   // cap too low
GET/activations

List your activations, newest first, cursor-paginated.

Parameters
  • limit — optional — page size 1–200, default 50
  • cursor — optional — nextCursor value from the previous page
Example
curl "https://smsape.com/api/v1/activations?limit=50" \
  -H "Authorization: Bearer sk_..."
Response
{
  "items": [ { "id": "act_abc", "service": "facebook", "country": "united-states",
               "number": "+1206...", "status": "SUCCESS", "priceCents": 21,
               "createdAt": "...", "updatedAt": "...", ...aliases } ],
  "nextCursor": "act_xyz"   // null when this is the last page
}
GET/activations/:id

Current status + every SMS received so far. While the activation is PENDING this endpoint also checks for new SMS server-side (at most once per 3 seconds), so polling it in a loop is the intended usage — 2–5s intervals are ideal.

Example
curl https://smsape.com/api/v1/activations/act_abc \
  -H "Authorization: Bearer sk_..."
Response
{
  "activation": {
    "id": "act_abc", "service": "facebook", "country": "united-states",
    "number": "+1206...", "status": "SUCCESS", "priceCents": 21,
    "createdAt": "...", "updatedAt": "...",
    "messages": [
      { "id": "sms_1", "fromNumber": "Facebook", "text": "FB-84921 is your code", "createdAt": "..." }
    ]
  }
}
POST/activations/:id/cancel

Cancel a PENDING activation and refund the wallet in full. A short hold window applies after purchase (≈2 minutes, up to 3 for some numbers) — cancelling earlier returns 425 with a Retry-After header telling you how many seconds to wait. Activations that already received an SMS can't be cancelled.

Example
curl -X POST https://smsape.com/api/v1/activations/act_abc/cancel \
  -H "Authorization: Bearer sk_..."
Response
{ "ok": true, "refundedCents": 21 }
425 { "error": "..." }   // + Retry-After: <seconds>

Rent

A rental is a long-lived private number that receives unlimited SMS from any sender for its whole duration. Pick a locale from /rent/catalog, buy with /rent/orders, read SMS from /rent/orders/:id.

GET/rent/catalog

Every rentable locale with its full per-duration price table. pricing values are cents keyed by duration; cheapestPrice is the lowest tier in dollars. Locales can sell out — a listed price is not a stock guarantee.

Example
curl https://smsape.com/api/v1/rent/catalog \
  -H "Authorization: Bearer sk_..."
Response
{
  "items": [
    { "rentalId": "US", "name": "United States", "tag": "US", "region": "US",
      "pricing": { "halfDay": 250, "oneDay": 300, "oneWeek": 900, "oneMonth": 2200, ... },
      "cheapestPrice": 2.5 }
  ]
}
POST/rent/orders

Rent a number. The wallet is debited at the catalog price for the chosen locale + duration; the number is returned immediately and stays yours until expiresAt.

Parameters
  • locale — required — rentalId from /rent/catalog (e.g. "US")
  • duration — required — one of the duration codes below
Body
{ "locale": "US", "duration": "oneWeek" }
Example
curl -X POST https://smsape.com/api/v1/rent/orders \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"locale":"US","duration":"oneWeek"}'
Response
201 {
  "rentOrder": { "id": "rent_xyz", "country": "US", "product": "oneWeek",
                 "number": "+1206...", "status": "PENDING", "priceCents": 900,
                 "expiresAt": "2026-07-28T12:00:00.000Z", "createdAt": "..." }
}
GET/rent/orders

List your rent orders, newest first, cursor-paginated (same limit/cursor params as activations). Each row includes messageCount so you can poll cheaply and fetch details only when it grows.

Example
curl https://smsape.com/api/v1/rent/orders \
  -H "Authorization: Bearer sk_..."
Response
{
  "items": [ { "id": "rent_xyz", "country": "US", "product": "oneWeek", "number": "+1206...",
               "status": "PENDING", "priceCents": 900, "expiresAt": "...",
               "createdAt": "...", "updatedAt": "...", "messageCount": 3 } ],
  "nextCursor": null
}
GET/rent/orders/:id

Rent order details + full SMS history (up to 500 messages, oldest first). Fresh SMS are pulled from the carrier on each call (throttled to once per 5s per order, bounded to 2.5s so the response never stalls) — polling this endpoint is the intended way to read incoming messages.

Example
curl https://smsape.com/api/v1/rent/orders/rent_xyz \
  -H "Authorization: Bearer sk_..."
Response
{
  "rentOrder": {
    "id": "rent_xyz", "country": "US", "product": "oneWeek", "number": "+1206...",
    "status": "PENDING", "priceCents": 900, "expiresAt": "...", "createdAt": "...", "updatedAt": "...",
    "messages": [ { "id": "rsms_1", "fromNumber": "Telegram", "text": "Code: 84921", "receivedAt": "..." } ]
  }
}
POST/rent/orders/:id/extend

Extend an active rental by another duration. The wallet is debited at the current catalog price for that duration and expiresAt moves forward. Cancelled rentals can't be extended.

Parameters
  • duration — required — duration code to add
Body
{ "duration": "oneMonth" }
Example
curl -X POST https://smsape.com/api/v1/rent/orders/rent_xyz/extend \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"duration":"oneMonth"}'
Response
{
  "rentOrder": { "id": "rent_xyz", ..., "expiresAt": "2026-08-28T12:00:00.000Z" },
  "extension": { "durationAdded": "oneMonth", ... }
}
POST/rent/orders/:id/cancel

Cancel an active rental and refund its price. Not possible once the rental has expired (it was fully used) or is already cancelled. If the carrier refuses the cancel, 400 is returned and nothing is charged or changed.

Example
curl -X POST https://smsape.com/api/v1/rent/orders/rent_xyz/cancel \
  -H "Authorization: Bearer sk_..."
Response
{
  "rentOrder": { "id": "rent_xyz", ..., "status": "CANCELLED" },
  "refundedCents": 900
}

Rent durations

Not every locale supports every duration — /rent/catalog lists the tiers each locale actually sells.

  • halfDay (12h)
  • oneDay
  • oneWeek
  • oneMonth
  • threeMonths
  • sixMonths
  • twelveMonths

Volume pricing

High-volume accounts get an automatic wholesale discount based on your confirmed numbers per calendar month— activations that received an OTP (cancelled or failed numbers don't count). The tier resets at the start of each month and scales with volume:

  • 1,000 → 20%
  • 2,000 → 25%
  • 3,000 → 30%
  • 5,000 → 40%
  • 10,000+ → 50%

Error handling

Every error is JSON with an error message. Status codes you should handle:

  • 400 — invalid payload (Zod issues returned in issues)
  • 401 — missing or revoked API key
  • 402 — insufficient balance
  • 404 — resource not found or not yours
  • 409 — price moved above your cap (suggestedPriceCents included — raise maxPrice and retry)
  • 425 — cancel attempted before the hold window (Retry-After in seconds)
  • 429 — rate-limited (Retry-After in seconds)
  • 502 — no numbers currently available (out of stock — retry later or try another country)
  • 503 — a dependency is temporarily unavailable (rent provider) — safe to retry with backoff