Sub-accounts
A sub-account is a workspace your organization creates for one of your own customers — and pays for. Build your own product on Telenow (your login, your admin, your payment gateways) and create one sub-account per customer: each gets its own agents, numbers, calls, knowledge bases, webhooks and its own API key, fully separated from every other customer's, while every charge comes out of your wallet.
| Your organization (the parent) | Each sub-account | |
|---|---|---|
| Wallet & credits | ✅ Yours — top up here | ❌ None of its own — every charge is posted to the parent's wallet |
| Pricing | Your rates and negotiated platform fee | Exactly the parent's |
| Invoice / Billing page | Shows the parent's spend and every sub-account's — each ledger row names the sub-account that caused it (sourceOrgId), and the charges-by-agent table names each sub-account's agents | Shows only what that sub-account spent — no balance, cap or top-up: the wallet is the parent's |
| Settings on the account | Data retention, live-call caps, integration plan, consent enforcement | Inherits the parent's unless it has its own. A web-call cap inherited from you is shared — your own and every inheriting sub-account's web calls count against it together. Consent enforcement can be tightened, never switched off below the parent's |
| Alerts | Yours, and every sub-account's | None — its alerts come to you |
| API key | Your keys | Its own key, returned when you create it |
| Limits | Your own usage limits | Set by the parent: live calls, monthly spend, total spend, suspended |
| Webhooks | Receives its own events and every sub-account's | Receives its own |
Creating a sub-account is quiet: it is not a sign-up, so its users get no Telenow emails, OTPs or welcome credit, and nobody from Telenow contacts them.
Who can use these endpoints: the parent's owner or admin — with an
owner/adminAPI key, or signed in. Adeveloperorviewerkey gets403: a sub-account spends your money and carries a credential. A sub-account's own key gets403too — a sub-account cannot have sub-accounts. Writes are also refused (403) to a Telenow support session, a reseller's exchanged session and a solution partner's seat: they would mint a credential, or move your money, that outlives the session.
Endpoints
The same operations exist on both surfaces. Use the Integration API with your API key from your backend; the dashboard routes are what the Workplace → Sub-accounts page uses.
| Method | Integration API (X-API-Key, flat JSON) | Dashboard API ({ success, data }) | Purpose |
|---|---|---|---|
GET | /api/v1/sub-accounts | /api/orgs/{orgId}/sub-accounts | List, newest first |
POST | /api/v1/sub-accounts | /api/orgs/{orgId}/sub-accounts | Create — returns the sub-account and its API key |
GET | /api/v1/sub-accounts/{id} | /api/orgs/{orgId}/sub-accounts/{id} | One sub-account |
PATCH | /api/v1/sub-accounts/{id} | /api/orgs/{orgId}/sub-accounts/{id} | Rename, set your reference, change limits |
POST | /api/v1/sub-accounts/{id}/suspend | …/{id}/suspend | Refuse new calls; end live ones |
POST | /api/v1/sub-accounts/{id}/resume | …/{id}/resume | Lift a suspension |
GET | /api/v1/sub-accounts/{id}/usage | …/{id}/usage | Calls, minutes and spend for a window |
POST | /api/v1/sub-accounts/{id}/keys | …/{id}/keys | Mint a new key (revokes the others by default) |
DELETE | /api/v1/sub-accounts/{id} | …/{id} | Close for good |
A sub-account id is its organization id. Another organization's id returns 404.
Create a sub-account
curl -X POST https://api.telenow.ai/api/v1/sub-accounts \
-H "X-API-Key: vai_live_…" -H "Content-Type: application/json" \
-d '{
"name": "Acme Dental",
"externalId": "cust_8412",
"limits": {
"maxConcurrentCalls": 5,
"monthlySpendLimitUsd": 200,
"totalSpendLimitUsd": null
}
}'
Returns 201 Created:
{
"subAccount": {
"id": "6f1c…",
"name": "Acme Dental",
"externalId": "cust_8412",
"status": "active",
"limits": { "maxConcurrentCalls": 5, "monthlySpendLimitUsd": 200.0, "totalSpendLimitUsd": null },
"suspendedAt": null,
"suspendedReason": null,
"closedAt": null,
"createdAt": "2026-10-16T09:12:44.120Z",
"updatedAt": "2026-10-16T09:12:44.120Z",
"usage": { "monthToDateSpendUsd": 0.0, "activeCalls": 0 }
},
"apiKey": {
"id": "a41e…",
"key": "vai_live_…",
"lastFour": "Xk2Q",
"name": "Sub-account key",
"role": "admin",
"createdAt": "2026-10-16T09:12:44.120Z"
}
}
apiKey.key is shown exactly once. Store it encrypted on your server; it is the sub-account's credential for every other Telenow API — agents, numbers, calls, campaigns, webhooks, knowledge bases — exactly as your own key is for you. Never send it to a browser: mint a client token with it instead.
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. Up to 160 characters. |
externalId | string | none | Your id for this customer, unique among your open sub-accounts (409 if reused). A closed sub-account frees it, so a returning customer can get a new one. Up to 200 characters. Find a sub-account by it with ?externalId=. |
limits | object | no limits | See Limits. |
keyName | string | Sub-account key | Name of the key minted with it. |
keyRole | string | admin | admin, developer or viewer. Never owner. |
Unknown fields are rejected with 400 rather than ignored — a misspelt limit would otherwise create a sub-account with no limits.
The sub-account copies your country, currency, timezone, consent enforcement, compliance mode and data-residency region when it is created.
Limits
| Field | Meaning |
|---|---|
maxConcurrentCalls | Live calls at once — inbound, outbound, web and campaign calls together. |
monthlySpendLimitUsd | USD this sub-account may spend in the current billing month (the same calendar month, India time, as your own cap and invoice). |
totalSpendLimitUsd | USD this sub-account may spend, ever. |
Each one: absent = unchanged (on create: no limit), null = no limit, a number = that limit, 0 = stopped. Spend is what the sub-account's calls and purchases were charged, the same figure that left your wallet.
When a limit is reached, the sub-account's new calls are refused the way an out-of-credit account's are: an outbound or API call gets 403 (or 429 for the live-call limit), a campaign waits and retries, a web or widget call is refused, and an inbound call is turned away busy before it is answered on Twilio, Plivo, SIP trunks and WhatsApp calling. On Vobiz and Vonage a refused inbound call follows the number's missed-call setting (forward or message), as it does for any account; on Exotel and Smartflo the call is dropped when its audio stream opens.
Calls already live are weighed too: once a minute the spend guard adds what they are accruing (their elapsed time at the sub-account's recent rate) to what has settled, and ends them when that reaches a spend limit — a call admitted just under a limit cannot run on for hours. A suspension through this API ends them at once.
If your organization is deleted, every paid action of its sub-accounts stops — calls, numbers, AI — until it is restored. If it is then permanently purged, its sub-accounts are purged with it, in the same purge: one under a legal hold, or with a call in progress, holds the whole purge back.
A sub-account is also refused when your account would be: your wallet out of credits (prepaid), your account suspended, or your monthly spend cap reached — your cap counts every sub-account's spend. What a sub-account is told in those cases never includes your balance, cap or spend.
Update, suspend, resume
curl -X PATCH https://api.telenow.ai/api/v1/sub-accounts/6f1c… \
-H "X-API-Key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "limits": { "monthlySpendLimitUsd": 500, "maxConcurrentCalls": null } }'
PATCH takes name, externalId (null clears it) and limits; an empty body is 400. A closed sub-account can't be changed (409).
curl -X POST https://api.telenow.ai/api/v1/sub-accounts/6f1c…/suspend \
-H "X-API-Key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "reason": "Invoice overdue" }'
Suspending refuses every new call at once and ends the sub-account's live calls. The optional reason (up to 500 characters) is appended to the refusal its calls get. …/resume lifts it. A typical product suspends a customer's sub-account when that customer's own balance with you runs out, and resumes it when they top up.
Usage
curl "https://api.telenow.ai/api/v1/sub-accounts/6f1c…/usage?from=2026-10-01T00:00:00Z" \
-H "X-API-Key: vai_live_…"
{
"subAccountId": "6f1c…",
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-16T09:20:00Z",
"calls": 412,
"billableSeconds": 51840,
"spendUsd": 63.12,
"activeCalls": 2,
"monthToDateSpendUsd": 63.12,
"totalSpendUsd": 180.4,
"limits": { "maxConcurrentCalls": 5, "monthlySpendLimitUsd": 200.0, "totalSpendLimitUsd": null },
"status": "active"
}
from and to are RFC 3339 instants (URL-encode a +05:30 offset as %2B05:30 — a bare + arrives as a space); they default to the start of the current billing month and now. calls and billableSeconds count calls that started in the window; spendUsd is what was charged in it — a call is charged when it settles, shortly after it ends. Use these figures to bill your customer.
For per-call detail, call GET /api/v1/calls with the sub-account's key, or subscribe your own endpoint to call.ended — see Webhooks.
List
GET /api/v1/sub-accounts — query: status (active, suspended, closed), externalId (exact), limit (default 50, max 200), offset.
{ "subAccounts": [ { "id": "6f1c…", "name": "Acme Dental", "status": "active", "usage": { "monthToDateSpendUsd": 63.12, "activeCalls": 2 }, "…": "…" } ], "total": 1, "limit": 50, "offset": 0 }
Keys
curl -X POST https://api.telenow.ai/api/v1/sub-accounts/6f1c…/keys \
-H "X-API-Key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "keyRole": "admin", "revokeExisting": true }'
Mints a new key (returned once, as on create) and — unless you send "revokeExisting": false — revokes every other key of that sub-account in the same step. Use it to rotate, or if a key leaked. Returns { "subAccountId", "revokedKeys", "apiKey" } with 201.
A sub-account's keys act as that sub-account's own service identity — a user nobody can sign in as, a member of that one sub-account and nothing else. So a key reaches its own sub-account only, no person from your team becomes its owner, and nobody leaving your team turns its keys off. Rotating also moves a key made before this to the service identity.
Close
DELETE /api/v1/sub-accounts/{id} closes it for good: its keys are revoked, new calls are refused, live ones are ended, running campaigns are paused and the workspace is deleted. It is refused with 409 while the sub-account still holds phone numbers — a rented one would keep charging you — so release them first (with the sub-account's key). Closing twice is harmless, and finishes a close whose clean-up was interrupted. A sub-account cannot be deleted through the ordinary organization delete — close it here.
Webhooks
Every event a sub-account raises carries subAccountId (its id) and is delivered to:
- the sub-account's own endpoints, as usual, and
- every org-wide endpoint of the parent (one with
agentId: null) whoseeventsmatch.
So one endpoint on your own organization receives every customer's calls; route them by subAccountId. Your agent-scoped endpoints never receive a sub-account's events. The parent's copy appears in your delivery log, and a legal hold on the sub-account keeps your copies of its events too. See Webhook events.
Money alerts go to you. A sub-account's numbers are rented on your wallet, so billing.number_renewal_due, billing.number_renewal_failed, billing.number_release_pending and billing.number_released_nonpayment for its numbers are sent to your endpoints and owners/admins (with subAccountId), not to the sub-account — it has no wallet to top up.
So do all its other alerts. Nobody signs in to a sub-account, so the in-app notifications and alert emails it raises — an integration that stopped connecting, a voice provider key failing on its calls, anything else — go to your owners and admins, titled with the sub-account's name and linking to your Sub-accounts page. Fix the cause with the sub-account's API key.
Your apps see its charges. An app installed on your organization with billing:read receives a sub-account's charge.settled too, with subAccountId and your wallet's balance after the charge — so one budget alert covers every customer.
Errors
| Status | When |
|---|---|
400 | Invalid field, unknown field, empty update, bad timestamp. |
403 | Not an owner/admin (person or key role); a sub-account's key; a demo workspace; a support session, reseller session or solution-partner seat writing. |
404 | Not one of your sub-accounts. |
409 | externalId already used by an open sub-account; changing, suspending, resuming or minting a key for a closed one; closing while it holds numbers. (Closing a closed one is 200.) |