MCP is live.Set upAsk on Discord
OmniDimension

Reseller API

Run your clients from your own product: create them, set what they can do, fund them with credits, verify them, and buy them numbers.

A reseller account owns client accounts. Each client is a separate organization with its own balance, its own agents and its own numbers, and you decide what each one may do and what it pays per minute.

This API is that control panel, so your clients never have to see ours. Every call names one of your clients by user_id or child_organization_id, and only ever one of your own: any other id is rejected.

Available to reseller accounts. Request access if you do not have it yet.

The shape of it

createthe accountfundcredits, at your ratecomplete eKYCwhere requiredbuy a numberthen it can call
Verification only applies where the region asks for it. Everything else is the same for every client.

Authentication

Send your reseller key as a bearer token on every request. See Authentication.

Authorization: Bearer YOUR_API_KEY

The key must belong to the reseller account owner or an admin on it. A teammate with viewer access is refused.

Your clients

List everything under your account, with each organization's balance, rate, concurrency limit and users:

GET /api/v1/reseller/organizations

Create a client. This makes the user and their organization together, and links it to you:

POST /api/v1/reseller/users/add

{
  "name": "Demo User",
  "email": "demo@example.com",
  "phone": "+15551234567",
  "password": "...",
  "cost_per_min": 0.20,
  "concurrent_call_limit": 2,
  "expiry_date": "2026-12-31",
  "user_currency": "USD"
}

Only the first four are required. The rest are the terms you are selling on, and you can change them later. cost_per_min sets both the basic and the premium model rate for that client.

You cannot price a client below your own rate.

cost_per_min has to be at least what you pay, for both rates. A lower value is rejected rather than quietly costing you money on every call the client makes. welcome_minutes_to_credit is capped the same way, at the minutes your own balance can cover.

What a client can do

Three things are yours to set, and a client cannot change any of them itself.

Dashboard access. Turn menu areas on and off. Only the flags you pass change, and flags outside your own permissions are ignored rather than escalating.

POST /api/v1/reseller/users/access-control

{ "user_id": 1234, "dashboard_menu_access": { "is_bots_menu_access": true } }

Expiry. Set a date the account stops working, or clear it.

POST /api/v1/reseller/users/expiry

{ "user_id": 1234, "expiry_date": "2026-12-31" }

Pass null to remove the expiry.

Concurrency. How many calls a client can run at once.

POST /api/v1/reseller/concurrency

{ "child_organization_id": 5678, "new_limit": 5 }

new_limit is the absolute number you want, not a change to the current one. Assigning is free: any figure is accepted, nothing is deducted from you, and the call never fails for lack of capacity.

Assigning does not create capacity.

Your own concurrent call limit is the ceiling for your whole family at dial time. What you set per client is a cap; your own limit is the capacity those caps compete for. Give five clients ten each while you hold twenty, and the twenty-first simultaneous call is refused no matter whose it is.

Credits

You hold a balance. You move minutes from it to a client at a rate you choose, and you can take unused minutes back.

Two different amounts move on every transfer: your balance is debited at your rate, and the client is credited at the rate you set. The gap is your margin. A revert is the exact mirror.

your balancethe clienttransfer: you pay your rate, they get your sell raterevert: they are deducted at their rate, you get yours back
A revert needs no rate from you: the client is deducted at their current rate, which is what they were charged, and you are refunded at yours, which is what it cost you.

Preview either direction before committing. Nothing moves on this call:

POST /api/v1/reseller/credits/calculate

{ "minutes": 100, "cost_per_min": 0.20 }

Then transfer. Your balance is debited immediately, and a cost_per_min below your own rate is refused as a loss:

POST /api/v1/reseller/credits/transfer

{ "to_organization_id": 5678, "minutes": 100, "cost_per_min": 0.20 }

To take unused minutes back, pass only how many. No rate: the client is deducted at their current rate and you are refunded at yours:

POST /api/v1/reseller/credits/revert

{ "from_organization_id": 5678, "minutes": 40 }

Every move is recorded:

GET /api/v1/reseller/credits/logs

Verify a client

Where a region requires it, a client has to be identity-verified before it can buy a number. For India that is an Aadhaar eKYC, so each check clears in minutes.

You do not hardcode the order of the checks. You ask which step is next, run it, and the response tells you the one after it. That way the sequence can change without breaking your integration.

Read the status once to get the first step, then follow next_step until the status is completed.

GET /kyc/statusthe first next_stepPOST /kyc/steps/{next_step}next_stepno next stepstatus: completedthe client can buy a number
One read to start, then each step response hands you the next one. When a step comes back completed, verification is done and the client can buy. That is the same state the purchase endpoint checks, so there is nothing else to poll.
GET /api/v1/reseller/kyc/status?user_id=CLIENT_ID

The response has a regions array, one entry per region, each with that region's status, its next_step, and whether the client can_purchase yet.

status = GET /kyc/status?user_id=CLIENT_ID
next   = status.regions.find(r => r.region == "IN").next_step

while next:
    step = POST /kyc/steps/{next}   { user_id: CLIENT_ID, region: "IN", ...fields }
    next = step.next_step

# status is completed. The client can now buy a number.

The steps

One endpoint runs every step: the step name is the last part of the path, and the fields it needs go in the body. India is the region with step-by-step verification today.

Each step's body fields, the next_step it answers with, and the message it returns are listed in the step reference. You can also read the same thing as data, so your integration never hardcodes it:

GET /api/v1/reseller/kyc/requirements?region=IN

Two things the sequence does not show. verify-gst and skip-gst are a choice, run one or the other. And if the client says they did not get their code, call resend-otp while they are on the OTP step: it is never returned as a next_step, so it sits outside the loop.

The Aadhaar steps have a cooldown.

Roughly 30 seconds between attempts. A failed verify can burn the reference and a rapid resend trips the per-Aadhaar limit, which locks your client out, so a retry loop has to wait. Calling too soon returns 429.

Your obligation on the data

You are relaying what your client types into your interface, nothing more. Do not store the values you send in these requests, and redact them from your logs. That covers the PAN, the Aadhaar number, and every one-time code.

Buy them a number

Once a client is verified, buying is not a reseller endpoint: it is the ordinary phone number API with your client's user_id added. The number lands on their account and the rental comes out of their balance, not yours.

GET  /api/v1/phone_number/search?region=IN&user_id=CLIENT_ID
POST /api/v1/phone_number/purchase   { "region": "IN", "phone_number": "+91...", "user_id": CLIENT_ID }

A client who completed verification in the dashboard instead of through this API passes the same gate, so there is nothing to repeat.

Reference

Full request and response schemas, with a playground, are in the API reference.

On this page