Create an agent
POST /api/v1/agents — create a saved agent from a full AgentConfig body.
POST /api/v1/agentsCreate a saved agent. The request body is an Agent Config — there is no wrapper object. The returned uuid is the value you pass as agentId to POST /call.
Authentication
X-API-Key: zsk_...Request body
The full field reference lives on the Agent Config schema page. The short version:
| You must send | Everything else |
|---|---|
name (1–120 chars), llm: { provider, model }, stt: { provider }, and tts: { provider } | Optional — defaults are filled in: English, per-provider default models and voices, an auto-seeded end_call tool, and every knob at its platform default. |
One extra field exists only on this endpoint and PATCH, as a sibling of the config fields:
| Field | Type | Notes |
|---|---|---|
webhookSecret | string | Write-only. Popped off before validation and stored as the HMAC key for outbound webhook signatures. Never returned by any endpoint. |
Strict validation
Unknown or misspelled keys anywhere in the body are rejected (400) with the exact field path. If a request fails, the details array tells you precisely which field and why.
Response
201 Created with an agent envelope — server-managed metadata wrapping the full validated config. The config you get back is the resolved form: every default materialized, message lists cleaned, and the end_call tool present even if you didn't send one.
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"status": "active",
"createdAt": "2026-08-06T10:30:00Z",
"updatedAt": "2026-08-06T10:30:00Z",
"config": {
"name": "Sales bot",
"description": "Qualifies B2B SaaS leads.",
"languages": ["en"],
"systemPrompt": "You qualify inbound leads. Ask about company size and budget. Be concise.",
"timezone": "Asia/Kolkata",
"greeting": {
"firstMessages": ["Hi! Are you looking to evaluate our product?"],
"interruptible": false,
"speakFirst": "agent",
"agentDelayS": 0.0,
"userTimeoutS": 3.0
},
"llm": { "provider": "openai", "model": "gpt-5.4-mini", "temperature": 1.0, "maxTokens": 251, "prewarm": true },
"stt": { "provider": "soniox", "model": null, "interruptionMinWords": 0, "...": "per-provider blocks" },
"tts": { "provider": "elevenlabs", "model": null, "voice": null, "...": "per-provider blocks" },
"tools": [
{
"type": "endCall",
"name": "end_call",
"description": "End the call when the user says goodbye, asks to stop, or the conversation is clearly finished.",
"config": { "messageType": "custom", "customMessages": ["Goodbye!"], "audioRecordingId": null }
}
],
"...": "all remaining config sections at their defaults"
}
}The agent is addressed by uuid in every CRUD path; there is no integer id. webhookSecret is never present in the envelope.
Examples
A minimal create — everything not sent lands on defaults:
curl -X POST https://dashboard.zoxa.ai/api/v1/agents \
-H "X-API-Key: zsk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Sales bot",
"description": "Qualifies B2B SaaS leads.",
"systemPrompt": "You qualify inbound leads. Ask about company size and budget. Be concise.",
"greeting": { "firstMessages": ["Hi! Are you looking to evaluate our product?"] },
"llm": { "provider": "openai", "model": "gpt-5.4-mini" },
"stt": { "provider": "soniox" },
"tts": { "provider": "elevenlabs" }
}'const res = await fetch("https://dashboard.zoxa.ai/api/v1/agents", {
method: "POST",
headers: { "X-API-Key": "zsk_...", "Content-Type": "application/json" },
body: JSON.stringify({
name: "Sales bot",
description: "Qualifies B2B SaaS leads.",
systemPrompt: "You qualify inbound leads. Ask about company size and budget. Be concise.",
greeting: { firstMessages: ["Hi! Are you looking to evaluate our product?"] },
llm: { provider: "openai", model: "gpt-5.4-mini" },
stt: { provider: "soniox" },
tts: { provider: "elevenlabs" },
}),
});
const agent = await res.json();
console.log("Use this with POST /call:", agent.uuid);import httpx
resp = httpx.post(
"https://dashboard.zoxa.ai/api/v1/agents",
headers={"X-API-Key": "zsk_..."},
json={
"name": "Sales bot",
"description": "Qualifies B2B SaaS leads.",
"systemPrompt": "You qualify inbound leads. Ask about company size and budget. Be concise.",
"greeting": {"firstMessages": ["Hi! Are you looking to evaluate our product?"]},
"llm": {"provider": "openai", "model": "gpt-5.4-mini"},
"stt": {"provider": "soniox"},
"tts": {"provider": "elevenlabs"},
},
)
agent = resp.json()
print("Use this with POST /call:", agent["uuid"])A fuller create — picking voice, transcriber, languages, and a tool:
{
"name": "Hinglish support",
"systemPrompt": "You help {{customer_name}} with orders placed on Acme.",
"languages": ["en", "hi"],
"llm": { "provider": "qwen", "model": "qwen-flash" },
"stt": { "provider": "soniox", "interruptionMinWords": 0 },
"tts": { "provider": "sarvam", "voice": "ishita" },
"greeting": {
"firstMessages": ["Hi {{customer_name}}!", "Hello, thanks for calling Acme!"]
},
"tools": [
{
"type": "function",
"name": "check_order_status",
"description": "Look up the caller's order status by order id.",
"config": {
"server": { "url": "https://api.acme.com/orders/status", "method": "POST" },
"parameters": {
"type": "object",
"properties": { "order_id": { "type": "string" } },
"required": ["order_id"]
},
"messages": ["Let me pull that up for you."]
}
}
],
"contextVariables": { "customer_name": "there" },
"webhook": { "url": "https://your.app/zoxa-hook" }
}Errors
| Status | Shape | When |
|---|---|---|
400 | { "detail": { "error": "invalid_agent_config", "details": [ … ] } } | Any config validation failure — unknown field, bad range, unsupported language, invalid tool. |
400 | { "detail": "No organization selected" } | API key has no organization context. |
Each entry in details carries the failing path and a human message:
{
"detail": {
"error": "invalid_agent_config",
"details": [
{ "loc": ["llm", "temperature"], "msg": "Input should be less than or equal to 2", "type": "less_than_equal" },
{ "loc": ["languages"], "msg": "Value error, unknown language ids: ['xx']; valid ids: […]", "type": "value_error" }
]
}
}Related
GET /agents— listPATCH /agents/{agent_uuid}— partial update (deep-merge)POST /agents/preview-snapshot— resolve a config without saving- Agent Config schema — full field reference