# Reseller API (/docs/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](https://omnidim.io/contact-us?reason=reseller_api\&lock=1) if you do not have it yet.

## The shape of it [#the-shape-of-it]

  <svg viewBox="0 0 760 150" role="img" aria-label="A client goes through four stages: you create the account, fund it with credits at a rate you set, complete its eKYC where the region requires it, and then buy it a phone number so it can make calls." style="{ maxWidth: '100%', height: 'auto' }">
    <defs>
      <marker id="rl-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
        <path d="M0,0 L10,5 L0,10 z" fill="currentColor" stroke="none" />
      </marker>
    </defs>

    <g fill="none" stroke="currentColor" strokeWidth="1.4" fontFamily="ui-monospace, Menlo, monospace" fontSize="12">
      <rect x="10" y="40" width="150" height="46" rx="6" />

      <text x="85" y="62" textAnchor="middle" stroke="none" fill="currentColor">
        create
      </text>

      <text x="85" y="78" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        the account
      </text>

      <line x1="160" y1="63" x2="198" y2="63" markerEnd="url(#rl-a)" />

      <rect x="200" y="40" width="150" height="46" rx="6" />

      <text x="275" y="62" textAnchor="middle" stroke="none" fill="currentColor">
        fund
      </text>

      <text x="275" y="78" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        credits, at your rate
      </text>

      <line x1="350" y1="63" x2="388" y2="63" markerEnd="url(#rl-a)" />

      <rect x="390" y="40" width="150" height="46" rx="6" />

      <text x="465" y="62" textAnchor="middle" stroke="none" fill="currentColor">
        complete eKYC
      </text>

      <text x="465" y="78" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        where required
      </text>

      <line x1="540" y1="63" x2="578" y2="63" markerEnd="url(#rl-a)" />

      <rect x="580" y="40" width="170" height="46" rx="6" />

      <text x="665" y="62" textAnchor="middle" stroke="none" fill="currentColor">
        buy a number
      </text>

      <text x="665" y="78" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        then it can call
      </text>
    </g>
  </svg>

  
    Verification only applies where the region asks for it. Everything else is the same
    for every client.
  

## Authentication [#authentication]

Send your reseller key as a bearer token on every request. See [Authentication](/docs/get-started/authentication).

```http
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 [#your-clients]

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

```http
GET /api/v1/reseller/organizations
```

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

```http
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.

  `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 [#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.

```http
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.

```http
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.

```http
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.

  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 [#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.

  <svg viewBox="0 0 780 130" role="img" aria-label="On a transfer the reseller is debited at their own rate while the client is credited at the sell rate. On a revert the client is deducted at their rate and the reseller is refunded at theirs." style="{ maxWidth: '100%', height: 'auto' }">
    <defs>
      <marker id="rc-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
        <path d="M0,0 L10,5 L0,10 z" fill="currentColor" stroke="none" />
      </marker>
    </defs>

    <g fill="none" stroke="currentColor" strokeWidth="1.4" fontFamily="ui-monospace, Menlo, monospace" fontSize="12">
      <rect x="20" y="40" width="190" height="46" rx="6" />

      <text x="115" y="68" textAnchor="middle" stroke="none" fill="currentColor">
        your balance
      </text>

      <rect x="570" y="40" width="190" height="46" rx="6" />

      <text x="665" y="68" textAnchor="middle" stroke="none" fill="currentColor">
        the client
      </text>

      <path d="M210 54 H568" markerEnd="url(#rc-a)" />

      <text x="389" y="44" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        transfer: you pay your rate, they get your sell rate
      </text>

      <path d="M570 74 H212" markerEnd="url(#rc-a)" />

      <text x="389" y="96" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        revert: they are deducted at their rate, you get yours back
      </text>
    </g>
  </svg>

  
    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:

```http
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:

```http
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:

```http
POST /api/v1/reseller/credits/revert

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

Every move is recorded:

```http
GET /api/v1/reseller/credits/logs
```

## Verify a client [#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`.

  <svg viewBox="0 0 700 300" role="img" aria-label="Read KYC status once to get the first step. Post that step. Each step response carries the next step, so you loop. When the status comes back completed, verification is done and the client can buy." style="{ maxWidth: '100%', height: 'auto' }">
    <defs>
      <marker id="rk-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
        <path d="M0,0 L10,5 L0,10 z" fill="currentColor" stroke="none" />
      </marker>
    </defs>

    <g fill="none" stroke="currentColor" strokeWidth="1.4" fontFamily="ui-monospace, Menlo, monospace" fontSize="12">
      <rect x="210" y="20" width="280" height="44" rx="6" />

      <text x="350" y="47" textAnchor="middle" stroke="none" fill="currentColor">
        GET /kyc/status
      </text>

      <line x1="350" y1="64" x2="350" y2="114" markerEnd="url(#rk-a)" />

      <text x="362" y="93" stroke="none" fill="currentColor" fontSize="10.5">
        the first next_step
      </text>

      <rect x="210" y="116" width="280" height="48" rx="6" />

      <text x="350" y="145" textAnchor="middle" stroke="none" fill="currentColor">
        {"POST /kyc/steps/{next_step}"}
      </text>

      <path d="M210 152 H150 V128 H210" markerEnd="url(#rk-a)" />

      <text x="142" y="145" textAnchor="end" stroke="none" fill="currentColor" fontSize="10.5">
        next_step
      </text>

      <line x1="350" y1="164" x2="350" y2="214" markerEnd="url(#rk-a)" />

      <text x="362" y="193" stroke="none" fill="currentColor" fontSize="10.5">
        no next step
      </text>

      <rect x="210" y="216" width="280" height="52" rx="6" />

      <text x="350" y="240" textAnchor="middle" stroke="none" fill="currentColor">
        status: completed
      </text>

      <text x="350" y="258" textAnchor="middle" stroke="none" fill="currentColor" fontSize="10.5">
        the client can buy a number
      </text>
    </g>
  </svg>

  
    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.
  

```http
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.

```text
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 [#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](/docs/api-reference/reseller/submitResellerKycStep). You can also read
the same thing as data, so your integration never hardcodes it:

```http
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.

  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 [#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 [#buy-them-a-number]

Once a client is verified, buying is not a reseller endpoint: it is the ordinary
[phone number API](/docs/buy-a-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.

```http
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 [#reference]

Full request and response schemas, with a playground, are in the
[API reference](/docs/api-reference/reseller/listChildOrganizations).