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.

5 min read

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: GEThttps://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#

Also available under /api/v1: phone-numbers, knowledge-base 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
  • Campaigns — campaign progress and per-contact outcomes
  • Provider & Voice Catalog — every valid provider, model, voice and language id
Was this page helpful?