VerifiedCore API Reference
Developer APIs for eSIM data, voice calling and a prepaid wallet — one API key, official SDKs for Node.js, Python, PHP and Go, and a sandbox that needs no backend.
A plain REST API. Official SDKs for Node.js, Python, PHP and Go — or call it directly from any language. Base URL: https://api.verifiedcore.com/api/v1
The guide below is hand-written for onboarding. The live reference is generated directly from the running API — every endpoint, parameter, and schema reflects what's actually deployed right now.
Best fit: you're on Node.js, Python, PHP or Go and want typed methods and automatic retries instead of hand-writing HTTP calls.
- ✓ eSIM, call plans and wallet as typed resources
- ✓ Built-in retry on transient network errors
- ✓ Same method names across all four languages
Best fit: you're on Go, Ruby, Java, C#, or any language/framework without an official SDK — or you simply prefer working against plain HTTP. Every example below (including eSIM, Call Plans, and Wallet) is REST-only and copy-paste ready in cURL.
- ✓ Works from any language with an HTTP client — nothing to install
- ✓ Same endpoints and responses the SDKs use
- ✓ Full live reference generated from the running API (Swagger, linked above)
Quickstart
Make your first real call in 3 steps: install (or skip straight to a request), initialize, then list live eSIM plans and provision one for a user. Every code block has a tab per language — pick yours.
1. Install
npm install @verixo/sdk
2. Initialize
import Verixo from '@verixo/sdk' const vc = new Verixo(process.env.VC_API_KEY)
3. List plans + provision an eSIM
// List live eSIM plans — Nigeria, 7-day validity
const packages = await vc.esim.listPackages('NG', 7)
const pkg = packages[0]
// Provision one for your user — returns an install QR
const esim = await vc.esim.purchase({
packageId: pkg.packageId,
countryCode: 'NG',
priceUsd: pkg.priceUsd,
})
console.log(esim.qrCodeUrl)Wondering what it costs? Listing plans and the sandbox are free; you only pay for what you provision, from a prepaid wallet. No credit card required to start. See full pricing →
Authentication
Authenticated requests send your API key directly in the Authorization header --no Bearer prefix. Get your key from the dashboard after signing up.
# All authenticated requests require this header Authorization: vc_live_your_api_key_here
API keys are prefixed with vc_live_ for production and vc_test_ for sandbox. Sandbox keys use mock data and never charge your wallet.
Creating an API key
Sign up and log in to get a JWT, then mint an API key with it — keys are scoped and named so you can issue separate ones per environment or integration.
Scopes are enforced: verify, esim:read, esim:purchase, calls, wallet:read, webhooks:manage, automation, analytics:read. Omit scopes to get all of them. A call outside a key's scopes returns 403 INSUFFICIENT_SCOPE; team, key management and admin actions are dashboard-only.
Optionally lock a key to your servers with an IP allowlist (IPs or CIDR ranges, set when creating the key or later in the dashboard, or via PUT /auth/api-keys/{keyId}/allowed-ips). Calls from any other address return 403 IP_NOT_ALLOWED.
curl -X POST "https://api.verifiedcore.com/api/v1/auth/api-keys" \
-H "Authorization: Bearer <your JWT>" \
-H "Content-Type: application/json" \
-d '{
"name": "Production backend",
"scopes": ["verify", "esim:read"],
"environment": "live"
}'
# Response
{
"key": "vc_live_8f2a91c4b7e6...",
"meta": { "id": "key_abc123", "name": "Production backend", "environment": "live", "createdAt": "2026-06-22T10:00:00Z" }
}The raw key is shown once, at creation. Store it in a secrets manager or encrypted env var, never in client-side code or version control — anyone holding a vc_live_ key can debit your wallet.
Manage existing keys with GET /auth/api-keys (list), DELETE /auth/api-keys/{keyId} (revoke one), or DELETE /auth/api-keys (revoke all).
Verify API
Send one-time codes to your own users for sign-up, login and 2FA, then check what they typed. The SMS carries your brand ("Acme Bank: your verification code is 482913…"), is delivered through Infobip with automatic Twilio failover, and is billed to your wallet per code sent. Codes are never stored — only a keyed hash.
Test mode: with a vc_test_ key nothing is sent or charged, and the code is always 123456.
Current status: Verify is open for integration in test mode. Live SMS is being enabled per network; until then, live keys return 503 LIVE_SENDING_DISABLED.
POST /verify/start
to must be an E.164 mobile number. Pass your end user's ip to enable per-IP abuse limits. Starting again for the same number cancels the previous code.
const res = await fetch("https://api.verifiedcore.com/api/v1/verify/start", {
method: "POST",
headers: { Authorization: process.env.VC_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ to: "+2348031234567", ip: req.ip }),
})
const verification = await res.json() // keep verification.id for the check stepPOST /verify/check
Send the id from start (or the to number) and the code. valid is true only for a correct, unexpired code. Each code allows 5 attempts and expires after 5 minutes by default.
const res = await fetch("https://api.verifiedcore.com/api/v1/verify/check", {
method: "POST",
headers: { Authorization: process.env.VC_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ id: verification.id, code: userInput }),
})
const result = await res.json()
if (result.valid) { /* sign the user in */ }Other endpoints
GET /api/v1/verify/{id} # one verification
GET /api/v1/verify?page=0&size=25 # recent verifications
GET /api/v1/verify/settings # brand name, allowed countries, code length, expiry
PUT /api/v1/verify/settings # {"brandName":"Acme Bank","allowedCountries":["NG","GH"],"codeLength":6,"ttlSeconds":300}Errors & fraud protection
Errors return {"error":{"code","message"}}. Built-in protection you should know about:
BUSINESS_VERIFICATION_REQUIRED(403) — live codes need a business-verified account; submit your CAC details under Dashboard → Business. Test keys are unaffected.COUNTRY_NOT_ALLOWED(403) — only countries in your settings are served (default NG, GH, KE, ZA).COUNTRY_REQUIRES_APPROVAL(403) — the destination is outside the self-serve list; request it and we'll review.UNSUPPORTED_NUMBER_TYPE/INVALID_NUMBER(400) — premium-rate and non-mobile numbers are refused.RESEND_TOO_SOON/TOO_MANY_REQUESTS(429) — 30-second resend cooldown and per-number, per-IP and per-account limits.VERIFY_PAUSED(429) — sending pauses automatically when many codes go out but very few are verified (SMS pumping).INSUFFICIENT_BALANCE(402),DELIVERY_FAILED(502 — you are not charged).
Subscribe a webhook to verification.approved and verification.failed to react without polling.
Webhooks
Register an HTTPS endpoint and we POST events to it as they happen: esim.provisioned, wallet.credited, wallet.low_balance, callplan.subscribed and special.package.purchased. Every request carries an X-VerifiedCore-Signature: sha256=… header — an HMAC-SHA256 of the raw body using your endpoint's signing secret.
# Register a webhook
curl -X POST "https://api.verifiedcore.com/api/v1/webhooks" \
-H "Authorization: $VC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://yourapp.com/hooks/verifiedcore","events":["esim.provisioned","wallet.low_balance"]}'
# List your webhooks
curl "https://api.verifiedcore.com/api/v1/webhooks" -H "Authorization: $VC_API_KEY"
# Send a test event
curl -X POST "https://api.verifiedcore.com/api/v1/webhooks/{id}/test" -H "Authorization: $VC_API_KEY"
# Remove one
curl -X DELETE "https://api.verifiedcore.com/api/v1/webhooks/{id}" -H "Authorization: $VC_API_KEY"Delivery rules: endpoints must be public https:// URLs. Reply with any 2xx within 5 seconds; we retry twice (after 2s and 10s) on timeouts, 5xx, 408 and 429, and don't follow redirects. The same event can arrive more than once — dedupe on its id.
There's no update endpoint yet — to change a webhook's URL or event list, delete it and create a new one.
// esim.provisioned
{
"id": "evt_3f2c9a…",
"event": "esim.provisioned",
"createdAt": "2026-09-23T12:00:04Z",
"data": {
"profileId": "…",
"iccid": "8901…",
"qrCodeUrl": "https://…",
"activationCode": "LPA:1$…",
"countryCode": "NG",
"dataGb": 1,
"validityDays": 7,
"priceUsd": 3.85
}
}
// Verify the signature (Node.js)
import crypto from 'crypto'
const expected = 'sha256=' + crypto.createHmac('sha256', SIGNING_SECRET).update(rawBody).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-verifiedcore-signature']))eSIM API
Provision real eSIM data packages worldwide — available as esim in every SDK, or call the REST endpoints below directly.
GET /esim/packages
Public — browse available data packages for a country. No API key required.
// GET /esim/packages?countryCode=NG
{
"packages": [
{ "packageId": "esim_ng_1gb_7d", "dataGb": 1, "days": 7, "priceUsd": 6.99, "networks": "Airtel [4G]" },
{ "packageId": "esim_ng_3gb_30d", "dataGb": 3, "days": 30, "priceUsd": 18.99, "networks": "Airtel [4G]" },
{ "packageId": "esim_ng_5gb_30d", "dataGb": 5, "days": 30, "priceUsd": 28.99, "networks": "Airtel [4G]" }
]
}POST /esim/purchase
Provisions a real eSIM via our carrier partner and debits your wallet. Returns everything needed to activate the SIM — a QR code, a one-tap iOS 17.4+ install link, and manual SM-DP+ details as a fallback.
curl -X POST "https://api.verifiedcore.com/api/v1/esim/purchase" \
-H "Authorization: $VC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"packageId":"esim_ng_1gb_7d","countryCode":"NG"}'
# Response
{
"profileId": "esim_a1b2c3",
"iccid": "8901000000000000001",
"activationCode": "LPA:1$smdp.example.com$matching-id-here",
"qrCodeUrl": "https://esim.airalo.com/qr/abc123",
"directAppleInstallUrl": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=...",
"smdpAddress": "smdp.example.com",
"matchingId": "matching-id-here",
"apnType": "automatic",
"apnValue": "internet",
"isRoaming": false,
"carrierName": "Airtel",
"dataGbIncluded": 1,
"validityDays": 7,
"countryCode": "NG",
"status": "ACTIVE"
}GET /esim/{iccid}/usage
Check remaining data balance for a provisioned eSIM. Rate limited to 10 requests/min per ICCID.
curl "https://api.verifiedcore.com/api/v1/esim/8901000000000000001/usage" \ -H "Authorization: $VC_API_KEY"
GET /esim/my
List every eSIM you've provisioned, with current status and remaining data.
curl "https://api.verifiedcore.com/api/v1/esim/my" -H "Authorization: $VC_API_KEY"
Call Plans API
Subscription-based outbound calling minutes. Three fixed tiers — not yet wrapped by the SDKs, REST only.
GET /call-plans
Public — list the available tiers.
// GET /call-plans
[
{ "id": "basic", "tier": "BASIC", "name": "Basic Plan", "minutes": 50, "priceUsd": 4.99, "perMin": 0.10, "popular": false,
"features": ["50 outbound minutes", "Incoming free", "10 countries", "Standard quality"] },
{ "id": "standard", "tier": "STANDARD", "name": "Standard Plan", "minutes": 150, "priceUsd": 9.99, "perMin": 0.067, "popular": true,
"features": ["150 outbound minutes", "Incoming free", "40 countries", "HD voice", "Call recording"] },
{ "id": "premium", "tier": "PREMIUM", "name": "Premium Plan", "minutes": -1, "priceUsd": 19.99, "perMin": 0,
"popular": false, "features": ["Unlimited outbound", "Incoming free", "80+ countries", "Ultra-HD voice", "Recording + transcription", "Priority routing"] }
]POST /call-plans/subscribe
curl -X POST "https://api.verifiedcore.com/api/v1/call-plans/subscribe" \
-H "Authorization: $VC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tier":"STANDARD","paymentProvider":"STRIPE","paymentReference":"pi_abc123"}'Manage your subscription
GET /call-plans/my — active subscription (204 if none) · DELETE /call-plans/my — cancel · GET /call-plans/history — past subscriptions
Looking for one-off bundles instead of a recurring plan? See GET /special-packages and POST /special-packages/purchase — same auth model, separate product (starter/business/developer/regional bundles mixing SMS, minutes, and eSIM data).
Wallet & Billing
Every paid call — number purchase, eSIM provision, call plan subscription — debits a prepaid wallet balance. Top up via the Payments API (currency-routed: NGN → OPay, GHS → Flutterwave, everything else → Stripe).
# Current balance curl "https://api.verifiedcore.com/api/v1/wallet" -H "Authorization: $VC_API_KEY" # Transaction history curl "https://api.verifiedcore.com/api/v1/wallet/transactions" -H "Authorization: $VC_API_KEY"
To fund your wallet programmatically, see POST /payment/initiate and GET /payment/currencies in the live API reference — full payment-provider documentation lives there since it covers bank transfers, virtual accounts, and exchange rates in addition to card payments.
Email API
Send transactional email through our infrastructure — useful if you're already calling our API for OTP delivery and don't want a second provider just for receipts or notifications. REST only, not yet wrapped by any SDK.
POST /email/send
curl -X POST "https://api.verifiedcore.com/api/v1/email/send" \
-H "Authorization: $VC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Your order is confirmed",
"body": "Thanks for your purchase — your order #1234 is on its way."
}'
# Response
{ "messageId": "msg_abc123" }to, subject, and body are required. from is optional and defaults to our sending domain.
The Numbers, Sessions, WebSocket and delivery-analytics endpoints below belong to the retired OTP number-rental product. They're documented for existing integrations only — don't build new products on them. To verify your own users (sign-up, login, 2FA), use the Verify API.
Numbers API
GET /numbers/search
Returns virtual numbers ranked by Health Score™ for a given service and country.
serviceSlugstringRequired. Service identifier, e.g. whatsapp, google, telegramcountryCodestringISO 3166-1 alpha-2. e.g. NG, US, DEminScorenumberMinimum Health Score (0–100). Default: 0limitnumberMax results (1–50). Default: 10const { numbers } = await vc.numbers.search({
serviceSlug: 'whatsapp',
countryCode: 'NG',
minScore: 80,
limit: 5,
})Sessions API
A session is created when you purchase a number. The 90-second SLA clock starts at session creation.
90-second guaranteed delivery SLA. If the OTP isn't delivered within 90 seconds, your wallet is refunded automatically — no support ticket, no manual review. This is enforced server-side by the same watchdog regardless of whether you're using an SDK or calling the REST API directly.
POST /numbers/purchase
// Request
{ "numberId": "num_abc123" }
// Response
{
"sessionToken": "sess_xyz789",
"expiresAt": "2026-05-21T12:00:90Z",
"price": 0.15,
"currency": "USD"
}GET /sessions/{token}
// Response
{
"sessionToken": "sess_xyz789",
"status": "DELIVERED", // PENDING | DELIVERED | EXPIRED | REFUNDED
"otp": "847291",
"deliveredAt": "2026-05-21T12:00:04Z",
"latencyMs": 4200,
"healthScore": 94
}WebSocket push
Connect to wss://api.verifiedcore.com/ws and subscribe to your session token via STOMP. OTPs are pushed in real time — no polling needed.
// Purchase picks the best available number server-side --
// there's no numberId param, since a candidate from search()
// may already be gone by the time you'd reference it back.
const session = await vc.numbers.purchase({
serviceSlug: 'whatsapp',
countryCode: 'NG',
})
vc.subscribe(session.sessionToken, ({ otp, latencyMs }) => {
console.log(`OTP: ${otp} in ${latencyMs}ms`)
})The Go, Ruby, Java, and C# examples above poll GET /sessions/{token} every 2 seconds for simplicity — that's enough for most integrations given the 90-second SLA window. For true real-time push without polling, connect any STOMP-over-WebSocket client available for your language to the same wss://api.verifiedcore.com/ws endpoint and subscribe to /topic/otp/{sessionToken} — the Node.js, Python, and PHP SDKs do exactly this internally.
Analytics
GET /analytics/rates returns 30-day rolling delivery rates per service and country.
curl "https://api.verifiedcore.com/api/v1/analytics/rates?serviceSlug=whatsapp" \ -H "Authorization: $VC_API_KEY"
SDKs
Node.js, Python, and PHP SDKs are published and live today, covering Numbers, Sessions, Analytics, and real-time OTP push via subscribe(). Native Go, Ruby, and Java packages are in development — until they ship, the above already has working REST examples in Go, Ruby, Java, and C#, alongside cURL. eSIM, Call Plans, and Wallet aren't wrapped by any SDK yet either — those sections are REST-only for every language for now.
@verixo/sdkverixoverixo/sdkgithub.com/titicodes/verixo-goverixocom.verixo:sdkFramework examples
Copy-paste recipes for the most common frameworks. Each example lists eSIM plans and provisions one using the official SDK where available, or plain HTTP where not.
// pubspec.yaml — add dio
// dependencies:
// dio: ^5.4.0
//
// Call the API from YOUR backend, not the app — never ship an API key in a
// mobile binary. This service talks to your own server, which proxies to
// VerifiedCore.
import 'package:dio/dio.dart';
class ESimService {
final _dio = Dio(BaseOptions(baseUrl: 'https://your-backend.example.com'));
Future<List<dynamic>> plans(String countryCode) async {
final res = await _dio.get('/esim/plans', queryParameters: {'country': countryCode});
return res.data as List<dynamic>;
}
Future<String> buy(String packageId) async {
final res = await _dio.post('/esim/buy', data: {'packageId': packageId});
return res.data['qrCodeUrl'] as String; // show with qr_flutter
}
}Don't see your framework? Every example above maps directly to the REST examples — any language with an HTTP client works identically.
Error reference
AUTH_001401Missing or invalid API key.AUTH_002401JWT token expired.NUMBER_001404Number ID not found or already reserved.NUMBER_002422Health score below your requested minScore.WALLET_001402Insufficient wallet balance.SESSION_001404Session token not found or expired.SLA_001408OTP not delivered within 90s — auto-refunded.RATE_001429Rate limit exceeded. See Retry-After header.Rate limits
Default: 5 requests/second per API key, burst up to 20. Enterprise plans have custom limits. Rate-limited requests return HTTP 429 with a Retry-After header — applies identically whether you're on an SDK or raw REST.
Changelog
Base URL corrected to https://api.verifiedcore.com/api/v1. Quickstart now starts with eSIM; OTP number-rental endpoints moved to Legacy.
Privacy mode (zero OTP storage) is now generally available. EU IPs get privacy mode by default.
eSIM data packages API launched. Nigeria, Ghana, Kenya, South Africa, US, UK, EU, India, Australia. 1 GB packages from $2.99.
Voice OTP via Telnyx now available. Useful for users who can't receive SMS.