---
title: "Vyora AI API Documentation — Webhooks & Developer Reference"
description: "Vyora API reference — trigger outbound AI calls, receive webhook events, integrate AI voice agents. Auth, endpoints, events, and error codes."
canonical_url: "https://www.vyora.ai/docs/api"
---

API Reference

# Vyora API

Trigger AI voice calls and receive real-time events. One endpoint. Works with any backend, Zapier, or Make.

Connecting this to a CRM? See [how Vyora integrates with Salesforce, HubSpot, and Zoho](https://www.vyora.ai/blog/ai-voice-agent-crm-phone-integration-india).

One endpoint

POST /v1/calls to trigger any call instantly

Real-time events

Receive transcripts and analysis via webhook

API key auth

Simple header-based auth, no OAuth complexity

Base URL

`https://api.vyora.ai`

Want to run Vyora from a chat instead of code? Connect Claude, ChatGPT or Cursor with the [Vyora MCP server](https://www.vyora.ai/mcp): same agents and credits, no API key.

## Authentication

All requests require an `X-API-KEY` header. Generate your key from [Settings, Integrations](https://app.vyora.ai/settings?tab=api).

Keep your API key secret. Never expose it in client-side JavaScript or public repos. If compromised, revoke it immediately from Settings and generate a new one.

X-API-KEY: vya\_live\_your\_key\_here

## Rate limits

When rate limited, the API returns `429 Too Many Requests`. Use exponential backoff before retrying.

API requests · Per API key

`60 / minute`

Concurrent calls · Per workspace

`Plan dependent`

Webhook timeout · Per delivery attempt

`5 seconds`

// Exponential backoff, retry up to 3 times
async function callWithRetry(payload, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const res = await fetch('https://api.vyora.ai/v1/calls', { ... });
    if (res.status !== 429) return res;
    await new Promise(r => setTimeout(r, Math.pow(2, i) \* 1000));
  }
  throw new Error('Rate limit exceeded after retries');
}

GET`/v1/agents`

## List agents

Returns all active agents in your workspace. Pass an agent's `id` as `agent_id` when triggering calls. Agent IDs are also shown in your dashboard under Agents. The `variables` array lists exactly what that agent expects in `custom_args` — read it before you trigger a call rather than guessing the names. An empty array means the agent takes no variables.

Parameters

`limit`

integer · optional

Max agents to return. Default 50, max 100.

`offset`

integer · optional

Number of agents to skip. Default 0. Use with limit for pagination.

curl https://api.vyora.ai/v1/agents \\
  -H "X-API-KEY: vya\_live\_your\_key\_here"

200 OK

{
  "agents": \[
    {
      "id": "abc123-...", "name": "Home Loan Agent", "language": "hi-IN",
      "agent\_type": "outbound",
      "variables": \["callee\_name", "callee\_first\_name", "mobile\_number"\]
    },
    {
      "id": "def456-...", "name": "Admission Enquiry", "language": "en-IN",
      "agent\_type": "outbound",
      "variables": \[\]
    }
  \],
  "total": 2,
  "limit": 50,
  "offset": 0
}

GET`/v1/numbers`

## List numbers

Returns the caller-line numbers assigned to your workspace, each with its `id`. Pass that id as `from_number_id` when triggering a call to select which line places it. It is required on every call and must be an ID; raw phone numbers are not accepted.

curl https://api.vyora.ai/v1/numbers \\
  -H "X-API-KEY: vya\_live\_your\_key\_here"

200 OK

{
  "numbers": \[
    { "id": "5d7f9a2b-...", "phone\_number": "+918031137388", "display\_name": "Vyora Line 1" },
    { "id": "12dcae4c-...", "phone\_number": "+18085158398",  "display\_name": "US Line" }
  \]
}

GET`/v1/calls`

## List calls

Returns calls in your workspace, most recent first. Paginated. Filter by status to fetch only completed, failed, or ongoing calls. Each row carries the AI analysis, including your Post-Call Analysis fields under `analysis.custom`. For the full transcript, use `GET /v1/calls/:id`.

Parameters

`limit`

integer · optional

Max calls to return. Default 50, max 100.

`offset`

integer · optional

Number of calls to skip. Default 0. Use with limit for pagination.

`status`

string · optional

Filter by status, e.g. completed, failed, ongoing, registered.

curl "https://api.vyora.ai/v1/calls?limit=20&status=completed" \\
  -H "X-API-KEY: vya\_live\_your\_key\_here"

200 OK

{
  "calls": \[
    {
      "call\_id": "abc123xyz",
      "agent\_id": "def456-...",
      "agent\_name": "Home Loan Agent",
      "contact\_name": "Rahul Sharma",
      "phone\_number": "+919876543210",
      "status": "completed",
      "direction": "outbound",
      "duration\_seconds": 47,
      "recording\_url": "https://lnogjboxjhnngectkffk.supabase.co/functions/v1/get-recording?call\_id=abc123xyz",
      "analysis": {
        "summary": "Lead expressed strong interest, asked about EMI options.",
        "classification": "Interested",
        "custom": { "is\_correct\_person": true, "openness\_to\_discuss": true }
      },
      "campaign\_id": null,
      "voicemail": false,
      "custom\_args": { "name": "Rahul Sharma", "product": "Home Loan" },
      "called\_at": "2026-05-24T10:30:00Z"
    }
  \],
  "total": 134,
  "limit": 20,
  "offset": 0
}

GET`/v1/calls/:call_id`

## Get call

Fetch the full record for a single call: status, transcript, recording URL, and AI analysis. Use the `call_id` returned by `POST /v1/calls`. Any Post-Call Analysis fields you configured on the agent come back under `analysis.custom`, keyed by your own field names. Transcript and analysis appear only once processing has finished; poll with the snippet on the right if you are not using webhooks. `recording_url` is a permanent redirect — store it as-is and fetch it whenever you like; it resolves to a freshly-signed audio URL on every request, so it never goes stale.

curl https://api.vyora.ai/v1/calls/abc123xyz \\
  -H "X-API-KEY: vya\_live\_your\_key\_here"

200 OK

{
  "call\_id": "abc123xyz",
  "agent\_id": "def456-...",
  "agent\_name": "Home Loan Agent",
  "contact\_name": "Rahul Sharma",
  "phone\_number": "+919876543210",
  "status": "completed",
  "direction": "outbound",
  "duration\_seconds": 47,
  "recording\_url": "https://lnogjboxjhnngectkffk.supabase.co/functions/v1/get-recording?call\_id=abc123xyz",
  "transcript": \[
    { "bot": "Hi Rahul, I'm calling about your home loan inquiry..." },
    { "user": "Yes, I was interested in learning more..." }
  \],
  "analysis": {
    "summary": "Lead expressed strong interest, asked about EMI options.",
    "classification": "Interested",
    "custom": {
      "is\_correct\_person": true,
      "openness\_to\_discuss": true
    }
  },
  "campaign\_id": null,
  "voicemail": false,
  "custom\_args": { "name": "Rahul Sharma", "product": "Home Loan" },
  "called\_at": "2026-05-24T10:30:00Z"
}

404

{ "error": "Call not found" }

POST`/v1/calls`

## Trigger a call

Starts an outbound AI call immediately. Identify the agent with `agent_id` (from `GET /v1/agents`) and the caller line with `from_number_id` (from `GET /v1/numbers`); both required, both IDs. The agent calls the recipient, runs the conversation, and delivers results via webhook. A variable mismatch never blocks the call — if `custom_args` is missing something the agent declares, or carries a name it does not, the call still goes out and the response includes a `warnings` array saying so. Check it in staging: an empty variable is a silently worse call, not an error.

Parameters

`phone_number`

string · required

The number to call (the recipient). E.164 format, e.g. +919876543210

`agent_id`

string · required

Agent ID from GET /v1/agents. Identifies which agent runs the call.

`from_number_id`

string · required

Caller-line ID from GET /v1/numbers. Selects which of your assigned numbers places the call. Must be an ID.

`custom_args`

object · optional

Values for the variables the agent declares. Get the exact names from the variables array on GET /v1/agents. Anything the agent declares but you omit is sent empty and reported in warnings; anything it does not declare is ignored.

`transliterate_name`

boolean · optional

Optional, default false. Writes the callee name (callee\_name and callee\_first\_name) in the agent’s language script before the call, so the voice pronounces it correctly: "Rahul" becomes "राहुल" for a Hindi agent. Uses the agent’s non-English language (primary, then secondary), or Hindi for English-only agents. Also trims callee\_name to the first name and drops titles like Mr, Dr, Shri.

`agent_name`

string · optional

Legacy. Case-insensitive agent name, accepted as a fallback when agent\_id is omitted. Prefer agent\_id.

curl -X POST https://api.vyora.ai/v1/calls \\
  -H "X-API-KEY: vya\_live\_your\_key\_here" \\
  -H "Content-Type: application/json" \\
  -d '{
    "phone\_number": "+919876543210",
    "agent\_id": "def456-...",
    "from\_number\_id": "5d7f9a2b-...",
    "custom\_args": {
      "name": "Rahul Sharma",
      "callee\_first\_name": "Rahul",
      "product": "Home Loan"
    },
    "transliterate\_name": true
  }'

200 OK

{
  "call\_id": "abc123xyz",
  "status": "registered"
}

200 + warnings

{
  "call\_id": "abc123xyz",
  "status": "registered",
  "warnings": \[
    "Agent declares variable \\"callee\_first\_name\\" but no value was provided - it will be empty during the call.",
    "Ignored \\"first\_name\\" - not a variable declared on this agent."
  \]
}

4xx / 5xx

{
  "error": "Insufficient credits"
}

## Webhooks

Register a webhook URL in [Settings, Integrations](https://app.vyora.ai/settings?tab=api). Vyora POSTs to that URL after each call event. Your endpoint must respond with `200` within 5 seconds.

Sending a WhatsApp message after a call? See the [WhatsApp integrations](https://www.vyora.ai/integrations/whatsapp) (AiSensy, Interakt, Wati).

`X-Vyora-Event`

Event name, e.g. call\_completed

`X-Vyora-Signature`

HMAC-SHA256 signature for verification

`User-Agent`

Always Vyora-Webhooks/1.0

### Signature verification

Every webhook includes an `X-Vyora-Signature` header, an HMAC-SHA256 of the raw request body signed with your webhook secret (format `whsec_…`). A secret is created the first time you save a webhook URL. Always verify it before processing to prevent spoofed requests, and use **timing-safe comparison** (never `===`).

// Express.js, verify signature before processing
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig    = req.headers\['x-vyora-signature'\];
  const secret = process.env.VYORA\_WEBHOOK\_SECRET;

  if (!sig || !verifySignature(req.body, sig, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const body  = JSON.parse(req.body);
  const event = req.headers\['x-vyora-event'\];

  if (event === 'call\_completed') {
    console.log(\`Call ${body.call\_id} ended, status: ${body.status}\`);
    console.log('Summary:', body.analysis?.summary);
    // Your Post-Call Analysis fields, keyed by the names you configured
    console.log('Custom:',  body.analysis?.custom);
  }

  res.status(200).json({ received: true });
});

Request headers

POST https://your-server.com/webhook
Content-Type: application/json
X-Vyora-Event: call\_completed
X-Vyora-Signature: sha256=a4b3c2d1e0f9...
User-Agent: Vyora-Webhooks/1.0

Example payload

{
  "event": "call\_completed",
  "call\_id": "abc123xyz",
  "phone\_number": "+919876543210",
  "contact\_name": "Rahul Sharma",
  "agent\_id": "your-agent-id",
  "agent\_name": "Home Loan Agent",
  "status": "completed",
  "direction": "outbound",
  "duration\_seconds": 47,
  "recording\_url": "https://lnogjboxjhnngectkffk.supabase.co/functions/v1/get-recording?call\_id=abc123xyz",
  "transcript": \[
    { "bot": "Hi Rahul, I'm calling about your home loan inquiry..." },
    { "user": "Yes, I was interested in learning more..." }
  \],
  "analysis": {
    "summary": "Lead expressed strong interest, asked about EMI options.",
    "classification": "Interested",
    "custom": {
      "is\_correct\_person": true,
      "openness\_to\_discuss": true
    },
    "platform": { "...": "full Ringg platform analysis" }
  },
  "campaign\_id": "campaign\_456",
  "voicemail": false,
  "custom\_args": { "name": "Rahul Sharma", "product": "Home Loan" },
  "called\_at": "2026-05-24T10:30:00Z",
  "timestamp": "2026-05-24T10:31:15Z"
}

## Event types

One webhook is delivered per call. It fires once everything for that call is finished — the recording is stored and the AI analysis has run — so a single payload carries the full result and you never have to stitch several events together. Both the `event` field and the `X-Vyora-Event` header are always `call_completed`.

`call_completed`

Sent once per call, after processing finishes. Carries status, duration, recording URL, transcript, AI analysis and your custom analysis fields.

`test`

Sent only when you press Send test in Settings, Integrations. Same signature scheme, dummy body — use it to validate your verification code.

## Error codes

400

Bad Request

Missing or invalid field. Check phone\_number format, agent\_id / agent\_name, or JSON body

401

Unauthorized

API key is missing, invalid, or revoked

402

Payment Required

Insufficient credits. Top up or upgrade your plan

403

Forbidden

Workspace is inactive or suspended

404

Not Found

No agent found matching agent\_id / agent\_name, or call ID does not exist

405

Method Not Allowed

Wrong method for that path, e.g. POST to /v1/agents. Only POST /v1/calls triggers a call

429

Too Many Requests

Rate limit exceeded. Back off and retry with exponential delay. The Retry-After header tells you how many seconds remain in the current window

500

Internal Server Error

Something went wrong on our end. Retry with backoff

502

Bad Gateway

Webhook delivery failed. Your endpoint did not respond 200

Ready to build?

Generate your API key from the dashboard and make your first call in under 2 minutes.

[Get API Key](https://app.vyora.ai/settings?tab=api)
