Skip to content

Developers

Temporary SMS API for QA and staging

GetVerifyCode gives your server temporary virtual numbers for inbound SMS. Use shared free numbers for public inbox checks, or paid private activations when you need isolation. Lawful QA, staging, privacy, and travel only.

Base URL https://getverifycode.com/api/v1. Money is integer USD cents. The client cannot set the price. Retail cents on /api/v1 use API markup from admin Pricing and profit, so they can differ from website shop prices. Machine spec: /openapi.yaml.

Responses expose retail cents, stock, phone numbers, SMS, and vendor-neutral package labels only. They never name wholesale suppliers, upstream order ids, adapter names, or cost. Errors stay generic (no_stock, provider_unavailable, and similar).

  • Shared free numbers are public inboxes. No wallet and no API key. Anyone can read the same messages.
  • Private activations need a bearer token, confirmed email, and wallet credits (crypto top-ups from $1). Unused holds return if no SMS arrives before the timeout.
  • There is no free paid-activation quota. Many apps reject VoIP or recycled numbers. Delivery is not guaranteed.
  • Allowed use is in the Acceptable Use Policy.

Quick start

  1. Call GET /catalog/meta and check free_numbers and that the v1 surface is live.
  2. For a public demo, list free-number countries, pick a number, and poll messages. No Authorization header.
  3. For a private OTP, create an account, confirm email, create an API key, top up, then quote → buy → poll → cancel on /activations.

Prefer private activations for secrets. A personal SIM in staging leaks OTPs into production accounts and breaks CI.

Create a key

You need an account. We show the token once. A new key replaces the old one. Keys last one year.

Sign in first. Then you can create a key.

Authentication

Send this header on private requests. Catalog, health, packages, and free numbers stay public when their product flags allow them.

Header
Authorization: Bearer YOUR_API_KEY

Errors

Domain failures use this shape. Upstream payloads, keys, and internal hostnames are never included. Validation failures from form requests use Laravel's message and errors object instead.

Typed error
{
  "error": {
    "code": "insufficient_funds",
    "message": "Insufficient available balance."
  }
}
Paused surface
HTTP 503

{
  "error": {
    "code": "public_api_off",
    "message": "The public API is paused."
  }
}

Missing or invalid tokens return HTTP 401 with Unauthenticated. Another user's activation, top-up, or key returns HTTP 404.

Health

Liveness for the public v1 surface. No token. Still returns 503 when the public API flag is off.

GET/healthPublic
curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/health'
200
{
  "status": "ok"
}

Catalog

Public. No token. Same surface the website buy panel uses. Retail cents and live stock only. No wholesale fields and no supplier names. Sample prices below are examples. fresh=1 on offers and routes busts the short cache.

GET/catalog/servicesPublic
curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/services'
200
{
  "services": [
    {
      "slug": "whatsapp",
      "name": "WhatsApp",
      "from_cents": 52,
      "in_stock": true,
      "stock": 12
    }
  ]
}
GET/catalog/countriesPublic

Full country directory. No retail required.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/countries'
GET/catalog/countries/{country}/servicesPublic

Services with retail cents and stock for one country.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/countries/US/services'
GET/catalog/services/{service}/countriesPublic

Optional query operator, default any. Prefer /destinations when you want the shop-style country list (including Virtual and Rent rows).

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/services/whatsapp/countries?operator=any'
200
{
  "service": "whatsapp",
  "countries": [
    {
      "iso": "US",
      "slug": "usa",
      "name": "United States",
      "code": "US",
      "dial": "+1",
      "in_stock": true,
      "retail_cents": 52
    }
  ]
}
GET/catalog/services/{service}/destinationsPublic

Shop country picker. Each row includes destination (any, virtual, or rent), product_kind, and a display package_label. When you call routes, send the ISO in country and map rent or virtual rows with destination.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/services/whatsapp/destinations'
200
{
  "service": "whatsapp",
  "destinations": [
    {
      "iso": "US",
      "slug": "usa",
      "name": "United States",
      "code": "US",
      "dial": "+1",
      "in_stock": true,
      "retail_cents": 111,
      "stock": 449,
      "destination": "any",
      "package_label": "United States",
      "operator_hint": "any",
      "product_kind": "activation",
      "duration_label": null
    }
  ]
}
GET/catalog/services/{service}/offersPublic

Legacy flat offer rows. Prefer /routes for the package cards the buy panel shows.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/services/whatsapp/offers'

Free numbers

Shared public inboxes for lawful QA demos. No bearer token. No wallet charge. Anyone can read the same messages, so never use these for private accounts. When catalog/meta.free_numbers is false, these routes return HTTP 404. Shop cookie routes under /api/free-numbers match the same payloads. UI mirror: /free-numbers.

GET/free-numbers/countriesPublic, flag on
curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/free-numbers/countries'
200
{
  "countries": [
    {
      "code": "1",
      "iso": "US",
      "slug": "united-states",
      "name": "United States",
      "dial": "+1",
      "number_count": 12
    }
  ]
}
GET/free-numbers/countries/{country}/numbersPublic, flag on

country may be a dial code (1), ISO, or slug (united-states).

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/free-numbers/countries/1/numbers'
200
{
  "country": "1",
  "numbers": [
    {
      "number": "2025550100",
      "phone": "+12025550100",
      "last_seen": "1 min",
      "is_archive": false
    }
  ]
}
GET/free-numbers/countries/{country}/numbers/{number}/messagesPublic, flag on

Optional page query (default 1). Bodies may include an extracted otp_code when present.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/free-numbers/countries/1/numbers/2025550100/messages?page=1'
200
{
  "country": "1",
  "number": "2025550100",
  "messages": [
    {
      "id": "msg-1",
      "sender": "WhatsApp",
      "body": "Your code is 482913",
      "otp_code": "482913",
      "received_at": "2026-09-26T10:00:00Z",
      "received_label": "1 min"
    }
  ],
  "page": 1,
  "last_page": 1
}

Packages

After the user picks a service and country, call routes. That is the same package list as the right-hand buy panel: Open stock, Ranked delivery, Virtual, Wide stock, US mobile (US only), and Rent day packages when rentals are on. Only in-stock packages are returned. Labels and operators match the panel. Retail cents follow API markup, not the website shop markup. Pass the chosen operator into quote and buy.

OperatorBuy panel labelNotes
route_openOpen stockAny working number from open stock.
route_rankedRanked deliveryPrefers stronger delivery history.
route_virtualVirtualHosted numbers. Some apps reject them.
route_wideWide stockExtra stock when other routes are thin.
route_usUS mobileUnited States only.
rent_{days}Rent N daysWhen destination=rent and rentals are enabled.
GET/catalog/services/{service}/routesPublic

Required query country (ISO-2). Optional destination=any|rent and fresh=1. Each card is live-verified. Empty or out-of-stock packages are omitted. Fields match the shop: label, lane, blurb, package_label, product_kind, retail_cents, stock, optional rate (delivery success percent when known).

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/services/whatsapp/routes?country=US&destination=any'
200
{
  "service": "whatsapp",
  "country": "US",
  "destination": "any",
  "routes": [
    {
      "service": "whatsapp",
      "operator": "route_ranked",
      "iso": "US",
      "slug": "usa",
      "name": "USA",
      "code": "US",
      "label": "Ranked delivery",
      "lane": "Ranked delivery",
      "blurb": "Prefers numbers with stronger delivery history.",
      "product_kind": "activation",
      "duration_label": null,
      "duration_days": null,
      "retail_cents": 111,
      "stock": 449,
      "in_stock": true,
      "rate": 6.52,
      "package_label": "US · Ranked delivery",
      "verified": true
    },
    {
      "service": "whatsapp",
      "operator": "route_open",
      "iso": "US",
      "slug": "usa",
      "name": "USA",
      "code": "US",
      "label": "Open stock",
      "lane": "Open stock",
      "blurb": "Any working number from open stock.",
      "product_kind": "activation",
      "duration_label": null,
      "duration_days": null,
      "retail_cents": 312,
      "stock": 113184,
      "in_stock": true,
      "rate": null,
      "package_label": "US · Open stock",
      "verified": true
    }
  ]
}

For rentals, call the same routes endpoint with destination=rent. Each returned operator looks like rent_7 with duration_days and product_kind: "rental". Feature-flagged helpers also exist:

GET/catalog/rentals/servicesPublic
GET/catalog/rentals/countriesPublic

Empty when rentals are off. Optional country on services and service on countries.

GET/catalog/quotePublic

Required service and country. Pass the package operator from routes (for example route_open). Default any still works as a best-available pick. Response includes the same lane and product_kind the panel shows.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/catalog/quote?service=whatsapp&country=US&operator=route_open'
200
{
  "service": "whatsapp",
  "country": "US",
  "operator": "route_open",
  "retail_cents": 312,
  "in_stock": true,
  "currency": "USD",
  "lane": "Open stock",
  "product_kind": "activation",
  "duration_label": null,
  "duration_days": null
}

Account

Token required. Same wallet as the website. available_cents can be spent. held_cents are reserved for a live number.

GET/meBearer
curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/me'
200
{
  "user": {
    "id": 1,
    "name": "Ada",
    "email": "[email protected]",
    "email_verified": true,
    "banned": false,
    "max_purchase_cents": null,
    "google": false,
    "referral_code": "ADA12",
    "totp_enabled": false,
    "wallet": {
      "available_cents": 1000,
      "held_cents": 0,
      "currency": "USD"
    }
  }
}

Wallet

GET/walletBearer
curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/wallet'
200
{
  "available_cents": 1000,
  "held_cents": 0,
  "currency": "USD"
}
GET/wallet/ledgerBearer

Laravel pagination, 25 rows per page. Use ?page=2. Amounts are integer cents.

curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/wallet/ledger'
200
{
  "data": [
    {
      "id": 10,
      "type": "top_up",
      "amount_cents": 500,
      "available_delta": 500,
      "held_delta": 0,
      "available_after": 1500,
      "held_after": 0,
      "reference_type": "top_up",
      "reference_id": 3,
      "created_at": "2026-09-18T12:00:00+00:00"
    }
  ],
  "current_page": 1,
  "per_page": 25,
  "total": 1
}
GET/wallet/packagesPublic

No token. Package cents come from shop settings. Soft floor is min_custom_cents (default 100). Each coin may require more via min_credit_cents. Max is max_topup_cents (default 50000). crypto_services lists live Cryptomus coins and networks for invoice creation.

curl
curl -s -H 'Accept: application/json' 'https://getverifycode.com/api/v1/wallet/packages'
200
{
  "packages_cents": [500, 1000, 2500, 5000, 10000],
  "currency": "USD",
  "min_cents": 100,
  "custom_topup_enabled": true,
  "min_custom_cents": 100,
  "min_custom_cents": 100,
  "crypto_enabled": true,
  "withdrawals_enabled": false,
  "min_withdraw_cents": 500,
  "withdraw_currency": "USDT",
  "withdraw_network": "TRON",
  "crypto_fee_bps": 65,
  "crypto_min_usdt": "0.5",
  "crypto_pay_currency": "USDT",
  "crypto_pay_network": "TRON",
  "crypto_invoice_lifetime_seconds": 3600,
  "crypto_services": [
    {
      "currency": "USDT",
      "network": "TRON",
      "is_available": true,
      "min_amount": "1",
      "max_amount": "100000",
      "fee_percent": "0.65",
      "rate_usd": "1",
      "min_credit_cents": 100,
      "blockchain_label": "TRC20"
    }
  ]
}

Top-ups

Token required. Email must be verified. amount_cents may be a listed package or any custom amount in the allowed min/max range. Optional to_currency and network pick the Cryptomus pay method from crypto_services. Omit them to use the preferred USDT TRON default when available.

The response includes a payment object with the deposit address, network, exact payer amount, and expiry. Build your own QR from payment.address. checkout_url may still be present for reference. Credits land after the Cryptomus payment webhook, not when the invoice is created. Crypto top-ups are non-refundable. Repeat the same idempotency_key (8 to 80 characters) to avoid a double invoice.

POST/wallet/top-ups/cryptomusBearer
Body
{
  "amount_cents": 500,
  "idempotency_key": "topup-2026-09-18-01",
  "to_currency": "USDT",
  "network": "TRON"
}
curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"amount_cents":500,"idempotency_key":"topup-2026-09-18-01","to_currency":"USDT","network":"TRON"}' \
  'https://getverifycode.com/api/v1/wallet/top-ups/cryptomus'
201
{
  "top_up": {
    "id": 12,
    "provider": "cryptomus",
    "status": "pending",
    "amount_cents": 500,
    "credited_cents": null,
    "commission_cents": null,
    "merchant_net_cents": null,
    "paid_usd_cents": null,
    "checkout_url": "https://pay.cryptomus.com/pay/...",
    "paid_at": null,
    "payment": {
      "address": "TXyz...",
      "network": "TRON",
      "payer_amount": "5.033",
      "payer_currency": "USDT",
      "amount_usd": "5.00",
      "expired_at": "2026-09-22T02:00:00+00:00",
      "fee_bps": 65,
      "min_usdt": "0.5",
      "lifetime_seconds": 3600
    }
  }
}
GET/wallet/top-upsBearer

Latest 20 top-ups for the authenticated account.

curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/wallet/top-ups'
GET/wallet/top-ups/{id}Bearer, owner
curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/wallet/top-ups/12'
POST/wallet/top-ups/{id}/cancelBearer, owner

Cancels a pending invoice only. No credits were added. Send a new create call if the user changes coin or amount.

curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' -d '{}' \
  'https://getverifycode.com/api/v1/wallet/top-ups/12/cancel'

Top-up status is pending, paid, failed, cancelled, or reversed.

Withdrawals

Cash-out is paused on GetVerifyCode. Wallet credit is for buying numbers. When enabled later, withdrawals will be USDT on TRON (TRC20), minimum 500 cents, behind the wallet_withdrawals flag. Endpoints below stay in the contract for that future toggle.

POST/wallet/withdrawalsBearer
Body
{
  "amount_cents": 500,
  "address": "TDD97yguPESTpcrJMqU6h2ozZbibv4Vaqm",
  "idempotency_key": "wd-2026-09-21-01"
}
curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"amount_cents":500,"address":"TDD97yguPESTpcrJMqU6h2ozZbibv4Vaqm","idempotency_key":"wd-2026-09-21-01"}' \
  'https://getverifycode.com/api/v1/wallet/withdrawals'
GET/wallet/withdrawalsBearer
GET/wallet/withdrawals/{id}Bearer, owner

Status values: pending_review, processing, needs_review, paid, rejected, failed, cancelled. Idempotency keys are unique per user.

Activations

Holds retail cents, then returns a GetVerifyCode activation id and phone. Use the same operator from the packages list (for example route_open). Repeat the same idempotency key to avoid a double buy. A sent retail_cents field is ignored. Responses never include wholesale cents, supplier names, or upstream order ids.

POST/activationsBearer
Body
{
  "service": "whatsapp",
  "country": "US",
  "operator": "route_open",
  "idempotency_key": "buy-2026-09-18-01"
}
curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"service":"whatsapp","country":"US","operator":"route_open","idempotency_key":"buy-1"}' \
  'https://getverifycode.com/api/v1/activations'
201
{
  "activation": {
    "id": 88,
    "status": "pending",
    "service": "whatsapp",
    "country": "US",
    "operator": "route_open",
    "phone_number": "+12015550148",
    "retail_cents": 312,
    "currency": "USD",
    "product_kind": "activation",
    "duration_label": null,
    "expires_at": "2026-09-18T12:20:00+00:00",
    "completed_at": null,
    "sms": []
  }
}
GET/activations/{id}Bearer, owner

Poll about every 2.5 seconds while status is pending. When an SMS arrives, sms[].otp_code is the parsed code. Unused holds return if you cancel in time or if the 20 minute window ends with no SMS.

curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/activations/88'
POST/activations/{id}/cancelBearer, owner
curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' \
  'https://getverifycode.com/api/v1/activations/88/cancel'
GET/activationsBearer

Owner list. Latest 50 rows.

curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/activations'
200
{
  "activations": [
    {
      "id": 88,
      "status": "received",
      "service": "whatsapp",
      "country": "US",
      "operator": "route_open",
      "phone_number": "+12015550148",
      "retail_cents": 312,
      "currency": "USD",
      "product_kind": "activation",
      "duration_label": null,
      "expires_at": "2026-09-18T12:20:00+00:00",
      "completed_at": "2026-09-18T12:04:12+00:00",
      "sms": [
        {
          "sender": "WhatsApp",
          "body": "Your code is 482193",
          "otp_code": "482193"
        }
      ]
    }
  ]
}

After purchase

  1. GET destinations, then GET routes for the chosen country.
  2. Optional GET quote with that operator to confirm retail cents.
  3. POST activations with the same operator. Save the id and phone_number.
  4. Paste the number only where you are allowed to complete a check.
  5. GET activations/id until sms has otp_code, or until timeout.
  6. POST cancel if you do not want to wait. That releases the hold.

Status values

  • pending. Number is live. Wallet cents are held.
  • received. SMS stored. Hold captured.
  • canceled. You or support released the hold.
  • timeout. Window ended. Hold returned.
  • failed. Stock or price check refused the sale. Hold returned.
  • refunded. Admin force refund after pending or received.

Refunds

POST /activations/{id}/cancel is the developer refund. It only works while status is pending. The unused hold returns to available cents. The 20 minute timeout does the same and sets status to timeout. After received, there is no public refund route. Poll until status is refunded if an admin credits the captured amount. The website Ask for a refund path opens a billing ticket. Do not add a client-side force refund.

Wallet ledger rows stay integer cents. A cancel writes a release. An admin refund after received writes a credit.

API keys

Create the first key on the website. These routes manage that key after you have one.

GET/api-keysBearer
curl
curl -s -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/api-keys'
200
{
  "keys": [
    {
      "id": 4,
      "name": "developer",
      "last_used_at": null,
      "expires_at": "2027-09-18T12:00:00+00:00",
      "created_at": "2026-09-18T12:00:00+00:00"
    }
  ]
}
POST/api-keysBearer
curl
curl -s -X POST -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/api-keys'
201
{
  "key": {
    "id": 5,
    "name": "developer",
    "last_used_at": null,
    "expires_at": "2027-09-18T12:00:00+00:00",
    "created_at": "2026-09-18T12:00:00+00:00"
  },
  "token": "5|plaintext-shown-once"
}
DELETE/api-keys/{id}Bearer, owner
curl
curl -s -X DELETE -H 'Accept: application/json' -H 'Authorization: Bearer YOUR_API_KEY' 'https://getverifycode.com/api/v1/api-keys/5'
200
{
  "ok": true
}

Limits

  • Catalog, health, packages, and free numbers: 60 requests per minute.
  • Buy a number: 30 per hour per account.
  • Cancel: 20 per minute.
  • Create a key: 10 per minute. Revoke: 20 per minute.
  • Start a top-up: 30 per minute.
  • Number window is 20 minutes unless shop settings change it.

Error codes

  • insufficient_funds. Add credits first. HTTP 422.
  • wallet_frozen. Admin froze the wallet. HTTP 403.
  • banned. The account is suspended. HTTP 403.
  • max_purchase_exceeded. Raise or clear max purchase price.
  • negative_margin. That number is unavailable at a safe price.
  • provider_unavailable. Numbers are paused or busy right now.
  • provider_error. The sale could not be completed. Retry or pick another package.
  • no_stock. That package is out of stock.
  • invalid_country. That country name is not in the catalog.
  • invalid_number. That free number is not available.
  • invalid_operator. That package operator is not available.
  • not_cancellable. The number is no longer pending.
  • public_api_off. The v1 surface is paused. HTTP 503.

Email [email protected] with a token prefix, never the full secret.