API is live

MRF SMS Developer API

Sell phone numbers and OTP verifications on your own website using the MRF SMS backend. Your users buy numbers through your site, MRF SMS handles the provider, delivery, and refunds.

Base URL https://your-domain.com/api/v1

Introduction

The MRF SMS API lets you offer phone-number rental and OTP verification to your customers without operating your own SMS infrastructure. You send a request, MRF SMS returns a real phone number, and you poll for the OTP when it arrives.

This documentation is aimed at developers integrating MRF SMS into their own website, app, bot, or software. Everything you need โ€” endpoints, examples, ready-to-copy code โ€” is on this page.

What you can do with this API

  • Sell verification numbers for 25+ services including WhatsApp, Facebook, Instagram, Telegram, TikTok, Google, Amazon, and more.
  • Access 170+ countries with per-country pricing tiers.
  • Automatically receive OTP codes in your app.
  • Cancel and refund orders if the OTP doesn't arrive.
  • Charge your own margin on top of MRF SMS prices.

How it works

  1. Your customer picks a service (e.g. WhatsApp) and country on your website.
  2. Your backend calls POST /api/v1/orders with your API key.
  3. MRF SMS purchases a real number from a provider and returns it.
  4. Your site displays the number to the customer.
  5. Your backend polls GET /api/v1/orders/:id every few seconds.
  6. When the OTP arrives, you display it to your customer.
  7. If it never arrives, you call cancel and the money is refunded.

Quick Start (2 Minutes)

Copy this into your terminal, replace YOUR_API_KEY, and you'll have a working phone number in seconds.

Buy a WhatsApp number for USA
curl -X POST "https://your-domain.com/api/v1/orders" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"service":"whatsapp","countryId":187}'

You'll get a response like this:

{
  "orderId": "API-A1B2C3D4E5F6G7H8",
  "phoneNumber": "+12025551234",
  "status": "pending",
  "price": 100,
  "expiresAt": "2025-01-01T12:25:00.000Z"
}

Then poll the order for the OTP:

Check for the OTP
curl "https://your-domain.com/api/v1/orders/API-A1B2C3D4E5F6G7H8" \
  -H "Authorization: Bearer YOUR_API_KEY"
๐Ÿ’ก Where's my API key? Log in to MRF SMS โ†’ 3-dots menu โ†’ ๐Ÿ”Œ Developer API. Your key is auto-generated and shown at the top of the page (with an eye toggle).

Authentication

Every request must include your API key as a Bearer token in the Authorization header.

Authorization: Bearer mrf_your_api_key_here
๐Ÿ”’ Keep your API key secret.
  • Never expose it in browser JavaScript, mobile apps, or public code repositories.
  • Always call the MRF SMS API from your backend server, never directly from the browser.
  • If a key is leaked, click Regenerate Key in your dashboard immediately.
  • Anyone with your key can spend your balance.

Testing your key

Verify your key works
curl "https://your-domain.com/api/v1/balance" \
  -H "Authorization: Bearer YOUR_API_KEY"

A successful response returns your current balance. A 401 means the key is invalid or missing.

Rate Limits

Every API key is limited to 60 requests per minute. If you exceed the limit, the API returns HTTP 429 Too Many Requests.

Best practice: When polling for OTPs, wait 3โ€“5 seconds between polls. This gives the provider time to receive the SMS while staying well under the rate limit.

Available Services

Below are all services you can order numbers for. Use the serviceType value in your API requests.

Note: Not every service is available in every country. Use GET /api/v1/services/:service/countries to see live availability.

Countries & Country IDs

Every country has a numeric countryId that you must include when creating an order. Get the full list via the countries endpoint.

Popular country IDs

CountryCodecountryId
๐Ÿ‡บ๐Ÿ‡ธ USA+1187
๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom+4416
๐Ÿ‡ฎ๐Ÿ‡ณ India+9122
๐Ÿ‡ต๐Ÿ‡ฐ Pakistan+9266
๐Ÿ‡ฎ๐Ÿ‡ฉ Indonesia+626
๐Ÿ‡ท๐Ÿ‡บ Russia+70
๐Ÿ‡จ๐Ÿ‡ณ China+863
๐Ÿ‡ง๐Ÿ‡ท Brazil+5573
๐Ÿ‡ฉ๐Ÿ‡ช Germany+4943
๐Ÿ‡น๐Ÿ‡ท Turkey+9062

To see every country + live pricing for a service, use:

GET /api/v1/services/whatsapp/countries

Pricing & Tiers

Prices are always in PKR (Pakistani Rupee) and set on the server side. You cannot pass a custom price.

Tiers (Bronze / Silver / Gold)

Some countries offer multiple tiers for the same service. Higher tiers usually have better delivery rates but cost more. If you don't specify a tier, the cheapest available provider is used automatically.

FieldMeaning
tierNumber1 = Bronze (cheapest), 2 = Silver, 3 = Gold, etc.
rankHuman-readable tier name.
priceCost in PKR for that tier.
๐Ÿ’ฐ Your margin: MRF SMS charges you the API price. You can charge your customer anything you want on top of that. The difference is your profit.

API Reference

Full list of all endpoints with request and response examples.

GET /api/v1/services

List every service you can order numbers for.

Response

{
  "services": [
    {
      "serviceType": "whatsapp",
      "serviceName": "WhatsApp Number",
      "canOrder": true,
      "countryCount": 85
    },
    {
      "serviceType": "facebook",
      "serviceName": "Facebook Number",
      "canOrder": true,
      "countryCount": 78
    }
  ]
}
GET /api/v1/countries?service=whatsapp

List countries available for a service, with base prices.

Query Parameters

NameTypeRequiredDescription
servicestringOptionalService type. Defaults to whatsapp.

Response

{
  "service": "whatsapp",
  "countries": [
    {
      "name": "USA",
      "code": "+1",
      "countryId": 187,
      "flag": "๐Ÿ‡บ๐Ÿ‡ธ",
      "price": 120
    }
  ]
}
GET /api/v1/services/:service/countries

List countries for a service with all available tiers/ranks.

Response

{
  "service": "whatsapp",
  "countries": [
    {
      "name": "USA",
      "code": "+1",
      "countryId": 187,
      "flag": "๐Ÿ‡บ๐Ÿ‡ธ",
      "price": 120,
      "tiers": [
        { "tierNumber": 1, "price": 100, "rank": "Bronze" },
        { "tierNumber": 2, "price": 150, "rank": "Silver" }
      ]
    }
  ]
}
GET /api/v1/balance

Check your API account balance.

Response

{
  "balance": 5000,
  "currency": "PKR"
}
POST /api/v1/orders

Request a new phone number. This is the main endpoint.

Body (JSON)

FieldTypeRequiredDescription
servicestringRequiredService type from the services endpoint.
countryIdnumberRequiredCountry ID from the countries endpoint.
tierNumbernumberOptionalSpecific tier. If omitted, cheapest available is used.
providerIdstringOptionalForce a specific provider (rarely needed).

Response 201

{
  "orderId": "API-A1B2C3D4E5F6G7H8",
  "service": "whatsapp",
  "country": "USA",
  "countryId": 187,
  "price": 100,
  "phoneNumber": "+12025551234",
  "status": "pending",
  "expiresAt": "2025-01-01T12:25:00.000Z"
}
๐Ÿ›ก๏ธ Price firewall: Every order goes through MRF SMS's protected purchase system. If the provider cost is higher than the selling price at that moment, the purchase is automatically blocked and you get a 409 error โ€” no money is spent.
GET /api/v1/orders

List your recent orders (most recent first).

Query Parameters

NameTypeRequiredDescription
limitnumberOptionalDefault 50, max 200.
offsetnumberOptionalFor pagination. Default 0.
GET /api/v1/orders/:orderId

Poll this endpoint every 3โ€“5 seconds to check for the OTP.

Response

{
  "orderId": "API-A1B2C3D4E5F6G7H8",
  "service": "whatsapp",
  "country": "USA",
  "countryId": 187,
  "price": 100,
  "status": "active",
  "phoneNumber": "+12025551234",
  "otpCode": "123456",
  "createdAt": "2025-01-01T12:00:00.000Z",
  "completedAt": null
}

The otpCode field is populated as soon as the SMS arrives. Status becomes active.

POST /api/v1/orders/:orderId/cancel

Cancel an order and refund it back to your balance.

Response

{
  "orderId": "API-A1B2C3D4E5F6G7H8",
  "status": "cancelled",
  "refunded": true,
  "refundAmount": 100,
  "message": "Order cancelled & refunded."
}
Cancellation rules:
  • You must wait ~17 seconds after order creation before cancelling.
  • You cannot cancel once the OTP has arrived.
  • A refund may be blocked if the provider reports the number was already used elsewhere.

Full Integration Tutorial

Complete guide for adding MRF SMS to your own website โ€” from zero to working number sales.

  1. Get your API key

    Log in to MRF SMS โ†’ open the 3-dots menu โ†’ click ๐Ÿ”Œ Developer API. Copy the key from the amber banner.

  2. Store the key in your backend

    Put your API key in an environment variable โ€” never hardcode it and never expose it to the browser. Example: MRF_API_KEY=mrf_...

  3. Build a country selector on your site

    Fetch /api/v1/services/:service/countries from your backend to show country options + prices to your customer. Cache the response for 5โ€“10 minutes to reduce API calls.

  4. Handle "Buy" button click

    When your customer clicks buy, call POST /api/v1/orders from your backend. Add your margin to the price. Show your customer the phone number.

  5. Poll for the OTP

    Every 3โ€“5 seconds, call GET /api/v1/orders/:orderId. When otpCode is not null, show it to the customer.

  6. Handle timeout / cancel

    If no OTP arrives after ~5 minutes, call POST /api/v1/orders/:orderId/cancel. You'll get an automatic refund.

  7. Track your balance

    Call /api/v1/balance daily. Top up your MRF SMS balance from the dashboard when it runs low.

Drop-in HTML Widget

The fastest way to try this API on your own site. This is a complete, working HTML page โ€” save it as test.html, replace the two placeholders, and open it in your browser.

โš ๏ธ For testing only. This example calls the API directly from the browser, which exposes your API key. In production, always call from your backend. See the code examples below for proper backend integration.
test.html โ€” complete demo widget
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>MRF SMS Test</title>
<style>
  body { font-family: system-ui; max-width: 500px; margin: 40px auto; padding: 20px; }
  button { padding: 12px 24px; background: #10b981; color: white; border: none;
           border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 15px; }
  button:disabled { opacity: 0.5; cursor: not-allowed; }
  .box { padding: 16px; background: #f1f5f9; border-radius: 8px; margin: 12px 0; }
  .otp { font-size: 32px; font-weight: 900; color: #10b981; letter-spacing: 4px; }
</style>
</head>
<body>
<h1>Buy a WhatsApp Number</h1>
<select id="country">
  <option value="187">๐Ÿ‡บ๐Ÿ‡ธ USA</option>
  <option value="16">๐Ÿ‡ฌ๐Ÿ‡ง UK</option>
  <option value="22">๐Ÿ‡ฎ๐Ÿ‡ณ India</option>
  <option value="66">๐Ÿ‡ต๐Ÿ‡ฐ Pakistan</option>
</select>
<button id="buy">Buy Number</button>
<div id="result"></div>

<script>
const API_BASE = 'https://your-domain.com/api/v1';
const API_KEY = 'YOUR_API_KEY_HERE'; // โ† replace me

async function api(path, opts = {}) {
  const r = await fetch(API_BASE + path, {
    ...opts,
    headers: {
      'Authorization': 'Bearer ' + API_KEY,
      'Content-Type': 'application/json',
      ...(opts.headers || {})
    }
  });
  return r.json();
}

document.getElementById('buy').onclick = async () => {
  const btn = document.getElementById('buy');
  const out = document.getElementById('result');
  btn.disabled = true;
  out.innerHTML = 'Buying number...';

  const order = await api('/orders', {
    method: 'POST',
    body: JSON.stringify({
      service: 'whatsapp',
      countryId: Number(document.getElementById('country').value)
    })
  });

  if (order.error) { out.innerHTML = 'โŒ ' + order.error; btn.disabled = false; return; }

  out.innerHTML = `
    <div class="box">
      <strong>Number:</strong> <code>${order.phoneNumber}</code><br>
      <strong>Order:</strong> ${order.orderId}<br>
      <strong>Status:</strong> <span id="s">${order.status}</span>
    </div>
    <div class="box">Waiting for OTP…</div>
  `;

  for (let i = 0; i < 60; i++) {
    await new Promise(r => setTimeout(r, 4000));
    const check = await api('/orders/' + order.orderId);
    document.getElementById('s').textContent = check.status;
    if (check.otpCode) {
      out.innerHTML += '<div class="box">OTP: <div class="otp">' + check.otpCode + '</div></div>';
      break;
    }
    if (['expired','cancelled'].includes(check.status)) break;
  }
  btn.disabled = false;
};
</script>
</body>
</html>

Code Examples

Backend integration examples in the most common languages. All examples do the same thing: buy a number โ†’ poll for OTP โ†’ cancel if none arrives.

Node.js โ€” complete flow
const API_BASE = 'https://your-domain.com/api/v1';
const API_KEY = process.env.MRF_API_KEY;

const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json'
};

async function buyAndPoll(service, countryId) {
  // 1. Create order
  const order = await fetch(`${API_BASE}/orders`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ service, countryId })
  }).then(r => r.json());

  if (order.error) throw new Error(order.error);
  console.log('Got number:', order.phoneNumber);

  // 2. Poll for OTP (max ~4 minutes)
  for (let i = 0; i < 50; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const status = await fetch(`${API_BASE}/orders/${order.orderId}`, { headers })
      .then(r => r.json());

    if (status.otpCode) {
      console.log('OTP received:', status.otpCode);
      return { order, otp: status.otpCode };
    }
    if (['expired', 'cancelled'].includes(status.status)) break;
  }

  // 3. Cancel if no OTP
  await fetch(`${API_BASE}/orders/${order.orderId}/cancel`, {
    method: 'POST', headers
  });
  throw new Error('OTP did not arrive โ€” order cancelled and refunded.');
}

// Usage
buyAndPoll('whatsapp', 187)
  .then(({ order, otp }) => console.log('Success:', otp))
  .catch(err => console.error('Failed:', err.message));
PHP โ€” complete flow
<?php
$API_BASE = 'https://your-domain.com/api/v1';
$API_KEY  = getenv('MRF_API_KEY');

function api($method, $path, $body = null) {
    global $API_BASE, $API_KEY;
    $ch = curl_init($API_BASE . $path);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Authorization: Bearer $API_KEY",
        "Content-Type: application/json"
    ]);
    if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $response;
}

function buyAndPoll($service, $countryId) {
    // 1. Create order
    $order = api('POST', '/orders', ['service' => $service, 'countryId' => $countryId]);
    if (!empty($order['error'])) throw new Exception($order['error']);
    echo "Number: " . $order['phoneNumber'] . "\n";

    // 2. Poll for OTP
    for ($i = 0; $i < 50; $i++) {
        sleep(5);
        $status = api('GET', '/orders/' . $order['orderId']);
        if (!empty($status['otpCode'])) {
            echo "OTP: " . $status['otpCode'] . "\n";
            return ['order' => $order, 'otp' => $status['otpCode']];
        }
        if (in_array($status['status'], ['expired', 'cancelled'])) break;
    }

    // 3. Cancel if timeout
    api('POST', '/orders/' . $order['orderId'] . '/cancel');
    throw new Exception('OTP did not arrive โ€” order cancelled and refunded.');
}

buyAndPoll('whatsapp', 187);
?>
Python โ€” complete flow
import os, time, requests

API_BASE = 'https://your-domain.com/api/v1'
API_KEY  = os.environ['MRF_API_KEY']
HEADERS  = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}

def buy_and_poll(service, country_id):
    # 1. Create order
    order = requests.post(f'{API_BASE}/orders',
        json={'service': service, 'countryId': country_id},
        headers=HEADERS
    ).json()
    if 'error' in order:
        raise Exception(order['error'])
    print('Number:', order['phoneNumber'])

    # 2. Poll for OTP
    for _ in range(50):
        time.sleep(5)
        status = requests.get(f"{API_BASE}/orders/{order['orderId']}",
                              headers=HEADERS).json()
        if status.get('otpCode'):
            print('OTP:', status['otpCode'])
            return order, status['otpCode']
        if status['status'] in ('expired', 'cancelled'):
            break

    # 3. Cancel on timeout
    requests.post(f"{API_BASE}/orders/{order['orderId']}/cancel", headers=HEADERS)
    raise Exception('OTP did not arrive โ€” order cancelled and refunded.')

buy_and_poll('whatsapp', 187)
cURL โ€” one-liners
# Check balance
curl "https://your-domain.com/api/v1/balance" \
  -H "Authorization: Bearer YOUR_API_KEY"

# List services
curl "https://your-domain.com/api/v1/services" \
  -H "Authorization: Bearer YOUR_API_KEY"

# List countries for WhatsApp
curl "https://your-domain.com/api/v1/countries?service=whatsapp" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Buy a WhatsApp number for USA
curl -X POST "https://your-domain.com/api/v1/orders" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"service":"whatsapp","countryId":187}'

# Check for OTP
curl "https://your-domain.com/api/v1/orders/API-A1B2C3D4E5F6G7H8" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Cancel and refund
curl -X POST "https://your-domain.com/api/v1/orders/API-A1B2C3D4E5F6G7H8/cancel" \
  -H "Authorization: Bearer YOUR_API_KEY"
Go โ€” complete flow
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

const apiBase = "https://your-domain.com/api/v1"

var apiKey = os.Getenv("MRF_API_KEY")

func apiCall(method, path string, body map[string]any) (map[string]any, error) {
    var buf io.Reader
    if body != nil {
        b, _ := json.Marshal(body)
        buf = bytes.NewReader(b)
    }
    req, _ := http.NewRequest(method, apiBase+path, buf)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var out map[string]any
    json.NewDecoder(resp.Body).Decode(&out)
    return out, nil
}

func main() {
    order, _ := apiCall("POST", "/orders", map[string]any{
        "service": "whatsapp", "countryId": 187,
    })
    fmt.Println("Number:", order["phoneNumber"])
    orderID := order["orderId"].(string)

    for i := 0; i < 50; i++ {
        time.Sleep(5 * time.Second)
        s, _ := apiCall("GET", "/orders/"+orderID, nil)
        if otp, ok := s["otpCode"]; ok && otp != nil {
            fmt.Println("OTP:", otp)
            return
        }
    }
    apiCall("POST", "/orders/"+orderID+"/cancel", nil)
}

Common Patterns

Adding your margin

MRF SMS charges you the API price (say 100 PKR). You can charge your customer more (say 150 PKR). Store the margin as a config value in your backend:

const MARGIN_PERCENT = 50;  // 50% markup
const apiPrice = country.price;
const yourPrice = Math.ceil(apiPrice * (1 + MARGIN_PERCENT / 100));

Caching the countries list

Countries and prices don't change every second. Cache the response for 5โ€“10 minutes to avoid hitting the rate limit:

let cache = null; let cacheTime = 0;
async function getCountries(service) {
  if (cache && Date.now() - cacheTime < 600000) return cache;
  cache = await api(`/services/${service}/countries`);
  cacheTime = Date.now();
  return cache;
}

Handling rate limits (429)

If you get a 429, wait 60 seconds and retry. Or better โ€” throttle your requests up front.

async function safeCall(fn) {
  try { return await fn(); }
  catch (err) {
    if (err.status === 429) {
      await new Promise(r => setTimeout(r, 60000));
      return fn();
    }
    throw err;
  }
}

Auto-refund on failed orders

Give the customer a refund on your side automatically when the API cancels the order:

const cancel = await api(`/orders/${orderId}/cancel`, { method: 'POST' });
if (cancel.refunded) {
  await refundCustomer(customerId, orderPrice);
}

Order Statuses

StatusMeaning
pendingNumber was purchased, waiting for the OTP SMS to arrive.
activeOTP arrived. Read otpCode for the code.
completedOrder fully completed.
cancelledYou cancelled the order (may or may not be refunded).
expiredNo OTP arrived within the timeout. Auto-refunded.

Error Codes

All errors return JSON: { "error": "message" }

CodeMeaningWhat to do
400Bad requestCheck your request body / parameters.
401Missing / invalid API keyCheck the Authorization header.
403Account suspendedContact MRF SMS support.
404Not foundOrder ID or resource doesn't exist.
409No numbers available OR price blockedTry again shortly, or use a different tier.
429Rate limit exceededWait 60s and retry.
500Server errorRetry with exponential backoff.

FAQ

How much does the API cost?

The API itself is free to use. You pay per number you buy โ€” the price depends on the service and country. See your dashboard for live prices, or call GET /api/v1/services/whatsapp/countries.

Do I need to add money before using the API?

Yes. Top up your MRF SMS wallet from your dashboard first. Each API order deducts from that balance.

Can I charge my customers more than the API price?

Yes โ€” the difference is your profit. MRF SMS only charges you the base API price.

What if the OTP doesn't arrive?

Call the cancel endpoint. If eligible, you'll get an automatic refund. If the number expires by itself (no cancel), it's still auto-refunded.

Why did I get a 409 error saying price blocked?

MRF SMS's strict price firewall blocked the purchase because the provider cost was higher than the selling price. No money was spent. Try again in a moment or pick a different tier.

How long is my API key valid?

Forever, until you regenerate it. If it leaks, regenerate immediately from the dashboard.

Can I use one API key on multiple websites?

Yes, but they share the same balance and rate limit. If you need separation, generate a separate account per site.

Is there a sandbox / test mode?

Not currently. Every API order buys a real number. Start with a small balance while integrating.

Can my customers use the OTP for account verification on WhatsApp / Facebook / etc?

Yes. Every number is a real, working phone number from the provider. Your customer receives the OTP just like any regular verification.

How do I regenerate my API key?

Dashboard โ†’ 3-dots menu โ†’ ๐Ÿ”Œ Developer API โ†’ click Regenerate Key. Your old key stops working instantly.