The Voice Agent API includes a REST API for creating and managing reusable voice agents. An agent stores its system prompt, greeting, voice, and tools server-side, so you can deploy it across channels by referencing its id.
For a guided walkthrough, see Create an agent.
Base URL: https://agents.assemblyai.com
Authentication
Every request requires your AssemblyAI API key in the Authorization header. The raw key works directly; a Bearer prefix is accepted and stripped:
Authorization: <YOUR_API_KEY>
| Response | Meaning |
|---|
401 {"detail": "Unauthorized"} | Key missing, malformed, or not entitled for Voice Agents. |
401 {"detail": "Missing authorization header"} | No Authorization header sent. |
Endpoints
| Method | Path | Description | Success |
|---|
POST | /v1/agents | Create an agent | 201 |
GET | /v1/agents | List your agents | 200 |
GET | /v1/agents/{id} | Retrieve one agent | 200 |
PUT | /v1/agents/{id} | Update an agent | 200 |
DELETE | /v1/agents/{id} | Delete an agent | 204 |
Create an agent
POST /v1/agents
curl -X POST https://agents.assemblyai.com/v1/agents \
-H "Authorization: $ASSEMBLYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Support Assistant",
"system_prompt": "You are a friendly support agent.",
"greeting": "Hi, how can I help?",
"voice": { "voice_id": "ivy" }
}'
# pip install requests
import os
import requests
resp = requests.post(
"https://agents.assemblyai.com/v1/agents",
headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
json={
"name": "Support Assistant",
"system_prompt": "You are a friendly support agent.",
"greeting": "Hi, how can I help?",
"voice": {"voice_id": "ivy"},
},
)
resp.raise_for_status()
print(resp.json()["id"])
// Node 18+ has fetch built in
const res = await fetch("https://agents.assemblyai.com/v1/agents", {
method: "POST",
headers: {
Authorization: process.env.ASSEMBLYAI_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Support Assistant",
system_prompt: "You are a friendly support agent.",
greeting: "Hi, how can I help?",
voice: { voice_id: "ivy" },
}),
});
const data = await res.json();
console.log(data);
Returns 201 with the full agent record.
List agents
GET /v1/agents returns a lightweight array of id, name, and timestamps, not full configurations.
curl https://agents.assemblyai.com/v1/agents \
-H "Authorization: $ASSEMBLYAI_API_KEY"
# pip install requests
import os
import requests
resp = requests.get(
"https://agents.assemblyai.com/v1/agents",
headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
)
resp.raise_for_status()
print(resp.json())
// Node 18+ has fetch built in
const res = await fetch("https://agents.assemblyai.com/v1/agents", {
method: "GET",
headers: {
Authorization: process.env.ASSEMBLYAI_API_KEY,
},
});
const data = await res.json();
console.log(data);
Retrieve an agent
GET /v1/agents/{id} returns the full agent record. Tool header values are masked as "***" (see HTTP tool config).
Update an agent
PUT /v1/agents/{id} accepts the same body as create, but every field is optional. Send only what changes. Returns 200 with the updated record.
curl -X PUT https://agents.assemblyai.com/v1/agents/$AGENT_ID \
-H "Authorization: $ASSEMBLYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "greeting": "Thanks for calling Acme. What can I do for you?" }'
# pip install requests
import os
import requests
resp = requests.put(
f"https://agents.assemblyai.com/v1/agents/{os.environ['AGENT_ID']}",
headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
json={"greeting": "Thanks for calling Acme. What can I do for you?"},
)
resp.raise_for_status()
print(resp.json())
// Node 18+ has fetch built in
const res = await fetch(`https://agents.assemblyai.com/v1/agents/${process.env.AGENT_ID}`, {
method: "PUT",
headers: {
Authorization: process.env.ASSEMBLYAI_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ greeting: "Thanks for calling Acme. What can I do for you?" }),
});
const data = await res.json();
console.log(data);
Delete an agent
DELETE /v1/agents/{id} returns 204 with no body.
Request body
POST /v1/agents takes the agent as one JSON object; PUT /v1/agents/{id} takes the same body with every field optional (only name, system_prompt, and voice are required). For a copy-ready body with every field populated, see A complete agent. For exact field types, defaults, and validation, see the create-agent API reference.
| Field | Type | Required | Default | Notes |
|---|
name | string | Yes | - | Tool name the model calls. |
description | string | Yes | - | When to call it. The model’s main signal. |
parameters | Parameters | No | null | A JSON Schema describing the tool’s arguments. |
http | HttpToolConfig | No | null | The outbound request. Omit for a client-handled tool. |
timeout_seconds | integer | No | 120 | Range 1–300. |
execution_mode | "interactive" | "hold" | No | "interactive" | How the agent behaves while the tool runs. See tool calling. |
Parameters (JSON Schema)
parameters is a standard JSON Schema object describing the tool’s arguments. The same shape for HTTP and client-side tools:
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The customer's order ID.",
"examples": ["AB-12345", "ZZ-90001"],
"pattern": "[A-Z]{2}-\\d{5}"
}
},
"required": ["order_id"]
}
Each property accepts standard JSON-Schema keywords. Beyond type and description, these sharpen tool-calling and turn-detection accuracy:
| Keyword | Type | Notes |
|---|
enum | string[] | Restrict to a fixed set of allowed values. |
examples | array | Concrete example values. Improves recognition and turn-taking. |
pattern | string | Regex the value must match to be accepted. |
format | string | A named JSON-Schema format (email, date-time, …). |
If you omit these, the agent infers the expected shape from each property’s description at runtime. See Parameter hints for guidance and examples.
Write shape (create / update):
{
"url": "https://api.example.com/orders",
"http_method": "POST",
"headers": [
{ "name": "Authorization", "value": "Bearer xyz" }
]
}
Read shape (get / list):
{
"url": "https://api.example.com/orders",
"http_method": "POST",
"headers": [
{ "name": "Authorization", "last_set_at": "2026-06-20T10:00:00Z" }
]
}
| Field | Type | Required | Default | Notes |
|---|
url | string | Yes | - | Must be https and a public host. Max 2048 chars. |
http_method | HTTP method | No | "POST" | GET, POST, PUT, PATCH, or DELETE. |
headers | HttpToolHeader[] | null | No | null | Sent on every call. Values are encrypted at rest and write-only. |
HttpToolHeader (write): { name, value?, remove? }. Provide value to set or rotate the header. Provide name only (no value) to keep the stored value unchanged (useful for round-tripping on update). Set remove: true to delete it. Sending both value and remove: true is rejected.
HttpToolHeader (read): { name, last_set_at }. Values are never returned.
How arguments are sent: GET/DELETE → query string (stringified, null dropped); POST/PUT/PATCH → JSON body. Query params already in url are merged with the model’s arguments.
Rotating a secret: send { "name": "Authorization", "value": "Bearer <new>" } in the headers list of a PUT /v1/agents/{id}. Any other headers you want to keep, round-trip as name-only entries.
Server-side execution constraints. AssemblyAI makes the request on your behalf and enforces: https only; public hosts only (private/loopback/link-local/CGNAT IPs blocked, including obfuscated literals); redirects not followed (a 3xx is returned to the model as an error); response body capped at 8 KiB before being fed to the model; per-call timeout from timeout_seconds.
Agent object
The full record returned by create, get, and update:
{
"id": "7ad24396-b822-4dca-871a-be9cc4781cf9",
"name": "Weather Buddy",
"system_prompt": "You are Weather Buddy...",
"greeting": "Hey there, which city would you like the weather for?",
"tools": [
{
"id": "f29a0fd4-6f8b-4acb-8a74-e60f417f6dbb",
"name": "get_weather",
"description": "Fetch current weather for a geographic point...",
"http": {
"url": "https://api.open-meteo.com/v1/forecast?current=temperature_2m&temperature_unit=fahrenheit",
"http_method": "GET",
"headers": {}
},
"parameters": {
"type": "object",
"properties": {
"latitude": { "type": "number", "description": "Latitude in decimal degrees." },
"longitude": { "type": "number", "description": "Longitude in decimal degrees." }
},
"required": ["latitude", "longitude"]
},
"timeout_seconds": 30,
"execution_mode": "interactive"
}
],
"voice": { "voice_id": "ivy" },
"input": { "type": "audio", "format": { "encoding": "audio/pcm", "sample_rate": 24000 }, "keyterms": null, "turn_detection": null },
"output": { "type": "audio", "voice": "ivy", "format": { "encoding": "audio/pcm", "sample_rate": 24000 }, "volume": null },
"created_at": "2026-06-08T12:04:27.607110Z",
"updated_at": "2026-06-08T12:04:27.607113Z"
}
Tools gain a generated id. Header values are masked as "***" (here the tool has no headers, so headers is {}).
Validation errors
| Status | When | Example |
|---|
400 | A field fails a domain rule | Invalid voice 'xyz'. Must be one of: ..., tools[0].http.url: webhook URL must use https://, 'tools[0].timeout_seconds' must be between 1s and 300s |
401 | Auth failed | {"detail": "Unauthorized"} |
422 | Malformed JSON or wrong types | Standard schema-validation body naming the offending field. |
Deploy a stored agent
Once created, connect by agent_id. Over the WebSocket, bind to the agent in your first session.update:
{
"type": "session.update",
"session": { "agent_id": "7ad24396-b822-4dca-871a-be9cc4781cf9" }
}
agent_id is mutually exclusive with inline session fields (system_prompt, greeting, tools, input, output). See Deploy your agent.