Agents API

Create, read, update and delete voice agents over REST — full field reference including prompt, languages, LLM/STT/TTS providers, variables, transfer and webhook configuration.

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/agents201 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:

  1. Prompt versioning. Changing prompt creates 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.
  2. Live propagation. Changing the prompt, greeting, voice or provider config invalidates the voice backends' caches. A 200 means 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 200 and 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 uses elevenlabs, google, sarvam, openai, azure_tts, deepgram_tts. Using a TTS id in sttProvider is 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.

workspaceId is not part of this API. Your key is workspace-scoped and the workspace always comes from the key. If you send a workspaceId in 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