API Overview
The Edesy Voice AI REST API lets you build and configure agents, give them tools, place calls, and read back transcripts and outcomes — everything the dashboard does for agent configuration and call placement, backed by the same services.
- Base URL:
https://voice-agent.edesy.in - All endpoints are under:
/api/v1 - Machine-readable spec:
GET https://voice-agent.edesy.in/api/v1/openapi.json(no auth required)
The OpenAPI document is generated from the running code at request time, so it cannot disagree with the deployed API. Use it to generate a typed client in your language of choice.
Endpoint index
| What you want to do | Endpoint | Page |
|---|---|---|
| Create an agent | POST /api/v1/agents |
Agents |
| Update an agent, or any part of its configuration | PATCH /api/v1/agents/{id} |
Agents |
| List / read / delete agents | GET, DELETE /api/v1/agents[/{id}] |
Agents |
| Add a tool to an agent | POST /api/v1/functions |
Tools |
| Update a tool | PATCH /api/v1/functions/{id} |
Tools |
| List / read / delete tools | GET, DELETE /api/v1/functions[/{id}] |
Tools |
| Place an outbound or test call | POST /api/v1/calls |
Calls |
| Call history | GET /api/v1/calls |
Calls |
| Individual call details, transcript, recording | GET /api/v1/calls/{id} |
Calls |
| Be notified when a call ends | callbackUrl on POST /api/v1/calls |
Calls |
| Valid provider, model, voice and language ids | — | Provider & Voice Catalog |
Also available under /api/v1: phone-numbers, knowledge-base, campaigns
(read-only) and workflows/{id}/trigger. See the OpenAPI spec for their shapes.
Authentication
Every request carries a server API key. Create one in the dashboard under Settings → API Keys. The key is displayed once at creation and cannot be recovered afterwards — store it in your secrets manager immediately.
| Prefix | Purpose |
|---|---|
vp_live_… |
Production credentials |
vp_test_… |
Non-production credentials |
Either header works:
curl https://voice-agent.edesy.in/api/v1/agents \
-H "Authorization: Bearer vp_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# or
curl https://voice-agent.edesy.in/api/v1/agents \
-H "X-API-Key: vp_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Server-side only. A
vp_key grants full workspace access at its scopes. Never ship it to a browser, mobile app, or anything a user can inspect — call the API from your own backend and proxy the results to your front end.
Scopes
A key is granted a set of scopes at creation. Grant the narrowest set that works — a reporting integration should not be able to delete an agent.
| Scope | Grants |
|---|---|
agents:read |
List / read agents |
agents:write |
Create, update, delete agents |
functions:read |
List / read tools |
functions:write |
Create, update, delete tools |
calls:read |
Read calls and transcripts |
calls:write |
Place outbound calls |
phone_numbers:read / phone_numbers:write |
Manage phone numbers |
knowledge_base:read / knowledge_base:write |
Manage knowledge bases |
workflows:read / workflows:write |
Manage workflows |
Wildcards are supported: agents:* grants both agent scopes, and * grants
everything. Note that write does not imply read — grant both if you need both.
A key with no scopes authenticates successfully but is authorized for nothing.
Rate limits
Default 120 requests/minute per key (configurable per key). Every response carries the current state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Requests allowed per minute |
X-RateLimit-Remaining |
Requests left in the current window |
X-RateLimit-Reset |
Unix seconds when the window resets |
Retry-After |
Seconds to wait (present only on 429) |
Back off using these headers rather than parsing error bodies.
Rotation
Rotating a key from the dashboard mints the replacement first and puts the old key on a grace deadline (24 hours by default), so you can redeploy without an outage window. Revoking is immediate — use it for a leaked credential.
Conventions
Response envelope
Success:
{ "success": true, "data": {} }
Failure:
{
"success": false,
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE",
"details": []
}
Always branch on the HTTP status or success. code is stable and safe to switch on;
error is for humans and may be reworded. details is present on VALIDATION_ERROR
and contains the per-field issues.
Pagination
List endpoints accept limit (default 50, max 100) and offset (default 0), and
return total alongside the results.
Timestamps
ISO-8601 UTC strings. Agent objects expose createdAt / updatedAt.
Errors common to every endpoint
| Status | Code | When |
|---|---|---|
| 401 | MISSING_API_KEY |
No Authorization or X-API-Key header |
| 401 | INVALID_KEY_FORMAT |
Not a well-formed vp_live_ / vp_test_ key |
| 401 | INVALID_API_KEY |
Key does not match any active credential |
| 401 | KEY_REVOKED |
Key was revoked |
| 401 | KEY_EXPIRED |
Key expired, or its rotation grace period ended |
| 403 | INSUFFICIENT_SCOPE |
Key lacks the scope this operation requires |
| 429 | RATE_LIMIT_EXCEEDED |
Per-key rate limit exceeded — see Retry-After |
| 500 | INTERNAL_ERROR |
Unexpected server error |
Every key is bound to exactly one workspace. Requesting a resource that belongs to a
different workspace returns 404 NOT_FOUND, never another tenant's data.
Quick start
Create an agent, give it a tool, place a call, and read the transcript.
export EDESY_API_KEY="vp_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
export BASE="https://voice-agent.edesy.in/api/v1"
# 1. Create the agent
AGENT_ID=$(curl -sS -X POST "$BASE/agents" \
-H "Authorization: Bearer $EDESY_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Order Status Bot",
"language": "hindi_english",
"prompt": "You are Acme support. Ask for the order number, call lookup_order, and read back the status.",
"greetingMessage": "Namaste! Acme support. Aapka order number bataiye.",
"llmProvider": "gemini-live-2.5",
"callProvider": "twilio"
}' | jq -r '.data.id')
# 2. Attach a tool
curl -sS -X POST "$BASE/functions" \
-H "Authorization: Bearer $EDESY_API_KEY" -H "Content-Type: application/json" \
-d "{
\"agentId\": $AGENT_ID,
\"name\": \"lookup_order\",
\"description\": \"Look up an order's status. Call as soon as the customer gives an order number.\",
\"parametersSchema\": { \"order_id\": { \"type\": \"string\", \"description\": \"Order number\" } },
\"requiredParams\": [\"order_id\"],
\"httpMethod\": \"POST\",
\"httpUrl\": \"https://api.example.com/orders/lookup\",
\"httpHeaders\": { \"Content-Type\": \"application/json\" },
\"httpBody\": \"{\\\"orderId\\\": \\\"{{order_id}}\\\"}\"
}"
# 3. Place a call
CONV_ID=$(curl -sS -X POST "$BASE/calls" \
-H "Authorization: Bearer $EDESY_API_KEY" -H "Content-Type: application/json" \
-d "{ \"agentId\": $AGENT_ID, \"phoneNumber\": \"+919876543210\" }" \
| jq -r '.data.conversationId')
# 4. After the call ends, read the transcript
curl -sS "$BASE/calls/$CONV_ID" \
-H "Authorization: Bearer $EDESY_API_KEY" | jq '.data | {status, duration, summary, fullText}'
import os
import requests
BASE = "https://voice-agent.edesy.in/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['EDESY_API_KEY']}"
def post(path, payload):
r = session.post(f"{BASE}{path}", json=payload, timeout=30)
r.raise_for_status()
return r.json()["data"]
# 1. Create the agent
agent = post("/agents", {
"name": "Order Status Bot",
"language": "hindi_english",
"prompt": "You are Acme support. Ask for the order number, call lookup_order, and read back the status.",
"greetingMessage": "Namaste! Acme support. Aapka order number bataiye.",
"llmProvider": "gemini-live-2.5",
"callProvider": "twilio",
})
# 2. Attach a tool
post("/functions", {
"agentId": agent["id"],
"name": "lookup_order",
"description": "Look up an order's status. Call as soon as the customer gives an order number.",
"parametersSchema": {"order_id": {"type": "string", "description": "Order number"}},
"requiredParams": ["order_id"],
"httpMethod": "POST",
"httpUrl": "https://api.example.com/orders/lookup",
"httpHeaders": {"Content-Type": "application/json"},
"httpBody": '{"orderId": "{{order_id}}"}',
})
# 3. Place a call
call = post("/calls", {"agentId": agent["id"], "phoneNumber": "+919876543210"})
# 4. After the call ends, read the transcript
r = session.get(f"{BASE}/calls/{call['conversationId']}", timeout=30)
r.raise_for_status()
result = r.json()["data"]
print(result["status"], result["duration"], result["summary"])
print(result["fullText"])
const BASE = "https://voice-agent.edesy.in/api/v1";
const headers = {
Authorization: `Bearer ${process.env.EDESY_API_KEY}`,
"Content-Type": "application/json",
};
async function call(path, method = "GET", body) {
const res = await fetch(`${BASE}${path}`, {
method,
headers,
...(body ? { body: JSON.stringify(body) } : {}),
});
if (!res.ok) throw new Error(`Edesy API ${res.status}: ${await res.text()}`);
return (await res.json()).data;
}
// 1. Create the agent
const agent = await call("/agents", "POST", {
name: "Order Status Bot",
language: "hindi_english",
prompt:
"You are Acme support. Ask for the order number, call lookup_order, and read back the status.",
greetingMessage: "Namaste! Acme support. Aapka order number bataiye.",
llmProvider: "gemini-live-2.5",
callProvider: "twilio",
});
// 2. Attach a tool
await call("/functions", "POST", {
agentId: agent.id,
name: "lookup_order",
description:
"Look up an order's status. Call as soon as the customer gives an order number.",
parametersSchema: {
order_id: { type: "string", description: "Order number" },
},
requiredParams: ["order_id"],
httpMethod: "POST",
httpUrl: "https://api.example.com/orders/lookup",
httpHeaders: { "Content-Type": "application/json" },
httpBody: '{"orderId": "{{order_id}}"}',
});
// 3. Place a call
const placed = await call("/calls", "POST", {
agentId: agent.id,
phoneNumber: "+919876543210",
});
// 4. After the call ends, read the transcript
const result = await call(`/calls/${placed.conversationId}`);
console.log(result.status, result.duration, result.summary);
console.log(result.fullText);
<?php
const BASE = 'https://voice-agent.edesy.in/api/v1';
function edesy(string $path, string $method = 'GET', ?array $body = null): array {
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EDESY_API_KEY'),
'Content-Type: application/json',
],
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException("Edesy API {$status}: {$response}");
}
return json_decode($response, true)['data'];
}
// 1. Create the agent
$agent = edesy('/agents', 'POST', [
'name' => 'Order Status Bot',
'language' => 'hindi_english',
'prompt' => 'You are Acme support. Ask for the order number, call lookup_order, and read back the status.',
'greetingMessage' => 'Namaste! Acme support. Aapka order number bataiye.',
'llmProvider' => 'gemini-live-2.5',
'callProvider' => 'twilio',
]);
// 2. Attach a tool
edesy('/functions', 'POST', [
'agentId' => $agent['id'],
'name' => 'lookup_order',
'description' => "Look up an order's status. Call as soon as the customer gives an order number.",
'parametersSchema' => ['order_id' => ['type' => 'string', 'description' => 'Order number']],
'requiredParams' => ['order_id'],
'httpMethod' => 'POST',
'httpUrl' => 'https://api.example.com/orders/lookup',
'httpHeaders' => ['Content-Type' => 'application/json'],
'httpBody' => '{"orderId": "{{order_id}}"}',
]);
// 3. Place a call
$call = edesy('/calls', 'POST', [
'agentId' => $agent['id'],
'phoneNumber' => '+919876543210',
]);
// 4. After the call ends, read the transcript
$result = edesy('/calls/' . $call['conversationId']);
echo $result['status'], ' ', $result['duration'], ' ', $result['summary'], PHP_EOL;
echo $result['fullText'];
Practical notes
Idempotency. POST /api/v1/calls is not idempotent — a retry places a second call.
Retry only on 429, 503 and 504, with backoff, and de-duplicate on your side using
metadata.
Partial updates. Both PATCH endpoints write only the fields you send. To clear a
nullable field send null; omitting it leaves it unchanged.
Object-valued fields. llmConfig, sttConfig, ttsConfig and variables are
replaced wholesale, not deep-merged. Read, modify, write back.
Caching. Agent and tool writes invalidate the voice backends' caches synchronously,
so a 2xx means the change is live for the next call.
Multi-tenancy. A key can only ever reach its own workspace. Ids belonging to another
workspace return 404, not 403, so ids cannot be probed.
Next steps
- Agents — create and configure agents
- Tools — let an agent call your APIs mid-conversation
- Calls — place calls, read history and transcripts
- Provider & Voice Catalog — every valid provider, model, voice and language id