Two APIs, one key, one balance. Numbers speak the SMS-Activate protocol, SMM speaks the standard panel protocol - so in most cases you point existing code at us and it just works. Top up in Mobile Money and resell at your own prices.
Three steps, and none of them need us to approve you.
Create an account and open API keys. Both are already there — one for numbers, one for SMM.
Fund the balance with MTN Mobile Money or Orange Money, from 2,500 XAF. API orders spend the same balance as the website.
One HTTPS request. No SDK to install, no OAuth dance, no sandbox key to swap out later.
curl "https://kamiverify.com/api/v2" \ -d "key=YOUR_API_KEY" \ -d "action=balance" # {"balance":"12.4400","currency":"USD"}
<?php $ch = curl_init('https://kamiverify.com/api/v2'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'key' => 'YOUR_API_KEY', 'action' => 'balance', ])); $res = json_decode(curl_exec($ch), true); echo $res['balance'];
import requests r = requests.post('https://kamiverify.com/api/v2', data={ 'key': 'YOUR_API_KEY', 'action': 'balance', }) print(r.json()['balance'])
You have two keys, not one. A different key opens each API:
api_keyv1, SMS numbers. Your SMS key. Works on /api/v1 and /stubs/handler_api.php.keyv2, SMM services. Your SMM key. Works on /api/v2.They are deliberately separate. You can give a developer or a child panel access to your SMM catalogue without handing over the ability to spend your balance on numbers, and you can rotate one after a leak without breaking the other integration.
A key only opens its own API. Presenting the SMM key to /api/v1 fails exactly as an unknown key would, and the reverse is true too — that is the whole point of having two.
Both live on your API keys page, with a button to replace either one. There is no signature and no token to refresh.
v2 expects a POST form field. v1 accepts GET or POST — the SMS-Activate convention is GET, and most client libraries use it. Be aware that a key in a URL ends up in server logs, browser history and referrer headers, so prefer POST wherever your library allows it.
API orders and website orders draw on the same balance. There is no separate developer wallet to keep topped up, and no minimum monthly spend.
Balances are held in USD and converted at a fixed 611 XAF to the dollar. The rate does not move between your top-up and your order, so a price quoted by the API is the price charged.
You set your own prices for your own customers. What you charge them is between you and them — we bill you the API price and nothing else. There is no revenue share and no per-call fee.
add or getNumber call against an empty balance returns an error immediately. Poll balance on a schedule and top up before you run dry — an order that fails at 2am is a customer you lose.
Rent a real SMS-capable number in any of 230 countries, read the code it receives, and release it. The whole lifecycle is usually under two minutes.
api_key, replies are plain colon-delimited strings like ACCESS_NUMBER:880412:237650112233, and setStatus takes numbers rather than words. Three actions return JSON instead — that inconsistency is in the protocol itself, and we copy it so existing client libraries keep working.
Base URL https://kamiverify.com/api/v1
The conventional path https://kamiverify.com/stubs/handler_api.php reaches exactly the same handler. Most SMS-Activate client libraries hard-code that path, so pointing one at KamiVerify is usually a one-line hostname change.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getBalance. |
Replies ACCESS_BALANCE:12.44.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getCountries. |
Replies JSON: {"18":{"id":18,"eng":"Cameroon"}}.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getNumbersStatus. |
| country | integer | Restrict to one country. |
Replies JSON keyed service_country: {"wa_18":"12"}.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getPrices. |
| country | integer | Restrict to one country. |
| service | string | Restrict to one service. |
Replies JSON: {"18":{"wa":{"cost":0.31,"count":12}}}.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getNumber. |
| servicerequired | string | Service code, e.g. wa for WhatsApp, tg for Telegram. |
| country | integer | Country id. Omit, leave empty, or send any to take the cheapest anywhere. |
| maxPrice | decimal | Refuse the rental above this price. |
Replies ACCESS_NUMBER:880412:237650112233 — the id first, then the number. Keep that id; every later call needs it.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal getStatus. |
| idrequired | integer | The id from getNumber. |
Replies STATUS_WAIT_CODE, then STATUS_OK:438201 once the code lands, or STATUS_CANCEL if the rental ended.
| Parameter | Type | What it is |
|---|---|---|
| api_keyrequired | string | Your API key. |
| actionrequired | string | The literal setStatus. |
| idrequired | integer | The id from getNumber. |
| statusrequired | integer | 1 ready · 3 ask for another code · 6 finish · 8 cancel and refund. |
Replies ACCESS_READY, ACCESS_RETRY_GET, ACCESS_ACTIVATION or ACCESS_CANCEL respectively.
# 1. rent it curl "https://kamiverify.com/api/v1?api_key=KEY&action=getNumber&service=wa&country=18" # ACCESS_NUMBER:880412:237650112233 # 2. poll every 5s curl "https://kamiverify.com/api/v1?api_key=KEY&action=getStatus&id=880412" # STATUS_WAIT_CODE # STATUS_OK:438201 # 3. finish (or status=8 to cancel and be refunded) curl "https://kamiverify.com/api/v1?api_key=KEY&action=setStatus&id=880412&status=6" # ACCESS_ACTIVATION
<?php function kv1($q) { $q['api_key'] = 'YOUR_API_KEY'; return trim(file_get_contents( 'https://kamiverify.com/api/v1?' . http_build_query($q))); } $res = kv1(['action'=>'getNumber', 'service'=>'wa', 'country'=>18]); if (strpos($res, 'ACCESS_NUMBER') !== 0) { throw new RuntimeException($res); // NO_NUMBERS, NO_BALANCE, BAD_KEY } list(, $id, $phone) = explode(':', $res); do { sleep(5); $st = kv1(['action'=>'getStatus', 'id'=>$id]); } while ($st === 'STATUS_WAIT_CODE'); if (strpos($st, 'STATUS_OK:') === 0) { echo substr($st, 10); // the code kv1(['action'=>'setStatus', 'id'=>$id, 'status'=>6]); }
import requests, time API = 'https://kamiverify.com/api/v1' KEY = 'YOUR_API_KEY' def kv1(**q): return requests.get(API, params={'api_key': KEY, **q}).text.strip() res = kv1(action='getNumber', service='wa', country=18) if not res.startswith('ACCESS_NUMBER'): raise RuntimeError(res) _, oid, phone = res.split(':') while True: time.sleep(5) st = kv1(action='getStatus', id=oid) if st != 'STATUS_WAIT_CODE': break if st.startswith('STATUS_OK:'): print(st[10:]) kv1(action='setStatus', id=oid, status=6)
ACCESS_NUMBER:id:phoneRented. The id comes first.STATUS_WAIT_CODENo code yet. Keep polling.STATUS_OK:codeThe code arrived. Stop polling.STATUS_CANCELCancelled, expired, or already finished.ACCESS_READYstatus=1 accepted.ACCESS_RETRY_GETstatus=3 accepted — another code requested.ACCESS_ACTIVATIONstatus=6 accepted — rental closed.ACCESS_CANCELstatus=8 accepted — refunded to your balance.BAD_KEYMissing, wrong, or the account is suspended.BAD_ACTIONNo such action.BAD_SERVICENo service was sent.BAD_STATUSNot 1, 3, 6 or 8 — or the rental has already ended.NO_NUMBERSNothing in stock for that pair. Try another country.NO_BALANCETop up. Nothing was charged.NO_ACTIVATIONThat id is not yours, or does not exist.EARLY_CANCEL_DENIEDCancelled inside the first two minutes. Wait, then retry.ERROR_SQLWe failed to record it. Nothing was charged.setStatus at all.
And a cancel inside the first two minutes is refused: without that rule, renting and instantly cancelling in a loop would be a free way to scan our stock, so the protocol denies it and so do we.
Followers, views, likes and comments across the major platforms. Orders are placed by link and quantity, and progress is readable while they run.
Base URL https://kamiverify.com/api/v2 — always POST.
| Parameter | Type | What it is |
|---|---|---|
| keyrequired | string | Your API key. |
| actionrequired | string | The literal services. |
| Parameter | Type | What it is |
|---|---|---|
| keyrequired | string | Your API key. |
| actionrequired | string | The literal add. |
| servicerequired | integer | Service id from services. |
| linkrequired | string | The post or profile URL the order runs against. |
| quantityrequired | integer | How many. Must sit between the service's min and max. |
| Parameter | Type | What it is |
|---|---|---|
| keyrequired | string | Your API key. |
| actionrequired | string | The literal status. |
| orderrequired | integer | The order id returned by add. |
| Parameter | Type | What it is |
|---|---|---|
| keyrequired | string | Your API key. |
| actionrequired | string | The literal orders. |
| limit | integer | How many to return, newest first. Default 100, maximum 300. |
| Parameter | Type | What it is |
|---|---|---|
| keyrequired | string | Your API key. |
| actionrequired | string | The literal multistatus. |
| ordersrequired | string | Comma-separated order ids, 100 maximum. status accepts this too. The reply is keyed by order id, and an unknown id carries its own error rather than failing the batch. |
# place it curl "https://kamiverify.com/api/v2" \ -d "key=YOUR_API_KEY" -d "action=add" \ -d "service=1043" \ -d "link=https://instagram.com/p/Cx1y2z3" \ -d "quantity=1000" # {"order":238104} # read it back curl "https://kamiverify.com/api/v2" \ -d "key=YOUR_API_KEY" -d "action=status" -d "order=238104" # { # "charge": "0.2700", # "start_count": "1240", # "status": "In progress", # "remains": "320", # "currency": "USD" # }
<?php function kv2($fields) { $ch = curl_init('https://kamiverify.com/api/v2'); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query($fields + ['key' => 'YOUR_API_KEY'])]); return json_decode(curl_exec($ch), true); } $r = kv2([ 'action' => 'add', 'service' => 1043, 'link' => 'https://instagram.com/p/Cx1y2z3', 'quantity' => 1000, ]); if (isset($r['error'])) { throw new RuntimeException($r['error']); } echo 'order ' . $r['order'];
import requests API = 'https://kamiverify.com/api/v2' KEY = 'YOUR_API_KEY' def kv2(**f): return requests.post(API, data={'key': KEY, **f}).json() r = kv2(action='add', service=1043, link='https://instagram.com/p/Cx1y2z3', quantity=1000) if 'error' in r: raise RuntimeError(r['error']) print('order', r['order'])
PendingAccepted, not started.In progressRunning. remains counts down.CompletedDelivered in full.PartialSome delivered. The undelivered portion is refunded to your balance automatically.CanceledNot delivered. Refunded in full.ProcessingBeing handed to the provider.The two protocols report failure differently, and that is deliberate rather than untidy — each matches its own standard. v1 returns a bare string such as BAD_KEY; every one is listed in the v1 section above. v2 returns JSON, described here.
v2 errors come back as 200 OK with an error key, which is what panel software expects. Check for the key, not for the HTTP status — a 4xx makes many child panels report "provider down" instead of showing your message.
{ "error": "Not enough balance" }Incorrect API keyMissing, mistyped, or regenerated since you last saved it.Not enough balanceTop up. Nothing was charged and no order was made.Incorrect service IDThe service id does not exist, or is no longer sold.Incorrect quantityBelow the service minimum or above its maximum.Incorrect order IDNo such order, or it belongs to someone else.The account is inactiveThe account is suspended. Contact support.add or getNumber times out, the order may still have been created and charged. Re-read with status before retrying, or you will pay twice for one order.
| Action | Guidance | Why |
|---|---|---|
| getStatus, status | 1 per second per order | Poll every 5 seconds. Faster does not make codes arrive sooner. |
status with orders | 100 ids per call | Use it instead of looping single lookups. |
| add, getNumber | 10 per second | Per account. |
| services | 1 per minute | The catalogue barely moves. Cache it for an hour. |
Short, and we mean them.
No contract to sign, no minimum volume, no monthly fee. Stop whenever you like; whatever is left on your balance stays spendable.
Create an account, top up from 2,500 XAF, and place your first API order in a few minutes.