Skip to content

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 key

Not 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.

  1. The developer API is enabled for your organization. Until then every request answers 403 not_enabled.
  2. 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 SettingsScopeAllows
View agentsagents:readList voices and read agents
Create, edit and publish agentsagents:writeCreate, 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.

GET/voicesagents:read

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 } ] }
POST/agentsagents:write
FieldRequiredMeaning
nameyesYour name for the agent
voice_idyesAn id from /voices
languageLanguage code. Defaults to ta
system_promptHow the agent should behave
first_messageWhat it says when the call connects. May contain {{placeholders}}
display_nameThe name it introduces itself by. Defaults to the voice's name
gendermale 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" }
GET/agentsagents:read

One page of your agents. page starts at 1; page_size is 1 to 100 (default 20). Returns agents, total, page, page_size.

GET/agents/{id}agents:read
PATCH/agents/{id}agents:write

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.

DELETE/agents/{id}agents:write

Answers 204.

POST/agents/{id}/publishagents:write · Live key only

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." } }
StatusCodeWhat to do
401unauthorizedThe key is missing, wrong or revoked
403forbiddenThe key lacks the scope named in the message
403not_enabledThe API is not switched on for your organization
403test_keyA test key cannot do this. Use a Live key
404not_foundNo such agent or endpoint in your organization
422invalid_requestThe message names the field
422invalid_voiceUse a voice id from /voices
422publish_refusedThe message says what to fix in the agent
429rate_limitedWait a moment and retry
503unavailableTemporary. Retry shortly
500internal_errorOur 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.