Endpoint Reference
All endpoints below live under https://voice.vokaai.com/api/v1.
Headers required on every authenticated request:
Authorization: Bearer voka_live_...
Content-Type: application/json (only on POST)
Headers returned on every response:
| Header | Purpose |
|---|---|
X-Request-Id | UUID — share this with support when filing issues |
X-RateLimit-Limit | Per-customer ceiling (currently 60/min) |
X-RateLimit-Remaining | Remaining requests in this window |
Retry-After | Seconds to wait — sent on 429 only |
GET /auth/whoami
Connection-test endpoint. No scope required — any valid key works.
curl https://voice.vokaai.com/api/v1/auth/whoami \
-H "Authorization: Bearer voka_live_..."
{
"customer_id": "0bf91...",
"customer_name": "Acme Dental",
"mode": "live",
"permissions": { "read_calls": true, "manage_webhooks": true }
}
GET /assistants
List voice assistants. Scope: read_assistants.
Query params: id (specific assistant), limit (default 50, max 100), offset.
curl https://voice.vokaai.com/api/v1/assistants \
-H "Authorization: Bearer voka_live_..."
{
"data": [
{
"id": "550e8400-...",
"name": "Reception Agent",
"description": "Handles new-patient calls",
"language": "en-US",
"voice_id": "natural-female",
"is_active": true,
"phone_numbers": ["+12025551111"],
"created_at": "2026-01-15T00:00:00Z",
"updated_at": "2026-04-22T00:00:00Z"
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}
GET /calls
Unified list of inbound and outbound calls. Scope: read_calls.
| Param | Type | Description |
|---|---|---|
direction | inbound | outbound | Filter by direction |
assistant_id | uuid | Filter by assistant |
from | ISO-8601 | Inclusive lower bound on started_at |
to | ISO-8601 | Inclusive upper bound on started_at |
limit | int 1-100 (default 25) | Page size |
cursor | string | Keyset pagination — pass next_cursor from prior response |
page | int ≥ 0 | Zapier-compat offset alias |
curl "https://voice.vokaai.com/api/v1/calls?direction=inbound&limit=10" \
-H "Authorization: Bearer voka_live_..."
{
"data": [
{
"id": "550e8400-...",
"direction": "inbound",
"status": "completed",
"started_at": "2026-05-10T12:00:00Z",
"answered_at": "2026-05-10T12:00:05Z",
"ended_at": "2026-05-10T12:03:30Z",
"duration_seconds": 205,
"from_number": "+12025551111",
"to_number": "+12025552222",
"assistant_id": "550e8400-...",
"assistant_name": "Reception Agent",
"caller_name": "John Doe",
"has_transcript": true,
"recording_available": true,
"created_at": "2026-05-10T12:00:00Z"
}
],
"next_cursor": "MjAyNi0wNS0xMFQxMjowMDowMC4wMDBafDU1MGU4NDAw..."
}
next_cursor is null when you've reached the end. Pass it as ?cursor=... to fetch the next page. Cursor pagination is recommended for stable iteration; ?page=N is a fallback for tools that can't handle opaque cursors.
GET /calls/{id}
Single-call detail. Scope: read_calls.
Same fields as the list shape plus: summary, disconnect_reason, conversation_id, cost, billable_seconds.
Returns 404 (not 403) when the call belongs to another customer — we never leak existence.
GET /calls/{id}/transcript
On-demand transcript pull. Scope: read_calls.
- Cached transcript: returned immediately (sub-100ms)
- Not yet cached, conversation_id present: fetched from carrier, persisted, returned
- Conversation still settling:
202withstatus: "pending"— subscribe tocall.transcript.readyfor push delivery
{
"call_id": "550e8400-...",
"conversation_id": "conv_abc123",
"transcript": {
"full_text": "Caller: hi...\n\nAssistant: ...",
"segments": [
{ "speaker": "caller", "text": "hi", "timestamp": "2026-05-10T12:00:00Z" }
]
}
}
POST /outbound-calls
Place an outbound call from one of your assistants. Scope: outbound_calls.
curl https://voice.vokaai.com/api/v1/outbound-calls \
-H "Authorization: Bearer voka_live_..." \
-H "Content-Type: application/json" \
-d '{
"assistant_id": "550e8400-...",
"to_number": "+12025551111",
"callback_url": "https://your-app.example.com/voka/callback",
"metadata": { "your_internal_id": "abc-123" }
}'
Response (call is initiated; lifecycle webhooks follow):
{
"id": "550e8400-...",
"status": "queued",
"to_number": "+12025551111",
"assistant_id": "550e8400-..."
}
Cost protection: every key has a daily $ cap and a country allowlist (default US + CA). Calls outside the allowlist or beyond the cap are rejected before reaching the carrier.
See outbound calls for the full lifecycle and callback contract.
GET /outbound-calls/{id}/transcript
Same shape as /calls/{id}/transcript but scoped to API-initiated outbound calls. Scope: read_analytics.
POST /webhook-subscriptions
Subscribe to one or more events. Scope: manage_webhooks.
curl https://voice.vokaai.com/api/v1/webhook-subscriptions \
-H "Authorization: Bearer voka_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/webhooks/voka",
"events": ["call.completed", "call.transcript.ready"],
"description": "Production receiver"
}'
{
"id": "sub_01H...",
"url": "https://your-app.example.com/webhooks/voka",
"events": ["call.completed", "call.transcript.ready"],
"secret": "whsec_...",
"is_active": true,
"created_at": "2026-05-10T12:00:00Z"
}
secret is shown onceStore it now — used to verify HMAC signatures on incoming events. We can rotate it but cannot show it again.
Idempotent: posting the same (url, events) set returns the existing subscription with 200 (no new secret) instead of creating a duplicate.
Make.com verification probe: if you POST with ?challenge=<token> query param, we echo { challenge: token } with 202 Accepted — no auth, no DB write. This is the contract Make's connection-validation step expects.
GET /webhook-subscriptions
List your active subscriptions. Scope: manage_webhooks.
DELETE /webhook-subscriptions/{id}
Unsubscribe. Scope: manage_webhooks. Also fired automatically when a subscription has 10 consecutive delivery failures (auto-disable, not delete — re-enable via the dashboard).
Pagination conventions
All list endpoints support both pagination styles so wrapper authors can pick:
Cursor (recommended):
curl "https://voice.vokaai.com/api/v1/calls?limit=25"
# Response includes: "next_cursor": "..."
curl "https://voice.vokaai.com/api/v1/calls?limit=25&cursor=..."
Offset (Zapier-friendly):
curl "https://voice.vokaai.com/api/v1/calls?limit=25&page=0"
curl "https://voice.vokaai.com/api/v1/calls?limit=25&page=1"
When both cursor and page are passed, cursor wins. Cursor is O(1) per page; page is O(N) for deep pages so prefer cursor for sync jobs.