> ## Documentation Index
> Fetch the complete documentation index at: https://www.vobiz.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboard your customers with Vobiz sub-accounts

> End-to-end guide for platforms that give their own customers phone numbers on Vobiz: create one sub-account per customer, run hosted KYC inside your app, buy and assign a number, place calls, cap each customer's CPS and concurrency, and bill usage from CDRs.

Use this guide when you run a platform and your customers rent phone numbers through you. A typical case is an AI voice agent platform: your agent talks to callers over a WebSocket stream, and each customer needs a verified number of their own.

The model is simple. Every customer gets one Vobiz sub-account, created with a plus-slugged copy of their email. Your main account pays for everything. The customer completes KYC inside your app, and their numbers and calls stay scoped to their sub-account.

<CardGroup cols={2}>
  <Card title="One sub-account per customer" icon="users">
    Numbers, applications, calls, and CDRs are scoped to the customer. If one customer misuses a number, it is traceable to them and your main account stays clean.
  </Card>

  <Card title="One wallet, yours" icon="wallet">
    Every call and number is charged to your main account. You set your own prices and credit limits for each customer.
  </Card>
</CardGroup>

<Info>
  Not sure this is the right model? [Sub-accounts vs partner accounts](/docs/concepts/sub-accounts-vs-partner-accounts) compares it with partner accounts, where each customer gets their own wallet and capacity.
</Info>

## The whole flow

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant C as Your customer
  participant P as Your platform
  participant V as Vobiz
  C->>P: Signs up and asks for a number
  P->>V: Create customer_use sub-account (parent credentials)
  V-->>P: SA_ auth_id and auth_token (token shown once)
  P->>V: Initialize KYC, flow_type redirect (parent credentials)
  V-->>P: widget_url
  P->>C: Open widget_url in an iframe
  C->>V: PAN + Aadhaar, or PAN + GST/CIN
  V-->>P: kyc.completed webhook
  P->>V: Get KYC status (parent credentials)
  V-->>P: kyc_calls_blocked false
  P->>V: Buy number, application, calls (sub-account credentials)
  V-->>P: CDRs with cost per call
  P->>C: Usage and bill at your prices
```

## Before you start

Two credential pairs are in play. Keeping them apart prevents most integration bugs.

| Credential | Looks like | Use it for |
| - | - | - |
| **Parent** `X-Auth-ID` / `X-Auth-Token` | `MA_XXXXXXXX` | Setting the customer up: create the sub-account, run KYC, check KYC status, assign numbers from your pool. |
| **Sub-account** `auth_id` / `auth_token` | `SA_XXXXXXXX` | Running the customer: buy their numbers, create their applications, place their calls, read their CDRs. |

All requests go to `https://api.vobiz.ai/api/v1`.

<Warning>
  Path casing matters. Sub-account and KYC routes use lowercase `/accounts/` and `/sub-accounts/`, number assignment uses `/account/`, and numbers, applications, and calls use `/Account/`. The wrong casing returns `401` or `404` even with valid credentials.
</Warning>

## Step-by-step

<Steps>
  <Step title="Create a sub-account for the customer">
    Call [Create a Subaccount](/docs/sub-accounts/create-subaccount) with your **parent** credentials when the customer signs up. Set `kyc_mode` to `customer_use` so the customer verifies in their own name. Set `business_type` so the KYC widget asks for the right documents.

    ```bash cURL theme={null}
    curl -X POST "https://api.vobiz.ai/api/v1/accounts/MA_XXXXXXXX/sub-accounts/" \
      -H "X-Auth-ID: MA_XXXXXXXX" \
      -H "X-Auth-Token: YOUR_AUTH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Asha Rao",
        "email": "asha+k7f3q9@example.com",
        "phone": "+919812345678",
        "password": "Str0ng!Passw0rd",
        "kyc_mode": "customer_use",
        "business_type": "individual"
      }'
    ```

    The response carries the sub-account's `auth_credentials`. Save `auth_id` and `auth_token` against the customer in your database. **The `auth_token` is shown only once.** The sub-account starts with `kyc_calls_blocked: true`.

    | Field | Send it? | Why |
    | - | - | - |
    | `name` | Always | Shows up in your sub-account list. |
    | `email` | Always, with a plus slug | Required for `customer_use`. Must be unique on Vobiz, so always add a slug. See the warning below. |
    | `phone` | Recommended | Helps support identify the customer. |
    | `password` | Recommended | Lets the customer sign in to the Vobiz console if you allow it. |
    | `kyc_mode` | Always | `customer_use` for customers. Use `personal_use` only for your own teams, which inherit your main account's KYC. |
    | `business_type` | Always | `individual`, `proprietorship`, `private_limited`, `llp`, and so on. Drives the KYC documents. |
    | `rate_limit` | Usually skip | Defaults to `1000` API requests. It does not limit calls. |

    <Warning>
      **Always add a plus slug to the customer's email. This is mandatory.** Send `asha+k7f3q9@example.com`, never the bare `asha@example.com`.

      One email can hold only one Vobiz sub-account, and a second create with the same email fails because the email already exists. Your customer may already have a Vobiz account of their own, or a sub-account through another platform. Without a slug, their email is taken and you cannot onboard them.

      The slug gives every customer a unique email on your platform. Mail servers ignore everything after the `+`, so the customer still receives every KYC email in their normal inbox.
    </Warning>

    Follow these rules for the slug:

    * **Generate it on your side** and store it with the customer, so you can always rebuild the exact email.
    * **Make it unique per sub-account,** such as a short random or hashed ID.
    * **Keep it to about 10 characters.** The part before `@` is limited to 64 characters. A long name plus a full UUID can exceed it, and the create fails.
    * **Add it even for first-time customers.** You cannot know whether the bare email is already in use.
  </Step>

  <Step title="Start a hosted KYC session inside your app">
    Call [Initialize KYC](/docs/sub-accounts/kyc/kyc-session) with your **parent** credentials and the sub-account in the path. Use `flow_type: "redirect"` to get a `widget_url` back immediately.

    ```bash cURL theme={null}
    curl -X POST "https://api.vobiz.ai/api/v1/sub-accounts/SA_XXXXXXXX/kyc-sessions" \
      -H "X-Auth-ID: MA_XXXXXXXX" \
      -H "X-Auth-Token: YOUR_AUTH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "account_auth_id": "SA_XXXXXXXX",
        "flow_type": "redirect",
        "redirect_url": "https://your-app.example.com/kyc/done",
        "webhook_url": "https://your-app.example.com/webhooks/vobiz-kyc"
      }'
    ```

    ```json 201 Created theme={null}
    {
      "session_id": "1a0f7da5-2abb-47bf-a6c3-eb5cff7feda5",
      "account_auth_id": "SA_XXXXXXXX",
      "status": "link_ready",
      "expires_at": "2026-10-26T10:00:00Z",
      "widget_url": "https://kyc.vobiz.ai/verify?token=kst_...",
      "message": "Redirect your customer to widget_url to begin."
    }
    ```

    Open `widget_url` in an iframe or modal on top of your app, or redirect the customer to it. The widget asks for the documents that match `business_type`:

    | Customer | Documents |
    | - | - |
    | Individual, proprietorship, HUF | PAN + Aadhaar through DigiLocker |
    | Company, LLP, partnership | PAN + GST and/or CIN |

    Verification usually takes under a minute.

    <Tip>
      Store `session_id`, `widget_url`, and `expires_at` with the customer. Vobiz keeps each document's state, so a customer who drops off after Aadhaar returns with Aadhaar still verified. Show a **Resume KYC** button that reopens the stored `widget_url` until it expires, then create a new session.
    </Tip>

    <Accordion title="Prefer email? Use the email flow">
      Set `flow_type: "email"` and `customer_email` instead. Vobiz emails the customer a signed link from `kyc@vobiz.ai`. It works without any UI on your side, but customers take longer to finish, so the in-app redirect flow converts better. See [Email flow and webhook](/docs/sub-accounts/kyc/email-flow-webhook).
    </Accordion>
  </Step>

  <Step title="Know when KYC is done">
    Vobiz POSTs an event to your `webhook_url` at each stage of the session. Verify the `X-Vobiz-Signature` header, an HMAC-SHA256 of the raw body keyed with your parent `auth_token`, and return `2xx`. Details are in [Redirect flow and webhook](/docs/sub-accounts/kyc/redirect-flow-webhook).

    | Event | Meaning | What your app does |
    | - | - | - |
    | `kyc.initiated` | Session created | Show "Verification pending". |
    | `kyc.submitted` | Documents submitted | Show "Under review". |
    | `kyc.completed` | Verification passed | Confirm with KYC status, then unlock calling. |
    | `kyc.failed` | A document failed | Ask the customer to retry with a new session. |
    | `kyc.session_expired` | Link expired unused | Create a new session on **Resume KYC**. |

    The webhook tells you the stage. [Get KYC Status](/docs/sub-accounts/kyc/kyc-status) tells you exactly which documents are done and whether calls are allowed. Call it with your **parent** credentials when a webhook arrives, when the customer logs in again, or on a schedule if a webhook was missed.

    ```bash cURL theme={null}
    curl "https://api.vobiz.ai/api/v1/sub-accounts/SA_XXXXXXXX/kyc/status" \
      -H "X-Auth-ID: MA_XXXXXXXX" \
      -H "X-Auth-Token: YOUR_AUTH_TOKEN"
    ```

    ```json 200 OK theme={null}
    {
      "sub_account_id": "SA_XXXXXXXX",
      "kyc_mode": "customer_use",
      "business_type": "individual",
      "overall_status": "pending",
      "kyc_calls_blocked": true,
      "verifications": {
        "pan": "not_started",
        "aadhaar": "verified",
        "gst": "not_started",
        "cin": "not_started"
      }
    }
    ```

    Gate calling on one field: `kyc_calls_blocked` must be `false`. In the example above, the customer finished Aadhaar but not PAN, so **Resume KYC** picks up at PAN.

    <Tip>
      Build against [KYC test mode](/docs/sub-accounts/kyc/test-mode) first. Magic inputs return verified, failed, or pending results with no real documents.
    </Tip>
  </Step>

  <Step title="Give the customer a number">
    A customer can get a number before KYC finishes, but cannot place calls until it does. Pricing, KYC, and capacity are the same either way. The difference is which account the number lives in.

    <Tabs>
      <Tab title="Buy inside the sub-account (recommended)">
        Use the **sub-account's** credentials. The number lands directly on the customer, and billing still goes to your main account. Find one with [List Inventory Numbers](/docs/account-phone-number/list-inventory-numbers), then call [Purchase from Inventory](/docs/account-phone-number/purchase-from-inventory).

        ```bash cURL theme={null}
        curl -X POST "https://api.vobiz.ai/api/v1/Account/SA_XXXXXXXX/numbers/purchase-from-inventory" \
          -H "X-Auth-ID: SA_XXXXXXXX" \
          -H "X-Auth-Token: SUB_ACCOUNT_AUTH_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{ "e164": "+919876543210" }'
        ```
      </Tab>

      <Tab title="Buy in your pool, then assign">
        Use this when you keep a stock of numbers ready to hand out. Buy with your **parent** credentials, then call [Assign DID to Sub-Account](/docs/account-phone-number/assign-subaccount). Encode `+` as `%2B` in the path.

        ```bash cURL theme={null}
        curl -X POST "https://api.vobiz.ai/api/v1/account/MA_XXXXXXXX/numbers/%2B919876543210/assign-subaccount" \
          -H "X-Auth-ID: MA_XXXXXXXX" \
          -H "X-Auth-Token: YOUR_AUTH_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{ "sub_account_id": "SA_XXXXXXXX" }'
        ```

        Taking a number back with [Unassign DID](/docs/account-phone-number/unassign-subaccount) has a 15-day cool-off if it carried a call in the last 15 days.
      </Tab>
    </Tabs>

    <Warning>
      Buying a number spends real money from your main account balance. Run these calls only with your own credentials and after the customer has confirmed the number.
    </Warning>
  </Step>

  <Step title="Point the number at your agent">
    Create an application **inside the sub-account** with [Create an Application](/docs/applications/create-application). Its `answer_url` returns the VobizXML that connects the call to your agent, usually a `<Stream>` to your WebSocket. Then [attach the number](/docs/applications/attach-number) to it.

    ```bash cURL theme={null}
    curl -X POST "https://api.vobiz.ai/api/v1/Account/SA_XXXXXXXX/Application/" \
      -H "X-Auth-ID: SA_XXXXXXXX" \
      -H "X-Auth-Token: SUB_ACCOUNT_AUTH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "app_name": "Asha Rao - voice agent",
        "answer_url": "https://your-app.example.com/vobiz/answer",
        "answer_method": "POST"
      }'
    ```

    Inbound calls to the number now reach your agent, and every call is recorded against the customer's sub-account.
  </Step>

  <Step title="Place calls from the sub-account">
    Call [Make an Outbound Call](/docs/call/make-call) with the **sub-account's** credentials and its own number as `from`. It succeeds only once `kyc_calls_blocked` is `false`.

    ```bash cURL theme={null}
    curl -X POST "https://api.vobiz.ai/api/v1/Account/SA_XXXXXXXX/Call/" \
      -H "X-Auth-ID: SA_XXXXXXXX" \
      -H "X-Auth-Token: SUB_ACCOUNT_AUTH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "from": "919876543210",
        "to": "919812345678",
        "answer_url": "https://your-app.example.com/vobiz/answer",
        "answer_method": "POST"
      }'
    ```

    <Note>
      A number bought in your pool and assigned to the sub-account can also be dialled with your parent credentials. Avoid it. The call then runs in your main account's scope instead of the customer's, which defeats the point of the sub-account.
    </Note>
  </Step>

  <Step title="Cap each customer's CPS and concurrency">
    Calls per second (CPS) and concurrent calls are pooled. Your main account and every sub-account draw from the same limits, and Vobiz does not split them per customer. Without your own caps, one busy customer can use most of the pool and starve the rest.

    Enforce a per-customer limit in your dialler before you call Make an Outbound Call. Most platforms tie it to the customer's plan:

    | Example plan | CPS | Concurrent calls |
    | - | - | - |
    | Starter | 1 | 3 |
    | Growth | 5 | 25 |
    | Scale | 20 | 200 |

    Two numbers help with sizing:

    * **CPS × 60 is your peak dial rate per minute.** 25 CPS starts up to 1,500 calls a minute.
    * **Concurrency counts every live call**, including calls still ringing, not just answered ones. Watch it with the [Concurrency API](/docs/account/concurrency).

    The [CPS and concurrency visualizer](/docs/resources/cps-concurrency-visualizer) shows how the two limits interact.
  </Step>

  <Step title="Bill each customer for their usage">
    Vobiz charges everything to your main account and does not track credit per customer. You keep each customer's balance or credit limit in your own system.

    To price a customer's usage, call [List CDRs](/docs/cdr/list-cdrs) with the **sub-account's** credentials. Each call record carries its duration and cost, and the `summary` block totals `total_cost` and `total_billable_seconds` for the filtered range. Apply your own rates on top.

    ```bash cURL theme={null}
    curl -G "https://api.vobiz.ai/api/v1/Account/SA_XXXXXXXX/cdr" \
      -H "X-Auth-ID: SA_XXXXXXXX" \
      -H "X-Auth-Token: SUB_ACCOUNT_AUTH_TOKEN" \
      --data-urlencode "start_date=2026-10-01" \
      --data-urlencode "end_date=2026-10-31"
    ```

    <Note>
      Use CDRs, not the transactions ledger, for per-customer usage. A [Transactions](/docs/account/transactions) query with an `SA_` ID returns your main account's full ledger. [List Account Phone Numbers](/docs/account-phone-number/list-account-phone-numbers) with your parent credentials shows which sub-account owns each number.
    </Note>
  </Step>
</Steps>

## Which credentials, at a glance

| Step | Endpoint | Credentials |
| - | - | - |
| Create sub-account | `POST /accounts/{MA}/sub-accounts/` | Parent |
| Initialize KYC | `POST /sub-accounts/{SA}/kyc-sessions` | Parent |
| KYC status | `GET /sub-accounts/{SA}/kyc/status` | Parent |
| Buy number in the sub-account | `POST /Account/{SA}/numbers/purchase-from-inventory` | Sub-account |
| Buy number in your pool | `POST /Account/{MA}/numbers/purchase-from-inventory` | Parent |
| Assign pool number | `POST /account/{MA}/numbers/{e164}/assign-subaccount` | Parent |
| Create application | `POST /Account/{SA}/Application/` | Sub-account |
| Attach number | `POST /Account/{SA}/numbers/{e164}/application` | Sub-account |
| Place a call | `POST /Account/{SA}/Call/` | Sub-account |
| Usage and cost | `GET /Account/{SA}/cdr` | Sub-account |

## Launch checklist

* Every sub-account email carries a short, unique plus slug. No bare emails.
* Sub-accounts are created with `customer_use` and a `business_type`.
* Each sub-account's `auth_token` is stored encrypted at creation, because it is shown only once.
* Your KYC webhook verifies `X-Vobiz-Signature` and returns `2xx` quickly.
* Your app re-checks KYC status on login, so a missed webhook never blocks a customer.
* Calling is gated on `kyc_calls_blocked` being `false`.
* Numbers, applications, and calls use the sub-account's credentials.
* Your dialler enforces per-customer CPS and concurrency caps.
* Your billing reads CDRs per sub-account and applies your own rates.

## Next steps

<CardGroup cols={2}>
  <Card title="Sub-account onboarding API flow" icon="route" href="/docs/sub-accounts/onboarding-flow">
    The same flow as a compact API reference, with every request and response.
  </Card>

  <Card title="Sub-account KYC" icon="id-card" href="/docs/sub-accounts/kyc/overview">
    Document matrix, call-blocking gate, and test mode.
  </Card>

  <Card title="Build an AI voice agent" icon="robot" href="/docs/guides/ai-voice-agent">
    Connect calls on the customer's number to your agent over WebSockets.
  </Card>

  <Card title="Sub-accounts vs partner accounts" icon="scale-balanced" href="/docs/concepts/sub-accounts-vs-partner-accounts">
    When to give customers their own wallet and capacity instead.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.