Agents API
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
POST /api/v1/agents → 201 Created
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
GET /api/v1/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
PATCH /api/v1/agents/{id} → 200 OK
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, 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, speechmatics |
sttModel |
string | See the catalog |
sttConfig |
object | Provider-specific STT options |
ttsProvider |
string | google, openai, elevenlabs, azure_tts, cartesia, sarvam, soniox, heypixa, deepgram_tts, playht, gemini_genai_tts, gemini_tts |
ttsVoice |
string | Voice id — see the catalog (541 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.
Vertex AI (Gemini Live only)
Keyed by the provider id. Vertex is the default for Gemini Live because it is substantially faster than AI Studio.
{ "llmConfig": { "gemini-live-2.5": { "vertexai": { "enabled": true, "region": "us-central1" } } } }
Only us-central1 is confirmed for Gemini Live 2.5.
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