API reference
Vexa REST API v1
Base URL: https://api.vexavoice.tech/v1. All endpoints accept and return JSON. This is the v1 API shape.
Authentication
Every request must include a bearer token in the Authorization header. Generate keys in your dashboard under Settings → API keys. Keys have the prefix vexa_sk_.
Authorization: Bearer vexa_sk_live_xxxxxxxxxxxxxxxxxxxxTest-mode keys (prefix vexa_sk_test_) run calls against a stubbed simulator: no real telephony, no metered usage. Use them in development and CI dry runs.
Rate limits
Rate limits are applied per API key. When you exceed a limit the API returns 429 Too Many Requests with a Retry-After header in seconds.
| Plan | Runs / min | Concurrent calls | API requests / min |
|---|---|---|---|
| Free | 2 | 4 | 60 |
| Pro | 20 | 50 | 300 |
| Scale | Unlimited | Unlimited | 2,000 |
Error shape
All errors return a JSON object with a machine-readable code and a human-readable message. HTTP status codes follow standard conventions.
{
"error": {
"code": "agent_not_found",
"message": "No agent with id 'agent_xxxxx' exists in this workspace.",
"param": "agentId",
"status": 404
}
}Common error codes:
unauthorized— missing or invalid API keyforbidden— key does not have permission for this actionagent_not_found— the referenced agent ID does not existsuite_not_found— the referenced suite ID does not existrun_in_progress— a run is already active for this agent and suiterate_limit_exceeded— slow down and retry after theRetry-Afterdelayvalidation_error— a request body field is invalid; checkparam
Agents
/agentsRegister a new voice agent by its endpoint and protocol. Vexa connects to this endpoint during every simulated call, exactly as a real telephony platform would. The returned id is required for all run requests.
Request body (JSON)
namestringrequiredA human-readable label for this agent. Shown in the dashboard and run results.endpointstringrequiredThe WebSocket or SIP URI Vexa connects to. Must be publicly reachable.protocolstringrequiredTelephony protocol. One of: twilio-twiml, vapi, retell, raw-sip.metadataobjectoptionalArbitrary key-value pairs stored with the agent. Useful for version tags.Example request
curl -X POST https://api.vexavoice.tech/v1/agents \
-H "Authorization: Bearer vexa_sk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Card Bot v2",
"endpoint": "wss://bot.acme.com/voice",
"protocol": "twilio-twiml",
"metadata": { "version": "2.4.1", "env": "staging" }
}'Example response
{
"id": "agent_01J9KXPR7WQVHB2N5M8ZD3CA4T",
"name": "Card Bot v2",
"endpoint": "wss://bot.acme.com/voice",
"protocol": "twilio-twiml",
"metadata": { "version": "2.4.1", "env": "staging" },
"createdAt": "2025-03-14T09:41:22Z"
}/agentsReturns all agents registered in the workspace, ordered by creation date descending. Use the cursor parameter to paginate.
Path / query parameters
limitintegeroptionalNumber of agents to return. Default: 20. Max: 100.cursorstringoptionalPagination cursor from a previous response's next_cursor field.Example request
curl https://api.vexavoice.tech/v1/agents \
-H "Authorization: Bearer vexa_sk_live_xxxx"Example response
{
"agents": [
{
"id": "agent_01J9KXPR7WQVHB2N5M8ZD3CA4T",
"name": "Card Bot v2",
"endpoint": "wss://bot.acme.com/voice",
"protocol": "twilio-twiml",
"createdAt": "2025-03-14T09:41:22Z"
}
],
"hasMore": false,
"nextCursor": null
}Runs
/runsStart a new run: execute a suite of scenarios against a registered agent. Vexa dials all scenarios concurrently and scores every turn. The run transitions from pending to running to complete (or failed on infrastructure error). Poll GET /runs/:id or use a webhook to know when it is done.
Request body (JSON)
agentIdstringrequiredID of the agent to test. Must already be registered.suiteIdstringrequiredID of the scenario suite to run. Use a Vexa built-in or a custom suite you created.config.concurrencyintegeroptionalMax simultaneous calls. Default: 8. Scale plan: up to 200.config.voicesstring[]optionalSubset of voice IDs to use. Defaults to all voices assigned to the suite.config.timeoutMsintegeroptionalPer-call timeout in milliseconds. Default: 60000 (60 seconds).config.thresholdsobjectoptionalOverride default pass/fail thresholds. E.g. { taskSuccess: 0.90, werPct: 6.0 }.Example request
curl -X POST https://api.vexavoice.tech/v1/runs \
-H "Authorization: Bearer vexa_sk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"agentId": "agent_01J9KXPR7WQVHB2N5M8ZD3CA4T",
"suiteId": "suite_card_replacement",
"config": {
"concurrency": 8,
"voices": ["alex", "priya", "dev"],
"timeoutMs": 60000,
"thresholds": { "taskSuccess": 0.85 }
}
}'Example response
{
"id": "run_01J9KZ7YH5CQNM3FX8WBR4DV2E",
"agentId": "agent_01J9KXPR7WQVHB2N5M8ZD3CA4T",
"suiteId": "suite_card_replacement",
"status": "pending",
"verdict": null,
"overall": null,
"createdAt": "2025-03-14T09:41:22Z",
"completedAt": null
}/runs/:idRetrieve a run by its ID. When status is complete, all scored fields are present including the full turns array. When status is running, partial results may appear in turns but aggregate fields are null.
Path / query parameters
idstringrequiredThe run ID returned by POST /runs.Example request
curl https://api.vexavoice.tech/v1/runs/run_01J9KZ7YH5CQNM3FX8WBR4DV2E \
-H "Authorization: Bearer vexa_sk_live_xxxx"Example response
{
"id": "run_01J9KZ7YH5CQNM3FX8WBR4DV2E",
"agentId": "agent_01J9KXPR7WQVHB2N5M8ZD3CA4T",
"suiteId": "suite_card_replacement",
"status": "complete",
"verdict": "pass",
"overall": 0.91,
"taskSuccess": 0.89,
"werPct": 4.2,
"avgLatencyMs": 340,
"bargeIns": 2,
"turns": [
{
"speaker": "agent",
"text": "Hi, this is Maya. How can I help you today?",
"latencyMs": 320,
"bargeIn": false,
"success": true,
"tone": "warm",
"flag": null,
"note": null
},
{
"speaker": "sim",
"text": "I need to replace my damaged card.",
"latencyMs": 0,
"bargeIn": false,
"success": true,
"tone": "neutral",
"flag": null,
"note": null
},
{
"speaker": "agent",
"text": "Of course. Can I confirm the last four digits?",
"latencyMs": 295,
"bargeIn": false,
"success": true,
"tone": "professional",
"flag": null,
"note": null
}
],
"failingTurn": null,
"regression": null,
"createdAt": "2025-03-14T09:41:22Z",
"completedAt": "2025-03-14T09:49:37Z"
}Voices
/voicesReturns all voices available for simulation. Each voice carries an accent tag, a region, and a set of scenario affinities. Pass voice IDs in a run's config.voices array to limit which voices Vexa uses. See the Voices catalog at /voices for the full list with regional groupings.
Path / query parameters
regionstringoptionalFilter by region slug. E.g. north-america, british-isles, south-asia.Example request
curl https://api.vexavoice.tech/v1/voices \
-H "Authorization: Bearer vexa_sk_live_xxxx"Example response
{
"voices": [
{
"id": "alex",
"name": "Alex",
"accent": "General American",
"region": "north-america",
"locale": "en-US",
"descriptor": "Male-presenting",
"sampleScenario": "Card replacement flow"
},
{
"id": "priya",
"name": "Priya",
"accent": "British English",
"region": "british-isles",
"locale": "en-GB",
"descriptor": "Female-presenting",
"sampleScenario": "Booking confirmation"
},
{
"id": "dev",
"name": "Dev",
"accent": "Australian English",
"region": "australia-nz",
"locale": "en-AU",
"descriptor": "Male-presenting",
"sampleScenario": "Outage alert"
}
],
"total": 18
}Scenarios
/scenariosReturns all scenarios available to the workspace: Vexa built-ins and any custom scenarios you have created. A scenario defines the caller persona, opening utterance, success condition, and the flag set used to evaluate each turn.
Path / query parameters
suiteIdstringoptionalFilter to scenarios belonging to a specific suite.tagsstringoptionalComma-separated tag filter. E.g. barge-in,angry-caller.Example request
curl "https://api.vexavoice.tech/v1/scenarios?suiteId=suite_card_replacement" \
-H "Authorization: Bearer vexa_sk_live_xxxx"Example response
{
"scenarios": [
{
"id": "scen_card_01",
"suiteId": "suite_card_replacement",
"name": "Standard card replacement",
"persona": "Polite caller requesting a replacement for a damaged card.",
"opening": "Hi, I need to get a new card. Mine is damaged.",
"successCondition": "Agent confirms replacement and verifies mailing address.",
"tags": ["banking", "card-services"],
"voices": ["alex", "priya", "marcus", "eleanor"]
},
{
"id": "scen_card_02",
"suiteId": "suite_card_replacement",
"name": "Caller refuses identity verification",
"persona": "Impatient caller who refuses to provide last four digits.",
"opening": "I want a new card but I'm not giving you my card number.",
"successCondition": "Agent holds verification policy without losing caller.",
"tags": ["banking", "card-services", "edge-case"],
"voices": ["marcus", "eleanor", "chidi"]
}
],
"total": 12
}Turn flags
Every scored turn can carry a flag that explains why it did not pass. Flags appear in turns[].flag and in failingTurn.flag.
| Flag | Meaning |
|---|---|
| task_incomplete | Agent ended the turn or call without completing the required task step. |
| wrong_info | Agent stated something factually incorrect given the scenario context. |
| barge_in_unhandled | The simulated caller interrupted and the agent did not recover gracefully. |
| latency_spike | Agent first-response latency exceeded 1,500 ms on this turn. |
| tone_mismatch | Agent tone did not match the scenario's expected tone target (e.g. warm vs. flat). |
| off_script | Agent departed from the expected call flow in a way that was not a recovery. |
| identity_skip | Agent skipped a required identity verification step. |
| early_termination | Agent ended the call before the scenario's success condition was met. |
Run completion webhooks
Instead of polling GET /runs/:id, register a webhook URL in Settings → Webhooks. Vexa sends a POST to your URL when a run transitions to complete or failed. The payload is the full run object.
Webhook deliveries are signed with a secret you configure. The X-Vexa-Signature header contains an HMAC-SHA256 signature of the raw request body using your webhook secret. Verify it before processing.
// Node.js signature verification
const crypto = require("crypto");
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}Vexa retries failed webhook deliveries up to 5 times with exponential back-off over 24 hours. A delivery is considered successful when your endpoint returns any 2xx status.
Every voice, tested.
Connect your first agent and run a suite free.