API operational

VaatAI Developer API

Place phone calls that run your voice agents, read their transcripts and recordings, seed each call with your own variables, and get signed webhooks as calls progress — all over a simple, versioned REST API.

Base URL
https://voxa.datavonix.com/api/public/v1

All requests are HTTPS and authenticated with an API key sent as a bearer token. Responses are JSON. Jump to the live tester to run any endpoint with your own key.

Authentication

Every request must carry your API key in the Authorization header:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

Mint keys — and choose exactly what each key can do — in the dashboard under Settings → Developer API. The raw key is shown once at creation; store it securely. A key belongs to one workspace and can only ever see that workspace's data.

Keep keys secret. Never ship a sk_live_ key in browser or mobile client code — call the API from your server. Revoke a leaked key immediately in the dashboard.

Scopes

Each key is granted a set of scopes. A request that needs a scope the key doesn't hold returns 403. Grant the minimum a given integration needs.

ScopeGrants
calls:readList/read calls, transcripts and recordings
calls:writePlace calls and hang them up
agents:readList the agents a call can run
numbers:readList the phone numbers you can call from
webhooks:writeCreate, list and delete webhook endpoints

Quickstart

Place a call in one request. The agent runs when the callee answers.

# Place an outbound call
curl https://voxa.datavonix.com/api/public/v1/calls \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "<your-agent-id>",
    "to": "+14155550123",
    "variables": { "customer_name": "Asha" }
  }'
# → 202 Accepted
{
  "id": "CA9f2b…",          // use this to track the call
  "status": "dialing",
  "to": "+14155550123",
  "variables": { "customer_name": "Asha" }
}

Then poll GET /calls/{id} or subscribe to webhooks to follow it to completion.

▶ Try it live

Paste an API key and run real requests against the live API, right here. Nothing is sent anywhere except the API itself; your key stays in this browser.

GET /me
GET /agents
GET /numbers
POST /calls
GET /calls/{id}
GET /calls/{id}/transcript
POST /calls/{id}/hangup
GET /webhooks
POST /webhooks
// response appears here

Place a call

POST/callscalls:write

Starts an outbound call that runs agent_id when answered. Returns 202 immediately; the call runs in the background. Track it via GET /calls/{id} or webhooks.

FieldTypeNotes
agent_idstringRequired. The agent to run — see GET /agents.
tostringRequired. Destination in E.164, e.g. +14155550123.
fromstringOne of your numbers. Defaults to your first number for the provider.
providerstringtwilio (default) or plivo.
variablesobjectPredefined agent variables — see below.
metadataobjectOpaque; echoed back on webhooks for your own correlation.

The returned id (the carrier call id) is what you use everywhere else in the API.

Predefined agent variables

Pass variables to seed values the agent can use during the call. They become available:

{
  "agent_id": "…", "to": "+14155550123",
  "variables": {
    "customer_name": "Asha",
    "appointment_time": "3 PM",
    "clinic": "City General Hospital"
  }
}
Keys must be alphanumeric/underscore (so they resolve as {global.<key>}), up to 50 per call. Values are strings the agent reads at answer time.

List calls

GET/callscalls:read

Newest first. Query params: agent_id, status (active|completed), limit (1–200), offset. Returns { data, limit, offset, count }.

Get a call

GET/calls/{id}calls:read

Full detail including the transcript. Accepts the carrier id or the internal UUID.

{
  "id": "CA9f2b…", "status": "completed", "provider": "plivo",
  "direction": "outbound", "from": "…", "to": "…",
  "agent_id": "…", "agent_name": "…",
  "started_at": "…", "ended_at": "…", "duration_seconds": 13,
  "message_count": 2, "has_recording": true,
  "transcript": [ { "role": "agent", "text": "…", "at": "…" } ]
}

Get transcript

GET/calls/{id}/transcriptcalls:read

Just the turn-by-turn transcript: { id, message_count, transcript }.

Get recording

GET/calls/{id}/recordingcalls:read

The stereo WAV tape (left = caller, right = agent), streamed back or redirected to a time-limited URL depending on server config. 404 if the call has no recording.

Hang up a call

POST/calls/{id}/hangupcalls:write

Ends a live call now. Safe no-op (200) if the call already ended.

List agents

GET/agentsagents:read

The agents in your workspace a call can run: [{ id, name, type, created_at }].

List numbers

GET/numbersnumbers:read

Phone numbers you can place calls from: [{ id, phone_number, provider, status, agent_id }].

Webhooks

Register endpoints to receive call-lifecycle events. Manage them in the dashboard (Settings → Developer API) or via the API with the webhooks:write scope.

POST/webhookswebhooks:write
GET/webhookswebhooks:write
DEL/webhooks/{id}webhooks:write

Events we deliver:

EventFires when
call.initiatedthe API accepted your place-call request (dialing started)
call.answeredthe call connected and the agent went live
call.completedthe call ended normally (includes transcript)
call.failedthe call could not be placed / did not connect

Each delivery is a JSON envelope:

{
  "id": "<delivery-uuid>",
  "event": "call.completed",
  "created_at": "2026-08-21T13:38:09Z",
  "data": { /* the call object, as in GET /calls/{id} */ }
}

Verifying signatures

Every delivery is signed so you can trust it came from us. Compute an HMAC-SHA256 of the raw request body keyed by your endpoint's secret and compare to the header:

X-Voxa-Signature: sha256=<hex>
// Node.js (Express)
const crypto = require('crypto');
function verify(req, secret){
  const sig = req.headers['x-voxa-signature'];
  const mac = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(sig), Buffer.from(mac));
}
# Python (Flask)
import hmac, hashlib
def verify(request, secret):
    sig = request.headers.get('X-Voxa-Signature','')
    mac = 'sha256=' + hmac.new(
        secret.encode(), request.get_data(),
        hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, mac)
The secret is shown once when you create the endpoint. Deliveries retry with backoff; inspect attempts in the dashboard's webhook delivery log.

Errors

Standard HTTP status codes with a JSON { "detail": "…" } body.

CodeMeaning
401Missing or invalid API key
402Workspace out of credits
403Key lacks the required scope
404Call / agent / number not found in your workspace
409Not placeable (e.g. number on your Do-Not-Call list)
422Invalid request body
429Rate limit exceeded

Rate limits

Up to 120 requests per minute per key. Exceeding it returns 429 — back off and retry.