SMS verification API
Three calls cover the whole flow: order a number, poll until the code lands, release it if it does not. Bearer token, JSON in and out, and every action available in the panel is available over the same API.
- From
- $0.01per code
- Services
- 740
Pay per verification. Cancel an order that received nothing and the charge comes back.
The flow, as calls
GET /api/v2/tn101/get-services— the catalogue, with the identifier you will order by and its current price.POST /api/v2/tn101/request-number— order one. Takesservice_key, plus optionalstateand area codes. Answers with the order id, the number and when it expires.GET /api/v2/tn101/get-number-details— poll it withorder_id.pinis null until the code arrives and holds it afterwards. This is the cheapest read in the API and the one built to be called in a loop.POST /api/v2/tn101/reject-number— release a number that received nothing. This is also what returns the charge.
Nothing is pushed to you on this path, so step 3 is a poll. If you would rather be told than ask, the account also sends webhooks on order events, configured in the panel.
Authenticating
Every request carries Authorization: Bearer <token>. Tokens are generated in the panel under API access, and can be rotated there at any time — rotating invalidates the old token immediately, so swap it in your configuration first.
A request with no valid token gets 401. A suspended account, or one with API access switched off, gets 403. When the API is disabled site-wide, every endpoint answers 503 rather than pretending.
The response envelope
Every endpoint answers with the same three fields:
data carries the payload, success is the boolean to branch on, and summery is the human-readable message. That spelling is not a typo on this page — it is the field name the API has always shipped, it is in every integration written against it, and renaming it would break all of them to tidy up a keystroke. Read summery.
Validation failures answer 422 with data as an object of field names to error messages, so a caller can report which argument was wrong rather than just that something was.
Testing verification flows
The API is a practical way to exercise a signup or password-reset flow against a real carrier path instead of a mocked one — order a number, drive your own form with it, poll for the code, assert on what your application does next. It is a slower test than a stub and it catches the things a stub cannot: normalisation, line-type filtering, and the delivery delays your users will actually hit.
Limits
There is no per-second request limit on the API, but there are two quotas on ordering, and they are the same ones the panel enforces:
- Open orders. An account can hold only so many reserved numbers at once. Poll and release rather than fanning out.
- Cancelled and expired orders per day. Cross the daily allowance and ordering pauses for a while, extending on repeat. Every reservation costs real supply whether or not a code arrives, which is what the quota protects.
Both are described in more detail under verification numbers.
A worked example
Order, poll, release. Replace YOUR_API_TOKEN with a token from the panel and each of these runs as pasted.
# 1. Order a number.
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": "Google"}'
# 2. Poll until "pin" stops being null.
curl "https://smsportal.io/api/v2/tn101/get-number-details?order_id=12345" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# 3. Nothing arrived? Release it and get the charge back.
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": 12345}'What step 2 answers once the code lands
Before it arrives the same call returns the same shape with pin and message null.
{
"data": {
"order_id": 12345,
"number": "12085551234",
"service": "Google",
"status": "Reserved",
"state": "CA",
"pin": "418902",
"message": "418902 is your Google verification code.",
"price": 0.35,
"till_expiration": "2026-09-03T12:20:00.000Z"
},
"success": true,
"summery": "Success"
}Endpoints
Ten in total. All of them take the same bearer token and answer with the same envelope.
- GETAccount profile
/api/v2/profile - GETWallet balance
/api/v2/account-details - GETS1 service list
/api/v2/tn101/get-services - GETUS states
/api/v2/tn101/us-states - POSTOrder a number
/api/v2/tn101/request-number - POSTReuse a number
/api/v2/tn101/reuse-number - GETOrder detail
/api/v2/tn101/get-number-details - GETReserved numbers
/api/v2/tn101/get-reserved-list - GETOrder history
/api/v2/tn101/get-order-histories - POSTCancel and refund
/api/v2/tn101/reject-number
Create an account to generate a token — the full reference and your keys live in the panel under API access.
Questions
- How do I get a token?
- Create an account, then generate one in the panel under API access. It is shown in full there and can be rotated whenever you need.
- Does calling the API cost anything?
- Requests are free; numbers are not. You are charged for a number when `request-number` issues one, at the same per-verification price as an order placed in the panel.
- Is there a sandbox?
- No separate sandbox. There is one environment, and an ordered number is a real number with a real charge against it — which is also what makes the API worth testing against in the first place.
- How should I poll for the code?
- `get-number-details` is one indexed lookup with no upstream call, so a few seconds between polls is reasonable. Stop when `pin` is populated or when the order passes its expiry, and release anything that received nothing.
- Can I be notified instead of polling?
- Yes. Webhooks are configured per account in the panel and fire on order events, so a long-running integration does not have to sit in a loop.
Related
Ready to receive a code?
Registration takes a minute, and you only pay for numbers that deliver.
Create a free account