---
title: "Create and manage Speak AI voice agents"
description: "Create, list, update, and delete voice agents; the avatar and voice catalogs they draw from; and generating or refining a config from a prompt."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.speakai.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and manage Speak AI voice agents


import EndpointIndex from "@/components/api/EndpointIndex.astro";

The Speak AI API exposes 9 voice agents endpoints under the base URL `https://api.speakai.co/v1`. Every request needs the `x-speakai-key` and `x-access-token` headers described in [Authentication](/api/authentication/).

Create and manage voice agents. Mutations (create/update/delete) require the OWNER or ADMIN role.

## What can you do with the voice agents endpoints?

Speak AI groups these 9 endpoints under the voice agents resource. Each entry below links to the full reference for that endpoint further down this page.

### Agents

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | [`/voice/agents`](#get-voice-agents) | List Voice Agents |
| `POST` | [`/voice/agents`](#post-voice-agents) | Create Voice Agent |
| `GET` | [`/voice/agents/{agentId}`](#get-voice-agents-agent-id) | Get Voice Agent |
| `PUT` | [`/voice/agents/{agentId}`](#put-voice-agents-agent-id) | Update Voice Agent |
| `DELETE` | [`/voice/agents/{agentId}`](#delete-voice-agents-agent-id) | Delete Voice Agent |

### Avatars

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | [`/voice/avatars`](#get-voice-avatars) | List Voice Avatars |

### Voices

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | [`/voice/voices`](#get-voice-voices) | List Voices |

### Generation

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | [`/voice/agents/generation`](#post-voice-agents-generation) | Create Agent From Prompt |
| `POST` | [`/voice/agents/{agentId}/generation/generate`](#post-voice-agents-agent-id-generation-generate) | Generate Agent Config |

## Agents

<h2 id="get-voice-agents">List Voice Agents</h2>

#### List Agents

Returns every non-deleted agent in your company, newest first, each annotated with its conversation count.

##### Response

`agents` is an array of agent objects, each with an extra `conversationCount` field: the number of conversations recorded against that agent in your company.

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X GET 'https://api.speakai.co/v1/voice/agents' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'
```

</CodePanel>

<ResponsePanel id="get-voice-agents" statuses={[{"code":"200","description":"OK"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.agents` | object[] |  |

<details>
<summary>`data.agents` -- 25 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string | Required on create. Trimmed, non-empty. |
| `personality` | string | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object |  |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |
| `conversationCount` | integer | Count of conversations recorded against this agent. |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agents": [
  {
    "agentId": "6a1b2c3d4e5f60718293a4b5",
    "companyId": "6650a1b2c3d4e5f60000000a",
    "userId": "6650a1b2c3d4e5f60000001a",
    "name": "Support Agent",
    "personality": "Friendly and concise",
    "instructions": "Help callers troubleshoot billing questions.",
    "voice": {
      "provider": "elevenlabs",
      "voiceId": "21m00Tcm4TlvDq8ikWAM"
    },
    "llm": {
      "provider": "openai",
      "model": "gpt-5.4"
    },
    "conversationMode": "voice",
    "status": "active",
    "chatSettings": {
      "language": "en",
      "maxResponseLength": 500,
      "maxSessionLength": 1800,
      "conversationStarters": [],
      "topicsToAvoid": []
    },
    "creativityLevel": 0.7,
    "isDeleted": false,
    "createdAt": "2026-09-01T12:00:00.000Z",
    "updatedAt": "2026-09-23T19:33:24.232Z",
    "conversationCount": 42
  }
]
  }
}
```

</div>

</ResponsePanel>

</div>

<h2 id="post-voice-agents">Create Voice Agent</h2>

#### Create Agent

Requires the OWNER or ADMIN role.

##### Request Body

`name`, `personality`, `instructions`, and `voice` (with `voice.provider` and `voice.voiceId`) are required. Every other agent field can be set on create.

Set `avatar.avatarId` to attach an avatar from your catalog, the server resolves `avatarUrl`, `provider`, and any Tavus ids from that catalog row itself; you cannot set those directly.

### Request body

Fields marked **required** are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string, **required** | Required on create. Trimmed, non-empty. |
| `personality` | string, **required** | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string, **required** | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object, **required** | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object | Accepted on create and, unlike most other server-managed fields, NOT stripped on update -- but its shape is not validated by the request schema (any object is accepted as-is), and no other endpoint in this API populates it from a real phone number assignment (that relationship lives on the phone number's own agentId field -- see Phone Numbers). Treat this as a legacy field: writing to it has no confirmed effect. |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X POST 'https://api.speakai.co/v1/voice/agents' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

</CodePanel>

<ResponsePanel id="post-voice-agents" statuses={[{"code":"201","description":"Created"},{"code":"400","description":"Validation failed, or avatar.avatarId does not match a catalog row"},{"code":"403","description":"Caller's role is not OWNER or ADMIN"}]}>

<div slot="status-201">

**`201` Created**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.agent` | object |  |

<details>
<summary>`data.agent` -- 24 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string | Required on create. Trimmed, non-empty. |
| `personality` | string | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object |  |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agent": {
  "agentId": "6a1b2c3d4e5f60718293a4b5",
  "companyId": "6650a1b2c3d4e5f60000000a",
  "userId": "6650a1b2c3d4e5f60000001a",
  "name": "Support Agent",
  "personality": "Friendly and concise",
  "instructions": "Help callers troubleshoot billing questions.",
  "voice": {
    "provider": "elevenlabs",
    "voiceId": "21m00Tcm4TlvDq8ikWAM"
  },
  "llm": {
    "provider": "openai",
    "model": "gpt-5.4"
  },
  "conversationMode": "voice",
  "status": "active",
  "chatSettings": {
    "language": "en",
    "maxResponseLength": 500,
    "maxSessionLength": 1800,
    "conversationStarters": [],
    "topicsToAvoid": []
  },
  "creativityLevel": 0.7,
  "isDeleted": false,
  "createdAt": "2026-09-01T12:00:00.000Z",
  "updatedAt": "2026-09-23T19:33:24.232Z"
}
  }
}
```

</div>

<div slot="status-400">

**`400` Validation failed, or avatar.avatarId does not match a catalog row**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

</div>

<div slot="status-403">

**`403` Caller's role is not OWNER or ADMIN**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "message": "You do not have permission to manage agents"
}
```

</div>

</ResponsePanel>

</div>

<h2 id="get-voice-agents-agent-id">Get Voice Agent</h2>

#### Get Agent

Fetches a single agent by id, scoped to your company. Returns 404 for an agent belonging to another company or one that has been deleted, the response does not distinguish between the two.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `agentId` | path | string | Yes |  |

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X GET 'https://api.speakai.co/v1/voice/agents/AGENT_ID' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'
```

</CodePanel>

<ResponsePanel id="get-voice-agents-agent-id" statuses={[{"code":"200","description":"OK"},{"code":"404","description":"Agent not found"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.agent` | object |  |

<details>
<summary>`data.agent` -- 24 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string | Required on create. Trimmed, non-empty. |
| `personality` | string | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object |  |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agent": {
  "agentId": "6a1b2c3d4e5f60718293a4b5",
  "companyId": "6650a1b2c3d4e5f60000000a",
  "userId": "6650a1b2c3d4e5f60000001a",
  "name": "Support Agent",
  "personality": "Friendly and concise",
  "instructions": "Help callers troubleshoot billing questions.",
  "voice": {
    "provider": "elevenlabs",
    "voiceId": "21m00Tcm4TlvDq8ikWAM"
  },
  "llm": {
    "provider": "openai",
    "model": "gpt-5.4"
  },
  "conversationMode": "voice",
  "status": "active",
  "chatSettings": {
    "language": "en",
    "maxResponseLength": 500,
    "maxSessionLength": 1800,
    "conversationStarters": [],
    "topicsToAvoid": []
  },
  "creativityLevel": 0.7,
  "isDeleted": false,
  "createdAt": "2026-09-01T12:00:00.000Z",
  "updatedAt": "2026-09-23T19:33:24.232Z"
}
  }
}
```

</div>

<div slot="status-404">

**`404` Agent not found**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `requestId` | string |  |
| `code` | integer |  |
| `message` | string |  |
| `hints` | string[] |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "code": 404,
  "message": "Agent not found",
  "hints": []
}
```

</div>

</ResponsePanel>

</div>

<h2 id="put-voice-agents-agent-id">Update Voice Agent</h2>

#### Update Agent

Requires the OWNER or ADMIN role. Partial update, send only the fields you want to change; fields left out of the body are left untouched, including `avatar`. `companyId`, `agentId`, and other immutable fields are silently dropped if sent.

If the body sets `avatar.avatarId`, the server re-resolves `avatarUrl`/`provider`/Tavus ids from the catalog row and overwrites the agent's whole avatar sub-object with that resolved value; an unresolvable `avatarId` is rejected with 400 rather than saved.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `agentId` | path | string | Yes |  |

### Request body

Fields marked **required** are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string | Required on create. Trimmed, non-empty. |
| `personality` | string | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object | Accepted on create and, unlike most other server-managed fields, NOT stripped on update -- but its shape is not validated by the request schema (any object is accepted as-is), and no other endpoint in this API populates it from a real phone number assignment (that relationship lives on the phone number's own agentId field -- see Phone Numbers). Treat this as a legacy field: writing to it has no confirmed effect. |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X PUT 'https://api.speakai.co/v1/voice/agents/AGENT_ID' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

</CodePanel>

<ResponsePanel id="put-voice-agents-agent-id" statuses={[{"code":"200","description":"OK"},{"code":"400","description":"Validation failed, or avatar.avatarId does not match a catalog row visible to your company"},{"code":"403","description":"Caller's role is not OWNER or ADMIN"},{"code":"404","description":"Agent not found"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.agent` | object |  |

<details>
<summary>`data.agent` -- 24 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one; use agentId to identify it. |
| `agentId` | string | Server-generated UUID. Immutable, sent in the body on create or update is silently dropped. |
| `companyId` | string | Stamped from the caller's authenticated company. Sending your own value on create or update is silently dropped; you cannot re-parent an agent. |
| `userId` | string | Id of the user who created the agent. |
| `name` | string | Required on create. Trimmed, non-empty. |
| `personality` | string | Required on create. Free text describing the agent's tone. Can be cleared to an empty string on update. |
| `instructions` | string | Required on create. The agent's system instructions. Can be cleared to an empty string on update. |
| `voice` | object | Required on create. |
| `voice.provider` | string, **required** | TTS provider, e.g. elevenlabs or openai. |
| `voice.voiceId` | string, **required** |  |
| `voice.model` | string |  |
| `stt` | object | Speech-to-text provider config. Shape is not validated beyond being an object. |
| `llm` | object |  |
| `llm.provider` | string | Must be one of the voice-agent LLM providers if sent. |
| `llm.model` | string | Must be one of the voice-agent model ids if sent. |
| `liveVoice` | string | The voice id used when llm.model is a Live (speech-to-speech) model. Rejected with a 400 listing the valid ids if it is not one of the voices the resolved Live model offers. Cleared by sending an empty string or null. |
| `avatar` | object | Set avatar.avatarId to attach an avatar; the server looks it up in your avatar catalog and derives avatarUrl, provider, and any provider-specific ids (e.g. Tavus faceId/palId) from that catalog row. Any of those fields sent in the request body are ignored, they cannot be set directly. An avatarId with no matching catalog row is rejected with 400, not saved. |
| `avatar.avatarId` | string | Required to set an avatar. Must match a row in your company's avatar catalog (your own uploads or the shared system catalog). |
| `avatar.avatarUrl` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.provider` | string | Read-only in practice: always overwritten from the matched catalog row. |
| `avatar.tavusFaceId` | string | Read-only: derived server-side from the catalog row's tavus.faceId. Cannot be set by the client. |
| `avatar.tavusPalId` | string | Read-only: derived server-side from the catalog row's tavus.palId. Cannot be set by the client. |
| `conversationMode` | string | One of: `voice`, `avatar` . |
| `status` | string | One of the agent status values. |
| `chatSettings` | object |  |
| `websiteUrl` | string |  |
| `enableWebSearch` | boolean |  |
| `creativityLevel` | number |  |
| `folderId` | string |  |
| `telephony` | object |  |
| `telephony.enabled` | boolean |  |
| `telephony.phoneNumberId` | string |  |
| `telephony.handoff` | object |  |
| `telephony.settings` | object |  |
| `isDeleted` | boolean | DELETE soft-deletes: this flips to true and the agent stops appearing in list/get, but the document is not removed. |
| `deletedAt` | string (date-time) |  |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`chatSettings` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `welcomeMessage` | string |  |
| `conversationStarters` | string[] |  |
| `maxResponseLength` | integer |  |
| `topicsToAvoid` | string[] |  |
| `maxSessionLength` | integer |  |
| `language` | string |  |
| `responsePace` | string | One of the response-pace presets, if set. |

</details>

<details>
<summary>`telephony.handoff` -- 3 more fields</summary>

| Field | Type |
| --- | --- |
| `enabled` | boolean |
| `targetNumber` | string |
| `summaryPrompt` | string |

</details>

<details>
<summary>`telephony.settings` -- 1 more field</summary>

| Field | Type |
| --- | --- |
| `recordCalls` | boolean |

</details>

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agent": {
  "agentId": "6a1b2c3d4e5f60718293a4b5",
  "companyId": "6650a1b2c3d4e5f60000000a",
  "userId": "6650a1b2c3d4e5f60000001a",
  "name": "Support Agent",
  "personality": "Friendly and concise",
  "instructions": "Help callers troubleshoot billing questions.",
  "voice": {
    "provider": "elevenlabs",
    "voiceId": "21m00Tcm4TlvDq8ikWAM"
  },
  "llm": {
    "provider": "openai",
    "model": "gpt-5.4"
  },
  "conversationMode": "voice",
  "status": "active",
  "chatSettings": {
    "language": "en",
    "maxResponseLength": 500,
    "maxSessionLength": 1800,
    "conversationStarters": [],
    "topicsToAvoid": []
  },
  "creativityLevel": 0.7,
  "isDeleted": false,
  "createdAt": "2026-09-01T12:00:00.000Z",
  "updatedAt": "2026-09-23T19:33:24.232Z"
}
  }
}
```

</div>

<div slot="status-400">

**`400` Validation failed, or avatar.avatarId does not match a catalog row visible to your company**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

</div>

<div slot="status-403">

**`403` Caller's role is not OWNER or ADMIN**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "message": "You do not have permission to manage agents"
}
```

</div>

<div slot="status-404">

**`404` Agent not found**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `requestId` | string |  |
| `code` | integer |  |
| `message` | string |  |
| `hints` | string[] |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "code": 404,
  "message": "Agent not found",
  "hints": []
}
```

</div>

</ResponsePanel>

</div>

<h2 id="delete-voice-agents-agent-id">Delete Voice Agent</h2>

#### Delete Agent

Requires the OWNER or ADMIN role. Soft-deletes: the agent is flagged `isDeleted` and stops appearing in list/get, but the record and its conversation history are retained.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `agentId` | path | string | Yes |  |

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X DELETE 'https://api.speakai.co/v1/voice/agents/AGENT_ID' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'
```

</CodePanel>

<ResponsePanel id="delete-voice-agents-agent-id" statuses={[{"code":"200","description":"OK"},{"code":"403","description":"Caller's role is not OWNER or ADMIN"},{"code":"404","description":"Agent not found"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "success",
  "message": "Agent deleted successfully"
}
```

</div>

<div slot="status-403">

**`403` Caller's role is not OWNER or ADMIN**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "message": "You do not have permission to manage agents"
}
```

</div>

<div slot="status-404">

**`404` Agent not found**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `requestId` | string |  |
| `code` | integer |  |
| `message` | string |  |
| `hints` | string[] |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "code": 404,
  "message": "Agent not found",
  "hints": []
}
```

</div>

</ResponsePanel>

</div>

## Avatars

<h2 id="get-voice-avatars">List Voice Avatars</h2>

#### List Avatars

Returns the shared system avatar catalog plus any avatars your company has uploaded, newest first. Another company's private uploads are never included.

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X GET 'https://api.speakai.co/v1/voice/avatars' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'
```

</CodePanel>

<ResponsePanel id="get-voice-avatars" statuses={[{"code":"200","description":"OK"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.avatars` | object[] |  |

<details>
<summary>`data.avatars` -- 22 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `avatarId` | string | Server-generated UUID on create. Immutable. |
| `companyId` | string | Absent on a shared system-catalog avatar, which every company can read but only Speak can write. Present on a company's own uploaded avatar. |
| `name` | string | Required on create. |
| `provider` | string | Required on create. One of: `bey`, `tavus`, `heygen`, `synthesia`, `d-id` . |
| `imageUrl` | string |  |
| `videoUrl` | string |  |
| `localImagePath` | string | Local file path used by internal upload flows. |
| `localVideoPath` | string | Local file path used by internal upload flows. |
| `tavus` | object | Set when provider is tavus. |
| `bey` | object | Set when provider is bey. |
| `custom` | object | Set when provider is a custom/self-hosted avatar. |
| `custom.uploadedBy` | string |  |
| `custom.originalFileName` | string |  |
| `custom.fileSize` | number |  |
| `custom.mimeType` | string |  |
| `category` | string | Defaults to professional if not sent. One of: `professional`, `casual`, `stock`, `custom`, `premium` . |
| `tags` | string[] |  |
| `isPremium` | boolean | Defaults to false. |
| `status` | string | Defaults to active. One of: `active`, `inactive`, `processing`, `error` . |
| `isPublic` | boolean | Defaults to true. |
| `description` | string |  |
| `metadata` | object | Arbitrary metadata object. Shape is not validated. |
| `createdBy` | string | Id of the user who created this avatar. |
| `lastSyncedAt` | string (date-time) | Set by the system-catalog sync job for shared avatars. |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

<details>
<summary>`tavus` -- 7 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `faceId` | string | Required within this object if tavus is sent at all. |
| `palId` | string |  |
| `status` | string | One of: `started`, `completed`, `error` . |
| `isRecommended` | boolean |  |
| `trainingProgress` | string |  |
| `faceType` | string | One of: `user`, `system` . |
| `modelType` | string |  |

</details>

<details>
<summary>`bey` -- 8 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `avatarId` | string | Required within this object if bey is sent at all. |
| `gender` | string |  |
| `ethnicity` | string |  |
| `voiceId` | string |  |
| `ageRange` | string |  |
| `style` | string |  |
| `isDefault` | boolean |  |
| `languageCode` | string |  |

</details>

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"avatars": [
  {
    "avatarId": "r55e6793f10f",
    "name": "Rae",
    "provider": "tavus",
    "imageUrl": "https://cdn.replica.tavus.io/still.png",
    "tavus": {
      "faceId": "r55e6793f10f",
      "palId": "p135a0c8e542"
    },
    "category": "professional",
    "isPremium": false,
    "status": "active",
    "isPublic": true,
    "createdAt": "2026-08-01T12:00:00.000Z",
    "updatedAt": "2026-08-01T12:00:00.000Z"
  }
]
  }
}
```

</div>

</ResponsePanel>

</div>

## Voices

<h2 id="get-voice-voices">List Voices</h2>

#### List Voices

Lists the voices visible to your company: Speak's shared system catalog, plus your own company's uploaded voices, whether public or not.

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X GET 'https://api.speakai.co/v1/voice/voices' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'
```

</CodePanel>

<ResponsePanel id="get-voice-voices" statuses={[{"code":"200","description":"OK"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |
| `data.voices` | object[] |  |
| `data.total` | integer |  |

<details>
<summary>`data.voices` -- 23 more fields</summary>

| Field | Type | Description |
| --- | --- | --- |
| `_id` | string | MongoDB document id, included because every document has one. Not used to identify a voice anywhere in this API -- use voiceId for that. |
| `voiceId` | string | Client-supplied on create if you send one, otherwise server-generated. The identifier for this voice everywhere in this API (unlike most resources, this catalog has no separate server-generated UUID). |
| `companyId` | string | Absent on a shared system-catalog voice, which every company can read but only Speak can write. Present on a company's own uploaded voice. Not settable directly. |
| `createdBy` | string | Id of the user who created this voice. Not settable directly. |
| `name` | string | Required on create. |
| `provider` | string | Required on create. One of: `elevenlabs`, `openai`, `deepgram`, `cartesia`, `google`, `azure` . |
| `language` | string | Defaults to English. |
| `languageCode` | string | Defaults to en. |
| `gender` | string | One of: `male`, `female`, `neutral` . |
| `age` | string | One of: `young`, `middle-aged`, `old` . |
| `accent` | string |  |
| `description` | string |  |
| `previewUrl` | string |  |
| `category` | string | Defaults to premade. One of: `premade`, `cloned`, `custom`, `professional`, `conversational` . |
| `tags` | string[] |  |
| `isPremium` | boolean | Defaults to false. |
| `isMultilingual` | boolean | Defaults to false. |
| `isPublic` | boolean | Defaults to true. |
| `status` | string | Defaults to active. One of: `active`, `inactive`, `processing`, `error` . |
| `providerData` | object | Arbitrary provider-specific data. Shape is not validated. |
| `metadata` | object | Arbitrary metadata object. Shape is not validated. |
| `createdAt` | string (date-time) |  |
| `updatedAt` | string (date-time) |  |

</details>

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"voices": [
  {
    "_id": "6650a1b2c3d4e5f678901234",
    "voiceId": "b2c3d4e5-f678-9012-abcd-ef1234567890",
    "createdBy": "user-1",
    "name": "Rachel",
    "provider": "elevenlabs",
    "language": "English",
    "languageCode": "en",
    "gender": "female",
    "category": "premade",
    "tags": [],
    "isPremium": false,
    "isMultilingual": true,
    "isPublic": true,
    "status": "active",
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-01-01T00:00:00.000Z"
  }
],
"total": 1
  }
}
```

</div>

</ResponsePanel>

</div>

## Generation

<h2 id="post-voice-agents-generation">Create Agent From Prompt</h2>

#### Create Agent From Prompt

Requires the OWNER or ADMIN role. Prompt-first creation: creates a new agent server-side with placeholder config, then runs the same generation flow as Generate Agent Config against it. Response always includes the new agentId, plus either an agent key (generation succeeded) or needsFollowUp: true (prompt needs more detail) -- see Generate Agent Config for both shapes.

### Request body

Fields marked **required** are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.

| Field | Type | Description |
| --- | --- | --- |
| `prompt` | string, **required** | Required. Describe the agent you want in a sentence or two. |
| `name` | string |  |
| `manualInstructions` | string | Skips the thin-prompt follow-up question and generates immediately, folding this in as additional context. |

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X POST 'https://api.speakai.co/v1/voice/agents/generation' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

</CodePanel>

<ResponsePanel id="post-voice-agents-generation" statuses={[{"code":"201","description":"Created"},{"code":"400","description":"Validation failed"},{"code":"403","description":"Caller's role is not OWNER or ADMIN"}]}>

<div slot="status-201">

**`201` Created**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"agent": {
  "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Support Agent"
}
  }
}
```

</div>

<div slot="status-400">

**`400` Validation failed**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

</div>

<div slot="status-403">

**`403` Caller's role is not OWNER or ADMIN**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "message": "You do not have permission to manage agents"
}
```

</div>

</ResponsePanel>

</div>

<h2 id="post-voice-agents-agent-id-generation-generate">Generate Agent Config</h2>

#### Generate Agent Config

Requires the OWNER or ADMIN role. Runs the prompt-to-config pipeline against an existing agent: extracts a config from prompt via the LLM, and either persists it onto the agent (response has an agent key), or, when the prompt is too thin to act on and manualInstructions was not sent, returns a follow-up question instead (response has needsFollowUp: true) so you can call this again with more detail.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `agentId` | path | string | Yes |  |

### Request body

Fields marked **required** are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.

| Field | Type | Description |
| --- | --- | --- |
| `prompt` | string, **required** | Required. Describe the agent you want in a sentence or two. |
| `name` | string |  |
| `manualInstructions` | string | Skips the thin-prompt follow-up question and generates immediately, folding this in as additional context. |

<div slot="code">

<CodePanel label="Example request">

```bash
curl -X POST 'https://api.speakai.co/v1/voice/agents/AGENT_ID/generation/generate' \
  -H 'x-speakai-key: sk_test_speak_0000000000000000' \
  -H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

</CodePanel>

<ResponsePanel id="post-voice-agents-agent-id-generation-generate" statuses={[{"code":"200","description":"OK"},{"code":"400","description":"Validation failed"},{"code":"403","description":"Caller's role is not OWNER or ADMIN"},{"code":"404","description":"Agent not found"}]}>

<div slot="status-200">

**`200` OK**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `success` . |
| `data` | object |  |

Example response, `application/json`.

```json
{
  "status": "success",
  "data": {
"agent": {
  "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Support Agent"
}
  }
}
```

</div>

<div slot="status-400">

**`400` Validation failed**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

</div>

<div slot="status-403">

**`403` Caller's role is not OWNER or ADMIN**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `message` | string |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "message": "You do not have permission to manage agents"
}
```

</div>

<div slot="status-404">

**`404` Agent not found**

Response body, `application/json`.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | One of: `failed` . |
| `requestId` | string |  |
| `code` | integer |  |
| `message` | string |  |
| `hints` | string[] |  |

Example response, `application/json`.

```json
{
  "status": "failed",
  "code": 404,
  "message": "Agent not found",
  "hints": []
}
```

</div>

</ResponsePanel>

</div>

## Related pages

- [API reference](/api/) for the base URL, authentication, and the error format.
- [Authenticate with the Speak AI API using access tokens](/api/authentication/)
- [Upload audio and video to Speak AI and read insights](/api/media/)
- [Create and update live transcription sessions](/api/live-transcription/)
- [Analyze text notes with the Speak AI text endpoints](/api/text/)

Get an API key on the [Speak AI developer page](https://app.speakai.co/developers?utm_source=docs&utm_medium=referral&utm_campaign=api-reference&utm_content=api-voice-agents/agents).

Source: https://docs.speakai.co/api/voice-agents/agents/index.mdx
