An agent is the voice bot: its prompt, language, and its LLM / STT / TTS / telephony configuration.
| Method | Path | Scope |
|---|---|---|
| GET | /api/v1/agents |
agents:read |
| POST | /api/v1/agents |
agents:write |
| GET | /api/v1/agents/{id} |
agents:read |
| PATCH | /api/v1/agents/{id} |
agents:write |
| DELETE | /api/v1/agents/{id} |
agents:write |
Authentication, scopes and the response envelope are covered in the API Overview.
Create an agent#
Only name and language are required; everything else has a platform default.
curl -X POST https://voice-agent.edesy.in/api/v1/agents \
-H "Authorization: Bearer $EDESY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Status Bot",
"language": "hindi_english",
"prompt": "You are a support agent for Acme. Ask for the order number, look it up, and read back the status. Be concise and polite.",
"greetingMessage": "Namaste! Acme support se baat kar rahe hain. Aapka order number bataiye.",
"llmProvider": "gemini-live-2.5",
"geminiliveVoice": "Aoede",
"callProvider": "twilio",
"active": true
}'
import os
import requests
response = requests.post(
"https://voice-agent.edesy.in/api/v1/agents",
headers={"Authorization": f"Bearer {os.environ['EDESY_API_KEY']}"},
json={
"name": "Order Status Bot",
"language": "hindi_english",
"prompt": "You are a support agent for Acme. Ask for the order number, look it up, and read back the status. Be concise and polite.",
"greetingMessage": "Namaste! Acme support se baat kar rahe hain. Aapka order number bataiye.",
"llmProvider": "gemini-live-2.5",
"geminiliveVoice": "Aoede",
"callProvider": "twilio",
"active": True,
},
timeout=30,
)
response.raise_for_status()
agent = response.json()["data"]
print(agent["id"])
const response = await fetch("https://voice-agent.edesy.in/api/v1/agents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EDESY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Order Status Bot",
language: "hindi_english",
prompt:
"You are a support agent for Acme. Ask for the order number, look it up, and read back the status. Be concise and polite.",
greetingMessage:
"Namaste! Acme support se baat kar rahe hain. Aapka order number bataiye.",
llmProvider: "gemini-live-2.5",
geminiliveVoice: "Aoede",
callProvider: "twilio",
active: true,
}),
});
if (!response.ok) {
throw new Error(`Edesy API ${response.status}: ${await response.text()}`);
}
const { data: agent } = await response.json();
console.log(agent.id);
<?php
$ch = curl_init('https://voice-agent.edesy.in/api/v1/agents');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EDESY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Order Status Bot',
'language' => 'hindi_english',
'prompt' => 'You are a support agent for Acme. Ask for the order number, look it up, and read back the status. Be concise and polite.',
'greetingMessage' => 'Namaste! Acme support se baat kar rahe hain. Aapka order number bataiye.',
'llmProvider' => 'gemini-live-2.5',
'geminiliveVoice' => 'Aoede',
'callProvider' => 'twilio',
'active' => true,
]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 201) {
throw new RuntimeException("Edesy API {$status}: {$response}");
}
$agent = json_decode($response, true)['data'];
echo $agent['id'];
{
"success": true,
"data": {
"id": 1042,
"name": "Order Status Bot",
"language": "hindi_english",
"active": true,
"greetingMessage": "Namaste! Acme support se baat kar rahe hain. Aapka order number bataiye.",
"llmProvider": "gemini-live-2.5",
"llmModel": "gemini-2.5-flash",
"sttProvider": null,
"ttsProvider": null,
"ttsVoice": null,
"agentType": "AI_POWERED",
"createdAt": "2026-08-10T09:12:44.318Z",
"updatedAt": "2026-08-10T09:12:44.318Z"
}
}
Defaults applied when omitted: active = true, llmProvider = gemini,
llmModel = gemini-2.5-flash.
Supplying a prompt also seeds version 1 of the agent's prompt history, so the
version timeline in the dashboard matches the live prompt from the start.
Errors: 400 VALIDATION_ERROR, 409 NO_ATTRIBUTABLE_USER (the workspace has no
members, so the write cannot be attributed to a user).
List agents#
| Query | Type | Description |
|---|---|---|
search |
string | Case-insensitive substring match on name |
language |
string | Exact language match, e.g. hindi |
active |
true | false |
Filter by active state. Omit for no filter |
limit |
integer | Default 50, max 100 |
offset |
integer | Default 0 |
{
"success": true,
"data": { "agents": [], "total": 37, "limit": 50, "offset": 0 }
}
List results are summaries. Use GET/api/v1/agents/{id} for the full configuration
(prompt, llmConfig, sttConfig, ttsConfig, variables, transferConfig, webhook
fields, and more).
Get an agent#
GET/api/v1/agents/{id} → 200 OK, returning the full configurable surface.
A non-numeric id returns 400 INVALID_AGENT_ID; an unknown id returns 404 NOT_FOUND.
Update an agent#
Partial update — only the fields you send are written. Omitted fields are left
untouched; sending null clears a nullable field.
curl -X PATCH https://voice-agent.edesy.in/api/v1/agents/1042 \
-H "Authorization: Bearer $EDESY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "You are a support agent for Acme. Confirm the order number digit by digit before looking it up.",
"llmTemperature": 0.3,
"webhookUrl": "https://example.com/hooks/edesy",
"webhookEnabled": true
}'
import os
import requests
response = requests.patch(
"https://voice-agent.edesy.in/api/v1/agents/1042",
headers={"Authorization": f"Bearer {os.environ['EDESY_API_KEY']}"},
json={
"prompt": "You are a support agent for Acme. Confirm the order number digit by digit before looking it up.",
"llmTemperature": 0.3,
"webhookUrl": "https://example.com/hooks/edesy",
"webhookEnabled": True,
},
timeout=30,
)
response.raise_for_status()
const response = await fetch(
"https://voice-agent.edesy.in/api/v1/agents/1042",
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.EDESY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt:
"You are a support agent for Acme. Confirm the order number digit by digit before looking it up.",
llmTemperature: 0.3,
webhookUrl: "https://example.com/hooks/edesy",
webhookEnabled: true,
}),
}
);
if (!response.ok) {
throw new Error(`Edesy API ${response.status}: ${await response.text()}`);
}
<?php
$ch = curl_init('https://voice-agent.edesy.in/api/v1/agents/1042');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EDESY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'prompt' => 'You are a support agent for Acme. Confirm the order number digit by digit before looking it up.',
'llmTemperature' => 0.3,
'webhookUrl' => 'https://example.com/hooks/edesy',
'webhookEnabled' => true,
]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Edesy API {$status}: {$response}");
}
Returns the full agent object. Two things happen automatically:
- Prompt versioning. Changing
promptcreates a new prompt version and points the agent at it, so the version history never drifts from the live prompt. Every version is visible and restorable from the dashboard. - Live propagation. Changing the prompt, greeting, voice or provider config
invalidates the voice backends' caches. A
200means the change applies to the next call — not up to 24 hours later. A rollback-able configuration snapshot is also captured.
Errors: 400 VALIDATION_ERROR, 404 NOT_FOUND, 409 NO_ATTRIBUTABLE_USER.
Delete an agent#
DELETE/api/v1/agents/{id} → { "success": true }
Errors: 404 NOT_FOUND.
Field reference#
All fields below are accepted by both POST/api/v1/agents and
PATCH/api/v1/agents/{id}. On create, name and language are required.
Identity and behaviour#
| Field | Type | Notes |
|---|---|---|
name |
string (1–100) | Required on create |
language |
enum | Required on create. See Languages |
additionalLanguages |
enum[] (max 8) | Extra languages for a multilingual agent; language stays primary |
prompt |
string | null | The system prompt. Changing it creates a new prompt version |
greetingMessage |
string (≤500) | null | First thing the agent says |
endCallInstructions |
string (≤2000) | null | How the agent should wrap up and hang up |
summaryPrompt |
string (≤5000) | null | Custom prompt for the post-call summary. Supports a {transcript} placeholder; empty → platform default |
active |
boolean | Inactive agents will not answer or place calls |
agentType |
AI_POWERED | FLOW_BASED | AI_FLOW |
Which engine answers. Default AI_POWERED |
variables |
object[] | Declared prompt variables — see Variables |
LLM#
| Field | Type | Notes |
|---|---|---|
llmProvider |
string | openai, anthropic (available on request), groq, google, gemini-2.5, gemini-live, gemini-live-2.5, gemini-live-3.1, openai-realtime, openai-realtime-mini |
llmModel |
string | Model id for the provider — see the catalog |
llmTemperature |
number 0–1 | |
llmMaxTokens |
number 1–100000 | |
llmConfig |
object | Provider-specific tuning — see llmConfig |
geminiliveVoice |
string | Voice for native-audio (Gemini Live) providers |
Provider and model ids are not validated on write. These fields are plain strings — a typo returns
200and only surfaces when a call is placed, where the runtime may silently fall back to a default rather than failing. Copy ids exactly from the Provider & Voice Catalog.
Speech#
| Field | Type | Notes |
|---|---|---|
sttProvider |
string | deepgram, openai_whisper, azure, google_stt, assemblyai, sarvam_stt, soniox_stt, elevenlabs_stt |
sttModel |
string | See the catalog |
sttConfig |
object | Provider-specific STT options |
ttsProvider |
string | google, openai, elevenlabs, azure_tts, cartesia, sarvam, soniox, heypixa, deepgram_tts, gemini_genai_tts, gemini_tts |
ttsVoice |
string | Voice id — see the catalog (500+ voices) |
ttsConfig |
object | Provider-specific TTS options |
The speech-to-text and text-to-speech id lists are not interchangeable. Several providers carry a modality suffix on one side only: speech-to-text uses
elevenlabs_stt,google_stt,sarvam_stt,openai_whisper; text-to-speech useselevenlabs,sarvam,openai,azure_tts,deepgram_tts. Using a TTS id insttProvideris accepted on write and fails at call time.
STT and TTS are ignored for native audio-to-audio providers (gemini-live-*,
openai-realtime-*), which handle speech end to end.
Telephony and integration#
| Field | Type | Notes |
|---|---|---|
callProvider |
twilio | exotel | plivo | vobiz | alohaa | telnyx | edesy-ivr | edesy-sip | edesy |
Carrier used for this agent's calls |
webhookUrl |
URL (≤2000) | null | Post-call webhook target |
webhookHeaders |
object | Headers sent with the webhook |
webhookEnabled |
boolean | |
transferConfig |
object | null | Warm/cold transfer and DTMF routing |
{
"transferConfig": {
"enabled": true,
"announceMessage": "Please hold, connecting you to our team.",
"destinations": [
{ "id": "sales", "name": "Sales", "phoneNumber": "+919876543210", "description": "Sales desk" }
],
"dtmfTransferEnabled": true,
"dtmfMappings": [
{ "digit": "1", "destinationId": "sales", "announceMessage": "Connecting to Sales." }
]
}
}
Destination phone numbers are normalised to E.164 on save.
workspaceIdis not part of this API. Your key is workspace-scoped and the workspace always comes from the key. If you send aworkspaceIdin the body it is silently ignored — it will not move the agent into another workspace, and it is not an error.
Languages#
language and additionalLanguages accept 112 lowercase language ids across three
groups — single languages (hindi, tamil, assamese, …), bilingual pairs
(hindi_english, tamil_english, …), and multilingual combinations. multilingual
auto-detects the caller's language at runtime on native-audio providers.
Unlike provider ids, languages are validated — an unknown value returns
400 VALIDATION_ERROR with the message Invalid language selection. The complete list
is in the Provider & Voice Catalog.
Variables#
Declare the placeholders your prompt uses so they can be supplied per call.
{
"variables": [
{ "name": "customer_name", "type": "string", "label": "Customer name", "required": true },
{ "name": "order_id", "type": "string", "description": "Order reference", "required": true },
{ "name": "due_amount", "type": "number", "defaultValue": 0, "required": false }
]
}
| Field | Type | Notes |
|---|---|---|
name |
string (≤50) | Required. Referenced in the prompt as {{name}} |
type |
string | number | boolean | date |
Required |
description |
string (≤200) | |
defaultValue |
any | Used when the call does not supply the variable |
required |
boolean | Default false |
label |
string (≤100) | Display label |
fieldType |
short_text | paragraph | select | number | checkbox | date |
Input widget |
options |
string[] | For select |
maxLength |
number | For text types |
hidden |
boolean | Hide from input forms |
Values are supplied at call time via variables on
POST/api/v1/calls. Both {{key}} and {key}
placeholder forms are substituted, in the prompt and the greeting. Prefer multi-word
variable names — a single-word one can capture an incidental brace pair in the prompt.
llmConfig#
A free-form JSON object for provider tuning. It is replaced wholesale on write, so always read → modify → write back the whole object. Unknown keys are stored and ignored, which means a typo is silent — copy the keys below exactly.
Turn-taking#
How long the agent waits for silence before deciding the caller has stopped speaking.
vadProfile |
Silence | Use for |
|---|---|---|
low_latency (default) |
100 ms | Sales, fast-paced conversation |
balanced |
200 ms | Most agents; avoids cutting people off mid-sentence |
conservative |
350 ms | Hindi, Gujarati, Marathi, Assamese, Malayalam; elderly callers |
{ "llmConfig": { "vadProfile": "balanced" } }
Noise filtering#
Two independent layers for noisy environments. Applies to the STT → LLM → TTS pipeline, not to native-audio providers.
{
"llmConfig": {
"noiseFiltering": {
"enabled": true,
"sileroVad": { "confidence": 0.8, "minVolume": 0.7, "startSecs": 0.25, "stopSecs": 0.8 },
"turnStrategy": { "minWords": 2, "useInterim": true }
}
}
}
| Key | Default | Range | Meaning |
|---|---|---|---|
sileroVad.confidence |
0.7 | 0–1 | Speech-detection threshold; higher is stricter |
sileroVad.minVolume |
0.6 | 0–1 | Ignore audio quieter than this |
sileroVad.startSecs |
0.2 | 0.1–1.0 | Speech needed before detection starts |
sileroVad.stopSecs |
0.8 | 0.1–2.0 | Silence needed before detection stops |
turnStrategy.minWords |
1 | integer | Transcripts shorter than this don't trigger a reply — filters one-word noise |
turnStrategy.useInterim |
true |
boolean | Use interim transcripts for faster turn detection |
Suggested presets: light 0.75 / 0.65 / minWords 1 · moderate
0.8 / 0.7 / minWords 2 · aggressive 0.85 / 0.75 / minWords 3.
Voicemail detection#
{
"llmConfig": {
"voicemailDetection": {
"enabled": true,
"action": "hangup",
"leaveMessage": "",
"language": "en",
"gateTimeoutMs": 4000,
"patternConfidence": 0.8
}
}
}
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Turn detection on |
action |
"hangup" |
hangup, leave_message, or callback |
leaveMessage |
"" |
Spoken when action is leave_message |
language |
"en" |
Pattern language: en or hi |
gateTimeoutMs |
4000 |
How long the agent's audio is held while detecting |
patternConfidence |
0.8 |
Minimum confidence to call it voicemail |
On native-audio providers, prefer prompt-based detection: instruct the agent to call
end_call with disposition: "VOICEMAIL". It costs nothing and adds no latency.
Built-in functions#
{ "llmConfig": { "enabledBuiltins": ["end_call", "finalize_conversation"] } }
end_call lets the agent hang up; finalize_conversation makes it write its
structured outcome before ending.
sttConfig and ttsConfig#
Same rules as llmConfig: free-form, replaced wholesale, keys are provider-specific.
They carry tuning that has no dedicated column — for example:
{ "ttsConfig": { "heypixa": { "voice": "kriti", "top_p": 0.95, "repetition_penalty": 1.3 } } }
Because these objects are unvalidated, treat them as advanced tuning: set the dedicated
fields (ttsProvider, ttsVoice, sttModel) first, and only reach for the config
objects when you need a provider option that has no field of its own.
Next steps#
- Tools — let this agent call your APIs mid-conversation
- Calls — place a call with this agent
- Provider & Voice Catalog — every valid provider, model, voice and language id