# Create Agent
Source: https://docs.voicy.co/api-reference/agent-create
POST /v1/agent-create
Create a prompt-based voice agent
Creates a prompt-based voice agent and deploys it immediately — the returned
`agent_id` can be used right away as `override_agent_id` on [Dial](/api-reference/call-dial).
Only prompt-based agents can be created via the API. Flow agents (multi-step
conversational flows with branching, function calls, and knowledge bases) are
built in the [dashboard](https://app.voicy.co).
## Body Parameters
Agent display name.
System prompt that defines the agent's behavior.
Language code, e.g. `he` or `en`.
Voice display name. One of `Sapir`, `Steve`, `Sivan`, `Saar`, `Ori`, `Yael`, `Guy`, `Gefen`, `Gal`, `Gabriel`.
Message the agent speaks first when it answers. When empty, the agent opens
the conversation from the prompt.
Variables to supply at call time (via `vars_provided` on Dial).
Variable name.
Human-readable description.
Default value if not supplied at call time.
Variables the agent should extract from the conversation after the call.
Variable name.
Human-readable description.
Whether extraction is required.
Up to 5 webhooks fired when a call with this agent ends. See [Webhooks](/webhooks) for the payload and signature.
A label for this webhook, up to 50 characters (e.g. "CRM").
HTTPS endpoint that receives the POST. Private and internal addresses are rejected.
Signing secret for the `x-voicy-signature` HMAC-SHA256 header.
Up to 10 custom headers added to every request, e.g. `{"Authorization": "Bearer "}`. Reserved names (`Content-Type`, `x-voicy-event`, `x-voicy-signature`, `Host`, `Content-Length`, `Transfer-Encoding`) are rejected.
## Response
The created [Agent](/api-reference/models/agent) object.
```bash cURL theme={null}
curl -X POST https://api.voicy.co/functions/v1/agent-create \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Appointment Booker",
"language": "he",
"prompt": "You are a friendly receptionist that books appointments.",
"voice": "Ori",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [{ "name": "customer_name", "description": "The caller'\''s name" }],
"variables_extracted": [{ "name": "appointment_date", "description": "The date the caller chose", "required": true }],
"webhooks": [{ "name": "CRM", "url": "https://crm.example.com/voicy/call-ended", "secret": "whsec_example" }]
}'
```
```javascript JavaScript theme={null}
const agent = await (await fetch('https://api.voicy.co/functions/v1/agent-create', {
method: 'POST',
headers: {
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Appointment Booker',
language: 'he',
prompt: 'You are a friendly receptionist that books appointments.',
voice: 'Ori',
welcome_message: 'שלום, איך אפשר לעזור?',
}),
})).json();
```
```python Python theme={null}
import requests
agent = requests.post(
'https://api.voicy.co/functions/v1/agent-create',
headers={'Authorization': 'Bearer voicy_sk_live_xxx'},
json={
'name': 'Appointment Booker',
'language': 'he',
'prompt': 'You are a friendly receptionist that books appointments.',
'voice': 'Ori',
'welcome_message': 'שלום, איך אפשר לעזור?',
},
).json()
```
```json 201 Created theme={null}
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Appointment Booker",
"language": "he",
"voice": "Ori",
"agent_type": "singlePrompt",
"active": true,
"prompt": "You are a friendly receptionist that books appointments.",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [
{ "name": "customer_name", "description": "The caller's name" }
],
"variables_extracted": [
{ "name": "appointment_date", "description": "The date the caller chose" }
],
"webhooks": [
{ "id": "6f1c2b1e-8d0a-4c55-9a51-2f4a7e3b9c10", "name": "CRM", "url": "https://crm.example.com/voicy/call-ended", "has_secret": true, "has_headers": false }
],
"created_at": "2026-06-06T10:00:00Z",
"updated_at": "2026-06-06T10:00:00Z"
}
```
```json 400 Bad Request theme={null}
{
"status": "error",
"message": "Invalid voice \"Bob\". Valid voices: Sapir, Steve, Sivan, Saar, Ori, Yael, Guy, Gefen, Gal, Gabriel"
}
```
# Get Agent
Source: https://docs.voicy.co/api-reference/agent-get
GET /v1/agent-get/{agent_id}
Retrieve a single agent and its variable contract
Returns an [Agent](/api-reference/models/agent) object, including its
`variables_provided` and `variables_extracted`. Works for both prompt-based and
flow agents; an agent's internal flow configuration is never returned.
## Path Parameters
The agent's unique identifier (UUID).
## Response
An [Agent](/api-reference/models/agent) object.
```bash cURL theme={null}
curl https://api.voicy.co/functions/v1/agent-get/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer voicy_sk_live_xxx"
```
```javascript JavaScript theme={null}
const id = '550e8400-e29b-41d4-a716-446655440000';
const agent = await (await fetch(`https://api.voicy.co/functions/v1/agent-get/${id}`, {
headers: { 'Authorization': 'Bearer voicy_sk_live_xxx' },
})).json();
```
```python Python theme={null}
import requests
agent_id = '550e8400-e29b-41d4-a716-446655440000'
agent = requests.get(
f'https://api.voicy.co/functions/v1/agent-get/{agent_id}',
headers={'Authorization': 'Bearer voicy_sk_live_xxx'},
).json()
```
```json 200 OK theme={null}
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Appointment Booker",
"language": "he",
"voice": "Keren",
"agent_type": "singlePrompt",
"active": true,
"prompt": "You are a friendly receptionist that books appointments.",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [
{ "name": "customer_name", "description": "The caller's name" }
],
"variables_extracted": [
{ "name": "appointment_date", "description": "The date the caller chose" }
],
"webhooks": [
{ "id": "6f1c2b1e-8d0a-4c55-9a51-2f4a7e3b9c10", "name": "CRM", "url": "https://crm.example.com/voicy/call-ended", "has_secret": true, "has_headers": false }
],
"created_at": "2026-06-06T10:00:00Z",
"updated_at": "2026-06-06T10:00:00Z"
}
```
```json 404 Not Found theme={null}
{
"error": "Agent not found"
}
```
# List Agents
Source: https://docs.voicy.co/api-reference/agent-list
GET /v1/agent-list
List the agents in your account
Returns the agents in your account as an array of [Agent](/api-reference/models/agent)
objects. Identity fields only — call [Get Agent](/api-reference/agent-get) for an
agent's variable contract. Both prompt-based and flow agents are included.
## Response
An array of [Agent](/api-reference/models/agent) objects (without `variables_provided` / `variables_extracted`; `webhooks` is included).
```bash cURL theme={null}
curl https://api.voicy.co/functions/v1/agent-list \
-H "Authorization: Bearer voicy_sk_live_xxx"
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.voicy.co/functions/v1/agent-list', {
headers: { 'Authorization': 'Bearer voicy_sk_live_xxx' },
});
const agents = await response.json();
```
```python Python theme={null}
import requests
agents = requests.get(
'https://api.voicy.co/functions/v1/agent-list',
headers={'Authorization': 'Bearer voicy_sk_live_xxx'},
).json()
```
```json 200 OK theme={null}
[
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Appointment Booker",
"language": "he",
"voice": "Keren",
"agent_type": "singlePrompt",
"active": true,
"prompt": "You are a friendly receptionist that books appointments.",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [],
"variables_extracted": [],
"created_at": "2026-06-06T10:00:00Z",
"updated_at": "2026-06-06T10:00:00Z"
}
]
```
# Update Agent
Source: https://docs.voicy.co/api-reference/agent-modify
PATCH /v1/agent-modify/{agent_id}
Update a prompt-based voice agent
Updates a prompt-based agent and deploys the change immediately. Only the fields
you include in the body are changed.
Flow agents cannot be modified via the API — `PATCH` on a `conversationalFlow`
agent returns `422`. Edit flow agents in the [dashboard](https://app.voicy.co).
## Path Parameters
The agent's unique identifier (UUID).
## Body Parameters
All optional — include only what you want to change.
Voice display name (`Sapir`, `Steve`, `Sivan`, `Saar`, `Ori`, `Yael`, `Guy`, `Gefen`, `Gal`, `Gabriel`).
Replaces the provided-variable set. Same item shape as [Create Agent](/api-reference/agent-create).
Replaces the extracted-variable set. Same item shape as [Create Agent](/api-reference/agent-create).
Replaces the agent's webhook list (up to 5). A webhook you leave out is
deleted; `[]` removes them all. Omit the field to leave webhooks unchanged.
Same item shape as [Create Agent](/api-reference/agent-create), plus:
The `id` from the Agent response, to update that webhook in place. Without
an `id`, or with an `id` the agent no longer has, an existing webhook with
the same `url` is updated; otherwise a new one is created.
Omit or send `null` to keep the current secret. Send `""` to remove it.
Omit or send `null` to keep the current headers. Send `{}` to remove them.
When set, replaces all headers.
Secrets and header values are never returned, so sending back the `webhooks`
array from a GET response leaves every webhook unchanged.
## Response
The updated [Agent](/api-reference/models/agent) object.
```bash cURL theme={null}
curl -X PATCH https://api.voicy.co/functions/v1/agent-modify/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "prompt": "You are a friendly receptionist. Always confirm the date twice.", "voice": "Dafna" }'
```
```javascript JavaScript theme={null}
const id = '550e8400-e29b-41d4-a716-446655440000';
const agent = await (await fetch(`https://api.voicy.co/functions/v1/agent-modify/${id}`, {
method: 'PATCH',
headers: {
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt: 'Always confirm the date twice.', voice: 'Dafna' }),
})).json();
```
```python Python theme={null}
import requests
agent_id = '550e8400-e29b-41d4-a716-446655440000'
agent = requests.patch(
f'https://api.voicy.co/functions/v1/agent-modify/{agent_id}',
headers={'Authorization': 'Bearer voicy_sk_live_xxx'},
json={'prompt': 'Always confirm the date twice.', 'voice': 'Dafna'},
).json()
```
```json 200 OK theme={null}
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Appointment Booker",
"language": "he",
"voice": "Dafna",
"agent_type": "singlePrompt",
"active": true,
"prompt": "You are a friendly receptionist. Always confirm the date twice.",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [],
"variables_extracted": [],
"webhooks": [],
"created_at": "2026-06-06T10:00:00Z",
"updated_at": "2026-06-06T11:30:00Z"
}
```
```json 422 Unprocessable Entity theme={null}
{
"status": "error",
"message": "Only singlePrompt agents can be modified via the API. Edit conversationalFlow agents in the dashboard."
}
```
# Call Dial
Source: https://docs.voicy.co/api-reference/call-dial
POST /v1/call-dial
Initiate an outbound AI-powered phone call
Initiate an outbound phone call. The call will be handled by the AI agent assigned to the specified phone number.
The transport is chosen from the caller ID itself — a Twilio number dials via Twilio, a SIP DID dials out through its trunk. You do not need to say which; just pass the number as `from_number`.
## Request Body
Your phone number in E.164 format (e.g., `+15551234567`). Must be assigned to your account. Either a Twilio number or a SIP DID — the call is routed accordingly.
The destination phone number in E.164 format (e.g., `+15559876543`).
Optional UUID of an agent to use instead of the default agent assigned to the phone number.
Optional key-value pairs that will be injected into the agent's system prompt. Useful for personalizing calls.
```json theme={null}
{
"customer_name": "John",
"appointment_date": "January 15th"
}
```
## Response
Unique identifier for the call (UUID format).
Current status of the call. Will be `initiated` immediately after creation. Poll `/v1/call-get/{call_id}` to track status changes:
* `initiated` → `ringing` → `ongoing` → `ended` (successful call)
* `initiated` → `ringing` → `busy` (recipient busy)
* `initiated` → `ringing` → `no_answer` (no pickup)
* `initiated` → `error` (technical failure)
`phone_call` when the caller ID is a Twilio number, `sip_call` when it is a SIP DID and the call went out over its trunk.
ID of the agent handling the call.
The caller ID phone number.
The destination phone number.
Always `outgoing` for calls created via API.
```bash cURL theme={null}
curl -X POST https://api.voicy.co/functions/v1/call-dial \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from_number": "+15551234567",
"to_number": "+15559876543",
"vars_provided": {
"customer_name": "John"
}
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.voicy.co/functions/v1/call-dial', {
method: 'POST',
headers: {
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
from_number: '+15551234567',
to_number: '+15559876543',
vars_provided: {
customer_name: 'John',
},
}),
});
const call = await response.json();
```
```python Python theme={null}
import requests
response = requests.post(
'https://api.voicy.co/functions/v1/call-dial',
headers={
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
json={
'from_number': '+15551234567',
'to_number': '+15559876543',
'vars_provided': {
'customer_name': 'John',
},
},
)
call = response.json()
```
```json 201 Created theme={null}
{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_status": "initiated",
"call_type": "phone_call",
"agent_id": "550e8400-e29b-41d4-a716-446655440001",
"from_number": "+15551234567",
"to_number": "+15559876543",
"direction": "outgoing"
}
```
```json 400 Bad Request theme={null}
{
"error": "Invalid phone number format. Use E.164 (e.g. +11234567890)"
}
```
```json 403 Forbidden theme={null}
{
"error": "Phone number not assigned to your account"
}
```
# Call Get
Source: https://docs.voicy.co/api-reference/call-get
GET /v1/call-get/{call_id}
Retrieve details for a specific call
Retrieve complete details for a call, including transcript, recording URL, and summary.
## Path Parameters
The unique identifier of the call (UUID format).
## Response
Returns a [Call](/api-reference/models/call) object with the following fields:
Unique call identifier (UUID)
`phone_call` or `web_call`
Current call status (see [Call](/api-reference/models/call) for values)
Why the call never connected — `no_answer`, `busy`, `declined`, `not_in_service`, `carrier_rejected` or `failed`; `null` once connected (see [Call](/api-reference/models/call))
Agent that handled the call.
Agent name
Agent version used for this call
Caller's phone number
Called phone number
`incoming` or `outgoing`
Unix timestamp (ms) when the call started
Call duration in milliseconds
Structured transcript. Each entry has:
`agent`, `caller`, or `system`. `agent` and `caller` are spoken turns. `system` entries mark flow-agent automation events (e.g. a function-node call or a flow-state change). Consumers that only care about spoken dialogue can safely filter for `role in [agent, caller]`.
What was said (for `system` entries, a short event label).
Milliseconds from call start
Detected gender of the caller (`male` or `female`)
Signed URL to the call recording (expires in 1 hour)
AI-generated call summary
Variables provided when the call was created
Variables extracted after the call by AI processing
Variables collected during the call in real-time by the agent. Each value is
either a **string** (single-value var, populated by Collection or Set Var
nodes) or an **array of objects** (list var, populated by a List node — each
array entry is one item bundled from the var names declared in the node's
item schema). Consumers should check the runtime type per key.
Variable names beginning with `_` (e.g. `_temp`) are **call-local**: they are
usable during the call but stripped before persistence and never returned in
this field.
Whether this call was made by a test user contact
```bash cURL theme={null}
curl https://api.voicy.co/functions/v1/call-get/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer voicy_sk_live_xxx"
```
```javascript JavaScript theme={null}
const callId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://api.voicy.co/functions/v1/call-get/${callId}`, {
headers: {
'Authorization': 'Bearer voicy_sk_live_xxx',
},
});
const call = await response.json();
// Access collected variables (gathered during the call via Collection nodes)
const varsCollected = call.vars_collected; // e.g., { "preferred_time": "morning" }
// Access extracted variables (generated after the call by AI processing)
const varsExtracted = call.vars_extracted; // e.g., { "appointment_date": "2026-01-15" }
```
```python Python theme={null}
import requests
call_id = '550e8400-e29b-41d4-a716-446655440000'
response = requests.get(
f'https://api.voicy.co/functions/v1/call-get/{call_id}',
headers={
'Authorization': 'Bearer voicy_sk_live_xxx',
},
)
call = response.json()
# Access collected variables (gathered during the call via Collection nodes)
vars_collected = call.get('vars_collected', {}) # e.g., {"preferred_time": "morning"}
# Access extracted variables (generated after the call by AI processing)
vars_extracted = call.get('vars_extracted', {}) # e.g., {"appointment_date": "2026-01-15"}
```
```json 200 OK theme={null}
{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_type": "phone_call",
"call_status": "user_hangup",
"agent": {
"name": "Customer Support Agent",
"version": 2
},
"from_number": "+15551234567",
"to_number": "+15559876543",
"direction": "outgoing",
"start_timestamp": 1704067200000,
"duration_ms": 120000,
"transcript": [
{
"role": "agent",
"content": "Hello, how can I help you?",
"timestamp_ms": 0
},
{
"role": "caller",
"content": "Can you look up my order?",
"timestamp_ms": 2500
},
{
"role": "agent",
"content": "Your order has shipped — tracking 1Z999AA10123456784.",
"timestamp_ms": 3500
}
],
"detected_gender": "male",
"recording_url": "https://storage.voicy.co/recordings/...",
"summary": "Customer inquiry about order status.",
"vars_provided": {
"customer_name": "John"
},
"vars_extracted": {
"appointment_date": "2026-01-15"
},
"vars_collected": {
"preferred_time": "morning",
"products": [
{ "code": 1042, "qty": 3, "product_name": "חמאה", "price": 12.5 },
{ "code": 8810, "qty": 2, "product_name": "חלב", "price": 6.9 }
]
}
}
```
```json 404 Not Found theme={null}
{
"error": "Call not found"
}
```
# Call List
Source: https://docs.voicy.co/api-reference/call-list
POST /v1/call-list
List calls with optional filtering and pagination
Retrieve a list of calls with optional filtering by status, agent, direction, and time range.
This endpoint uses POST to support complex filter criteria in the request body.
## Request Body
Optional filters to apply.
Filter by agent IDs (array of UUIDs).
Filter by status. See [Call](/api-reference/models/call) for all status values.
Filter by direction: `incoming`, `outgoing`.
Filter by time range (Unix milliseconds).
```json theme={null}
{
"lower_threshold": 1704067200000,
"upper_threshold": 1704153600000
}
```
Include test calls in results. Test calls are excluded by default.
Include each call's summary in the response. Off by default — a summary is
a paragraph per call, so ask for it on narrow, caller-scoped lists rather
than on a browse.
Sort order: `ascending` or `descending` (by start time).
Maximum number of results (1-1000).
Cursor for pagination. Use the last `call_id` from the previous page.
## Response
Returns an object with `calls` (array of lightweight call objects) and `total_count`. These include core metadata (status, agent, timing) but **omit** `transcript`, `summary`, `recording_url`, `detected_gender`, `vars_provided`, `vars_extracted`, and `vars_collected` for performance. Use [Get Call](/api-reference/call-get) to retrieve the full call object.
Array of call objects with the following fields:
Unique call identifier (UUID)
`phone_call` or `web_call`
Current call status (see [Call](/api-reference/models/call) for values)
The call's summary. Present **only** when `filter_criteria.include_summary` was set on the request; absent otherwise
Why the call never connected — `no_answer`, `busy`, `declined`, `not_in_service`, `carrier_rejected` or `failed`; `null` once connected (see [Call](/api-reference/models/call))
Agent that handled the call.
Agent name
Agent version used for this call
Caller's phone number
Called phone number
`incoming` or `outgoing`
Unix timestamp (ms) when the call started
Call duration in milliseconds
Whether this call was made by a test user contact
Total number of calls matching the filter criteria (for pagination).
```bash cURL theme={null}
curl -X POST https://api.voicy.co/functions/v1/call-list \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"filter_criteria": {
"call_status": ["user_hangup", "agent_hangup"]
},
"sort_order": "descending",
"limit": 10
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.voicy.co/functions/v1/call-list', {
method: 'POST',
headers: {
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
filter_criteria: {
call_status: ['user_hangup', 'agent_hangup'],
},
sort_order: 'descending',
limit: 10,
}),
});
const calls = await response.json();
```
```python Python theme={null}
import requests
response = requests.post(
'https://api.voicy.co/functions/v1/call-list',
headers={
'Authorization': 'Bearer voicy_sk_live_xxx',
'Content-Type': 'application/json',
},
json={
'filter_criteria': {
'call_status': ['user_hangup', 'agent_hangup'],
},
'sort_order': 'descending',
'limit': 10,
},
)
calls = response.json()
```
```json 200 OK theme={null}
{
"calls": [
{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_type": "phone_call",
"call_status": "user_hangup",
"agent": {
"name": "Customer Support Agent",
"version": 2
},
"from_number": "+15551234567",
"to_number": "+15559876543",
"direction": "outgoing",
"start_timestamp": 1704067200000,
"duration_ms": 120000
},
{
"call_id": "550e8400-e29b-41d4-a716-446655440002",
"call_type": "phone_call",
"call_status": "agent_hangup",
"agent": {
"name": "Customer Support Agent",
"version": 1
},
"from_number": "+15559876543",
"to_number": "+15551234567",
"direction": "incoming",
"start_timestamp": 1704063600000,
"duration_ms": 180000
}
],
"total_count": 42
}
```
# Agent
Source: https://docs.voicy.co/api-reference/models/agent
The Agent object represents a voice agent in the Voicy platform
An Agent defines how a voice agent behaves. The API exposes an agent's
**interface** — its identity, voice, and variable contract — but not its
internal configuration.
There are two agent types. **Prompt-based** agents (`singlePrompt`) are fully
manageable via the API. **Flow** agents (`conversationalFlow`) are built in the
[dashboard](https://app.voicy.co); they appear in [List](/api-reference/agent-list)
and [Get](/api-reference/agent-get) but cannot be created or modified through
the API, and their internal flow configuration is never returned.
## Properties
Unique identifier (UUID). Pass this as `override_agent_id` on [Dial](/api-reference/call-dial).
Agent display name.
Language code, e.g. `he` or `en`.
Voice display name. One of `Sapir`, `Steve`, `Sivan`, `Saar`, `Ori`, `Yael`, `Guy`, `Gefen`, `Gal`, `Gabriel` for a NEW assignment. An agent created before a voice was retired keeps reporting that voice here — the read path is not narrowed.
`singlePrompt` (prompt-based, API-manageable) or `conversationalFlow` (dashboard-only, read-only here).
Whether the agent is enabled.
System prompt. Present for prompt-based agents only.
Message spoken first when the agent answers. Present for prompt-based agents only.
Variables the agent accepts at call time — supply them via `vars_provided` on Dial.
Variable name.
Human-readable description.
Value type — one of `text`, `number`, `boolean`, `phone`, `email`, `license_plate`, `enum`, `list`.
Set in the dashboard; `PATCH /agents/{id}` preserves it rather than resetting it.
For `enum` and `list` variables, the name of the type in the agent's Types section
supplying the allowed values (enum) or item fields (list). `null` otherwise, and
preserved across updates.
Variables the agent extracts from the conversation after the call.
Variable name.
Human-readable description.
Webhooks fired when a call with this agent ends. Returned for every agent
type, but only prompt-based agents can change them via the API. Secrets and
header values are write-only and never returned.
Stable identifier — send it back on update. Empty for webhooks created
before ids existed; those are matched by `url` and get an id on the next save.
Webhook label.
Endpoint that receives the POST.
Whether a signing secret is set.
Whether custom headers are set.
ISO 8601 timestamp.
ISO 8601 timestamp.
## Example
```json theme={null}
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Appointment Booker",
"language": "he",
"voice": "Keren",
"agent_type": "singlePrompt",
"active": true,
"prompt": "You are a friendly receptionist that books appointments.",
"welcome_message": "שלום, איך אפשר לעזור?",
"variables_provided": [
{ "name": "customer_name", "description": "The caller's name", "type": "text", "type_source": null },
{ "name": "garage", "description": "Which garage", "type": "enum", "type_source": "garages" }
],
"variables_extracted": [
{ "name": "appointment_date", "description": "The date the caller chose" }
],
"webhooks": [
{ "id": "6f1c2b1e-8d0a-4c55-9a51-2f4a7e3b9c10", "name": "CRM", "url": "https://crm.example.com/voicy/call-ended", "has_secret": true, "has_headers": false }
],
"created_at": "2026-06-06T10:00:00Z",
"updated_at": "2026-06-06T10:00:00Z"
}
```
# Call
Source: https://docs.voicy.co/api-reference/models/call
The Call object represents a voice call in the Voicy platform
The full Call object contains all information about a voice call, including status, transcript, recording, and variables. Returned by [Get Call](/api-reference/call-get).
[List Calls](/api-reference/call-list) returns a **lightweight** version of this object that omits `transcript`, `summary`, `recording_url`, `detected_gender`, `vars_provided`, `vars_extracted`, and `vars_collected` for performance. Use Get Call to retrieve the full object.
## Properties
Unique identifier for the call (UUID).
Type of call: `phone_call` or `web_call`.
Current status of the call.
**In Progress:**
| Status | Description |
| - | - |
| `initiated` | Call placed, waiting for recipient |
| `ringing` | Recipient's phone is ringing |
| `ongoing` | Call connected and active |
**Completed:**
| Status | Description |
| - | - |
| `user_hangup` | Caller hung up |
| `agent_hangup` | Agent ended the call |
| `call_transfer` | The agent handed the call to a person |
| `inactivity` | Timeout due to inactivity |
| `idle_disconnect` | The caller went quiet and the idle watchdog ended it |
| `max_duration` | Ended by the server at the maximum call-length limit |
| `error_inactivity` | Error during inactivity handling |
**Refused before connecting:**
| Status | Description |
| - | - |
| `blocked_caller` | The call was refused: the caller is on the blocklist, or withheld their number at an agent that rejects anonymous calls. Always `duration_ms: 0` — no conversation took place |
**Failed:**
| Status | Description |
| - | - |
| `busy` | Line was busy |
| `no_answer` | No answer after timeout |
| `error` | Technical failure |
| `canceled` | Call canceled before connecting |
Why the call never connected, in actionable terms. `null` once the call
connected, and while it is still in flight.
| Category | Meaning | What to do |
| - | - | - |
| `no_answer` | Rang out, or no final response before our dial timeout | Retry later |
| `busy` | The line was engaged | Retry later |
| `declined` | The recipient actively refused (e.g. Do Not Disturb) | Retry rarely |
| `not_in_service` | No such subscriber — the number is dead | Stop dialling it |
| `carrier_rejected` | The carrier would not place the call; the number may be fine | Usually trunk configuration or carrier congestion |
| `failed` | Any other failure | — |
Finer-grained than `call_status`, which cannot separate a dead number from a
misconfigured trunk. For calls placed before 2026-08-18 the two can disagree:
`call_status` was recorded before the outcome was classified, while this is
derived from the recorded failure — in which case this field is the accurate one.
Agent that handled the call. `null` if no agent assigned.
Agent name
Agent version used for this call
The caller's phone number in E.164 format.
The called phone number in E.164 format.
Call direction relative to the agent:
* `incoming` - Call initiated TO the agent (inbound calls, web calls)
* `outgoing` - Call initiated BY the agent (outbound calls via API)
Unix timestamp (milliseconds) when the call started.
Call duration in milliseconds. `null` if call hasn't ended.
Structured transcript with speaker roles and timestamps. `null` if no transcript.
`agent`, `caller`, or `system` (flow-agent automation events)
What was said
Milliseconds from call start
Detected gender of the caller: `male` or `female`. `null` if not detected.
Signed URL to the call recording. Expires in 1 hour. `null` if no recording.
AI-generated summary of the call. `null` if not yet generated.
Variables provided when the call was created. Keys are variable names, values are strings.
Variables extracted after the call by AI processing. Keys are variable names, values are strings.
Variables collected during the call in real-time by the agent (e.g., via Collection nodes). Keys are variable names, values are strings.
Variable names beginning with `_` (e.g. `_temp`) are **call-local**: they are usable during the call but stripped before persistence and never returned in this field.
## Example
```json theme={null}
{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_type": "phone_call",
"call_status": "user_hangup",
"failure_category": null,
"agent": {
"name": "Customer Support Agent",
"version": 2
},
"from_number": "+15551234567",
"to_number": "+15559876543",
"direction": "outgoing",
"start_timestamp": 1704067200000,
"duration_ms": 120000,
"transcript": [
{
"role": "agent",
"content": "Hello, how can I help you?",
"timestamp_ms": 0
},
{
"role": "caller",
"content": "I need help with my order",
"timestamp_ms": 2500
}
],
"detected_gender": "male",
"recording_url": "https://storage.voicy.co/recordings/...",
"summary": "Customer inquiry about order status.",
"vars_provided": {
"customer_name": "John"
},
"vars_extracted": {
"appointment_date": "2026-01-15"
},
"vars_collected": {
"preferred_time": "morning"
}
}
```
# API Overview
Source: https://docs.voicy.co/api-reference/overview
Overview of the Voicy API endpoints
# API Overview
The Voicy API provides RESTful endpoints for managing calls.
## Base URL
```
https://api.voicy.co/functions/v1
```
Each endpoint is an individual edge function at `{base_url}/{function-name}`.
## Endpoints
### Calls
| Method | Endpoint | Description |
| - | - | - |
| POST | `/call-dial` | Initiate an outbound call |
| GET | `/call-get/{call_id}` | Get call details with transcript, recording, and variables |
| POST | `/call-list` | List calls with filters, sorting, and pagination |
### Agents
| Method | Endpoint | Description |
| - | - | - |
| GET | `/agent-list` | List the agents in your account |
| GET | `/agent-get/{agent_id}` | Get an agent and its variable contract |
| POST | `/agent-create` | Create a prompt-based agent (deployed immediately) |
| PATCH | `/agent-modify/{agent_id}` | Update a prompt-based agent |
## Authentication
Include your API key in the Authorization header:
```
Authorization: Bearer voicy_sk_your_api_key
```
## Request Format
* Use `Content-Type: application/json` for request bodies
* Include `Authorization: Bearer ` header
* Send JSON-encoded request bodies
## Response Format
Successful responses return the requested data directly as JSON. Error responses:
```json theme={null}
{
"status": "error",
"message": "Description of what went wrong"
}
```
## HTTP Status Codes
| Code | Meaning |
| - | - |
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 422 | Unprocessable Entity |
| 500 | Internal Server Error |
## Pagination
`/call-list` supports cursor-based pagination:
```json theme={null}
{
"limit": 50,
"pagination_key": "last_call_id_from_previous_page"
}
```
The response includes `total_count` for displaying total results.
## Filtering
Filter calls by various criteria:
```json theme={null}
{
"filter_criteria": {
"agent_id": ["uuid1", "uuid2"],
"call_status": ["ended"],
"direction": ["incoming", "outgoing"],
"start_timestamp": {
"lower_threshold": 1704067200000,
"upper_threshold": 1704153600000
}
}
}
```
# Authentication
Source: https://docs.voicy.co/authentication
Learn how to authenticate with the Voicy API
# Authentication
The Voicy API uses API keys for authentication. All requests must include your API key in the `Authorization` header.
## API Key Format
Voicy API keys follow this format:
```
voicy_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
* `voicy_sk_live_` - Prefix identifying this as a Voicy secret key
* `xxxx...` - 28 characters of cryptographically random data
## Using Your API Key
Include the API key as a Bearer token in the `Authorization` header:
```bash theme={null}
curl https://api.voicy.co/functions/v1/call-list \
-H "Authorization: Bearer voicy_sk_live_your_key_here" \
-H "Content-Type: application/json"
```
**Keep your API keys secure!** Never commit them to source control or expose them in client-side code.
## Creating API Keys
Go to [app.voicy.co](https://app.voicy.co) and sign in with your account.
Click on **Account** in the sidebar (for account admins) or go to **Accounts** and select your account (for global admins).
Click **Create Key** and give it a descriptive name (e.g., "Production", "Development").
The full API key will only be shown once. Copy it immediately and store it securely.
API keys are scoped to your account. All calls made with a key will only access data within that account.
## Key Security
### Storage Best Practices
| Do | Don't |
| - | - |
| Store in environment variables | Commit to git repositories |
| Use secrets management (AWS Secrets Manager, etc.) | Hardcode in application code |
| Rotate keys periodically | Share keys between environments |
| Use separate keys for dev/staging/prod | Log full API keys |
### How Keys Are Stored
Your API keys are stored securely using industry-standard practices:
* **Never stored in plaintext** - We only store a SHA-256 hash
* **Prefix stored separately** - Enables fast O(1) lookup
* **Same pattern as Stripe, GitHub, OpenAI**
This means even Voicy engineers cannot retrieve your API key after creation.
## Revoking Keys
If a key is compromised:
1. Go to **Account Settings** in the dashboard
2. Find the compromised key in the list
3. Click the menu (⋮) and select **Revoke**
4. The key is immediately invalidated
Revoked keys return `401 Unauthorized` on all subsequent requests.
## Rate Limits
API keys are subject to rate limits based on your plan:
| Plan | Requests/minute | Concurrent calls |
| - | - | - |
| Standard | 60 | 10 |
| Enterprise | 600 | 100 |
See [Rate Limits](/rate-limits) for more details.
# Changelog
Source: https://docs.voicy.co/changelog
API updates and changes
# Changelog
All notable changes to the Voicy API will be documented here.
## September 2026
### Webhooks on the Agent API
Agent webhooks can now be managed through the API, so an agent created
programmatically no longer needs a trip to the dashboard:
* `POST /v1/agent-create` and `PATCH /v1/agent-modify/{agent_id}` accept a
`webhooks` array (name, URL, optional signing secret and custom headers).
On update it replaces the agent's list; omit it to leave webhooks unchanged.
* Every Agent response now includes `webhooks`. Secrets and header values are
write-only and never returned — only `has_secret` / `has_headers`.
This is additive: requests that don't send `webhooks` behave exactly as before.
As with every other field, only prompt-based agents can be modified via the API.
See [Webhooks](/webhooks#managing-webhooks-via-the-api).
## June 2026
### Agent API (prompt-based agents)
A minimal Agent API is now available:
* `GET /v1/agent-list` — list the agents in your account
* `GET /v1/agent-get/{agent_id}` — get an agent and its variable contract
* `POST /v1/agent-create` — create a prompt-based agent (deployed immediately)
* `PATCH /v1/agent-modify/{agent_id}` — update a prompt-based agent
The API manages **prompt-based** agents. Flow agents (built in the dashboard)
are listable and gettable but cannot be created or modified via the API, and
their internal configuration is never returned. An agent's `agent_id` is what
you pass as `override_agent_id` on Dial.
### `call-extract` removed from the public API
Retrying summary/variable extraction is now a dashboard-only operation. The
`POST /v1/call-extract/{call_id}` endpoint has been removed and returns `404`.
**Why:** A retry re-runs the full post-call orchestration — it re-fires
notification emails, webhooks, and campaign advancement as side effects — which
is not safe as a self-serve API operation.
**Action required:** if you were calling this endpoint with an API key, trigger
re-extraction from the dashboard's call view instead, or contact support.
### `count-calls` removed from the documentation
The `POST /v1/count-calls` endpoint was documented but never deployed (it
returned `404`). It has been removed from the reference. Use the `total_count`
field already returned by `POST /v1/call-list` — it reflects the full match
count for your `filter_criteria`, independent of `limit`/pagination.
### Transcript shape clarified
The public `transcript[]` returned by `GET /v1/call-get/{call_id}` is documented
as exactly `{ role, content, timestamp_ms }`. The `role` enum is `agent`,
`caller`, or `system`. Internal per-turn fields (latency breakdowns, caller
gender) and flow-agent `function_invocation`/`function_result` payloads are not
part of the public API and are not returned to API-key callers. This is a
documentation correction — API-key responses already behaved this way; no
response payloads changed.
## May 2026
### `call-delete` removed from the public API
Call deletion has been moved to a dashboard-only operation. The
`DELETE /v1/call-delete/{call_id}` and `POST /v1/call-delete` endpoints
no longer exist on `api.voicy.co` and now return `404`.
**Why:** Deleting a call is now treated as a billing write-off — deleted
calls are excluded from the account's consumed-minutes balance — so the
action has been restricted to internal/global-admin use via the
dashboard.
**Action required:** if you were calling these endpoints with an API
key, contact support to delete calls on your behalf.
## April 2026
### Function-call visibility on transcripts
The `transcript` array returned by `GET /v1/call-get/{id}` now exposes the HTTP
function invocations that flow-agent function nodes make during a call.
**Additive changes — no field removals or behavior changes on existing fields:**
* **New role** `system` on `transcript[].role`. Previously `role` was always
`agent` or `caller`; the enum now also includes `system` for flow-agent
automation events. The vast majority of consumers (which iterate by `role ==
'agent' || role == 'caller'`) will see no change beyond a few extra entries
they silently ignore.
* **New optional field** `transcript[].function_invocation` on `system`
entries — carries the request method, URL, headers (sensitive values
redacted), and request body captured just before the HTTP call dispatches.
* **New optional field** `transcript[].function_result` on `system` entries —
carries the status code, response body, response headers, extracted
variables, duration, and any classified error (`timeout`, `non_2xx`,
`ssrf_blocked`, `bad_response_json`, `type_validation_failed`,
`invalid_url`, `network`).
Each function-node call produces exactly two `system` turns (an invocation
and a result), correlated by a shared `invocation_id`. URL basic-auth
credentials, known-sensitive query parameters (`api_key`, `token`,
`access_token`, etc.), and sensitive request/response headers
(`Authorization`, `Cookie`, `Set-Cookie`, and anything matching
`token|secret|password|api_key|bearer`) are redacted at capture time, so the
persisted transcript is safe to share.
**Action required:** if your code uses an exhaustive `switch` on the `role`
field (`case 'agent': ... case 'caller': ... default: throw`), add handling
for `'system'` or ignore it.
## January 2026
### v1.0 Release
Initial public release of the Voicy API.
#### New Features
* **Phone Call API**: Create, retrieve, list, update, and delete phone calls
* **API Key Authentication**: Secure API access with account-scoped keys
* **Dual Authentication**: Support for both API keys and JWT tokens
* **Dynamic Variables**: Inject custom data into agent prompts
* **Call Metadata**: Attach custom metadata to calls for CRM integration
* **Transcript Access**: Full transcript available via API (text and structured)
* **Recording URLs**: Signed URLs for call recordings
#### Endpoints
| Endpoint | Description |
| - | - |
| `POST /v1/call-dial` | Initiate outbound calls |
| `GET /v1/call-get/{id}` | Retrieve call details |
| `POST /v1/call-list` | List and filter calls |
| `DELETE /v1/call-delete/{id}` | Delete calls |
***
## Coming Soon
### Planned Features
* **Webhooks**: Real-time notifications for call events
* **Web Call API**: Browser-based voice calls via WebRTC
* **Agent API**: Manage agents programmatically
* **Phone Number API**: Manage phone number assignments
* **Analytics API**: Call statistics and usage metrics
### Deprecation Policy
When we deprecate API features:
1. Announcements made at least 6 months in advance
2. Deprecated features marked in documentation
3. Migration guides provided
4. Old versions remain functional during deprecation period
***
Subscribe to our [status page](https://status.voicy.co) for real-time updates on API availability.
# Error Handling
Source: https://docs.voicy.co/error-handling
How to handle API errors
# Error Handling
The Voicy API uses conventional HTTP status codes to indicate success or failure of requests.
## Error Response Format
All error responses follow this format:
```json theme={null}
{
"error": "Description of what went wrong"
}
```
## HTTP Status Codes
### Success Codes
| Code | Meaning | When Used |
| - | - | - |
| 200 | OK | Successful GET, POST, PATCH, DELETE requests |
| 201 | Created | Successful resource creation (call-dial) |
### Client Error Codes
| Code | Meaning | Common Causes |
| - | - | - |
| 400 | Bad Request | Invalid JSON, missing required fields, invalid phone number format |
| 401 | Unauthorized | Invalid API key, expired token, missing Authorization header |
| 403 | Forbidden | API key doesn't have access to the requested resource |
| 404 | Not Found | Call ID doesn't exist or belongs to another account |
| 422 | Unprocessable Entity | Request is valid but cannot be processed |
| 429 | Too Many Requests | Rate limit exceeded (reserved; see [Rate Limits](/rate-limits)) |
### Server Error Codes
| Code | Meaning | What To Do |
| - | - | - |
| 500 | Internal Server Error | Retry with exponential backoff, contact support if persistent |
| 502 | Bad Gateway | Temporary issue, retry after a few seconds |
| 503 | Service Unavailable | Service is temporarily down, retry later |
## Common Errors
### Authentication Errors
```json theme={null}
// Missing Authorization header
{
"error": "Missing authorization"
}
// Invalid API key
{
"error": "Invalid API key"
}
// Expired JWT token (dashboard)
{
"error": "Invalid or expired token"
}
```
### Validation Errors
```json theme={null}
// Invalid phone number format
{
"error": "Invalid phone number format. Use E.164 (e.g. +11234567890)"
}
// Missing required field
{
"error": "to_number is required"
}
// Phone number not in your account
{
"error": "Phone number not found or not assigned to your account"
}
```
### Access Errors
```json theme={null}
// Trying to access another account's data
{
"error": "Access denied"
}
// Resource doesn't exist
{
"error": "Call not found"
}
```
## Best Practices
### Retry Logic
Implement exponential backoff for transient errors:
```javascript theme={null}
async function callWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status >= 500 && attempt < maxRetries - 1) {
// Wait 2^attempt seconds before retrying
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
continue;
}
throw error;
}
}
}
```
### Error Logging
Log errors with context for debugging:
```javascript theme={null}
try {
const call = await createPhoneCall(fromNumber, toNumber);
} catch (error) {
console.error('Failed to create call', {
fromNumber,
toNumber,
status: error.status,
message: error.message,
});
throw error;
}
```
### User-Friendly Messages
Map API errors to user-friendly messages:
```javascript theme={null}
function getUserMessage(error) {
switch (error.message) {
case 'Invalid phone number format. Use E.164 (e.g. +11234567890)':
return 'Please enter a valid phone number with country code (e.g., +1 555 123 4567)';
case 'Phone number not found or not assigned to your account':
return 'This phone number is not available. Please contact support.';
default:
return 'Something went wrong. Please try again.';
}
}
```
# Single prompt vs. flow agent
Source: https://docs.voicy.co/guides/agent-types
Voicy has two agent types. A single prompt agent runs a whole call from one set of instructions. A flow agent follows a visual map of steps that you design.
## Single prompt agents
You write one prompt that covers the agent's persona, goals and rules, and the agent follows it for the whole call. You can add a fixed welcome message and let the agent end the call when the caller is done. For most receptionist, FAQ and simple lead-capture agents, this is all you need.
Single prompt agents are the only type you can create and edit through the public API.
## Flow agents
You build the conversation on a canvas out of **nodes** (steps) connected by **transitions** (the conditions for moving between steps). Each node has its own job. One might talk with the caller, another collects details, calls your system or transfers the call. A global prompt holds the persona and shared rules, and every node uses it.
Use a flow agent when the call has to follow a set process. Examples are verifying details before an action, branching on what your CRM returns, sending an [SMS](/guides/sms) mid-call or handing the caller to a specific person.
## Comparison
| | Single prompt | Flow |
| - | - | - |
| How you build it | One prompt plus settings | Visual editor with nodes and transitions |
| Best for | Open conversations, FAQs, simple intake | Multi-step processes with branching |
| Collecting details during the call | Through the prompt; details you define are extracted after the call | Collection nodes that validate and store each answer as it's given |
| Calling your systems mid-call | No | Yes ([Function Call node](/guides/functions)) |
| SMS, transfer, outbound dial, lists | Can end the call | Yes, as dedicated nodes |
| [Knowledge bases](/guides/knowledge-base#markdown-file) | Yes | Yes |
| [Webhooks](/webhooks) and email summaries | Yes | Yes |
| Create or edit through the API | Yes | No. Build in the dashboard; the API can read it |
| Time to first test call | Minutes | Longer, depending on the flow |
**How to choose.** Pick a single prompt agent when the call is an open conversation: answering questions, giving information, general support. Pick a flow agent when the call follows a fixed process: collecting and checking details, calling your systems, or routing the caller to the right place. If you start with a single prompt and later need a flow, your prompt is a good starting point for the flow's global prompt.
## Both types share
* The same voices, [pronunciation](/pronunciation-guide) overrides and speech-to-text vocabulary.
* Provided and extracted [variables](/guides/variables).
* A built-in call simulator for test calls from the browser.
* Versioning: you edit a draft, and phone calls only see your changes after you click **Deploy**.
Next: [Single prompt agents](/guides/single-prompt-agents) · [Flow agents](/guides/flow-agents) · [Writing a good prompt](/guides/writing-prompts)
# Build your first phone agent
Source: https://docs.voicy.co/guides/first-agent
Create an agent, give it a prompt and a voice, test it in your browser, then put it on a phone number and call it.
## Before you start
* A Voicy dashboard login at `app.voicy.co`. If you don't have one, see Users and access.
* A phone number on your account. Numbers are added to your account by Voicy. If **Phones** shows no numbers, contact your Voicy representative, or bring your own SIP trunk.
* Google Chrome with a microphone, for the browser test call.
Open **Agents** and click **Add Agent**. Pick an agent type and click **Continue**. **Single Prompt** is one system prompt for the whole call. **Flow** gives you a visual editor for conversations with several stages. The new agent opens in the editor.
In the prompt editor, describe who the agent is, what it should do on the call and what it must not do. You can use [variables](/guides/variables) in square brackets, for example `[company_name]`, and fill them in per call. If you add a welcome message, the agent says it word for word at the start of every call. Leave it empty and the agent writes its own opening line from the prompt.
Open the **Language & Voice** dialog and choose a voice. You can also adjust the speaking speed there if the voice supports it.
In the right-hand panel, open the **Test Call** tab and click **Test Call**. Talk to the agent and watch the live transcript. Browser tests always use your latest edits. See Test Call in the browser.
Click **Deploy**. You can add a short comment, such as "first version". Phone callers only ever hear a deployed version. See Draft and deploy an agent.
Open **Phones**. On your number's row, open the ⋮ menu, choose **Assign Agent**, pick your agent and click **Assign**. You can't assign an agent that has never been deployed. See Phone numbers.
Dial the number from any phone. When the call ends, open **Calls** to see the transcript, the recording and the summary. See Call history, transcripts and recordings.
Editing the agent after this doesn't change what callers hear. Your edits go into a draft, and phone calls pick them up only when you click **Deploy** again.
## Next steps
* Send call data to your own systems with [Webhooks](/webhooks).
* Place calls from your code with [Make outbound calls via API](/api-reference/call-dial).
# Flow agents
Source: https://docs.voicy.co/guides/flow-agents
A flow agent follows a conversation map you build in a visual editor: nodes for each step and transitions that decide where the call goes next.
## The flow editor
The editor has a canvas on the left and a call simulator on the right. Drag nodes to arrange them, zoom and pan, and connect a node's output to another node's input to create a transition. Click a node's text, a transition or a condition to edit it. Each edit dialog applies your changes when you click **Save** and discards them on **Cancel**. Saved changes are stored automatically.
A new flow agent in the editor. The **Start** node is connected to an **Announcement** node, and the toolbar above the canvas holds the voice, **Add Node**, **Global Prompt** and **Deploy**.
During a test call in the simulator, the active node is highlighted live, so you can see exactly which step the agent is in. Press **Cmd/Ctrl+F** to search across every text field in the flow: prompts, questions, conditions, [SMS](/guides/sms) text, function settings and more.
## Nodes and transitions
Every flow begins at a **Start** node and ends at an **End** node, a **Transfer** or another way out. In between you add nodes that talk to the caller, collect details, make decisions behind the scenes or take actions. See the [Flow nodes reference](/guides/flow-nodes) for all 14 types.
A transition connects two nodes and has a **condition**. On nodes that talk to the caller, the condition is a plain-language description, for example "The caller confirmed the details." After each caller turn, Voicy checks which condition matches what the caller said and moves the call there. On a Logic Split node, conditions are exact rules on [variables](/guides/variables), for example .
A transition can also set a variable when it fires. In the transition's edit dialog, turn on **When condition fires, also set a variable**. A `{ }` marker then shows on that transition.
## Global prompt
The global prompt is added to the start of every node's prompt. Use it for the persona, language style, tone and rules that apply throughout the call. Each node's own prompt only adds the task for that step. See [Writing a good prompt](/guides/writing-prompts).
## Global nodes
A **Global** node can be reached from anywhere in the flow, not just from the node before it. Use one for situations that can come up at any point, such as "the caller asks for a human" or "the caller wants to end the call." If a global condition and a normal transition both match, the global wins.
By default a global node fires once per call. Turn on **Multi use** in its right-click menu for requests that repeat, such as questions about an order in progress. To bring the caller back to where they were after the detour, end the global branch with a **Return** node.
## Agent settings
Flow agents share the [single prompt agent](/guides/single-prompt-agents)'s settings: voice, [knowledge bases](/guides/knowledge-base#markdown-file), provided and extracted variables, email summaries, [webhooks](/webhooks), operating hours, [pronunciation](/pronunciation-guide) and speech-to-text vocabulary. They also have:
* **[Functions](/guides/functions)**: define an external API once, then call it from any Function Call node.
* **Types**: reusable types you create with **Add Type**. An **Enum** is a fixed set of options for a variable, such as inquiry types. A **List** defines the fields of each item for a [List node](/guides/flow-nodes#list).
* **Record transfers** (General): keeps recording and transcribing after the call is handed to a person. Off by default.
## Deploy
You always edit a draft. The simulator uses the draft, and phone calls use the last deployed version. Click **Deploy** to freeze the current draft and make it live. Deploy is blocked while a transition points nowhere, so connect every exit first.
Flow agents are built only in the dashboard. The public API can list and read them but can't create or change them.
# Flow nodes reference
Source: https://docs.voicy.co/guides/flow-nodes
There are 14 node types, and each flow uses only the ones it needs. This page covers what each one does, when to use it and what you configure.
## Start and end
### Start
Where every call begins. Each flow gets exactly one automatically. There's nothing to configure; just connect it to your first real node.
### End
Reaching this node hangs up the call. It has no settings and no exits.
**Every path should reach an End node.** If a call reaches a node with no way forward, the agent keeps talking until the caller hangs up, the agent's max call length is reached, or the caller stays silent long enough for the agent to say goodbye and disconnect.
## Talking to the caller
### Conversation
The main node, where the agent talks freely. It listens, answers according to your instructions and moves on when one of its transition conditions matches what the caller said.
**You configure:** the node prompt, the transitions, and **Speaks first**. With **Agent**, the agent speaks as soon as the node starts and isn't interrupted during that first line. With **Caller**, the agent waits for the caller to speak.
### Announcement
Says a fixed text word for word, then moves on automatically. The caller can't interrupt it. Use it for greetings, legal notices or anything that must be said exactly.
**You configure:** the text. It can include details already collected, like `[first_name]`.
### Collection
Asks the caller for specific details and stores the answers: a name, phone, email, order number and so on. It keeps asking until every required detail is collected, briefly confirms each one, then moves on automatically.
**You configure:** up to 3 details per node. For each: the type (Text, Number, Boolean, Phone, Email, License plate or Enum), the full question the agent asks, and whether it's required. A node can also be **Dynamic**: instead of a fixed set of questions, it asks the questions that a [Function Call node](/guides/functions) returned earlier in the same call.
## Behind the scenes
The caller doesn't hear these nodes. They only handle logic and data.
### Global
Reachable from anywhere in the flow. Use it for situations that can happen at any point, like a caller asking for a human. It fires once per call by default. Turn on **Multi use** in its menu (it shows a ↻ badge) if it needs to fire repeatedly.
**You configure:** a description of the situation that triggers it.
### Return
Used only inside a branch that starts at a Global node. It brings the call back to exactly where it was before the detour, keeping every detail already collected.
### Logic Split
Routes the call by conditions on [variables](/guides/variables), instantly and without the agent speaking. For example: VIP customers one way, everyone else another. The first matching condition wins, and a branch with no condition is the default.
**You configure:** the conditions and the destination for each.
### Set Var
Sets, changes or clears a stored value, then moves on instantly. Use it to reset a counter, record a status or prepare a value for a later step. You can also set a variable directly on a transition, with no separate node.
**You configure:** which variables to set or clear, and to what value.
## Actions
### Function Call
Calls one of your systems mid-call, for example to check stock, fetch an order from your CRM or update a record. It has two exits, success and failure. You can set text for the agent to say while it waits.
**You configure:** which function to call (defined once under the agent's **Functions**, reusable across nodes), which details to send, and which parts of the response to store.
### Message (SMS)
Sends a text message to the caller mid-call, from the agent's number to the caller's number. Neither number can be changed. The agent's number must support [SMS](/guides/sms), and this works on regular phone-number calls only, not on SIP trunk calls or in the simulator. It has success and error exits.
**You configure:** the message text, which can include collected details like a confirmation code.
### Outbound Call
Places a short call to another number while the caller stays on the line and hears nothing. For example, it can dial the line that opens a parking gate. It holds the line for a few seconds, then hangs up. It has one exit, and the flow continues even if nobody answers. It works on SIP trunk calls only. The dial doesn't appear in the transcript.
**You configure:** the number to dial (fixed, or a phone number collected during the call) and how long to hold the line (default 3 seconds, maximum 30).
### Transfer
Hands the call to a phone number, such as a human agent, or to another [flow agent](/guides/flow-agents) in your account. Collected details carry over when the call moves to another agent. A failure exit lets you handle a transfer that doesn't go through.
**You configure:** the destination. For phone transfers, you also choose whether the agent's number or the caller's number shows as the caller ID.
### List
Manages a list of items during the call, like a shopping cart. Use it when the caller builds an order from several items.
The fields of each item come from a **List** type you create first in the agent's **Types** section with **Add Type**.
**You configure:** the action (add, update, remove, find or clear), which list, and for update, remove and find, how to match the item. Find, update and remove have three exits: found, several found and not found.
# Functions
Source: https://docs.voicy.co/guides/functions
Define an HTTP request once, then call it from any point in a flow to fetch or send data mid-call.
Use functions to check stock, pull an order from your CRM, look up a customer, or update another system while the caller is on the line. Functions are available on [flow agents](/guides/flow-agents).
## 1. Define the function
Open the agent's settings and go to the **Functions** section. Add a function and fill in:
| Field | What to enter |
| - | - |
| Name | A unique name within the agent. It's the label you'll pick in the flow. |
| URL and method | `GET`, `POST`, `PUT`, `PATCH` or `DELETE`. Use `{placeholder}` segments in the URL for path variables, for example `https://api.example.co.il/orders/{order_id}`. |
| Headers | Key-value pairs, for example an [API key](/authentication) header. |
| Path Variables | One entry for each `{placeholder}` in the URL. Every placeholder must be declared, and every declared variable must appear in the URL. |
| Query Parameters | Values added to the URL's query string. |
| Body | For `POST`, `PUT` and `PATCH`: fields with a name, description, type and a Required flag. Fields can be nested objects and arrays. |
| Return Value | The shape of the JSON response. The whole response is stored in one variable, and you can read nested fields by dotted path, for example `[order.status]`. |
| Timeout | How long to wait for a response, from 1 to 60 seconds. The default is 10. |
Click **Test** to send a real request with sample values before any node uses the function.
Editing a function changes every node that uses it.
## 2. Call it from a Function Call node
Add a **Function Call** node to the flow and choose the function.
For each parameter, enter a fixed value or a `[variable]` collected earlier in the call.
Under **Store Return Values**, map response fields to variables for later nodes. Leave a field unset to skip it. See [Variables](/guides/variables).
Route **Success** and **Error** to the right next steps.
You can use the same function in as many nodes as you like.
## Success and error
**Success** means a 2xx response. The node takes **Error** on a 3xx, 4xx or 5xx response, a timeout, a network failure, a 2xx response that isn't valid JSON, or a response field that doesn't match its declared type. Always connect the Error branch to something the caller hears, such as an apology or a transfer.
## In the transcript
Each call to a function adds two system rows. The request row shows the method, URL and body. The result row shows the status, response, extracted variables and duration, plus an [error code](/error-handling) if it failed. Expand them to debug an integration without reading server logs.
## Security
* Common secret headers (such as `Authorization` and `X-Api-Key`) and secret-looking query parameters are shown as `[REDACTED]` in transcripts and the API. The real values are still sent to your endpoint.
* Header values are stored as plain text in the agent's configuration, not encrypted. Use a dedicated key with the smallest permissions your endpoint allows, and rotate it if needed.
* Requests to private or internal network addresses are blocked. Responses are capped at 1 MB.
# Introduction to Voicy
Source: https://docs.voicy.co/guides/introduction
Voicy is a platform to build, test, deploy and monitor AI voice agents that speak natural Hebrew — with telephony, knowledge bases, integrations and call data built in.
## Build
A Voicy agent is either a single prompt or a flow of nodes. Each fits a different kind of call.
See [Single prompt vs. flow agent](/guides/agent-types) for when a flow is the better fit.
Describe the whole agent in one prompt. Best for free, natural conversations.
Split the call into nodes in a visual editor, for full control over every step.
Upload a Markdown file, or connect a website the agent answers from.
Collect names, phone numbers, emails and choices, and reuse them anywhere in the call.
Call your CRM or any HTTP API during the call — check an order, update a record.
Transfer to a human or another agent, send an SMS, or dial out mid-call.
## Hebrew speech
Built from the ground up for Hebrew, not translated from English.
A library of natural Israeli voices, male and female.
The agent detects the caller's gender from their voice and addresses them correctly.
Fix how names, brands and terms are spoken, with niqqud-based overrides.
A streaming speech pipeline tuned for fast, natural turn-taking.
## Test
Try every change before an agent takes a real call.
Talk to your agent in the browser, by voice or text, and see the live transcript.
Ask the knowledge base a question and see which page the answer came from.
Broken links and missing settings are caught before you deploy.
## Deploy
Assign a number to your agent and start taking calls.
Connect your existing phone system or carrier over SIP.
Trigger calls from your own system with one API request.
Edit safely in a draft; callers only hear what you deploy.
## Monitor
Every call with its transcript and recording.
An automatic summary of each call, plus every detail the agent collected.
Receive the full call data on your server the moment a call ends.
## Developer friendly
Build everything in the dashboard with no code, or connect your systems through the API.
Dial calls, read call data and manage prompt agents.
Create an API key and send your first request.
## Next up
Build your first phone agent in 15 minutes.
Every node type, what it does and what to set.
# Knowledge base
Source: https://docs.voicy.co/guides/knowledge-base
A knowledge base gives your agent information about your business, so it can answer callers' questions. There are two types: a Markdown file and a website.
Jump to: [Markdown file](/guides/knowledge-base#markdown-file) · [Website](/guides/knowledge-base#website)
## Which type to choose
| | Markdown file | Website |
| - | - | - |
| Where the information comes from | A file you upload | The pages of your website |
| Size | Up to 15,000 tokens | Your whole site, hundreds of pages |
| Keeping it current | Upload a new file (**Replace**) | Automatic every night, or **Re-crawl** |
| How the agent uses it | The whole file is in front of the agent all the time | The agent looks up the relevant pages when a caller asks |
| Best for | Short, fixed information that must always be available | A large site, or information that changes often |
## Markdown file
Upload a Markdown file about your business, and your agent has its whole content in front of it for the entire call.
### What it is
A Markdown knowledge base is a text file you write and upload yourself. Use it for opening hours, branches, prices, policies and frequently asked questions. The whole file is part of the agent's context on every turn, so the agent can answer from it straight away, with no search step.
It works best for short, stable information that must always be available. For a large site or information that changes often, use a [website knowledge base](/guides/knowledge-base#website) instead. You can attach both to the same agent.
### Create a Markdown knowledge base
Go to **Knowledge Bases** and click **+ Create KB**.
Set **Type** to **Markdown**.
Give it a name (for example, "Hours and locations") and a short description of what it contains.
Pick a `.md` file (`.markdown` and `.txt` are accepted too). The file can be up to **15,000 tokens**, which is about a medium-length document.
### Update the content
On the knowledge base's page, click **Replace** and upload the new version of the file. Each call reads whatever the knowledge base holds when that call starts, so your change reaches the next call. You don't need to redeploy the agents that use it.
### The knowledge base page
| Control | What it does |
| - | - |
| **Rendered** / **Raw** | **Rendered** shows the formatted Markdown. **Raw** shows, character for character, the exact block the agent receives. |
| Direction toggle | Switches the preview between right-to-left and left-to-right. |
| Token count | Shows how much of the 15,000-token limit the file uses. |
| **Copy** / **Replace** / **Delete** | Copy the content, upload a new file, or delete the knowledge base. Deleting also removes it from every agent it's attached to. |
| **Properties** | The description, plus a list of the agents that use this knowledge base. |
| **Test Call** | Ask the knowledge base a question the way a caller would. See Testing a knowledge base. |
### Tips
* Use clear headings and short sections. The agent reads the file as written.
* The agent answers factual questions only from its sources. It won't guess from general knowledge. If callers ask for something the file doesn't cover, add it to the file.
* Check **Raw** view after you upload, to see exactly what the agent sees.
## Website
Connect your business's website, and your agent answers callers' questions by looking up the relevant pages during the call.
### How it works
Voicy reads your site's pages and keeps a stored copy up to date. When a caller asks about the business, the agent searches that copy, reads the most relevant pages, and answers from them. Nothing is fetched from your site during the call.
### Create a website knowledge base
In **Knowledge Bases**, click **+ Create KB**, set **Type** to **Website**, and enter your domain, for example `example.co.il`. Each domain can be added once per account.
The first crawl starts on its own. The status shows **Crawling · A of L** with an estimate of the time left, and the **Pages** table fills in as pages are read. The agent can answer from a page as soon as it's stored.
**Only pages listed in your sitemap are read.** Voicy doesn't follow links around your site. A page missing from it (a hidden page, a new page not yet added) won't be read, and the agent can't answer from it. Add such pages under **Extra URLs** in the knowledge base's properties, one full URL or path per line, up to **500**. They're read on every crawl from then on. Pages that aren't web pages, such as PDF price lists and images, aren't read.
### Keeping it up to date
* **Schedule:** your site is re-crawled automatically at night, between 01:00 and 06:00 Israel time.
* **Scan notes:** if your sitemap dates its pages reliably, Voicy checks only changed pages each night and does a full scan weekly ("Delta scan daily. Full scan weekly."). A red **(!)** means the dates are missing or unreliable, so the site gets a full scan every night. This isn't an error.
* **Re-crawl:** click **Re-crawl** after you change your site, for example after a price update. It reads every page. The button then shows **Queued** and **Crawling…**. After a run it's disabled for about 5 minutes, with a countdown.
During a re-crawl, agents keep answering from the current pages. If a crawl fails, the page gives the reason, for example "the site could not be reached" or "no pages were found in the sitemap".
### The Pages table
This table shows every page read, as a tree by URL path, with title, tokens, status and last update. A page that couldn't be read is marked in amber (**not read**), and folders holding one open automatically. Click a title to open the live page.
### Lookup results in the transcript
In a Test Call, each lookup appears as a system row. Expand it to see the question searched, the answer, the pages read (and which were cited), and the time taken.
| Row | Meaning |
| - | - |
| **נמצא** (found) | The answer came from a page on your site. |
| **לא מופיע באתר** (not on site) | The pages read don't contain it. Either the information is missing from the site, or it's on a page the search didn't reach. |
| **נדרש פירוט** (needs detail) | The site gives several answers (for example, a different price per branch), so the agent asks the caller which one they mean. |
| **נכשל** (failed) | The lookup couldn't run. The most common cause is that no pages are stored yet because the first crawl hasn't finished or failed. |
When a website is attached, the agent looks up every question about the business. If the answer isn't on the site, the agent says it doesn't have that information instead of guessing. For anything the agent must always know, use the prompt or a [Markdown knowledge base](/guides/knowledge-base#markdown-file).
## Attach a knowledge base to an agent
Open the agent. In its knowledge base section, click **+ Add Knowledge Base** and choose the knowledge base. The agent card shows the knowledge base's name, description and token count. To detach it, click **✕** on its card.
An agent can have any number of Markdown files and **one website**.
All Markdown knowledge bases attached to one agent share a single limit: together they can hold up to **15,000 tokens**. If an attachment would go over that, the save is refused.
Attaching a knowledge base changes the agent, so deploy the agent before you expect live calls to use it. See Draft and deploy.
## Test it
Ask the knowledge base a question the way a caller would, and see where the answer came from. See Test a knowledge base.
# Single prompt agents
Source: https://docs.voicy.co/guides/single-prompt-agents
A single prompt agent runs the whole call from one prompt, plus a few settings you configure in the dashboard or through the API.
On the Agents screen, click **Add Agent**, choose **Single Prompt**, and enter a name and voice.
Describe the persona, goal and rules. See [Writing a good prompt](/guides/writing-prompts).
Call the agent from the browser panel and read the live transcript.
Click **Deploy** to make the current version live on your phone numbers.
## What you configure
| Setting | What it does |
| - | - |
| **Prompt** | Instructions that govern the whole call. You can reference [variables](/guides/variables) as `[var_name]`. |
| **Welcome Message** | If set, the agent says it word for word when it answers, then waits for the caller. If empty, the agent writes its own opening from the prompt, so each call may start a little differently. |
| **Voice** | The agent's voice. Some voices also let you adjust speaking speed from 0.7x to 1.3x. |
| **Agent can end call** | When on, you set an **End call transition** (when to hang up, for example "The caller asks to end the call") and an **End call announcement** (the closing line the agent says before hanging up). |
| **Knowledge bases** | Attach business information the agent can answer from. See [Knowledge base](/guides/knowledge-base). |
| **Vars provided** | Values injected before the call starts, such as a customer name passed in when you place an outbound call. Each can have a default. |
| **[Vars extracted](/guides/variables#extracted-variables)** | Details pulled from the transcript after the call ends, such as the reason for calling. Shown on the call page and sent in [webhooks](/webhooks). |
| **Email summaries** | Addresses that receive a summary after each call. |
| **Webhooks** | Up to 5 HTTPS endpoints notified when a call ends, with an optional signing secret and custom headers. |
| **Operating hours** | Your business hours, available to the prompt as the `[is_open]` variable. |
| **[Pronunciation](/pronunciation-guide)** | Override how specific words are spoken. Preview each one with the play button, no test call needed. |
| **STT** | Terms the agent should recognize reliably, such as brand, street or product names. |
| **General** | **Max call length** (10, 15 or 20 minutes; default 15) and **Block anonymous calls** (callers who hide their number hear a short message and the call ends). |
Every setting edits the draft. Phone calls keep using the last deployed version until you click **Deploy**. The simulator always uses the draft.
## Managing through the API
Single prompt agents can be created and updated through the public API. The API covers the name, language, voice, prompt, welcome message, provided and extracted variables, and webhooks. A new agent created through the API is deployed right away. See the [API reference](/api-reference/overview) for [`agent-create`](/api-reference/agent-create) and [`agent-modify`](/api-reference/agent-modify).
Need steps, branching, [SMS](/guides/sms), transfers or calls to your systems? Move to a [flow agent](/guides/flow-agents). See [Single prompt vs. flow agent](/guides/agent-types).
# Send SMS during a call
Source: https://docs.voicy.co/guides/sms
Use a Message node to text the caller while they're still on the line, for example a confirmation code, an address or a link.
## How it works
When the flow reaches a Message node, Voicy sends the SMS and moves on right away through the node's **Success** or **Error** branch. The caller hears nothing from this node itself, so the next node usually tells them the message is on its way.
| | Value |
| - | - |
| **Sender** | The agent's phone number on this call: the number the caller dialed (inbound) or the number the agent called from (outbound). |
| **Recipient** | The caller's phone number. |
| **Body** | The text you write in the node. |
You can't change the sender or recipient in the node.
## Set it up
Add a **Message** node to your flow and open **Message Settings**.
Type the SMS body. Type `[` to insert a variable collected earlier, for example `שלום [customer_name], קוד האישור שלך הוא [confirmation_code]`. A character counter is shown. There's no length limit, and long messages are split into parts automatically.
Route **Success** to the next step and **Error** to a fallback, for example reading the details aloud instead.
## Requirements
SMS works only on calls to or from a phone number bought through Voicy, and that number must support SMS. On SIP trunk calls and browser test calls, the node always takes the **Error** branch without sending.
When you verify or deploy the flow, you get a warning (not a blocking error) if the agent has no phone number or its number isn't SMS-capable.
## In the transcript
Each send attempt adds one system row: **SMS sent** on success, or **Send failed** with the error detail. Expand it to see the sender, recipient, body and status. Both successful and failed attempts are recorded with the call.
## Tips
* Test the full flow on a real phone call. A browser test can only exercise the Error branch.
* Keep the message short and self-explanatory. The caller may read it after hanging up.
* If a variable in the body wasn't collected, check the flow order so it's collected before the Message node.
# Transfer a call
Source: https://docs.voicy.co/guides/transfer
Use a Transfer node to hand the caller over to a phone number, such as a human representative, or to another Voicy agent in your account.
## Add a Transfer node
Add a **Transfer** node to your flow and open it. Under **Transfer to**, choose **Phone Number** or **Agent**.
## Transfer to a phone number
| Setting | Options |
| - | - |
| **Phone number** | A fixed number, for example `054-1234567` or `+972541234567`, or a `[variable]` holding a number collected earlier in the call. |
| **Caller ID** | Which number the destination sees: **Agent number** (the default) or **User's number** (the caller's own number). |
Once the transfer is handed off, the agent leaves the call.
## Transfer to another agent
Choose **Agent** and select a target agent from your account. The list shows each agent's name and type. You can't transfer an agent to itself.
* The call continues on the same line, with no redial.
* The details collected so far go with the caller, so the new agent doesn't ask for them again.
* The new agent starts its own conversation, with its own prompt, flow and voice.
* The call stays recorded under the original agent in Call History. The transcript marks the handover.
## Where it works
| Call type | Phone transfer | Agent transfer |
| - | - | - |
| Phone numbers bought through Voicy | Yes | Yes |
| SIP trunk numbers | Yes | Yes |
| Browser test calls | No: takes the failure branch | Yes |
To test a phone transfer, place a real phone call to the agent. A browser test call always follows **Transfer failed**.
## The Transfer failed branch
Every Transfer node has a **Transfer failed** output. The call follows it when the transfer can't start, for example:
* The call type doesn't support phone transfers (browser test calls).
* The destination or caller ID is a `[variable]` that was never collected in this call.
* The phone system rejects the transfer request.
Connect **Transfer failed** to a node that tells the caller what happens next, for example offering a callback or taking a message. The transcript records the failed attempt.
## Tips
* Reach a transfer from a global node describing the situation (for example, "the caller asks for a human representative") rather than from a transition on an open question-and-answer node. That way a stray "yes" can't trigger it.
* If the destination comes from a variable, collect it as a phone type so it's in a dialable format.
# Variables
Source: https://docs.voicy.co/guides/variables
Variables carry information through a call: values you pass in, details the caller gives, results from your systems and data pulled from the transcript afterwards.
## Kinds of variables
| Kind | Where it comes from | Defined in |
| - | - | - |
| **Provided** | Passed in before the call starts (for example on an outbound call), or a default value | Agent settings → Vars provided |
| **System** | Filled automatically at the start of every call | Built in |
| **Collected** ([flow agents](/guides/flow-agents)) | Collection nodes, Set Var, function responses and list nodes during the call | The flow |
| **Extracted** | Pulled from the transcript after the call ends. See [Extracted variables](/guides/variables#extracted-variables) | Agent settings → Vars extracted |
The system variables are `user_number` (the caller's number), `user_full_number` (the same number in international format), `agent_number`, `now` (current date and time, Israel time), `is_open` (whether you're inside operating hours, when they're enabled) and `direction` (inbound, outbound or web call).
## Using variables in text
Write `[var_name]` in a prompt, welcome message, announcement, [SMS](/guides/sms) text or function field, and Voicy replaces it with the value.
The easiest way is to type `[` and pick the variable from the menu that opens. The menu lists every variable available at that point, grouped by kind, for example **SYSTEM**. An arrow next to a variable opens its modifiers, so you can insert a ready-made reference such as `[user_number:local]`.
You can add these modifiers after the name:
* `[code:digits]` reads the value digit by digit, which suits codes and IDs.
* `[amount:spoken]` reads numbers as Hebrew words.
* `[phone:e164]` and `[phone:local]` format a phone number as `+972…` or `05…`. This is handy when your system expects one format.
## Collecting details with a Collection node
Each Collection node gathers up to 3 details. For each one you set:
* **Type**: Text, Number, Boolean, Phone, Email, License plate or Enum. Answers are checked against the type. Phone accepts Israeli mobile and landline numbers. License plate accepts Israeli vehicle plates: 7 or 8 digits, or a military (צ), police (מ), military police (מצ), diplomatic (CD) or consular (CC) plate. It is stored as digits, or as the letter, a dash and the digits (`צ-123456`), and a second variable `_plate_type` records the kind (`civilian`, `military`, `police`, `military_police`, `diplomatic` or `consular`). An Enum only accepts one of its options.
* **Question**: the full question the agent asks, for example "What's your full name?"
* **Required**: the node won't move on until every required detail is collected. The caller can skip optional ones.
The agent confirms each detail briefly once it's captured. An Enum's options come from a type you create in the agent's **Types** section with **Add Type**, so several variables can reuse the same options.
## Setting and changing values
Use a **Set Var** node, or turn on **When condition fires, also set a variable** on a transition, to set or clear a value without asking the caller. Values can be expressions: arithmetic on numbers () or text that combines variables.
## Branching on values
A **Logic Split** node compares variables and routes instantly, for example . Conditions support comparisons (`== != < <= > >=`) and `&&`, `||`, `!`. The first match wins, and a branch with no condition catches everything else.
## Default values
Each variable under **Vars provided** can have a **Default value**. The agent uses it whenever the call doesn't bring its own value, for example on every inbound call, or on an outbound call that doesn't pass that variable.
When a call starts, values are filled in this order. A later step replaces an earlier one:
| Order | Source | Applies to |
| - | - | - |
| 1 | System variables, filled automatically | Every call |
| 2 | The **Default value** of each variable under Vars provided | Every call |
| 3 | Values passed in when the call is placed: `vars_provided` in the API, or the values you enter for a Phone Call test | Outbound calls |
| 4 | Your test values for system variables | Your own browser tests only |
System variables have no default value of their own: the dialog shows what they contain, but only a test value can change them.
## Passing variables into an outbound call
When you start a call with the `call-dial` API, send values in `vars_provided`, for example `{"customer_name": "Dana"}`. Declare each one under **Vars provided** first. Declared variables are your agent's **variable contract**: the API returns them in the agent's `variables_provided` with name, description and type. A variable that isn't passed uses its [default value](/guides/variables#default-values).
## Extracted variables
Extracted variables are filled **after the call**, from the whole transcript. Use them for reporting fields, like the reason for calling or whether the caller asked for a callback. For anything the call itself needs while it's running, use collected variables instead.
In the agent editor, open **Properties** and expand **Vars extracted**.
Add each field you want pulled from every call, such as `customer_name`, `reason_for_call` or `callback_requested`. Give it a name (lowercase letters and underscores) and a type: text, number, boolean, phone, email or license plate.
Write a clear description of what the field should hold. Clear names and descriptions give better results.
The values appear on the call's detail page, in your call-ended [webhook](/webhooks) and in the call-get API. See Call summary and extracted variables.
# Writing a good prompt
Source: https://docs.voicy.co/guides/writing-prompts
Practical guidelines for writing a Hebrew voice agent that sounds natural on the phone and behaves the way you expect.
## Know where each instruction goes
* **[Single prompt agent](/guides/single-prompt-agents):** everything lives in the agent prompt. It applies for the whole call.
* **Flow agent, [global prompt](/guides/flow-agents):** added to the start of **every** node's prompt. Put the persona, identity, language style, tone and rules that apply everywhere here.
* **Flow agent, node prompt:** only the task for this step, for example "Ask whether the caller wants to book or cancel." Don't repeat the persona in every node, because the global prompt already supplies it.
If a rule must hold in every step (never promise refunds, always use formal address), put it in the global prompt, not in one node.
## Define the persona and tone
Say who the agent is, which business it represents and how it should sound. For example: "You are Noa, the phone representative of a parking company. Speak warmly and to the point." Say whether it uses formal or casual Hebrew and how it handles callers it can't help.
## Write for the ear, not the eye
* Ask for **short spoken sentences**, one idea at a time. Long paragraphs that read well on a page sound robotic on a call.
* Ask **one question per turn**. A caller can't scroll back to see what you asked.
* Forbid lists, bullet points, emojis and headings in replies. None of them work in speech.
* For numbers, codes and IDs, use the speech modifiers in your text: `[order_id:digits]` reads digit by digit, and `[amount:spoken]` reads as Hebrew words. See [Variables](/guides/variables).
## Write fixed text without gender
Hebrew addresses a man and a woman differently. Replies the agent writes itself adapt automatically: they avoid gendered forms until the caller's gender is detected, then address the caller correctly.
Fixed text is different. The **Welcome Message**, [Announcement](/guides/flow-nodes#announcement) nodes and [SMS](/guides/sms) text are said or sent word for word, and the welcome message plays before the caller has spoken, so the agent can't know their gender yet. Phrase these without gendered forms.
| Instead of | Write |
| - | - |
| שלום, איך אוכל לעזור לך? | שלום, במה אפשר לעזור? |
| תודה שפנית אלינו | תודה על הפנייה |
| אתה מוזמן לחזור אלינו | אפשר לחזור אלינו בכל עת |
## Be explicit about goals and limits
* State the goal of the call and what counts as "done".
* List what the agent should **not** do, such as quoting prices that aren't in its information or giving legal advice.
* Tell it what to say when it doesn't know. An honest "I don't have that information" beats a guess.
## Don't hide rules in the knowledge base
A [knowledge base](/guides/knowledge-base#markdown-file) holds facts: hours, branches, prices, policies. Every instruction goes in the prompt, such as when to transfer, what never to say and how to confirm details.
## In flow agents, let the structure do the work
* Write transition conditions as plain descriptions of what the caller did, like "The caller asked to speak with a person."
* Use a **Collection** node to gather details rather than asking in a Conversation prompt. It validates answers and stores them.
* Use an **Announcement** node for text that must be said word for word, such as a legal notice.
## Test and iterate
Make test calls in the simulator and read the transcript. When the agent misbehaves, fix the one instruction that caused it rather than piling on new rules. Deploy when the draft behaves well.
# Introduction
Source: https://docs.voicy.co/introduction
Welcome to the Voicy API documentation
# Welcome to Voicy API
Voicy is a voice agent platform that enables you to build and deploy AI-powered phone agents. Our API provides programmatic access to manage calls, retrieve transcripts, and integrate voice AI into your applications.
## What can you do with the Voicy API?
Initiate AI-powered phone calls to any number programmatically
Access transcripts, recordings, and summaries for all your calls
List, filter, and organize calls with powerful query options
Attach custom data to calls for CRM integration
## Base URL
All API requests should be made to:
```
https://api.voicy.co
```
## Getting Started
Log in to the [Voicy Dashboard](https://app.voicy.co) and navigate to Account Settings to create an API key.
Use the API key to authenticate and list your calls:
```bash theme={null}
curl -X POST https://api.voicy.co/functions/v1/call-list \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"limit": 10}'
```
Check out the [API Reference](/api-reference/overview) for all available endpoints.
## API stability
The public API is intentionally small and stable. It exposes three call endpoints — [Dial](/api-reference/call-dial), [Get](/api-reference/call-get), and [List](/api-reference/call-list) — and nothing else. We keep the surface minimal on purpose so you can integrate against a contract that won't shift under you.
**What we promise:**
* **Additive-only fields.** We may add new fields to responses, but we will not remove or repurpose a documented field without announcing it in advance. Write your integration to ignore unknown fields.
* **Stable transcript shape.** Each `transcript[]` entry is exactly `{ role, content, timestamp_ms }`, where `role` is `agent`, `caller`, or `system`. Filter for `role in [agent, caller]` if you only want spoken dialogue.
**What is *not* part of the contract:**
* **Flow-agent internals.** Prompt-based agents are manageable through the [Agent API](/api-reference/agent-create) (name, prompt, voice, welcome message, and variable contract). But **flow agents** — multi-step conversational flows with branching, function calls, and knowledge bases — are built in the [dashboard](https://app.voicy.co); their internal structure is never exposed through this API and may change at any time. You reference any agent by `agent_id` (e.g. `override_agent_id` on Dial).
* **Internal fields.** Latency breakdowns, cost/billing figures, infrastructure identifiers, and any field not documented in the API Reference are internal, may change without notice, and are not returned to API-key callers.
## Need Help?
* **Dashboard**: [app.voicy.co](https://app.voicy.co)
* **Email**: [support@voicy.co](mailto:support@voicy.co)
# Pronunciation Guide
Source: https://docs.voicy.co/pronunciation-guide
Fix mispronounced words and phrases with shared pronunciation groups
## Overview
Voice agents may occasionally mispronounce certain words — especially proper nouns, brand names, street names, or Hebrew words with ambiguous pronunciation (Hebrew is written without vowels, so one spelling can be read several ways). **Pronunciation groups** let you define overrides once and reuse them across agents.
A pronunciation group is a named list of overrides. It belongs either to your **account** (any of your agents can use it) or is **global** (managed by Voicy, available to every account). An agent uses a group by attaching it; a global group can also be **pinned to all agents**, in which case every agent gets it without attaching anything.
Pronunciation overrides apply to every agent — both agent types run the chained Speech-to-Text → LLM → Text-to-Speech pipeline.
## Format
An override is **niqqud-pointed Hebrew** — the same vowel points used to disambiguate Hebrew in print. Each entry pairs a phrase with its pointed form:
| Field | Format | Example | Direction |
| - | - | - | - |
| Phrase | Plain Hebrew, as the agent would write it — one word or several | `שקל`, `בן יהודה` | RTL |
| Pronunciation | Niqqud-pointed Hebrew | `שֶׁקֶל`, `בֶּן יְהוּדָה` | RTL |
Both Text-to-Speech engines (Soniox and Gemini) read this one format, so an override keeps working if you change the agent's voice.
## How to Use
1. Go to your agent's settings in the [dashboard](https://app.voicy.co)
2. Open the **Pronunciation** section — it lists the groups attached to this agent
3. Click **+ Add group** to attach an existing group or create a new one for your account
4. Tap a group to open it, then **+ Add phrase** to add an entry: the Hebrew phrase, then its niqqud-pointed form
5. Press ▶ to hear it in the agent's own voice before saving
6. Save — the change takes effect on the next call of **every** agent that uses the group
### Priority
Groups are listed in **priority order**: when two groups define the same phrase, the group **higher in the list wins**. Drag a group to reorder it. Pinned global groups always sit at the bottom, so your own groups override them.
The section footer shows how many phrases are shadowed by a higher group, so you can see when an override is not taking effect.
## Tips
* **Only add mispronounced words** — most Hebrew words are pronounced correctly by default
* **Group by topic** — for example, one group of street names shared by every agent in a city, and one per agent for its own brand names
* **Test after adding** — make a test call to verify the pronunciation improved
* **Iterate** — if the first override doesn't sound right, try a different one
* **Include meaning for homographs** — if a word has multiple readings (like שקל), add separate entries with context in the phrase field (e.g., "שקל מטבע" and "שקל פועל")
## Generating Overrides
If you're unsure how to write the override:
[nakdan.dicta.org.il](https://nakdan.dicta.org.il/) adds vowel points to plain Hebrew text. The dashboard links to it directly under the override field.
## How It Works
The phrase is replaced with its niqqud-pointed form before the text reaches the Text-to-Speech engine, which renders the vowel points.
Because this is a deterministic text substitution (not a hint to a model), an override applies on every utterance where the phrase appears. Groups are resolved when a call starts, so editing a group never requires re-saving or re-deploying the agents that use it.
## Limitations
* Works best for **proper nouns, brand names, and uncommon words**
* Common Hebrew words are usually pronounced correctly without overrides
* An override only applies when the phrase appears exactly as written in the **phrase** field, and **as a separate word** — a Hebrew prefix or suffix glued to it (בוויסי, וויסים) will not match
* Niqqud cannot express **stress**, so a word whose only problem is which syllable is stressed cannot be fixed this way
* Maximum 200 entries per group
# Quickstart
Source: https://docs.voicy.co/quickstart
Make your first API call in under 5 minutes
# Quickstart
This guide will help you make your first API call to Voicy in just a few minutes.
## Prerequisites
* A Voicy account ([sign up here](https://app.voicy.co))
* An API key ([create one](/authentication#creating-api-keys))
* A phone number assigned to your account with an agent configured
## Step 1: Set Up Your Environment
Store your API key as an environment variable:
```bash macOS/Linux theme={null}
export VOICY_API_KEY="voicy_sk_live_your_key_here"
```
```powershell Windows theme={null}
$env:VOICY_API_KEY = "voicy_sk_live_your_key_here"
```
## Step 2: List Your Calls
Let's start by listing recent calls to verify your API key works:
```bash cURL theme={null}
curl -X POST https://api.voicy.co/functions/v1/call-list \
-H "Authorization: Bearer $VOICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 5}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.voicy.co/functions/v1/call-list', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VOICY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ limit: 5 }),
});
const calls = await response.json();
console.log(calls);
```
```python Python theme={null}
import os
import requests
response = requests.post(
'https://api.voicy.co/functions/v1/call-list',
headers={
'Authorization': f'Bearer {os.environ["VOICY_API_KEY"]}',
'Content-Type': 'application/json',
},
json={'limit': 5},
)
calls = response.json()
print(calls)
```
## Step 3: Make an Outbound Call
Now let's initiate an AI-powered phone call:
```bash cURL theme={null}
curl -X POST https://api.voicy.co/functions/v1/call-dial \
-H "Authorization: Bearer $VOICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_number": "+15551234567",
"to_number": "+15559876543",
"vars_provided": {
"customer_name": "John"
}
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.voicy.co/functions/v1/call-dial', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VOICY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from_number: '+15551234567',
to_number: '+15559876543',
vars_provided: {
customer_name: 'John',
},
}),
});
const call = await response.json();
console.log(`Call initiated: ${call.call_id}`);
```
```python Python theme={null}
import os
import requests
response = requests.post(
'https://api.voicy.co/functions/v1/call-dial',
headers={
'Authorization': f'Bearer {os.environ["VOICY_API_KEY"]}',
'Content-Type': 'application/json',
},
json={
'from_number': '+15551234567',
'to_number': '+15559876543',
'vars_provided': {
'customer_name': 'John',
},
},
)
call = response.json()
print(f'Call initiated: {call["call_id"]}')
```
Replace `+15551234567` with a phone number assigned to your account, and `+15559876543` with the destination number.
## Step 4: Get Call Details
After the call completes, retrieve the full details including transcript:
```bash cURL theme={null}
curl https://api.voicy.co/functions/v1/call-get/YOUR_CALL_ID \
-H "Authorization: Bearer $VOICY_API_KEY"
```
```javascript JavaScript theme={null}
const callId = 'YOUR_CALL_ID';
const response = await fetch(`https://api.voicy.co/functions/v1/call-get/${callId}`, {
headers: {
'Authorization': `Bearer ${process.env.VOICY_API_KEY}`,
},
});
const callDetails = await response.json();
console.log('Transcript:', callDetails.transcript);
console.log('Duration:', callDetails.duration_ms, 'ms');
```
```python Python theme={null}
import os
import requests
call_id = 'YOUR_CALL_ID'
response = requests.get(
f'https://api.voicy.co/functions/v1/call-get/{call_id}',
headers={
'Authorization': f'Bearer {os.environ["VOICY_API_KEY"]}',
},
)
call_details = response.json()
print(f'Transcript: {call_details["transcript"]}')
print(f'Duration: {call_details["duration_ms"]}ms')
```
## What's Next?
Explore all available endpoints
Learn how to handle API errors
# Rate Limits
Source: https://docs.voicy.co/rate-limits
API usage limits and how to build a resilient client
# Rate Limits
The Voicy API does not currently enforce per-key request quotas. To keep the service stable for everyone, please stay within these guidelines:
| Guideline | Value |
| - | - |
| Sustained request rate per API key | Up to 60 requests per minute |
| Bulk operations | Spread requests over time instead of sending bursts |
Need a higher sustained rate? Contact us at [support@voicy.co](mailto:support@voicy.co).
## Build for future limits
Per-key rate limiting is planned. When it is introduced, requests over the limit will receive a `429 Too Many Requests` response. We recommend building retry handling now so your integration is unaffected:
Retry `429` and `5xx` responses with increasing delays:
```javascript theme={null}
async function requestWithBackoff(fn, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
const retryable = error.status === 429 || error.status >= 500;
if (retryable && i < maxRetries - 1) {
const waitTime = Math.pow(2, i) * 1000; // 1s, 2s, 4s, 8s, 16s
await new Promise(r => setTimeout(r, waitTime));
continue;
}
throw error;
}
}
}
```
For bulk operations, pace requests through a queue:
```javascript theme={null}
class RateLimitedQueue {
constructor(requestsPerMinute = 60) {
this.queue = [];
this.interval = 60000 / requestsPerMinute;
this.processing = false;
}
async add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const { fn, resolve, reject } = this.queue.shift();
try {
resolve(await fn());
} catch (error) {
reject(error);
}
await new Promise(r => setTimeout(r, this.interval));
}
this.processing = false;
}
}
```
# Webhooks
Source: https://docs.voicy.co/webhooks
Receive real-time notifications when events occur
## Overview
Webhooks allow you to receive HTTP POST notifications when events occur in your Voicy account. When a call ends, Voicy sends the full call data — transcript (STT text, not audio), summary, extracted variables, a recording link, and more — to your server in real time.
Each agent supports up to **5 webhooks**, all firing in parallel. Use them to push call data to your CRM, analytics platform, or any custom integration.
## Creating a Webhook
In the [Voicy Dashboard](https://app.voicy.co), navigate to your agent and open the **Settings** tab. Scroll down to the **Webhooks** section and expand it.
Click the **+** button to add a new webhook. Fill in the fields:
| Field | Required | Description |
| - | - | - |
| **Name** | Yes | A label to identify this webhook (e.g. "CRM", "Analytics", "Slack") |
| **URL** | Yes | The HTTPS endpoint that will receive POST requests |
| **Signing Secret** | No | A secret string for HMAC-SHA256 signature verification |
| **Custom Headers** | No | Key-value pairs added to every request (e.g. `Authorization: Bearer `) |
Click **Add** to save.
Webhooks are part of the agent configuration. After adding or modifying webhooks, **deploy the agent** for changes to take effect on phone calls.
Changes take effect immediately for web (test) calls, but phone calls use the deployed version.
Make a test call from the dashboard's built-in call simulator. When the call ends, your endpoint will receive a POST request with the full call payload.
Use a service like [webhook.site](https://webhook.site) to inspect payloads during development.
### Managing Webhooks
* **Edit** — Click the pencil icon on any webhook to change its name, URL, or secret. Leave the secret field blank to keep the existing secret.
* **Delete** — Click the trash icon to remove a webhook.
* **Limit** — Up to 5 webhooks per agent. The badge shows your current count (e.g. "2/5").
### Managing Webhooks via the API
Prompt-based agents' webhooks can also be set through the [Agent API](/api-reference/agent-create), with the same fields and limits as the dashboard. Changes are deployed immediately — no separate deploy step.
* **Create** — pass `webhooks` to [Create Agent](/api-reference/agent-create).
* **Update** — pass `webhooks` to [Update Agent](/api-reference/agent-modify). It replaces the agent's whole list: a webhook you leave out is deleted, and `[]` removes them all. Omit the field to leave webhooks unchanged.
* **Read** — every [Agent](/api-reference/models/agent) response includes `webhooks`, with `has_secret` / `has_headers` in place of the values.
Secrets and headers are write-only. On update, omit them (or send `null`) to keep what is stored; send `""` to remove a secret, or `{}` to remove all headers. Include each webhook's `id` to update it in place — without one, a webhook with the same `url` is updated.
```bash theme={null}
curl -X PATCH https://api.voicy.co/functions/v1/agent-modify/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer voicy_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"webhooks": [
{ "id": "6f1c2b1e-8d0a-4c55-9a51-2f4a7e3b9c10", "name": "CRM", "url": "https://crm.example.com/voicy/call-ended" },
{ "name": "Analytics", "url": "https://analytics.example.com/hook", "headers": { "Authorization": "Bearer " } }
]
}'
```
This keeps the CRM webhook (and its secret), adds an Analytics webhook, and deletes any other webhook on the agent.
### Custom Headers
Custom headers let you attach arbitrary HTTP headers to every webhook request — useful when your receiving endpoint requires authentication (e.g. `Authorization: Bearer `) or a vendor-specific API key header.
**Limits:** Up to 10 headers per webhook. Names must be valid [RFC 7230 tokens](https://httpwg.org/specs/rfc7230.html#rule.token.separators). Values cannot contain line breaks.
**Reserved headers** that cannot be overridden: `Content-Type`, `x-voicy-event`, `x-voicy-signature`, `Host`, `Content-Length`, `Transfer-Encoding`.
**Security:** Header values are stored server-side and never returned by the API (same treatment as signing secrets). When editing a webhook, leave all value fields blank to preserve the existing headers.
## Events
### `call_ended`
Fired after all post-call processing completes (summary generation, variable extraction, email notifications). The payload contains the full call data in the same format as the [Get Call](/api-reference/call-get) API.
**Headers:**
| Header | Description |
| - | - |
| Custom headers | Any headers configured for this webhook (order relative to the headers below is not guaranteed) |
| `Content-Type` | `application/json` |
| `x-voicy-event` | Event name (`call_ended`) |
| `x-voicy-signature` | HMAC-SHA256 signature (only if signing secret is configured) |
**Payload:**
```json theme={null}
{
"event": "call_ended",
"call": {
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_type": "phone_call",
"call_status": "user_hangup",
"agent": {
"name": "My Agent",
"version": 3
},
"from_number": "+972501234567",
"to_number": "+972521234567",
"direction": "incoming",
"start_timestamp": 1710000000000,
"duration_ms": 45000,
"transcript": [
{ "timestamp_ms": 0, "role": "agent", "content": "שלום, איך אוכל לעזור?" },
{ "timestamp_ms": 3200, "role": "caller", "content": "אני צריך עזרה" }
],
"summary": "הלקוח ביקש עזרה בנושא...",
"vars_provided": { "from_number": "+972501234567" },
"vars_extracted": { "customer_name": "משה" },
"vars_collected": { "email": "user@example.com" },
"recording_url": "https://.../recordings/....wav?token=..."
}
}
```
`recording_url` is a signed link to the call recording, valid for **1 hour** from when the webhook fires. If you need the recording after it expires, re-fetch it via the [Get Call](/api-reference/call-get) API, which issues a fresh signed URL.
## Handling Webhooks
When your server receives a webhook, respond with a `200` status code to acknowledge receipt. Here's a minimal handler:
```javascript Express.js theme={null}
const express = require('express');
const app = express();
app.post('/voicy-webhook', express.json(), (req, res) => {
const { event, call } = req.body;
console.log(`Event: ${event}`);
console.log(`Call ID: ${call.call_id}`);
console.log(`Status: ${call.call_status}`);
console.log(`Duration: ${call.duration_ms}ms`);
if (call.summary) {
console.log(`Summary: ${call.summary}`);
}
if (call.vars_extracted) {
console.log('Extracted vars:', call.vars_extracted);
}
res.status(200).send('OK');
});
app.listen(3000);
```
```python Flask theme={null}
from flask import Flask, request
app = Flask(__name__)
@app.route('/voicy-webhook', methods=['POST'])
def webhook():
data = request.json
event = data['event']
call = data['call']
print(f"Event: {event}")
print(f"Call ID: {call['call_id']}")
print(f"Status: {call['call_status']}")
print(f"Duration: {call['duration_ms']}ms")
if call.get('summary'):
print(f"Summary: {call['summary']}")
if call.get('vars_extracted'):
print(f"Extracted vars: {call['vars_extracted']}")
return 'OK', 200
```
## Signature Verification
If a signing secret is configured, each webhook request includes an `x-voicy-signature` header containing an HMAC-SHA256 signature of the raw request body. Always verify signatures in production to ensure requests are genuinely from Voicy.
```javascript Node.js theme={null}
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/voicy-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-voicy-signature'];
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Call ended:', event.call.call_id);
res.status(200).send('OK');
});
```
```python Python theme={null}
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/voicy-webhook', methods=['POST'])
def webhook():
signature = request.headers.get('x-voicy-signature', '')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
abort(401)
data = request.json
print(f"Call ended: {data['call']['call_id']}")
return 'OK', 200
```
## Behavior
| Property | Value |
| - | - |
| **Method** | POST |
| **Timeout** | 5 seconds |
| **Retries** | None (fire-and-forget) |
| **Content-Type** | `application/json` |
| **Parallel** | All agent webhooks fire simultaneously |
If a webhook endpoint is unreachable or returns an error, the failure is logged but does not affect call processing or other webhooks.