---
title: "Start a built-in Voice AI outbound call"
description: "Served by the standalone Voice API. Requires a Bearer session for a workspace owner/admin/manager when VOICE_AI_TELEPHONY_TURN_CONTROL_V2 is enabled. Legacy flag-off access is preserved. Internal callers may instead send X-Voice-Service-Time (Unix milliseconds) and X-Voice-Service-Signature (hex HMAC-SHA256). The signature binds the method, full route including workspace, and JSON body, and expires after 60 seconds. Every non-queue dial checks outbound enablement, workspace DNC/suppressions, phone opt-out and contact-local calling hours before the dial-cap claim or Twilio. Refusals return 409 refused_compliance; flow callers skip the step. AI queue calls keep their existing SQL compliance gate. Workspace lease capacity applies to both paths; the outbound share defaults to 70 percent and preserves at least one inbound slot when the total is two or more."
---

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

Path: Channel Automation API › Workspaces

`POST /api/workspaces/{workspaceId}/voice-ai/outbound-call`

Served by the standalone Voice API. Requires a Bearer session for a workspace owner/admin/manager when VOICE_AI_TELEPHONY_TURN_CONTROL_V2 is enabled. Legacy flag-off access is preserved. Internal callers may instead send X-Voice-Service-Time (Unix milliseconds) and X-Voice-Service-Signature (hex HMAC-SHA256). The signature binds the method, full route including workspace, and JSON body, and expires after 60 seconds. Every non-queue dial checks outbound enablement, workspace DNC/suppressions, phone opt-out and contact-local calling hours before the dial-cap claim or Twilio. Refusals return 409 refused_compliance; flow callers skip the step. AI queue calls keep their existing SQL compliance gate. Workspace lease capacity applies to both paths; the outbound share defaults to 70 percent and preserves at least one inbound slot when the total is two or more.

## Authentication

- `BearerAuth`, http, header `Authorization`, format `JWT`

## Path parameters

- `workspaceId` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.path.workspaceId`; string, required)

## Header parameters

- `X-Voice-Service-Time` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.header.X-Voice-Service-Time`; string, optional) — Required with service signature; Unix milliseconds.
- `X-Voice-Service-Signature` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.header.X-Voice-Service-Signature`; string, optional) — Service request HMAC; alternative to Bearer authentication.

## Request body

- `to` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.to`; string, optional) — Required destination phone number.
- `from` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.from`; string, optional) — Optional active workspace caller ID.
- `contact_id` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.contact_id`; string, optional) — Contact in this workspace; when omitted, resolve by destination phone.
  - format `uuid`
- `voice_agent_id` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.voice_agent_id`; string, optional, nullable) — Explicit voice profile; otherwise the workspace default. Caller-ID assignments are inbound only. Unavailable explicit picks refuse before any lease or dial claim. VOICE_AGENT_ROUTING=default_only overrides picks and logs agent_fallback.
  - format `uuid`
- `idempotency_key` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.idempotency_key`; string, optional) — Stable run and node identity. Repeat requests return the existing request and CallSid without another dial. Omitted keys create independent requests.
  - maxLength 300
- `ai_queue_attempt_id` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.ai_queue_attempt_id`; string, optional) — Internal AI queue attempt; requires matching contact_id and call_queue_id.
  - format `uuid`
- `context` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.context`; string, optional) — Call context capped at 6000 characters.
- `firstMessage` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.firstMessage`; string, optional) — Opening message capped at 220 characters.

## Example request

```json
{
  "to": "string",
  "from": "string",
  "contact_id": "f245e2a3-e141-4d41-912c-b1c1f5bd5598",
  "voice_agent_id": "2fc8d568-5ce2-4e51-baa8-38c877b5dd08",
  "idempotency_key": "string",
  "ai_queue_attempt_id": "b1ac45e4-94c1-48df-b5e4-4b4adffae504",
  "context": "string",
  "firstMessage": "string"
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url 'https://api.channelautomation.com/api/workspaces/<workspaceId>/voice-ai/outbound-call' \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "to": "string",
  "from": "string",
  "contact_id": "f245e2a3-e141-4d41-912c-b1c1f5bd5598",
  "voice_agent_id": "2fc8d568-5ce2-4e51-baa8-38c877b5dd08",
  "idempotency_key": "string",
  "ai_queue_attempt_id": "b1ac45e4-94c1-48df-b5e4-4b4adffae504",
  "context": "string",
  "firstMessage": "string"
}
'
```

### TypeScript

```typescript
const url = 'https://api.channelautomation.com/api/workspaces/<workspaceId>/voice-ai/outbound-call';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json', Authorization: 'Bearer <token>'},
  body: JSON.stringify({
    to: 'string',
    from: 'string',
    contact_id: 'f245e2a3-e141-4d41-912c-b1c1f5bd5598',
    voice_agent_id: '2fc8d568-5ce2-4e51-baa8-38c877b5dd08',
    idempotency_key: 'string',
    ai_queue_attempt_id: 'b1ac45e4-94c1-48df-b5e4-4b4adffae504',
    context: 'string',
    firstMessage: 'string'
  })
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://api.channelautomation.com/api/workspaces/<workspaceId>/voice-ai/outbound-call"

payload = {
    "to": "string",
    "from": "string",
    "contact_id": "f245e2a3-e141-4d41-912c-b1c1f5bd5598",
    "voice_agent_id": "2fc8d568-5ce2-4e51-baa8-38c877b5dd08",
    "idempotency_key": "string",
    "ai_queue_attempt_id": "b1ac45e4-94c1-48df-b5e4-4b4adffae504",
    "context": "string",
    "firstMessage": "string"
}
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer <token>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
```

## Responses

### 200

Started or existing call, including request_id, call_sid and resolved_voice_agent_id (nullable). routing_mode=default_only and agent_fallback=true identify an infrastructure override.

### 202

Existing request has no confirmed CallSid yet. Do not create another request to retry an uncertain dial.

### 400

Invalid call or preview parameters

### 401

Missing or invalid session

### 403

Workspace owner/admin/manager required

### 404

Requested resource not found

### 409

Refused before dialing by compliance, voice_agent_unavailable, or the existing AI queue SQL gate

#### Example

```json
{
  "success": false,
  "status": "refused_compliance",
  "reason": "do_not_call"
}
```

- `success` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.response.409.success`; boolean, optional)
  - example `false`
- `status` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.response.409.status`; string, optional)
  - example `"refused_compliance"`
- `reason` (`post/api/workspaces/workspaceId/voice-ai/outbound-call.response.409.reason`; string, optional)
  - example `"do_not_call"`

### 429

Workspace outbound capacity or per-contact AI dial cap reached

### 500

Operation failed

### 503

Access verification or service unavailable


Source: https://developer.channelautomations.com/api/post-api-workspaces-workspace-id-voice-ai-outbound-call/index.md
