API Overview

Base URL, authentication, scopes, rate limits, response envelope and error codes for the Edesy Voice AI REST API, plus an index of every endpoint.

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