New

🚀 Get 100MB Free Mobile Proxy on ProxyPortal.io 🎁

OfferDeposit bonus: get +5% free balance on deposits from $500.Get started →
Refer & earnInvite friends and earn 5% of every deposit they make — paid straight to your balance.Sign up to get your link →

API access

Authenticate with Authorization: Bearer <token>.

Your tokens

Log in to generate your API token. Everything else on this page is open to read.

Getting started

Base URL — https://smsportal.io/api/v2. Every call is HTTPS and answers JSON.

Authentication — send your token in the Authorization header as Bearer <token> on every request. Tokens start with tv_. Rotating a token invalidates the old one at once; session cookies are never accepted.

Requests — GET endpoints read the query string. POST endpoints read a JSON body (Content-Type: application/json); form-encoded bodies are accepted too.

Responses — always the same envelope: data holds the result, success is true or false, and summery carries a human-readable message (the spelling is part of the interface). Check success, not just the HTTP status.

Money — prices are US dollars and are charged from your wallet balance the moment an order is accepted. Orders that fail, are cancelled in time, or expire without a code are refunded automatically.

Polling or webhooks — you can poll the detail endpoints every few seconds until a code arrives, or register a webhook and get the code pushed to you the moment it lands. Webhooks are faster and cheaper for both sides.

Rate limit — 120 requests per minute per account, counted across all your tokens and endpoints in one-minute windows. Over the limit you get 429 with Retry-After and X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers. Polling one order every 2–3 seconds uses about 20–30. Need more? Contact support and we can raise it for your account — or use webhooks, which cost nothing against it.

Order limit — each account may hold a limited number of open (unanswered) orders at a time; the default is 5. Finish or cancel orders to free a slot.

Example request

Every call carries your token in an Authorization header. Bodies and replies are JSON.

curl -X POST https://smsportal.io/api/v2/tn101/reuse-number \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_id": 12345}'

Response

Every endpoint answers with the same envelope: data holds the result, success says whether the call worked, and summery carries the message. Failures answer 401 (bad or missing token), 403 (account not active), 422 (validation, with the field errors in data) or 400.

{
  "data": {
    "order_id": 12346,
    "reused_from_order_id": 12345,
    "number": "12085551234",
    "service": "Google",
    "status": "Reserved",
    "state": "CA",
    "price": 0.35,
    "reuse": true,
    "till_expiration": "2026-01-31T12:20:00.000Z"
  },
  "success": true,
  "summery": "Reserved"
}

Errors and status codes

A failed call still answers with the envelope: success is false and summery says why, in words you can show a user.

  • 200

    It worked. success is true; read data.

  • 400

    The request was understood but could not be done — not enough balance, the service is out of stock or switched off, the order is no longer open. summery says which; nothing was charged.

  • 401

    Unauthenticated — the Authorization header is missing, malformed, or the token was revoked or rotated.

  • 402

    Not enough balance (US Real SIM purchases and renewals).

  • 403

    The account is not active, or API access was switched off for it.

  • 404

    No such order, number or rental on this account.

  • 409

    Not possible yet or any more, e.g. cancelling inside the lock window — data.retry_after_seconds says how long to wait.

  • 422

    Validation failed. data maps each field to its messages: { "order_id": ["The order_id field is required."] }.

  • 429

    Too many requests. Wait the number of seconds in the Retry-After header (also in data.retry_after_seconds) and try again.

  • 502

    The provider did not answer or refused. Safe to retry; you were not charged for an order that was not placed.

  • 503

    The API, or that product, is temporarily switched off.

  • —

    If summery reads "There is a technical issue on our side…", the problem is ours and we are already on it. Retrying will not help until it is fixed — contact support if it is urgent. You are never charged for an order that fails this way.

{
  "data": {
    "order_id": ["The order_id field is required."]
  },
  "success": false,
  "summery": "Error"
}

Endpoints

Open any endpoint for its parameters, a request in cURL, JavaScript, PHP and Python, and the reply it gives.

Account

GET/api/v2/profileAccount profile

Returns the account the API token belongs to. balance is the current wallet balance in USD as a number, and discount is your account discount in percent, already applied to every price the API quotes.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/profile" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "id": 4821,
    "name": "Jane Doe",
    "username": "janedoe",
    "email": "[email protected]",
    "contact_type": "telegram",
    "contact_info": "@janedoe",
    "discount": 0,
    "balance": 24.5,
    "email_verified": true,
    "created_at": "2026-03-14T09:21:07.000Z"
  },
  "success": true,
  "summery": "Profile info"
}
GET/api/v2/account-detailsWallet balance

The lightest way to read your wallet: your username and current balance in USD as a number. Check it before placing orders rather than relying on an order failing.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/account-details" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "username": "janedoe",
    "balance": 24.5
  },
  "success": true,
  "summery": "Balance info"
}

US Real SIM — /api/v2/real-sim

GET/api/v2/real-sim/get-servicesProducts, validity, prices and stock

Lists every US Real SIM option at your price (your account discount applied). Each validity window is its own product_id — pass that to request-number. stock_count is the exact number in stock (null when unknown) and stock is the same count as display text; available is false when a product is out of stock.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/real-sim/get-services" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "product_id": "118",
      "service": "WhatsApp",
      "country": "USA",
      "validity": "7–15 days",
      "min_days": 7,
      "max_days": 15,
      "price": "4.50",
      "stock": "1,284",
      "stock_count": 1284,
      "available": true
    },
    {
      "product_id": "119",
      "service": "WhatsApp",
      "country": "USA",
      "validity": "30 days",
      "min_days": 30,
      "max_days": 30,
      "price": "9.00",
      "stock": "7",
      "stock_count": 7,
      "available": true
    }
  ],
  "success": true,
  "summery": "Success"
}

Errors

  • 503

    US Real SIM is switched off for maintenance ("US Real SIM is currently unavailable").

GET/api/v2/real-sim/get-segmentsNumber segments and their stock

Lists the number segments a product can be bought from right now — the first digits of the number, country code included (1603 = +1 603…) — with the exact stock in each, most stocked first. Pass one as prefix to request-number to get a number from that segment. An empty data means segment choice is not offered for that product: buy without prefix. Stock moves quickly, so read it just before buying.

Query parameters

  • product_idstringrequired

    A product_id from get-services.

Example request

curl "https://smsportal.io/api/v2/real-sim/get-segments" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    { "prefix": "1231", "stock_count": 101 },
    { "prefix": "1603", "stock_count": 95 },
    { "prefix": "1937", "stock_count": 95 }
  ],
  "success": true,
  "summery": "Success"
}

Errors

  • 422

    product_id is missing or not a positive integer.

  • 503

    US Real SIM is switched off for maintenance ("US Real SIM is currently unavailable").

POST/api/v2/real-sim/request-numberBuy numbers (queued)

Charges your balance for each number and queues the purchase, answering at once with order_ids and status: "pending". Poll get-order-status every 2–3 seconds, or listen for order.number_assigned / order.failed on your webhooks. A number that cannot be delivered is refunded automatically; a delivered number is not refundable. If your balance covers only some of the quantity, those are queued and summery says how many.

JSON body

  • product_idstringrequired

    A product_id from get-services.

  • quantityintegeroptional

    How many numbers to buy, 1–100. Defaults to 1.

  • prefixstringoptional

    A number segment from get-segments, such as 1603. Every number in the order comes from that segment. Omit it for any segment (the widest stock).

Example request

curl -X POST "https://smsportal.io/api/v2/real-sim/request-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"118","quantity":2,"prefix":"1603"}'

Example response

{
  "data": {
    "order_ids": ["90412", "90413"],
    "status": "pending",
    "price": "4.50"
  },
  "success": true,
  "summery": "Queued — poll get-order-status for the number"
}

Errors

  • 402

    The balance does not cover even one number. price is per number.

  • 409

    The product is unknown, no longer offered or out of stock, the prefix segment is sold out or has fewer numbers than quantity, or you already hold the maximum number of numbers.

  • 422

    product_id is missing or not a positive integer, quantity is not an integer from 1 to 100, or prefix is not 1–7 digits.

  • 503

    US Real SIM is temporarily unavailable.

GET/api/v2/real-sim/get-order-statusPending, active or failed

Returns the orders you name, in the same shape as get-numbers. status is pending while the number is being obtained, then active with number, expires_at and a share_link, or failed with error (failed orders are refunded automatically). Ids that are not yours are left out of data rather than reported as errors.

Query parameters

  • order_idsstringrequired

    Comma-separated order ids from request-number, 1 to 100. A single order_id is accepted instead.

Example request

curl "https://smsportal.io/api/v2/real-sim/get-order-status?order_ids=90412%2C90413" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "order_id": "90412",
      "status": "active",
      "service": "WhatsApp",
      "validity": "7–15 days",
      "number": "12085551234",
      "price": "4.50",
      "code": null,
      "last_message": null,
      "message_count": 0,
      "expires_at": "2026-10-21T14:05:12.000Z",
      "share_link": "https://smsportal.io/n/Qm9oZ3hYc1R2a0xQ",
      "error": null,
      "renewal": null,
      "auto_renew": false,
      "created_at": "2026-10-10T14:05:08.000Z"
    },
    {
      "order_id": "90413",
      "status": "pending",
      "service": "WhatsApp",
      "validity": "7–15 days",
      "number": null,
      "price": "4.50",
      "code": null,
      "last_message": null,
      "message_count": 0,
      "expires_at": null,
      "share_link": null,
      "error": null,
      "renewal": null,
      "auto_renew": false,
      "created_at": "2026-10-10T14:05:08.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}

Errors

  • 422

    order_ids is missing, holds a non-numeric id, or names more than 100 ids.

GET/api/v2/real-sim/get-numbersYour numbers

Your US Real SIM numbers, newest first, at most 500. By default only active and pending ones are returned; status=all adds expired and failed orders. code and last_message hold the latest SMS, and renewal is "pending" while a renewal requested through renew-number has not landed yet.

Query parameters

  • statusstringoptional

    all to include expired and failed orders. Any other value (or none) returns active and pending numbers only.

Example request

curl "https://smsportal.io/api/v2/real-sim/get-numbers?status=active" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "order_id": "90412",
      "status": "active",
      "service": "WhatsApp",
      "validity": "7–15 days",
      "number": "12085551234",
      "price": "4.50",
      "code": "482913",
      "last_message": "Your WhatsApp code: 482-913. Don't share this code with others.",
      "message_count": 1,
      "expires_at": "2026-10-21T14:05:12.000Z",
      "share_link": "https://smsportal.io/n/Qm9oZ3hYc1R2a0xQ",
      "error": null,
      "renewal": null,
      "auto_renew": false,
      "created_at": "2026-10-10T14:05:08.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}
GET/api/v2/real-sim/get-messagesMessages on a number

Every SMS received on one of your numbers, newest first (up to 200), with the verification code picked out into code when one is found. Messages also arrive as sms.received webhook events.

Query parameters

  • order_idintegerrequired

    The order id of the number, from request-number or get-numbers.

Example request

curl "https://smsportal.io/api/v2/real-sim/get-messages?order_id=90412" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "sender": "32665",
      "message": "Your WhatsApp code: 482-913. Don't share this code with others.",
      "code": "482913",
      "received_at": "2026-10-10T14:07:41.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}

Errors

  • 404

    No number with that order_id belongs to you, or the order failed.

  • 422

    order_id is missing or not a number.

POST/api/v2/real-sim/renew-numberRenew numbers for another period

Charges the renewal price now for each active number (or one that expired in the last few days and can still be renewed) and extends it by the renewal period (days), usually within hours. get-numbers shows renewal: "pending" until it lands; a renewal not applied within the refund window (3 days by default) is refunded automatically. Numbers that are not yours, not active, or already have a renewal pending are counted in skipped; if the balance runs out part-way, the ones already paid for stand and summery says how many.

JSON body

  • order_idsstring | string[]required

    Order ids to renew, comma-separated or as an array, 1 to 100. A single order_id is accepted instead.

Example request

curl -X POST "https://smsportal.io/api/v2/real-sim/renew-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_ids":["90412","90413"]}'

Example response

{
  "data": {
    "renewed": ["90412", "90413"],
    "price": "9.50",
    "days": 30,
    "skipped": 0,
    "status": "pending"
  },
  "success": true,
  "summery": "Renewal requested"
}

Errors

  • 402

    The balance does not cover even one renewal. price is per number.

  • 409

    Nothing could be renewed: none of the numbers is active, a renewal is already pending, or renewal is not available right now.

  • 422

    order_ids is missing, holds a non-numeric id, or names more than 100 ids.

POST/api/v2/real-sim/set-auto-renewTurn auto-renew on or off

Turns auto-renew on or off. With it on, a number is renewed by itself about 30 hours before it expires, charged to your balance at the renew-number price; if the balance is too low you get an email and a notification beforehand, and the charge is retried hourly until the number expires. get-numbers shows auto_renew. Only active numbers can be switched on.

JSON body

  • order_idsstring | string[]required

    Order ids, comma-separated or as an array, 1 to 100. A single order_id is accepted instead.

  • auto_renewbooleanrequired

    true to turn it on, false to turn it off.

Example request

curl -X POST "https://smsportal.io/api/v2/real-sim/set-auto-renew" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_ids":["90412"],"auto_renew":true}'

Example response

{
  "data": { "updated": 1, "auto_renew": true, "skipped": 0 },
  "success": true,
  "summery": "Updated"
}

Errors

  • 404

    None of the order ids is an active number of yours (or, to turn it off, a number of yours).

  • 422

    order_ids or auto_renew is missing, or more than 100 ids are given.

Temp (s1) — /api/v2/tn101

GET/api/v2/tn101/get-servicesService list

Lists every service you can order, cheapest first. price is what you will pay, with your account discount already applied. Pass service_key to request-number. advanced_options_markup is the percentage added to the base price when you filter by state or area_codes.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/tn101/get-services" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "temporary": [
      {
        "service_id": "9b2f4c1e-6a3d-4e8f-b0a7-2d5c8e1f3a64",
        "service_key": "tg",
        "service": "Telegram",
        "custom_app_name": "Telegram",
        "country": "US",
        "price": 0.45,
        "has_premium": "1"
      },
      {
        "service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
        "service_key": "wa",
        "service": "WhatsApp",
        "custom_app_name": "WhatsApp",
        "country": "US",
        "price": 0.75,
        "has_premium": "0"
      }
    ],
    "advanced_options_markup": 20
  },
  "success": true,
  "summery": "Services"
}
GET/api/v2/tn101/us-statesUS states

The US states a number can be filtered to, keyed by the two-letter code you send as state to request-number. The list is static, so you can cache it.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/tn101/us-states" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "states": {
      "AK": "Alaska (AK)",
      "AL": "Alabama (AL)",
      "AR": "Arkansas (AR)",
      "AZ": "Arizona (AZ)",
      "CA": "California (CA)",
      "…": "…",
      "WY": "Wyoming (WY)"
    }
  },
  "success": true,
  "summery": "States"
}
POST/api/v2/tn101/request-numberOrder a number

Buys a number for one service. The price is charged before the number is requested. If no number can be supplied, the charge is refunded and the reply says you have not been charged. A state or area_codes filter adds advanced_options_markup percent to the price, and request_premium raises it by the bid percentage. Status is Reserved once a number is assigned, or Awaiting while a premium bid is still waiting for one (number is then null). Poll get-number-details with the returned order_id until pin arrives. The number expires at till_expiration. Each account can only have a limited number of open orders at once (5 by default).

JSON body

  • service_keystringrequired

    The service to order: its service_key or service_id from get-services (the display name also works, case-insensitively). service_id is accepted as an alias for this field.

  • statestringoptional

    Two-letter US state code from us-states, e.g. CA. Adds advanced_options_markup percent to the price.

  • area_codesstringoptional

    Preferred area codes, comma-separated, at most 100 characters (e.g. 208,212). Adds advanced_options_markup percent to the price.

  • request_premiumintegeroptional

    Priority bid in percent, from 10 to 2000, added to the service price. Only useful on services with has_premium "1". markup is accepted as an alias.

Example request

curl -X POST "https://smsportal.io/api/v2/tn101/request-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"service_key":"wa","state":"CA"}'

Example response

{
  "data": {
    "order_id": 918273,
    "number": "12085551234",
    "service": "WhatsApp",
    "status": "Reserved",
    "state": "CA",
    "markup": null,
    "price": 0.9,
    "till_expiration": "2026-10-10T14:32:18.000Z"
  },
  "success": true,
  "summery": "Requested"
}

Errors

  • 422

    service_key is missing or matches no orderable service, request_premium is not a number between 10 and 2000, state is not two letters, or area_codes is longer than 100 characters.

  • 400

    Your balance does not cover the price (summery says how much you need), the S1 server is disabled, the service is not available, or no number could be supplied. In every case you are not charged.

POST/api/v2/tn101/reuse-numberReuse a number

Re-reserves the same number from one of your earlier orders for the same service, so you can receive another code on it. Only an order with status Success can be reused. It creates and returns a new order: poll it by its own order_id. reused_from_order_id is the order you passed. A reuse is priced as a percentage of the first order you placed on that number, and may be free. If the number cannot be reserved again, the charge is refunded. Reuses count against your open-order limit like a new purchase.

JSON body

  • order_idintegerrequired

    The id of a completed order (status Success) whose number you want again.

Example request

curl -X POST "https://smsportal.io/api/v2/tn101/reuse-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_id":918273}'

Example response

{
  "data": {
    "order_id": 918402,
    "reused_from_order_id": 918273,
    "number": "12085551234",
    "service": "WhatsApp",
    "status": "Reserved",
    "state": "CA",
    "price": 0.25,
    "reuse": true,
    "till_expiration": "2026-10-10T15:05:41.000Z"
  },
  "success": true,
  "summery": "Reserved"
}

Errors

  • 422

    order_id is missing or is not an integer.

  • 400

    The order does not exist or is not yours, it is not a Success order, the service is no longer available, your balance does not cover the reuse price, or the number can no longer be reserved. You are not charged.

GET/api/v2/tn101/get-number-detailsOrder detail

Reads one of your orders, including the code once it arrives: pin is the extracted code and message the full SMS text, both null until it is received. This is the endpoint to poll while you wait. It reads only our own records, so it is cheap, but a few seconds between calls is plenty. Stop when status becomes Success, or when it is Expired, Rejected or Failed.

Query parameters

  • order_idintegerrequired

    The order_id returned by request-number or reuse-number.

Example request

curl "https://smsportal.io/api/v2/tn101/get-number-details?order_id=918273" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "order_id": 918273,
    "number": "12085551234",
    "service": "WhatsApp",
    "service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
    "status": "Success",
    "state": "CA",
    "pin": "482913",
    "message": "Your WhatsApp code: 482-913. Don't share this code with others.",
    "markup": null,
    "price": 0.9,
    "has_premium": "0",
    "till_expiration": "2026-10-10T14:32:18.000Z",
    "created_at": "2026-10-10T14:12:18.000Z"
  },
  "success": true,
  "summery": "Order details"
}

Errors

  • 422

    order_id is missing or is not an integer.

  • 404

    No order with that id exists on your account.

GET/api/v2/tn101/get-reserved-listReserved numbers

Your open orders (Reserving, Awaiting or Reserved) that have not yet expired, newest first, at most 20. data is an array. An order placed a moment ago appears here straight away, so you can list right after ordering.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/tn101/get-reserved-list" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "order_id": 918402,
      "number": "12085551234",
      "service": "WhatsApp",
      "service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
      "status": "Reserved",
      "state": "CA",
      "markup": null,
      "message": null,
      "pin": null,
      "price": 0.25,
      "has_premium": "0",
      "till_expiration": "2026-10-10T15:05:41.000Z",
      "created_at": "2026-10-10T14:45:41.000Z"
    }
  ],
  "success": true,
  "summery": "Reserved list"
}
GET/api/v2/tn101/get-order-historiesOrder history

Your finished orders (Success, Completed, Rejected, Expired and Failed), newest first, 20 per page. The orders are in data.data, and current_page, last_page, per_page, total, from and to describe the page. from and to are null on an empty page.

Query parameters

  • pageintegeroptional

    Page number, starting at 1. Defaults to 1. Anything below 1 or not a number is read as 1.

Example request

curl "https://smsportal.io/api/v2/tn101/get-order-histories?page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "current_page": 1,
    "last_page": 3,
    "per_page": 20,
    "total": 47,
    "from": 1,
    "to": 20,
    "data": [
      {
        "id": 918273,
        "number": "12085551234",
        "status": "Success",
        "service": "WhatsApp",
        "service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
        "markup": null,
        "pin": "482913",
        "message": "Your WhatsApp code: 482-913. Don't share this code with others.",
        "price": 0.9,
        "has_premium": "0",
        "created_at": "2026-10-10T14:12:18.000Z"
      },
      {
        "id": 917955,
        "number": "13055550187",
        "status": "Rejected",
        "service": "Telegram",
        "service_id": "9b2f4c1e-6a3d-4e8f-b0a7-2d5c8e1f3a64",
        "markup": null,
        "pin": null,
        "message": null,
        "price": 0.45,
        "has_premium": "1",
        "created_at": "2026-10-09T18:40:02.000Z"
      }
    ]
  },
  "success": true,
  "summery": "Order Histories"
}
POST/api/v2/tn101/reject-numberCancel and refund

Cancels an open order that has not received its SMS and refunds what it cost. A number that has already received a message is never refunded: we check with the provider before refunding, so cancelling right after a code lands is refused. refunded is false only when the order cost nothing. The reply shows the order as it now stands, with status Rejected.

JSON body

  • order_idintegerrequired

    The id of an open order (Awaiting or Reserved) to cancel.

Example request

curl -X POST "https://smsportal.io/api/v2/tn101/reject-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_id":918402}'

Example response

{
  "data": {
    "order_id": 918402,
    "number": "12085551234",
    "service": "WhatsApp",
    "status": "Rejected",
    "state": "CA",
    "markup": null,
    "price": 0.25,
    "has_premium": "0",
    "refunded": true,
    "till_expiration": "2026-10-10T15:05:41.000Z"
  },
  "success": true,
  "summery": "Rejected and balance reverse"
}

Errors

  • 422

    order_id is missing or is not an integer.

  • 404

    No order with that id exists on your account, or it has not been assigned a number yet. Retry in a moment if you have only just placed it.

  • 400

    The number has already received its message (That number already received its message), or the order is no longer open (That order is no longer open). Nothing is refunded.

  • 502

    The cancellation could not be completed upstream. The order stays open and charged; try again.

Temp (s2) — /api/v2/tn102

GET/api/v2/tn102/get-servicesService list, carriers and surcharges

Every Temp (s2) service you can order, cheapest first, with price already discounted for your account. carrier_markup_percent and area_code_markup_percent are the surcharges added to that price when an order picks a carrier or area codes; they stack when both are chosen. Pass service_key to the ordering endpoints.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/tn102/get-services" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "carriers": ["at&t", "tmobile"],
    "carrier_markup_percent": 20,
    "area_code_markup_percent": 20,
    "services": [
      {
        "service_id": "6f1c2a9e-3b4d-4e8f-9a17-2c5d8e0b4f63",
        "service_key": "whatsapp",
        "service": "WhatsApp",
        "price": 0.5,
        "stock": 412,
        "multiple_sms": false,
        "lifetime_minutes": 15
      },
      {
        "service_id": "b2e47d10-8c3a-4f5b-9e62-71a0d4c9e8f2",
        "service_key": "telegram",
        "service": "Telegram",
        "price": 0.75,
        "stock": 128,
        "multiple_sms": true,
        "lifetime_minutes": 20
      }
    ]
  },
  "success": true,
  "summery": "Success"
}
GET/api/v2/tn102/get-area-stockNumbers in stock per area code

Numbers in stock right now for one service, keyed by 3-digit area code; only codes with stock are listed. Use it to choose area_codes before calling request-number or request-multiple.

Query parameters

  • service_keystringrequired

    The service_key from get-services. The service name or service_id also work.

Example request

curl "https://smsportal.io/api/v2/tn102/get-area-stock?service_key=whatsapp" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "service_key": "whatsapp",
    "stock": {
      "212": 18,
      "305": 7,
      "415": 23
    }
  },
  "success": true,
  "summery": "Success"
}

Errors

  • 422

    service_key is missing or does not match an available service.

  • 503

    Stock could not be fetched right now; data is an empty array. Try again shortly.

POST/api/v2/tn102/request-numberOrder a number (carrier, area codes)

Buys a temporary number for one service and charges your balance at once. Choosing a carrier or area_codes each adds the surcharge percentage from get-services; a failed order is never charged. Poll get-number-details with the returned order_id until code is filled; a number that receives nothing is refunded automatically at till_expiration.

JSON body

  • service_keystringrequired

    The service_key from get-services. The service name or service_id also work, and service_id is accepted as the field name too.

  • carrierstringoptional

    at&t or tmobile. Adds the carrier surcharge.

  • keep_carrierbooleanoptional

    With carrier: fail the order instead of falling back to another carrier.

  • area_codesstringoptional

    Comma-separated 3-digit area codes, e.g. "212,305" (at most 200 characters). Adds the area-code surcharge.

  • lock_area_codebooleanoptional

    With area_codes: fail the order instead of falling back to another area code.

Example request

curl -X POST "https://smsportal.io/api/v2/tn102/request-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"service_key":"whatsapp","carrier":"tmobile","area_codes":"212,305"}'

Example response

{
  "data": {
    "order_id": 48213,
    "number": "12125550147",
    "service": "WhatsApp",
    "status": "Reserved",
    "code": null,
    "message": null,
    "carrier": "tmobile",
    "area_codes": "212,305",
    "price": 0.7,
    "till_expiration": "2026-10-10T14:35:12.000Z",
    "created_at": "2026-10-10T14:20:12.000Z"
  },
  "success": true,
  "summery": "Requested"
}

Errors

  • 400

    The order could not be placed: balance too low (summery says how much is needed), malformed area_codes, open-order or daily limit reached, the service or server is unavailable, or no number could be obtained. Nothing is charged.

  • 422

    service_key is missing or invalid, carrier is not at&t or tmobile, or area_codes is longer than 200 characters.

POST/api/v2/tn102/request-multipleOne number for several services

One number for several services (2–7). Each service becomes its own order with its own charge, so codes, cancellation and refunds work per order. It is all or nothing: if the number cannot be obtained, nothing is charged, and any service the provider leaves off the number is refunded. carrier and area_codes add the same surcharges as request-number to every order.

JSON body

  • service_keysstring | string[]required

    2–7 service_keys from get-services, as an array or a comma-separated string.

  • carrierstringoptional

    at&t or tmobile. Adds the carrier surcharge to each order.

  • keep_carrierbooleanoptional

    With carrier: fail instead of falling back to another carrier.

  • area_codesstringoptional

    Comma-separated 3-digit area codes, e.g. "212,305". Entries that are not 3 digits are ignored. Adds the area-code surcharge to each order.

  • lock_area_codebooleanoptional

    With area_codes: fail instead of falling back to another area code.

Example request

curl -X POST "https://smsportal.io/api/v2/tn102/request-multiple" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"service_keys":["whatsapp","telegram"]}'

Example response

{
  "data": {
    "number": "13055550198",
    "total": 1.25,
    "orders": [
      {
        "order_id": 48214,
        "number": "13055550198",
        "service": "WhatsApp",
        "status": "Reserved",
        "code": null,
        "message": null,
        "carrier": null,
        "area_codes": null,
        "price": 0.5,
        "till_expiration": "2026-10-10T14:40:03.000Z",
        "created_at": "2026-10-10T14:20:03.000Z"
      },
      {
        "order_id": 48215,
        "number": "13055550198",
        "service": "Telegram",
        "status": "Reserved",
        "code": null,
        "message": null,
        "carrier": null,
        "area_codes": null,
        "price": 0.75,
        "till_expiration": "2026-10-10T14:40:03.000Z",
        "created_at": "2026-10-10T14:20:03.000Z"
      }
    ]
  },
  "success": true,
  "summery": "Requested"
}

Errors

  • 400

    The order could not be placed: balance too low for all the services, more than the maximum number of services, open-order or daily limit reached, a service or the server is unavailable, or no number could be obtained. Nothing is charged.

  • 422

    Fewer than two service_keys, any key that does not match an available service (listed in the message), or carrier is not at&t or tmobile.

POST/api/v2/tn102/request-specific-numberAsk for a particular number

Asks for one particular US number for a service. It costs the service price plus the specific-number surcharge and is charged only when the number is obtained. Poll get-number-details as for any other order.

JSON body

  • numberstringrequired

    A 10-digit US number, e.g. 2125550147. A leading 1 and punctuation are accepted.

  • service_keystringrequired

    The service_key from get-services. The service name or service_id also work.

Example request

curl -X POST "https://smsportal.io/api/v2/tn102/request-specific-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"number":"2125550147","service_key":"whatsapp"}'

Example response

{
  "data": {
    "order_id": 48216,
    "number": "12125550147",
    "service": "WhatsApp",
    "status": "Reserved",
    "code": null,
    "message": null,
    "carrier": null,
    "area_codes": null,
    "price": 0.6,
    "till_expiration": "2026-10-10T14:36:40.000Z",
    "created_at": "2026-10-10T14:21:40.000Z"
  },
  "success": true,
  "summery": "Requested"
}

Errors

  • 400

    Not a valid 10-digit US number, the number is not available, balance too low, open-order or daily limit reached, or the service or server is unavailable. Nothing is charged.

  • 422

    number or service_key is missing, or service_key does not match an available service.

POST/api/v2/tn102/reuse-numberGet the same number again

Rents the number from one of your earlier orders again, for the same service, as a new order. Only an order whose number received its code (status Success or Completed) can be reused. The price is a percentage of the original order's price set by the site, and nothing is charged if the number cannot be rented again.

JSON body

  • order_idintegerrequired

    The earlier order whose number you want again.

Example request

curl -X POST "https://smsportal.io/api/v2/tn102/reuse-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_id":48213}'

Example response

{
  "data": {
    "order_id": 48302,
    "number": "12125550147",
    "service": "WhatsApp",
    "status": "Reserved",
    "code": null,
    "message": null,
    "carrier": null,
    "area_codes": null,
    "price": 0.7,
    "till_expiration": "2026-10-10T16:05:21.000Z",
    "created_at": "2026-10-10T15:50:21.000Z"
  },
  "success": true,
  "summery": "Requested"
}

Errors

  • 400

    The order is not yours or cannot be reused, it never received a code, its service is no longer available, balance too low, or the number could not be rented again. Nothing is charged.

  • 422

    order_id is missing or not a number.

GET/api/v2/tn102/get-number-detailsOrder detail and code

One of your Temp (s2) orders. Poll it after ordering: code holds the verification code and message the SMS text once it arrives, after which status becomes Success. Accepts GET with a query string or POST with a body.

Query parameters

  • order_idintegerrequired

    The order_id returned when the number was ordered.

Example request

curl "https://smsportal.io/api/v2/tn102/get-number-details?order_id=48213" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "order_id": 48213,
    "number": "12125550147",
    "service": "WhatsApp",
    "status": "Success",
    "code": "482913",
    "message": "Your WhatsApp code: 482-913. Don't share this code with others.",
    "carrier": "tmobile",
    "area_codes": "212,305",
    "price": 0.7,
    "till_expiration": "2026-10-10T14:35:12.000Z",
    "created_at": "2026-10-10T14:20:12.000Z"
  },
  "success": true,
  "summery": "Success"
}

Errors

  • 404

    No order with that order_id belongs to your account.

  • 422

    order_id is missing or not a number.

GET/api/v2/tn102/get-reserved-listOpen orders

Your open Temp (s2) orders (status Reserving or Reserved), newest first, up to 500. data is an array of orders in the same shape as get-number-details.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/tn102/get-reserved-list" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "order_id": 48216,
      "number": "12125550147",
      "service": "WhatsApp",
      "status": "Reserved",
      "code": null,
      "message": null,
      "carrier": null,
      "area_codes": null,
      "price": 0.6,
      "till_expiration": "2026-10-10T14:36:40.000Z",
      "created_at": "2026-10-10T14:21:40.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}
GET/api/v2/tn102/get-order-historiesOrder history

All your Temp (s2) orders in every status, newest first, 50 per page. No total is returned: keep increasing page until orders comes back with fewer than per_page entries.

Query parameters

  • pageintegeroptional

    Page number, starting at 1 (the default).

Example request

curl "https://smsportal.io/api/v2/tn102/get-order-histories?page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "page": 1,
    "per_page": 50,
    "orders": [
      {
        "order_id": 48216,
        "number": "12125550147",
        "service": "WhatsApp",
        "status": "Reserved",
        "code": null,
        "message": null,
        "carrier": null,
        "area_codes": null,
        "price": 0.6,
        "till_expiration": "2026-10-10T14:36:40.000Z",
        "created_at": "2026-10-10T14:21:40.000Z"
      },
      {
        "order_id": 48190,
        "number": "14155550123",
        "service": "Telegram",
        "status": "Completed",
        "code": "71035",
        "message": "Telegram code: 71035",
        "carrier": "at&t",
        "area_codes": null,
        "price": 0.9,
        "till_expiration": "2026-10-09T09:32:05.000Z",
        "created_at": "2026-10-09T09:12:05.000Z"
      }
    ]
  },
  "success": true,
  "summery": "Success"
}
POST/api/v2/tn102/reject-numberCancel and refund (after a short lock)

Cancels an open order that has not received a code and refunds its price to your balance. Cancelling is locked for a short window after purchase; until then the call answers 409 with data.retry_after_seconds. refunded is true when the charge was returned.

JSON body

  • order_idintegerrequired

    The open order to cancel.

Example request

curl -X POST "https://smsportal.io/api/v2/tn102/reject-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order_id":48216}'

Example response

{
  "data": {
    "order_id": 48216,
    "number": "12125550147",
    "service": "WhatsApp",
    "status": "Rejected",
    "code": null,
    "message": null,
    "carrier": null,
    "area_codes": null,
    "price": 0.6,
    "till_expiration": "2026-10-10T14:36:40.000Z",
    "created_at": "2026-10-10T14:21:40.000Z",
    "refunded": true
  },
  "success": true,
  "summery": "Rejected"
}

Errors

  • 400

    The number already received its code, or the order is no longer open.

  • 404

    No order with that order_id belongs to your account.

  • 409

    Too soon after purchase. data.retry_after_seconds says how long to wait before trying again.

  • 422

    order_id is missing or not a number.

  • 502

    The cancellation could not be completed right now; nothing changed. Try again.

Rental — /api/v2/rental

GET/api/v2/rental/get-servicesRental services and period prices

Rental services and the periods each can be rented for, priced for your account. Pass service_key and a period to request-number. Asking for specific area codes adds area_code_markup_percent to the price.

Query parameters

None.

Example request

curl "https://smsportal.io/api/v2/rental/get-services" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "area_code_markup_percent": 20,
    "services": [
      {
        "service_key": "telegram",
        "service": "Telegram",
        "periods": [
          { "period": "1d", "label": "1 day", "price": 1.2 },
          { "period": "7d", "label": "7 days", "price": 5.5 },
          { "period": "30d", "label": "30 days", "price": 14 }
        ]
      },
      {
        "service_key": "whatsapp",
        "service": "WhatsApp",
        "periods": [
          { "period": "3d", "label": "3 days", "price": 3.4 },
          { "period": "14d", "label": "14 days", "price": 9.8 }
        ]
      }
    ]
  },
  "success": true,
  "summery": "Success"
}
POST/api/v2/rental/request-numberRent a number

Rents one number for one service for the chosen period, charged from your balance, and answers with the rental — rental_id and number included. If the number cannot be issued you are not charged. Messages are collected about once a minute; read them with get-messages, or listen for sms.received on your webhooks.

JSON body

  • service_keystringrequired

    A service_key from get-services (case-insensitive).

  • periodstringrequired

    One of the periods get-services lists for that service: 1d, 3d, 7d, 14d or 30d.

  • area_codesstringoptional

    Preferred US area codes, three digits each, separated by commas or spaces (e.g. "212,305"). Adds the area-code markup to the price.

  • lock_area_codebooleanoptional

    With area_codes: only accept a number from those area codes. Ignored without area_codes.

  • auto_renewbooleanoptional

    Renew automatically from your balance shortly before the rental ends. Defaults to false.

Example request

curl -X POST "https://smsportal.io/api/v2/rental/request-number" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"service_key":"telegram","period":"7d","area_codes":"212,305","lock_area_code":false,"auto_renew":true}'

Example response

{
  "data": {
    "rental_id": 5521,
    "number": "12125550187",
    "service": "Telegram",
    "service_key": "telegram",
    "period": "7d",
    "period_label": "7 days",
    "status": "Active",
    "price": 6.6,
    "auto_renew": true,
    "renew_count": 0,
    "message_count": 0,
    "last_code": null,
    "last_message": null,
    "till_expiration": "2026-10-17T09:30:00.000Z",
    "created_at": "2026-10-10T09:30:00.000Z"
  },
  "success": true,
  "summery": "Rented"
}

Errors

  • 400

    The rental could not be placed: rentals are disabled, the offer is unavailable or out of stock, the balance is too low ("You need $… for this rental."), you already hold 50 rentals, or no number could be issued.

  • 422

    service_key or period is missing, or that service is not offered for that period.

GET/api/v2/rental/get-rentalsYour rentals

Your rentals, newest first, at most 500. By default only active ones (and any still being set up) are returned; status=all adds expired, cancelled and failed rentals. last_code and last_message hold the latest SMS.

Query parameters

  • statusstringoptional

    all to include expired, cancelled and failed rentals. Any other value (or none) returns active rentals only.

Example request

curl "https://smsportal.io/api/v2/rental/get-rentals?status=active" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "rental_id": 5521,
      "number": "12125550187",
      "service": "Telegram",
      "service_key": "telegram",
      "period": "7d",
      "period_label": "7 days",
      "status": "Active",
      "price": 6.6,
      "auto_renew": true,
      "renew_count": 0,
      "message_count": 1,
      "last_code": "71842",
      "last_message": "Telegram code: 71842. Do not give this code to anyone.",
      "till_expiration": "2026-10-17T09:30:00.000Z",
      "created_at": "2026-10-10T09:30:00.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}
GET/api/v2/rental/get-rental-detailsOne rental with its messages

One of your rentals, in any status, with every message received on it (newest first, up to 200) under messages. Accepts GET with a query string or POST with a body.

Query parameters

  • rental_idintegerrequired

    The rental_id from request-number or get-rentals.

Example request

curl "https://smsportal.io/api/v2/rental/get-rental-details?rental_id=5521" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": {
    "rental_id": 5521,
    "number": "12125550187",
    "service": "Telegram",
    "service_key": "telegram",
    "period": "7d",
    "period_label": "7 days",
    "status": "Active",
    "price": 6.6,
    "auto_renew": true,
    "renew_count": 0,
    "message_count": 1,
    "last_code": "71842",
    "last_message": "Telegram code: 71842. Do not give this code to anyone.",
    "till_expiration": "2026-10-17T09:30:00.000Z",
    "created_at": "2026-10-10T09:30:00.000Z",
    "messages": [
      {
        "sender": "Telegram",
        "message": "Telegram code: 71842. Do not give this code to anyone.",
        "code": "71842",
        "received_at": "2026-10-10T09:41:16.000Z"
      }
    ]
  },
  "success": true,
  "summery": "Success"
}

Errors

  • 404

    No rental with that rental_id belongs to you.

  • 422

    rental_id is missing or not a number.

GET/api/v2/rental/get-messagesMessages on a rental

Every message received on one of your rentals, newest first (up to 200), with the verification code picked out into code when one is found. New messages are collected about once a minute. Accepts GET with a query string or POST with a body.

Query parameters

  • rental_idintegerrequired

    The rental_id from request-number or get-rentals.

Example request

curl "https://smsportal.io/api/v2/rental/get-messages?rental_id=5521" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Example response

{
  "data": [
    {
      "sender": "Telegram",
      "message": "Telegram code: 71842. Do not give this code to anyone.",
      "code": "71842",
      "received_at": "2026-10-10T09:41:16.000Z"
    }
  ],
  "success": true,
  "summery": "Success"
}

Errors

  • 404

    No rental with that rental_id belongs to you.

  • 422

    rental_id is missing or not a number.

POST/api/v2/rental/renew-rentalRenew for another period

Extends an active rental by its period, charging the current price for that period (without the area-code markup) from your balance; charged is the amount taken. The new end is one period after the current end, or after now if that has passed. If the renewal does not go through you are not charged.

JSON body

  • rental_idintegerrequired

    The rental_id of an active rental.

Example request

curl -X POST "https://smsportal.io/api/v2/rental/renew-rental" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rental_id":5521}'

Example response

{
  "data": {
    "rental_id": 5521,
    "number": "12125550187",
    "service": "Telegram",
    "service_key": "telegram",
    "period": "7d",
    "period_label": "7 days",
    "status": "Active",
    "price": 6.6,
    "auto_renew": true,
    "renew_count": 1,
    "message_count": 1,
    "last_code": "71842",
    "last_message": "Telegram code: 71842. Do not give this code to anyone.",
    "till_expiration": "2026-10-24T09:30:00.000Z",
    "created_at": "2026-10-10T09:30:00.000Z",
    "charged": 5.5
  },
  "success": true,
  "summery": "Renewed"
}

Errors

  • 400

    The service or period is no longer offered, so the rental cannot be renewed.

  • 402

    The balance does not cover the renewal; summery gives the price.

  • 404

    No active rental with that rental_id belongs to you.

  • 409

    A renewal for this rental is already in progress.

  • 422

    rental_id is missing or not a number.

  • 502

    The renewal did not go through; you have not been charged.

POST/api/v2/rental/set-auto-renewSwitch auto-renew on or off

Turns auto-renew on or off for an active rental. With it on, the rental is renewed from your balance shortly before it ends (an hour by default); after repeated failed attempts (e.g. a low balance) it stops trying until you set it again, which resets the attempt count.

JSON body

  • rental_idintegerrequired

    The rental_id of an active rental.

  • auto_renewbooleanrequired

    true (or 1) to switch auto-renew on; any other value switches it off.

Example request

curl -X POST "https://smsportal.io/api/v2/rental/set-auto-renew" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rental_id":5521,"auto_renew":false}'

Example response

{
  "data": {
    "rental_id": 5521,
    "number": "12125550187",
    "service": "Telegram",
    "service_key": "telegram",
    "period": "7d",
    "period_label": "7 days",
    "status": "Active",
    "price": 6.6,
    "auto_renew": false,
    "renew_count": 1,
    "message_count": 1,
    "last_code": "71842",
    "last_message": "Telegram code: 71842. Do not give this code to anyone.",
    "till_expiration": "2026-10-24T09:30:00.000Z",
    "created_at": "2026-10-10T09:30:00.000Z"
  },
  "success": true,
  "summery": "Updated"
}

Errors

  • 404

    No active rental with that rental_id belongs to you.

  • 422

    rental_id is missing or not a number, or auto_renew is missing.

POST/api/v2/rental/cancel-rentalCancel and refund (no messages yet)

Cancels an active rental and refunds its current period in full — only while no message has arrived on the number (checked again with the provider before cancelling). The reply is the rental with status: "Rejected" and refunded.

JSON body

  • rental_idintegerrequired

    The rental_id of an active rental with no messages.

Example request

curl -X POST "https://smsportal.io/api/v2/rental/cancel-rental" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rental_id":5521}'

Example response

{
  "data": {
    "rental_id": 5521,
    "number": "12125550187",
    "service": "Telegram",
    "service_key": "telegram",
    "period": "7d",
    "period_label": "7 days",
    "status": "Rejected",
    "price": 6.6,
    "auto_renew": false,
    "renew_count": 0,
    "message_count": 0,
    "last_code": null,
    "last_message": null,
    "till_expiration": "2026-10-17T09:30:00.000Z",
    "created_at": "2026-10-10T09:30:00.000Z",
    "refunded": true
  },
  "success": true,
  "summery": "Cancelled"
}

Errors

  • 400

    A message has already arrived on the number, or the rental can no longer be cancelled.

  • 404

    No active rental with that rental_id belongs to you.

  • 422

    rental_id is missing or not a number.

  • 502

    The cancellation could not be completed right now; try again.

US Real SIM, Temp (s2) and Rental

US Real SIM sells real-SIM numbers kept for days. Buying is queued, so a slow carrier never times out your request:

  • get-services — every option; each validity window has its own product_id, price and stock.
  • get-segments — product_id; the number segments (first digits, e.g. 1603) you can pick from, with stock.
  • request-number — product_id, optional quantity (1–100), optional prefix (a segment from get-segments). Charges at once and answers with order_ids and status: "pending". 402 when the balance is too low.
  • get-order-status — order_ids (comma-separated, up to 100). pending while we get the number, then active with number, expires_at and a share_link, or failed with error — a failed number is refunded automatically. Polling every 2–3 seconds is fine; most numbers are ready within seconds.
  • get-numbers (status=active|all) and get-messages (order_id) — every SMS, newest first, with code picked out.
  • renew-number — order_ids; charges the renewal price now and extends each active number by the renewal period, usually within hours. get-numbers shows renewal: "pending" until it lands; one not applied within 3 days is refunded automatically.
  • A delivered number is not refundable. Buy one first when trying a service.
  • Webhooks: order.number_assigned when the number is ready, sms.received for every SMS, order.failed + order.refunded when it could not be delivered; these carry "provider": "us-real-sim".

Temp (s2) works like Temp (s1), with two extra choices on request-number:

  • service_key — required; the service_key from tn102/get-services.
  • carrier — optional, at&t or tmobile. keep_carrier: true fails the order instead of falling back to another carrier.
  • area_codes — optional, comma-separated, e.g. "212,305"; tn102/get-area-stock?service_key=… lists what is in stock. lock_area_code: true fails the order instead of falling back.
  • Each choice adds the surcharge percentage returned by get-services. A failed order is never charged.
  • request-multiple — service_keys (2–7, comma-separated or an array) plus the same carrier / area_codes options: one number for all of them, one order per service. All or nothing — if one service cannot go on the number, none is charged.
  • request-specific-number — number (10-digit US) and service_key: that exact number, for the service price plus a surcharge. Nothing is charged when it is not available.
  • reject-number works once a short lock after purchase has passed. Before that it answers 409 with data.retry_after_seconds.
  • Poll get-number-details; code holds the verification code once it arrives. Unused numbers are refunded automatically at expiry.

Rental keeps one number for one service for a period:

  • request-number — service_key and period (1d, 3d, 7d, 14d or 30d), both required; optional area_codes, lock_area_code, auto_renew. Answers with rental_id.
  • get-messages / get-rental-details — rental_id; every message received, newest first, with the code picked out.
  • renew-rental — rental_id; charges the current price for the same period and extends the end date. 402 when the balance is too low.
  • set-auto-renew — rental_id, auto_renew (true/false); renews from the balance shortly before the end.
  • cancel-rental — rental_id; refunds in full only while no message has arrived.

Order events for both reach your webhooks like any other order; rental events carry "provider": "s2-rental".

Webhooks

Instead of polling, let us call you. When something happens on your account — a number is assigned, an SMS arrives, an order is refunded, a deposit clears — we send an HTTPS POST with a signed JSON body to the URL you register. Webhooks cover orders placed on the website and through the API alike.

1. Set up an endpoint

  1. Open Webhooks in your account and add your URL. It must be public HTTPS — private and local addresses are refused, and redirects are not followed, so register the final URL.
  2. Pick the events you want, or leave all of them on.
  3. Copy the signing secret (it starts with whsec_) into your server's configuration. You can reveal or rotate it at any time from the same page.
  4. Press Send test event. It delivers a sample sms.received with "test": true in data so you can check your handler end to end without buying a number.
  5. Every delivery, its response code and the first part of your reply are listed under the endpoint, with a Redeliver button for any of them.

2. Events

  • order.created

    An order was accepted and charged. The number may not be known yet.

  • order.number_assigned

    A number is ready and waiting for its SMS — for US Real SIM, when a queued purchase completes.

  • sms.received

    An SMS arrived. pin holds the code we picked out, message the full text. Fires for every SMS on a Real SIM number or rental.

  • order.cancelled

    The order was cancelled (by you, or closed because the number became unavailable).

  • order.expired

    The order ran out of time. For temporary numbers this means no SMS arrived; it is followed by order.refunded.

  • order.failed

    A number could not be supplied. Followed by order.refunded.

  • order.refunded

    The charge went back to your balance. Follows cancelled, expired or failed.

  • deposit.confirmed

    A deposit cleared and your balance was credited.

  • bonus.credited

    A deposit or referral bonus was credited.

  • transfer.sent

    You sent a balance transfer to another user.

  • transfer.received

    Another user sent you a balance transfer.

A typical temporary number produces order.created → order.number_assigned → sms.received, or, when no code arrives, order.expired → order.refunded. Order events say which product they belong to in data.provider: us-real-sim (US Real SIM), s1 (Temporary Number), s2 (Temporary Number S2), s2-rental (Rental). data.order_id is the same id the API returns, so you can match an event to your own record.

3. The payload

Every delivery has the same envelope: a unique id, the event type, created_at, and data. Order events carry the order; wallet events carry the amount and your new balance. Amounts are decimal strings, so they never lose precision.

{
  "id": "3f6c2a9e-8b1d-4f7a-9c2e-5d4b1a7e0f63",
  "type": "sms.received",
  "created_at": "2026-10-10T12:04:31.000Z",
  "data": {
    "order_id": "48211",
    "provider": "s2",
    "service": "WhatsApp",
    "number": "12085551234",
    "status": "Success",
    "price": "0.32",
    "pin": "482913",
    "message": "Your WhatsApp code: 482-913. Don't share this code with others.",
    "expires_at": "2026-10-10T12:20:00.000Z",
    "created_at": "2026-10-10T12:00:02.000Z"
  }
}

4. Headers

  • X-Webhook-Signature

    sha256= followed by the hex HMAC-SHA256 of {timestamp}.{raw body}, keyed with the endpoint secret.

  • X-Webhook-Timestamp

    Unix seconds when this attempt was signed. Reject anything more than five minutes old to stop replays.

  • X-Webhook-Event

    The event type, e.g. sms.received — the same as type in the body.

  • X-Webhook-Event-Id

    Unique per event and identical on every retry of it. Store it and skip ids you have already handled.

  • X-Webhook-Delivery

    Our id for this delivery; quote it when asking support about one.

  • X-Webhook-Attempt

    1 on the first try, then 2, 3, … on retries.

  • Content-Type

    application/json. The User-Agent is SMSPortal-Webhooks/1.0.

5. Verify the signature

Anyone can POST to your URL, so check that each delivery really came from us before acting on it:

  1. Take the raw request body exactly as received. Do not parse and re-serialise it first — that changes the bytes and every signature will fail.
  2. Build the string {X-Webhook-Timestamp}.{raw body} and compute its HMAC-SHA256 with your signing secret, hex-encoded, prefixed with sha256=.
  3. Compare it to X-Webhook-Signature with a constant-time comparison, and reject timestamps more than five minutes old.
import crypto from 'node:crypto'
import express from 'express'

const SECRET = process.env.SMSPORTAL_WEBHOOK_SECRET // whsec_…
const seen = new Set() // use your database in production

const app = express()

// The raw body, exactly as sent — the signature covers these bytes.
app.post('/smsportal/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Webhook-Timestamp')
  const signature = req.get('X-Webhook-Signature') ?? ''
  const body = req.body.toString('utf8')

  // 1. Reject stale deliveries (replays).
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400)

  // 2. Recompute the signature and compare in constant time.
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${timestamp}.${body}`).digest('hex')
  const valid =
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  if (!valid) return res.sendStatus(401)

  const event = JSON.parse(body)

  // 3. Retries reuse the event id — handle each one once.
  if (seen.has(event.id)) return res.sendStatus(200)
  seen.add(event.id)

  // 4. Answer fast; do slow work after responding.
  res.sendStatus(200)

  if (event.type === 'sms.received') {
    console.log(`Code ${event.data.pin} for order ${event.data.order_id}`)
  }
})

app.listen(3000)

6. Responding, retries and duplicates

  • Answer with any 2xx status within 10 seconds. Anything else — a 3xx, 4xx, 5xx, a timeout or a connection error — counts as a failure.
  • A failed delivery is retried immediately, then after 1 min, 5 min, 15 min, 1 h, 3 h and 6 h — 7 attempts in all over roughly ten hours. After the last one it is marked failed; you can still redeliver it by hand.
  • An endpoint that fails 20 times in a row is switched off automatically and the reason is shown on the Webhooks page. Fix the receiver, then switch it back on.
  • Retries carry the same X-Webhook-Event-Id (and body id). Record the ids you have processed and ignore repeats — the same event can arrive twice, for example if your server took the request but timed out before answering.
  • Events are delivered independently and can arrive out of order. Use created_at, or fetch the order from the API, when order matters.
  • Do the slow work after you have answered: acknowledge first, then queue the job.

Legacy format

Endpoints created on the old site keep the body they always received: only sms.received, unsigned, in the shape below. Switch an endpoint to the current format from the Webhooks page to get every event and a signature — we recommend it.

{
  "type": "temporary",
  "data": {
    "number": "12085551234",
    "service": "WhatsApp",
    "pin": "482913",
    "message": "Your WhatsApp code: 482-913."
  }
}