Temporary phone number API
The VerifyWith API gets your program a private, non-VoIP US number, waits for a text to it, and returns the verification code as its own field, code. You authenticate with an API key sent as a bearer token. Each number is charged to your saved card at the same prices as the website: 1 day $3.99, 1 week $7.99, 1 month $12.99, 1 year $34.99. The price is held on the card until the first text arrives or 24 hours pass. It receives codes. It does not send them.
Checked by VerifyWith on

The three calls that do the whole job
Buy a number, wait for its text, read the code. Create a key on the Developers page of your account, put it in VERIFYWITH_API_KEY, and every address below starts from the base https://api.verifywith.io/v1.
1. Buy a number
KEY=$(uuidgen) # keep it: the same key never buys twice
curl -X POST https://api.verifywith.io/v1/numbers \
-H "Authorization: Bearer $VERIFYWITH_API_KEY" \
-H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"duration": "day"}'duration is day, week, month or year. A 201 means paid and ordered. The answer carries order and number: the number’s id is number.id and the number itself is number.phoneNumber, in E.164 (+1 and ten digits). For a few seconds number.status can be provisioning and phoneNumber null; read GET /numbers/$NUMBER_ID until it is active.
A 202 with order.nextAction means the card holder’s bank wants them to confirm the payment. Hand them order.nextAction.url, then send the same request again with the same Idempotency-Key to finish the same purchase. With no saved card the answer is 402 card_required, with a url where the person adds one.
2. Wait for its text
curl "https://api.verifywith.io/v1/numbers/$NUMBER_ID/messages/next?timeoutSeconds=60" \
-H "Authorization: Bearer $VERIFYWITH_API_KEY"The request stays open until a text arrives, then answers at once. timeoutSeconds is 1 to 90, 60 if you leave it out. When nothing came in time the answer is still a 200, with "status": "timeout"; ask again.
{
"object": "code_wait",
"status": "received",
"message": {
"object": "message",
"id": "665f1c2a9b3e4d0012a7c3f2",
"numberId": "665f1c2a9b3e4d0012a7c3f1",
"sender": "Acme",
"body": "Your Acme code is 482-913.",
"code": "482913",
"receivedAt": "November 13, 2026T10:00:00.000Z"
},
"code": "482913"
}3. Read the code, or list the texts
The code is in code: digits or letters, with the spaces and dashes taken out, so it can be typed straight into a form. It is null when the text had no code in it, and the whole text is always in message.body. To read what a number has received so far, newest first:
curl "https://api.verifywith.io/v1/numbers/$NUMBER_ID/messages?limit=5" \
-H "Authorization: Bearer $VERIFYWITH_API_KEY"Each item in data is the same message object, with sender, body, code and receivedAt. Pages are cursor-based: limit is 1 to 100, and you pass nextCursor back as cursor for the next page. The number object also keeps latestCode, the code from its newest text that had one.
How waiting for a text works
There are three ways to hear about a text, and none of them needs a fixed sleep. Pick by where your code runs.
- Long poll, for a script or a test:
GET /numbers/$NUMBER_ID/messages/next, as above. WithoutafterMessageIdit waits only for texts that arrive after the call starts, so pass the id of the last message you saw: a text that lands between two calls is then never missed. A text can beat your first call, so if the number may already have one, list its messages first. - Live stream, for a screen or a long-running process:
GET /streamis a server-sent event stream for the whole account. It sendsmessage.received(the number and the text) andnumber.updated, thenreadyonce caught up. Reconnect withLast-Event-IDand every event you missed is sent first;resyncmeans the gap was too long, so re-read your numbers. Add?watch=with up to five number ids to have us check with our provider when a text on one of them seems late. - Webhooks, for a server: register an https address with
POST /webhook-endpointsand the events it wants, such asmessage.received. Each event is a signed JSON POST (the Standard Webhooks spec:webhook-id,webhook-timestamp,webhook-signature), retried for about a day if your server does not answer. The signing secret is shown once.
Texts normally arrive by themselves. If you would rather not wait, POST /numbers/$NUMBER_ID/messages/check asks the carrier now. One number is asked at most once every 15 seconds, and an account gets 12 checks a minute.
Idempotency on buying calls
Every call that spends money needs an Idempotency-Key header: buying a number (POST /numbers) and extending one (POST /numbers/$NUMBER_ID/extend). A request without one is refused with 400 invalid_request. The key is 8 to 255 printable characters with no spaces, and it belongs to your API key.
- Same key, same body: you get the same purchase back, never a second charge. Use this after a timeout or a dropped connection, and after the card holder confirms a payment.
- Same key, different body:
409 idempotency_key_reused. Use a new key for a new purchase. - Same key while the first request is still being handled:
409 idempotency_in_progress. Repeat it in a few seconds.

Key levels and spending limits
A key has one of three levels, and each includes the one before. Give a program the lowest level that does its job: a reader that only waits for codes never needs to buy.
| Level | What it can do |
|---|---|
| read | List numbers, wait for and read texts, open the live stream |
| manage | Also rename, turn auto-renew on or off, cancel, manage webhooks |
| buy | Also buy and extend numbers, charged to the saved card |
A key that can buy always has two spending limits: the most it may pay for one number and the most it may spend in a day, $12.99 and $40.00 unless you change them. Renewals never count. A purchase over either limit is refused with 422 spending_limit_exceeded and nothing is charged. The whole key is shown once, when you create it. A key can never manage keys, cards or the account itself.
Rate limits and errors
A request over a limit gets 429 rate_limited with a Retry-After header in seconds.
- 120 requests a minute per key, 300 per person across all their keys, 600 per IP address.
- Buying calls: 20 an hour per key, 30 per person, 60 per IP address.
- At most 10 long-poll waits and 10 live streams open at once per person.
Every error has the same shape, with a url added when a person has to act in a browser (add a card, confirm with the bank):
{
"error": "spending_limit_exceeded",
"message": "What went wrong, in a sentence a person can read.",
"requestId": "req_7f3a2c1e"
}The full list of codes, every route and every object is in the API documentation, and the OpenAPI 3.1 description for generating a client is at https://api.verifywith.io/v1/openapi.json.
What this API is not
It is not a verification API in the Twilio Verify sense. Those services send codes from your app to your users’ phones and check what they type back. This API is the other side: it gives your program a number that receives a code someone else sent. If you are building sign-up for your own app, you need a sending service; if you are testing that sign-up, or an agent needs to receive a code, this is the tool.
- It cannot send texts, and it cannot make or receive calls, so a code sent by voice call never arrives.
- Numbers are US (+1) only, and you cannot choose the number or its area code.
- Most services that text codes to US numbers send to it, but some refuse some numbers, so no particular service is promised.
To connect an AI agent instead of writing the calls yourself, the same account works over MCP: see the SMS MCP server.
Questions people ask
- Is there a free tier for the API?
- No. The API sells the same four rentals as the website, charged to your saved card, and there are no credits or balance to top up. A number that has received no texts can be cancelled from 5 minutes to 24 hours after purchase, and when our supplier accepts, the money comes back.
- What do I do if the text never arrives?
- Cancel the number with POST /numbers/{id}/cancel while it has no texts, from 5 minutes to 24 hours after you bought it. When our supplier accepts, everything paid for it comes back; when it refuses, the answer is not_cancellable and the number stays yours until it ends.
- Can I pick the area code or a specific number?
- No. You choose only how long to keep the number. The API gives you a US number and returns it in number.phoneNumber once it is ready.
- How long are texts kept?
- For as long as the number is yours, and for 7 days after it ends. After that they are deleted.
- Can I use the API without a card on file?
- Reading texts, yes. Buying, no: a buying call with no saved card answers 402 card_required with a link where the account owner adds one, and the same request can then be sent again.