Docs/Reseller API
Reseller Platform

Reseller API Reference

Full REST API for the NumberOTP Reseller Platform. Manage sub-users, configure markup, handle payouts, and build automations on top of real-time webhooks.

1Authentication

All reseller endpoints require a Bearer token in the Authorization header. Generate an API key from your Developer Dashboard. Keys are prefixed with notp_.

Authorization: Bearer notp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The same key grants access to both standard activation endpoints and reseller endpoints. If the authenticated account is not a reseller, reseller endpoints return 403 Forbidden.

Base URL: https://api.numberotp.com/v1
All paths below are relative to this base.

2Profile & Settings

GET/reseller/profile

Get reseller profile

Returns account info including buying balance, earned balance, reseller public ID, brand settings, webhook URL, and sub-user defaults.

PATCH/reseller/profile

Update brand, webhook, and sub-user settings

Update brandName, logoUrl, color, webhookUrl, webhookSecret, allowSelfFundDefault, and minSubUserTopup. All fields are optional โ€” only provided keys are updated.

PATCH /reseller/profile โ€” Request Body

{
  "brandName": "Acme OTP",
  "logoUrl": "https://acme.com/logo.png",
  "color": "#6366f1",
  "webhookUrl": "https://acme.com/webhooks/numberotp",
  "webhookSecret": "my-hmac-secret",
  "allowSelfFundDefault": true,
  "minSubUserTopup": 10
}

GET /reseller/profile โ€” Response

{
  "success": true,
  "data": {
    "id": "64f3a...",
    "email": "you@company.com",
    "name": "Jane Smith",
    "buyingBalance": 42.50,
    "earnedBalance": 18.75,
    "resellerPublicId": "a26e2be6",
    "brand": {
      "name": "Acme OTP",
      "logoUrl": "https://acme.com/logo.png",
      "color": "#6366f1"
    },
    "webhookUrl": "https://acme.com/webhooks/numberotp",
    "minSubUserTopup": 10,
    "allowSelfFundDefault": true,
    "markupVersion": 3,
    "publicSignupUrl": "https://www.numberotp.com/join/a26e2be6"
  }
}

3Sub-User Management

GET/reseller/users

List sub-users

Returns all sub-users under this reseller account. Includes email, balance, and status.

POST/reseller/users

Create a sub-user

Manually create a sub-user. The new account is immediately active. Optionally allocate an initial balance from your buying credits.

GET/reseller/users/{id}

Get sub-user details

Returns full profile for a single sub-user including balance, API key count, and activation history summary.

PATCH/reseller/users/{id}

Update sub-user

Adjust balance (delta), toggle suspended status, or update allowSelfFund. Balance delta is applied atomically โ€” positive adds funds, negative deducts.

DELETE/reseller/users/{id}

Delete sub-user

Permanently deletes the sub-user account. Any remaining balance is forfeited and not returned to the reseller.

POST /reseller/users โ€” Request Body

{
  "email": "user@example.com",
  "password": "securepassword",
  "name": "John Doe",
  "initialBalance": 5.00,
  "allowSelfFund": true
}

PATCH /reseller/users/{id} โ€” Request Body

{
  "balanceDelta": 10.00,
  "suspended": false,
  "allowSelfFund": true
}

GET /reseller/users โ€” Response

{
  "success": true,
  "data": [
    {
      "id": "64f3b...",
      "email": "user@example.com",
      "name": "John Doe",
      "balance": 12.50,
      "suspended": false,
      "allowSelfFund": true,
      "createdAt": "2026-01-15T10:00:00.000Z"
    }
  ]
}

4Markup & Pricing

GET/reseller/markup

Get current markup configuration

Returns the global multiplier, per-service overrides, per-country overrides, and autoAdjustLoyalty flag.

PATCH/reseller/markup

Update markup

Set a global multiplier applied to all platform prices, plus optional per-service and per-country overrides. Busts the markup cache immediately.

PATCH /reseller/markup โ€” Request Body

{
  "global": 2.0,
  "overrides": {
    "wa": 1.8,
    "tg": 2.5
  },
  "countryOverrides": {
    "67": 1.5
  },
  "autoAdjustLoyalty": false
}
Markup logic: Sub-user price = platform_price ร— countryOverride[country] ร— serviceOverride[service] ร— global. Overrides multiply on top of each other. If autoAdjustLoyalty is true, the platform price used is already discounted by your loyalty tier.

GET /reseller/markup โ€” Response

{
  "success": true,
  "data": {
    "global": 2.0,
    "overrides": { "wa": 1.8 },
    "countryOverrides": {},
    "autoAdjustLoyalty": false,
    "version": 3
  }
}

5Balances & Payouts

POST/reseller/payout/convert

Convert earned balance โ†’ buying credits

Instantly moves funds from your earned balance (sub-user revenue) into your buying credits wallet. Zero fee. Atomic โ€” guarded against race conditions.

POST/reseller/payout/withdraw

Request crypto withdrawal

Submit a withdrawal request from your earned balance. Minimum $10. Processed within 24 hours to your specified crypto address.

POST/reseller/payments/crypto

Top up buying credits via crypto

Creates a NowPayments invoice to add buying credits. Minimum $20. Returns a payment URL to redirect your user to.

POST /reseller/payout/convert โ€” Request Body

{ "amount": 15.00 }

POST /reseller/payout/withdraw โ€” Request Body

{
  "amount": 25.00,
  "currency": "USDT",
  "address": "TYour...WalletAddress"
}

POST /reseller/payments/crypto โ€” Request Body

{ "amount": 50.00, "currency": "USDT" }

POST /reseller/payments/crypto โ€” Response

{
  "success": true,
  "paymentUrl": "https://nowpayments.io/payment/?iid=...",
  "invoiceId": "np_123456"
}

6Reseller Webhooks

Set a webhook URL in your reseller settings (via dashboard or PATCH /reseller/profile). NumberOTP posts HMAC-signed events to this URL for all reseller-relevant activity.

Events

eventTriggered when
sub_user.createdA sub-user registers (self or via API)
sub_user.topupA sub-user funds their own balance
sub_user.purchaseA sub-user buys a number (you earn the markup)

Webhook Payload

{
  "event": "sub_user.purchase",
  "resellerId": "64f3a...",
  "subUserId": "64f3b...",
  "subUserEmail": "user@example.com",
  "amount": 0.90,
  "resellerEarned": 0.45,
  "service": "wa",
  "country": "67",
  "timestamp": "2026-06-20T12:00:00.000Z"
}

Signature Verification (HMAC-SHA256)

Every request includes the header x-numberotp-reseller-signature: sha256=<hex>. Verify it server-side:

const crypto = require('crypto')

function verifyResellerWebhook(body, signatureHeader, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(typeof body === 'string' ? body : JSON.stringify(body))
    .digest('hex')
  const expected = `sha256=${hash}`
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  )
}

7Public Endpoints (No Auth)

These endpoints are called by your sub-users directly โ€” no API key needed. Replace {resellerPublicId} with your resellerPublicId from your profile.

GET/reseller-public/{resellerPublicId}/brandpublic

Get reseller brand info

Returns brandName, logoUrl, and color for the white-label signup page. Used by the hosted /join page automatically.

POST/reseller-public/{resellerPublicId}/signuppublic

Register a sub-user

Creates a new sub-user account linked to this reseller. Requires Cloudflare Turnstile token. Returns success; email OTP verification is sent automatically.

POST/reseller-public/{resellerPublicId}/topup/dodopublic

Sub-user card top-up (Dodo Payments)

Initiates a card payment for the sub-user. Minimum amount is the reseller's configured minSubUserTopup. Returns a checkout URL.

POST/reseller-public/{resellerPublicId}/topup/cryptopublic

Sub-user crypto top-up (NowPayments)

Creates a crypto invoice for the sub-user. Returns a NowPayments payment URL.

POST /reseller-public/{id}/signup โ€” Request Body

{
  "email": "newuser@example.com",
  "password": "securepassword",
  "name": "Jane Doe",
  "turnstileToken": "cf-turnstile-token-here"
}

POST /reseller-public/{id}/topup/crypto โ€” Request Body

{
  "amount": 20.00,
  "currency": "USDT",
  "subUserEmail": "user@example.com"
}

8Error Codes

StatusMeaning
401Missing or invalid Bearer token
403Authenticated but not a reseller, or accessing another reseller's sub-user
404Resource not found (sub-user, reseller public ID, etc.)
409Conflict โ€” e.g. sub-user email already registered
422Validation error โ€” missing or invalid fields in request body
402Insufficient balance for the requested operation
429Rate limit exceeded (100 req/min per IP for public endpoints)

Error Response Shape

{ "error": "Human-readable error message" }