Account
Authentication
Your code authenticates to Voxa with an API key, and this page covers keys, roles and permissions, rate limits, time zones and errors: the conventions every endpoint follows.
API conventions
| Base URL | https://voxa.abhinavyadav.in/api/v1 |
| Format | JSON in and out (Content-Type: application/json) |
| Authentication | An API key in X-API-Key or Authorization: Bearer |
| Time zone | ?timezone= or X-Timezone; India time by default |
| Money | Integers in paise: 50000 is ₹500.00 |
| Phone numbers | E.164, for example +919812345678 |
| Ids | 32-character hex strings (webhook ids have a prefix: we_, whd_, evt_) |
Versioning
The public API is versioned in the path: every endpoint is served under /api/v1. Build integrations on /api/v1 only. The console uses unversioned /api/... paths that can change with it; /api/v1 stays stable.
The full list of endpoints is in the API reference.
API keys
Create a key in the console under API keys. You need the owner, admin or developer role.
- A key looks like
vx_...and is shown once. Voxa stores only a hash of it. - A key belongs to one workspace. It never needs the
X-Workspace-IDheader. - A key can do what a developer can, and also read the balance and billing (see the table below).
- Revoking a key (in the console, or
DELETE /api/v1/keys/{id}) stops it at once.
Send the key in either header:
curl https://voxa.abhinavyadav.in/api/v1/agents -H "X-API-Key: $VOXA_API_KEY"
curl https://voxa.abhinavyadav.in/api/v1/agents -H "Authorization: Bearer $VOXA_API_KEY"
Keys can also be managed through the API: GET /api/v1/keys lists them (name, prefix, who created it, when it was last used), POST /api/v1/keys with {"name": "..."} creates one and returns key once.
Keep keys on your server. Anyone with a key can place calls that spend your credits.
Apps connected through the MCP server’s OAuth flow use access tokens that start with vxo_, sent as Authorization: Bearer. See MCP server.
Roles and permissions
Every member of a workspace has one role. Each role is a fixed set of permissions, and each endpoint asks for one permission.
| Permission | What it allows | Viewer | Developer | Admin | Owner | API key |
|---|---|---|---|---|---|---|
agents.view | Read agents, versions, numbers, catalogs, limits | Yes | Yes | Yes | Yes | Yes |
calls.view | Read calls, timelines, recordings | Yes | Yes | Yes | Yes | Yes |
logs.view | Read the workspace’s logs | Yes | Yes | Yes | Yes | Yes |
members.view | Read the member list | Yes | Yes | Yes | Yes | Yes |
agents.edit | Create, change, duplicate, delete and restore agents; test tools | Yes | Yes | Yes | Yes | |
calls.place | Place, cancel and end calls; browser calls | Yes | Yes | Yes | Yes | |
keys.manage | API keys and webhooks | Yes | Yes | Yes | Yes | |
billing.view | Balance, ledger, plan, invoices | Yes | Yes | Yes | ||
audit.view | The audit log | Yes | Yes | |||
numbers.manage | Connect phone numbers to agents | Yes | Yes | |||
members.manage | Invite and remove members; ask for higher limits | Yes | Yes | |||
billing.manage | Top up credits | Yes | ||||
kyc.manage | Business details (KYC) | Yes |
A request without the permission gets 403 with your role cannot do this (<permission>).
Workspace status
A new workspace starts in onboarding: it submits its business details and waits for approval. Until the workspace is active, most endpoints return:
{"detail": "workspace not active", "code": "workspace_inactive"}
with status 403. Billing, limits and onboarding endpoints work before activation.
Rate limits
Each API key may make a set number of requests per minute: 120 by default (the platform sets it). The window is a fixed calendar minute. Keyed responses carry:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute. |
X-RateLimit-Remaining | Requests left in this minute. |
X-RateLimit-Reset | Unix time (seconds) when the current window ends. |
Over the limit, you get 429 with {"detail": "rate limit exceeded; slow down"} and a Retry-After header in seconds. Wait that long and try again.
Time zones
Every /api/v1 endpoint returns timestamps in one time zone. Choose it per request with the timezone query parameter or the X-Timezone header, using an IANA name. The default is Asia/Kolkata.
curl "https://voxa.abhinavyadav.in/api/v1/calls/$CALL_ID?timezone=UTC" -H "X-API-Key: $VOXA_API_KEY"
curl https://voxa.abhinavyadav.in/api/v1/calls/$CALL_ID -H "X-API-Key: $VOXA_API_KEY" -H "X-Timezone: Europe/London"
- Fields named
at,dateortimestamp, or ending in_at(created_at,ended_at…), come back as ISO 8601 with the zone’s offset:2026-10-01T15:30:00+05:30. - The zone used is echoed in the
X-Timezoneresponse header. - An unknown zone returns
400with{"detail": "unknown timezone 'Mars/Base': use an IANA name such as Asia/Kolkata or UTC"}. - Webhook payloads are always in UTC.
- When sending times, include an offset or use UTC.
Errors
Errors are JSON with a detail field:
{"detail": "agent not found"}
| Status | Meaning |
|---|---|
400 | The request can’t be carried out as asked, for example no caller number, or an unknown time zone. |
401 | No key, or the key is invalid or revoked: sign in required. |
402 | Out of credits. |
403 | Your role lacks the permission, or the workspace isn’t active ("code": "workspace_inactive"). |
404 | Not found, or not in your workspace. |
409 | Conflicts with the current state, for example a plan limit, or a number used by another workspace. |
422 | Validation failed. detail lists each problem with its field. |
429 | Rate limited. |
502 | A provider (Plivo, Cartesia) didn’t answer. Try again. |
500 | Server error. The body has request_id: quote it when you contact support. |
A 422 from validation looks like this:
{
"detail": [
{"type": "less_than_equal", "loc": ["body", "call", "max_duration_seconds"], "msg": "Input should be less than or equal to 3600", "input": 7200}
]
}
Every response has an X-Request-ID header. It also identifies the request in your workspace’s logs in the console.