Virtnumber SMS API & MCP Server
Rent virtual phone numbers, read the SMS they receive, send SMS, and check pricing, paid from your prepaid balance.
REST resources, one URL/verb per action, RFC 9457 application/problem+json error bodies, and RateLimit-* response headers on every request. The same operations are available to AI agents through the MCP server at https://www.virtnumber.com/mcp.
Authentication
Every request needs both headers below. Server: https://www.virtnumber.com
x-api-user | Your user ID. Find it in the developer panel. |
x-api-token | Generate in the developer panel. Shown once, right after generation. The panel can't display an existing token again. |
Operational (~5ms avg response)
Countries
Read-only catalog. No balance required.
/api/v2/developer/countriesList available countries
Every country currently on sale, with its ISO2 code and calling code. No account balance required.
Responses
200OK
{
"countries": [
{
"countryid": "GB",
"countryfull": "United Kingdom",
"countrycode": "44"
},
{
"countryid": "US",
"countryfull": "United States",
"countrycode": "1"
}
]
}401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/countries' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'Pricing
Read-only catalog. No balance required.
/api/v2/developer/pricingList pricing plans
Every plan (7/30/180/360-day, etc.) grouped by country. No account balance required.
Query parameters
| Name | In | Description |
|---|---|---|
countryid | query | Filter to one country (case-insensitive). |
Responses
200OK
{
"pricing": [
{
"countryid": "GB",
"countryfull": "United Kingdom",
"countrycode": "44",
"plans": [
{
"id": 51,
"days": 30,
"price": "9.90",
"salestatus": "yes",
"requiresAddress": false
}
]
}
]
}400Request failed validation.
401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/pricing?countryid=GB' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'Numbers
Buy, list, and cancel numbers. Requires ≥1 EUR balance.
/api/v2/developer/numbersList your active numbers
Responses
200OK
{
"numbers": [
{
"number": "447911123456",
"numberstatus": "active",
"firstbuy": "2026-08-01 10:00:00",
"expirationday": "2026-08-30 22:00:00",
"webhook": "https://example.com/webhook"
}
]
}401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}403Account balance below the 1 EUR minimum required to use the API.
{
"type": "https://virtnumber.com/developer/errors/low-balance",
"title": "Account balance below minimum required to use the API",
"status": 403
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/numbers' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'/api/v2/developer/numbersBuy a number
Orders the next available number for the given plan. Get valid id/countryid pairs from GET /api/v2/developer/pricing first.
Request body
idrequired | Plan ID from the pricing endpoint. |
countryidrequired | |
webhook | Where incoming SMS to this number get POSTed. Can be set/changed later in number settings. |
Responses
200Purchased
{
"phone": "447911123456",
"plan": "30d",
"price": 9.9,
"days": 30,
"numberstatus": "active"
}400Request failed validation.
401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}402Balance too low for this specific purchase.
{
"type": "https://virtnumber.com/developer/errors/insufficient-balance",
"title": "Balance too low for this purchase",
"status": 402,
"detail": "This purchase costs 9.90 EUR; your balance is 3.20 EUR.",
"price": 9.9,
"balance": 3.2
}403Balance below the 1 EUR API minimum, or no billing details saved on the account (every number needs the buyer on record; plans with requiresAddress need an address in that country instead).
{
"type": "https://virtnumber.com/developer/errors/low-balance",
"title": "Account balance below minimum required to use the API",
"status": 403
}404Resource not found.
429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
502Upstream number provider couldn't fulfill the request.
curl -X POST 'https://www.virtnumber.com/api/v2/developer/numbers' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"id":51,"countryid":"GB","webhook":"https://example.com/webhook"}'Example request body
{
"id": 51,
"countryid": "GB",
"webhook": "https://example.com/webhook"
}/api/v2/developer/numbersCancel a number
Query parameters
| Name | In | Description |
|---|---|---|
numberrequired | query |
Responses
200Deleted
{
"message": "Number has been deleted"
}400Request failed validation.
401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}403Account balance below the 1 EUR minimum required to use the API.
{
"type": "https://virtnumber.com/developer/errors/low-balance",
"title": "Account balance below minimum required to use the API",
"status": 403
}404Resource not found.
409The number is still being set up and can't be cancelled yet.
{
"type": "https://virtnumber.com/developer/errors/number-pending",
"title": "This number is still being set up",
"status": 409,
"detail": "The number is still being set up and cannot be deleted yet."
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
502Upstream number provider couldn't fulfill the request.
curl -X DELETE 'https://www.virtnumber.com/api/v2/developer/numbers?number=...' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'Messages
Send SMS from an owned number. Only virtual numbers can send.
/api/v2/developer/send-pricingList send pricing
For every country you can send *from* (virtual numbers only), every destination that number is allowed to send to and the price per message. No account balance required.
Responses
200OK
{
"sendPricing": [
{
"countryid": "US",
"countryfull": "United States",
"destinations": [
{
"destination": "US",
"destinationName": "United States",
"price": 0.0284
},
{
"destination": "CA",
"destinationName": "Canada",
"price": 0.0284
},
{
"destination": "MX",
"destinationName": "Mexico",
"price": 0.6219
}
]
}
]
}401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/send-pricing' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'/api/v2/developer/messagesSend a message
Sends an SMS from one of your owned numbers. Only virtual numbers can send, and only to destinations listed for that number's country in GET /api/v2/developer/send-pricing.
Limits
| API requests | 20 / 15 min |
| New conversations | 4 / day, per sending number |
| Replies to an existing contact | 2 / min, per sending number |
Request body
numberrequired | One of your owned, active phone numbers. With or without a leading +. |
torequired | Destination number, E.164 or local format. |
textrequired |
Responses
200Sent
{
"sid": "SM1234567890abcdef1234567890abcdef",
"to": "+14155551234",
"price": 0.0284
}400Request failed validation.
401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}402Balance too low for this specific purchase.
{
"type": "https://virtnumber.com/developer/errors/insufficient-balance",
"title": "Balance too low for this purchase",
"status": 402,
"detail": "This purchase costs 9.90 EUR; your balance is 3.20 EUR.",
"price": 9.9,
"balance": 3.2
}404Resource not found.
409This number can't send messages.
{
"type": "https://virtnumber.com/developer/errors/cannot-send",
"title": "This number cannot send messages",
"status": 409,
"detail": "Only virtual numbers can send messages"
}429One of three limits: the general 20-per-15-minute API limit (`RateLimit-*` headers), the 4-new-conversations-per-day cap, or the 2-replies-per-minute cap.
{
"type": "https://virtnumber.com/developer/errors/rate-limited",
"title": "Too many requests",
"status": 429,
"detail": "Rate limit exceeded. Please slow down and retry later.",
"resetSeconds": 300
}502Upstream number provider couldn't fulfill the request.
curl -X POST 'https://www.virtnumber.com/api/v2/developer/messages' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"number":"447911123456","to":"+14155551234","text":"Your table is ready."}'Example request body
{
"number": "447911123456",
"to": "+14155551234",
"text": "Your table is ready."
}/api/v2/developer/messagesRead messages
Reads SMS received on (and sent from) one of your numbers, newest first. With after_id, returns only newer messages, oldest first. With wait (1 to 55 seconds) the request waits until a new incoming message arrives and returns it, so one request replaces repeated polling.
Limits
| API requests | 20 / 15 min |
Query parameters
| Name | In | Description |
|---|---|---|
numberrequired | query | One of your numbers, with or without +. |
after_id | query | Only messages with a larger id. |
from | query | Only messages whose sender contains this text. |
direction | query | |
limit | query | |
wait | query | Wait up to this many seconds for the next incoming message. |
Responses
200OK
{
"number": "447911123456",
"messages": [
{
"id": 48213,
"direction": "in",
"from": "Google",
"to": "447911123456",
"text": "G-482913 is your Google verification code.",
"receivedAt": "2026-09-24T10:15:02.000Z",
"codes": [
"482913"
]
}
],
"latestId": 48213
}400Request failed validation.
401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}404Resource not found.
429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/messages?number=...&after_id=...&from=...&direction=...&limit=...&wait=...' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'Balance
Your prepaid balance. The account owner tops it up on the website.
/api/v2/developer/balanceGet balance
Your balance in EUR. 1 credit = 1 EUR. The account owner tops up the balance on the website; API clients and agents spend the existing balance only.
Responses
200OK
{
"balanceEur": 12.5
}401Missing or invalid API credentials.
{
"type": "https://virtnumber.com/developer/errors/unauthorized",
"title": "Missing or invalid API credentials",
"status": 401
}429Too many requests: 20 per 15 minutes, shared with the legacy v1 endpoint.
curl -X GET 'https://www.virtnumber.com/api/v2/developer/balance' \
-H 'x-api-user: YOUR_USER_ID' \
-H 'x-api-token: YOUR_API_TOKEN'Webhooks
Incoming SMS
Sent to the webhook URL set on a number (via numbers, or later in number settings) every time it receives an SMS.
Payload fields
status | |
from | Sender's number. |
to | Your virtnumber number. |
text | |
datetime |
{
"status": "ok",
"from": "",
"to": "",
"text": "",
"datetime": "2030-05-06 21:37:28"
}MCP server for AI agents
| URL | https://www.virtnumber.com/mcp |
| Auth | OAuth 2.1 with PKCE, set up automatically by the client. Headless agents can send x-api-user and x-api-token headers instead. |
Tools
| Tool | Scope | What it does |
|---|---|---|
get_balance | (any) | Balance, this app's spending limit, permissions |
get_pricing | pricing:read | Countries, plans and prices |
list_my_numbers | numbers:read | Your rented numbers |
list_messages | messages:read | Read SMS on a number, with detected codes |
wait_for_sms | messages:read | Wait up to 55 s for the next SMS |
buy_number | numbers:buy | Rent a number (idempotency_key supported) |
send_sms | messages:send | Send an SMS |
delete_number | numbers:delete | Cancel a number |
Connect
Claude (web and desktop)
Settings, Connectors, Add custom connector, paste the server URL.
Claude Code
claude mcp add --transport http virtnumber https://www.virtnumber.com/mcp
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"virtnumber": { "url": "https://www.virtnumber.com/mcp" }
}
}VS Code (.vscode/mcp.json)
{
"servers": {
"virtnumber": { "type": "http", "url": "https://www.virtnumber.com/mcp" }
}
}Headless agent (API key)
POST https://www.virtnumber.com/mcp x-api-user: <your user id> x-api-token: <your API token>
Frequently asked questions
- Does the API support bulk SMS or mass texting?
- No. A number may start four new conversations per day, so the API is built for transactional messages and replies, not campaigns. If you need to reach thousands of recipients at once, a bulk SMS provider is the right tool.
- What are the rate limits?
- Twenty requests per fifteen minutes per token, plus the four new conversations per number per day noted above. Reading messages and listing numbers count against the request limit only.
- Can an AI agent use this directly?
- Yes. The MCP server above exposes the same operations as tool calls, so an agent can buy a number, read messages and check pricing without you wrapping the REST API yourself.
- Which numbers can send SMS?
- Numbers in the United States send to the US, Canada and Mexico. Swedish numbers send across Europe. Every number receives SMS, and the panel shows the sending price per destination before you buy.