Kevali Voice API
Build voice agents and publish them from your own software, with the Python library or plain HTTPS requests. Placing calls through the API comes next.
Get an API keyNot available yet. This guide describes an API that is still being built. It is not live, no organization has it switched on, and the Python library is not published, so the examples below do not work today.
Before you start
Two things have to be true for your organization.
- The developer API is enabled for your organization. Until then every request answers
403 not_enabled. - You have an API key. Log in, then create a key on the API keys page. Existing CRM organizations can also use Settings → API Keys → Developer Keys.
Every address below is relative to https://api.zettatech.in/agents-api/v1. The Python library uses it by default.
An interactive reference will be available once the API is live.
Quickstart
from zetta_voice import Client
client = Client(api_key="zta_live_...")
# 1. Pick a voice
voice = client.voices.list(language="en")[0]
# 2. Create an agent (it starts as a draft) and publish it
agent = client.agents.create(
name="Reception",
voice_id=voice["id"],
language="en",
system_prompt="You are a polite receptionist for Acme Dental. Book appointments.",
first_message="Hello {{customer_name}}, this is Acme Dental. Is now a good time?",
)
version = client.agents.publish(agent["id"], change_summary="first version")
print("published version", version["version_number"])
# 3. Change the draft later; callers hear it once you publish again
client.agents.update(agent["id"], first_message="Hi {{customer_name}}! Acme Dental here.")
client.agents.publish(agent["id"], change_summary="friendlier greeting")
The same first step over plain HTTPS:
GET https://api.zettatech.in/agents-api/v1/voices?language=en
Authorization: Bearer zta_live_...
Keys
Send your key on every request: Authorization: Bearer zta_live_…. The full key is shown once, when it is created. Keep it on your server; never put it in a web page or a mobile app.
Each key can only do what was ticked when it was created.
| In Settings | Scope | Allows |
|---|---|---|
| View agents | agents:read | List voices and read agents |
| Create, edit and publish agents | agents:write | Create, edit, delete and publish agents. Comes with agents:read |
A Test key (zta_test_…) is for building: it can create and edit drafts and read everything. It cannot change what callers hear, so it cannot publish an agent or delete a published one. Those answer 403 test_key; use a Live key for them.
Agents
An agent has a draft and published versions. Editing changes the draft only. Calls always use the last published version, so nothing a caller hears changes until you publish.
The voices an agent can speak with. Optional filters: language (for example ta, en, hi) and gender (male, female, neutral).
{ "voices": [ { "id": "9cb42936-…", "name": "Vidya", "gender": "female",
"languages": ["en", "ta"], "accent": null, "age_group": null } ] }
| Field | Required | Meaning |
|---|---|---|
name | yes | Your name for the agent |
voice_id | yes | An id from /voices |
language | Language code. Defaults to ta | |
system_prompt | How the agent should behave | |
first_message | What it says when the call connects. May contain {{placeholders}} | |
display_name | The name it introduces itself by. Defaults to the voice's name | |
gender | male or female. Defaults to the voice’s |
Answers 201 with the agent. Unknown fields are refused.
{ "id": "3f9fce6b-…", "name": "Reception", "status": "draft", "language": "en",
"display_name": "Vidya", "gender": "female", "voice_id": "9cb42936-…",
"system_prompt": "You are a polite receptionist…", "first_message": "Hello {{customer_name}}…",
"published_version_id": null, "created_at": "2026-10-03T08:12:42Z", "updated_at": "2026-10-03T08:12:42Z" }
One page of your agents. page starts at 1; page_size is 1 to 100 (default 20). Returns agents, total, page, page_size.
Send only the fields you want to change. A field you leave out, or send as null, is not changed; send "" to empty a text field.
Answers 204.
Freezes the draft as a new version. Optional body: {"change_summary": "…"}. If the draft hasn’t changed since the last publish, no new version is made: you get the current one back, with the same version_number.
{ "id": "15a8d9c9-…", "agent_id": "3f9fce6b-…", "version_number": 1,
"change_summary": "first version", "published_at": "2026-10-03T08:12:42Z" }
Errors
Every error has the same shape. Branch on code; show message to a person.
{ "error": { "code": "test_key",
"message": "A test key cannot publish an agent. Use a live key." } }
| Status | Code | What to do |
|---|---|---|
| 401 | unauthorized | The key is missing, wrong or revoked |
| 403 | forbidden | The key lacks the scope named in the message |
| 403 | not_enabled | The API is not switched on for your organization |
| 403 | test_key | A test key cannot do this. Use a Live key |
| 404 | not_found | No such agent or endpoint in your organization |
| 422 | invalid_request | The message names the field |
| 422 | invalid_voice | Use a voice id from /voices |
| 422 | publish_refused | The message says what to fix in the agent |
| 429 | rate_limited | Wait a moment and retry |
| 503 | unavailable | Temporary. Retry shortly |
| 500 | internal_error | Our fault. Retry, and tell us if it persists |
In Python each status is its own exception: AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, InvalidRequestError, RateLimitError, and APIError for the rest. All carry .code and .message.
Limits
- 100 requests per minute per key.
Not available yet: placing and reading phone calls through the API (the calls permissions on older keys do nothing until then), voice previews, and libraries for languages other than Python.