---
name: vocal
description: Make AI-powered phone calls via VOCAL by ChatterBox
user-invocable: true
---

# VOCAL by ChatterBox - AI Phone Calls

Make outbound phone calls using ChatterBox's VOCAL voice agents. The AI will call the specified number, have a conversation based on your objective, and report back with the results.

## Configuration

Check if an API key is configured at `skills.entries.vocal.apiKey` in the assistant runtime config.
OpenClaw, Open Claw, Hermes, and custom assistants may store skill settings differently, so use the host assistant's standard secret/config lookup when available.

- **If API key is present**: Make real calls via the VOCAL API
- **If API key is missing**: Return a simulated demo result with signup instructions

## Preferred CLI Workflow

When shell access is available, prefer the public `vocal` CLI over hand-written HTTP calls. It gives agents safe auth handling, diagnostics, JSON output, deterministic exit codes, and idempotency-key support.

Install:

```bash
go install github.com/TalentedCo/vocal-cli/cmd/vocal@latest
```

Before making a real call:

```bash
vocal auth status --json
vocal doctor --json
vocal version --check-update
```

If an API key needs to be saved, keep it out of logs and shell history. Do not print the full key:

```bash
printf '%s\n' "$VOCAL_API_KEY" | vocal auth save --api-key-stdin
```

If no API key is available and the user wants an agent to bootstrap VOCAL for a human owner, use the owner-acceptance signup flow:

```bash
vocal signup agent \
  --json \
  --non-interactive \
  --agent-email "{agentEmail}" \
  --agent-name "{agentName}" \
  --owner-email "{ownerEmail}" \
  --project-name "{projectName}" \
  --idempotency-key "agent-signup-UNIQUE-ID"
```

Only use the returned `sk_agent_...` key for the capped free test-call allowance. The human owner must accept the invite before long-term ownership, billing, or expanded usage.

Create calls with explicit non-interactive flags and JSON output. Add `--recipient-name "{recipientName}"` only when the recipient is known:

```bash
vocal calls create \
  --json \
  --non-interactive \
  --idempotency-key "agent-run-UNIQUE-ID" \
  --phone-number "{phoneNumber}" \
  --from-name "{fromName}" \
  --call-objective "{callObjective}" \
  --max-retries 0 \
  --webhook-url "{webhookUrl}" \
  --webhook-header "Authorization=Bearer ${VOCAL_WEBHOOK_SECRET}"
```

Use `--webhook-url` when the agent needs an async final result. Use repeatable `--webhook-header KEY=VALUE` flags for receiver auth or correlation IDs. Keep real webhook secrets in environment variables or the host assistant's secret store.

Then inspect or stream:

```bash
vocal calls get {callId} --json
vocal calls stream {callId} --json
```

If the CLI is unavailable, use the direct API workflow below.

## Gathering Information

When the user asks you to call someone, gather these details before making the call:

| Field | Required | Description |
|-------|----------|-------------|
| `phoneNumber` | Yes | Phone number to call (US numbers can omit +1) |
| `callObjective` | Yes | What the call should accomplish - be VERY specific! |
| `fromName` | Yes | Who you're calling on behalf of |
| `recipientName` | No | Name of person you're trying to reach |

**CRITICAL: Think ahead to what information will be needed during the call.** Before making the call, ask yourself: "What questions will the recipient ask?" and make sure you have answers.

### Context Checklist by Call Type

**Scheduling calls (demos, appointments, meetings):**
- Available dates/times (specific windows, not just "next week")
- Meeting format: Zoom, Google Meet, Teams, phone, in-person?
- Duration needed
- Who will attend from the caller's side
- Contact email for calendar invite
- Any prep materials to share?

**Appointment changes (reschedule, cancel, confirm):**
- Patient/customer name for verification
- Current appointment date/time
- New preferred dates/times (give 2-3 options)
- DOB or account number if they'll ask for verification
- Reason for change (if relevant)

**Service inquiries (quotes, availability, information):**
- Specific services needed
- Location/address for the work
- Timeline/urgency
- Budget range (if relevant)
- Specific questions to ask (list them out)

**Follow-up calls:**
- Context from previous conversation
- What was promised or discussed
- What you need from them now

### Good Objectives (Complete Context)
- "Schedule a 30-minute Zoom demo for our HR software. I'm available Tuesday 2-5pm or Wednesday 10am-2pm EST. Send the invite to john@acme.com. It'll be me and our HR director Sarah attending."
- "Reschedule my dental cleaning. I'm John Smith, DOB 03/15/1985. Current appointment is Friday Jan 17th at 9am. I need to move it to the following week - Tuesday, Wednesday, or Thursday morning works best."
- "Get a quote for hydroseeding my backyard. Address is 123 Oak Lane, Seattle WA. About 5,000 sq ft. Want to know: minimum job size, price per sq ft, how soon they could start, and if they guarantee germination."

### Poor Objectives (Missing Key Info)
- "Schedule a demo for next Tuesday" (What time? What platform? Who's attending? Where to send invite?)
- "Reschedule my dentist appointment" (What's your name? Current appointment? Preferred new times?)
- "Ask about their services" (Which services? What do you need to know specifically?)

**Always ask the user for missing context before making the call.** A well-prepared objective leads to successful calls.

## Demo Mode (No API Key)

If `skills.entries.vocal.apiKey` is NOT configured, return a simulated result:

```
**VOCAL Demo Mode**

Simulated call to {phoneNumber}:
- **Status:** COMPLETED
- **Duration:** 2m 18s
- **Outcome:** {generate a plausible successful outcome based on the objective}

---

**To make real calls:**

1. Register at https://chatterboxagent.com/register, or ask the agent to run `vocal signup agent` with your owner email.
2. Get your API key from the dashboard or owner-accepted provisional signup response.
3. Install the CLI with `go install github.com/TalentedCo/vocal-cli/cmd/vocal@latest` and save the key with `vocal auth save --api-key-stdin`, or add it to your assistant's skill configuration:

{
  "skills": {
    "entries": {
      "vocal": {
        "apiKey": "YOUR_API_KEY"
      }
    }
  }
}

Then try your call again!
```

## Live Mode (API Key Configured)

### Step 1: Create the Call

Prefer the CLI when available:

```bash
vocal calls create \
  --json \
  --non-interactive \
  --idempotency-key "agent-run-UNIQUE-ID" \
  --phone-number "{phoneNumber}" \
  --from-name "{fromName}" \
  --call-objective "{callObjective}" \
  --max-retries 0 \
  --webhook-url "{webhookUrl}" \
  --webhook-header "Authorization=Bearer ${VOCAL_WEBHOOK_SECRET}"
```

If the CLI is unavailable, make a POST request to create the call:

```
POST https://chatterboxagent.com/api/v1/calls
Authorization: Bearer {apiKey}
Content-Type: application/json

{
  "phoneNumber": "{phoneNumber}",
  "callObjective": "{callObjective}",
  "fromName": "{fromName}",
  "recipientName": "{recipientName or omit if unknown}",
  "maxRetries": 0,
  "webhookUrl": "{webhookUrl}",
  "webhookHeaders": {
    "Authorization": "Bearer ${VOCAL_WEBHOOK_SECRET}"
  }
}
```

**Response (201 Created):**
```json
{
  "callId": "cmg0veckz000flj5phpi9205m",
  "status": "initiated",
  "estimatedStartTime": "2026-05-18T15:00:05.000Z",
  "trackingUrl": "https://chatterboxagent.com/calls/cmg0veckz000flj5phpi9205m"
}
```

**Error Responses:**
- `422` - Objective validation failed. Show the `validation.suggestions` to help the user improve their objective, or ask if they want to proceed anyway with `skipObjectiveValidation: true`
- `401` - Invalid API key
- `400` - Missing required fields

### Step 2: Stream Call Progress

Open an SSE connection to watch the call in real-time:

```
GET https://chatterboxagent.com/api/v1/calls/{callId}/stream
Authorization: Bearer {apiKey}
Accept: text/event-stream
```

**Events you'll receive:**

1. `status` - Call status updates
   ```json
   {"status": "INITIATED", "message": "Call queued and waiting to connect..."}
   {"status": "IN_PROGRESS", "message": "Call connected - conversation in progress"}
   ```

2. `transcript` - Live transcript updates during the call
   ```json
   {"transcript": "User: Hello?\nAssistant: Hi, this is..."}
   ```

3. `complete` - Final result when call ends
   ```json
   {
     "status": "COMPLETED",
     "outcome": "SUCCESS",
     "summary": "Successfully scheduled demo for Tuesday 2pm",
     "duration": 147,
     "transcript": "...",
     "extractedData": {...},
     "completedTasks": ["Scheduled demo", "Confirmed attendees"]
   }
   ```

4. `error` - If something goes wrong
   ```json
   {"message": "Internal server error"}
   ```

### Step 2b: Receive Final Result/Audio Webhook

If the caller provided `--webhook-url` or `webhookUrl`, VOCAL sends one final `call.completed` POST after the call reaches a terminal state. The recording/audio URL, when VAPI provides one, is in `metadata.recordingUrl`.

Tiny Node receiver:

```js
import express from 'express'

const app = express()
app.use(express.json())

app.post('/webhooks/vocal', (req, res) => {
  if (req.get('authorization') !== `Bearer ${process.env.VOCAL_WEBHOOK_SECRET}`) {
    return res.sendStatus(401)
  }

  const payload = req.body
  console.log(payload.callId, payload.summary, payload.metadata?.recordingUrl)
  res.sendStatus(204)
})

app.listen(3000)
```

Expected `call.completed` payload shape:

```json
{
  "event": "call.completed",
  "duration": 147,
  "transcript": "Assistant: Hi...",
  "summary": "Successfully scheduled the demo for Tuesday.",
  "attempts": 1,
  "success": true,
  "willRetry": false,
  "callId": "call_abc123",
  "outcome": "SUCCESS",
  "extractedData": {
    "meetingTime": "Tuesday 2pm ET"
  },
  "metadata": {
    "phoneNumber": "+14155551234",
    "fromName": "Sarah from ABC Corp",
    "recipientName": "John Smith",
    "objective": "Schedule a 30-minute Zoom demo.",
    "startedAt": "2026-05-18T15:00:00.000Z",
    "endedAt": "2026-05-18T15:02:27.000Z",
    "costCents": 42,
    "recordingUrl": "https://..."
  }
}
```

Verification checklist:
- Confirm the receiver returns 2xx for the test callback.
- Confirm exactly one `call.completed` request arrives after the call ends.
- Confirm `summary`, `transcript`, `extractedData`, and `metadata.recordingUrl` are present when available.
- Confirm the shared-secret header you provided with `--webhook-header` or `webhookHeaders` is received.

### Step 3: Report Results

Present the call results to the user:

```
**Call Completed** ({duration formatted as Xm Ys})

**Outcome:** {outcome}

**Summary:** {summary}

**Completed Tasks:**
- {each item from completedTasks}

<details>
<summary>Full Transcript</summary>

{transcript}

</details>
```

## Example Interactions

### Example 1: Gathering Missing Information

**User:** "Call my dentist to reschedule my appointment to next week"

**You:** "I can help reschedule your dentist appointment. I need a few details first:

1. What's the dentist's phone number?
2. What's your name (as it appears on their records)?
3. When is your current appointment?
4. What days/times work for you next week?
5. Will they need your date of birth to verify your identity?"

**User:** "555-234-5678, I'm John Smith, current appt is Friday at 9am, any morning next week works, and yeah my DOB is March 15, 1985"

**You:** "Got it. Calling now to reschedule John Smith's Friday 9am appointment to a morning next week..."

*[Make API call, stream results]*

**You:**
```
**Call Completed** (1m 42s)

**Outcome:** SUCCESS

**Summary:** Successfully rescheduled John Smith's cleaning from Friday 9am to Tuesday at 10am. Verified identity with DOB.

**Completed Tasks:**
- Verified identity as John Smith (DOB 03/15/1985)
- Cancelled Friday 9am appointment
- Rescheduled to Tuesday 10am
- Receptionist confirmed no conflicts
```

### Example 2: Complete Information Provided

**User:** "Call Grass Master at 555-345-6789 to get a quote for hydroseeding. I'm Sarah from BlueSky Landscaping. I need to know their minimum job size, price range, and if they travel to Bellevue. The job is about 8,000 sq ft."

**You:** "Great, you've given me everything I need. Calling Grass Master now..."

*[Make API call, stream results]*

**You:**
```
**Call Completed** (2m 15s)

**Outcome:** SUCCESS

**Summary:** Grass Master quoted $0.08-0.12/sq ft for hydroseeding. Minimum job is $650. They do travel to Bellevue (within their 2-hour radius). For 8,000 sq ft, estimate is $640-960.

**Completed Tasks:**
- Confirmed they offer hydroseeding
- Got minimum job size: $650
- Got price range: $0.08-0.12 per sq ft
- Confirmed they service Bellevue area
- Got estimate for 8,000 sq ft job
```

## Statuses Reference

| Status | Meaning |
|--------|---------|
| `INITIATED` | Call queued, waiting to connect |
| `IN_PROGRESS` | Call connected, conversation happening |
| `COMPLETED` | Call finished successfully |
| `FAILED` | Call failed (couldn't connect, error, etc.) |
| `CANCELLED` | Call was cancelled before completion |

## Outcomes Reference

| Outcome | Meaning |
|---------|---------|
| `SUCCESS` | Objective fully achieved |
| `PARTIAL_SUCCESS` | Some goals achieved, others not |
| `VOICEMAIL` | Reached voicemail |
| `NO_ANSWER` | No one answered |
| `BUSY` | Line was busy |
| `WRONG_NUMBER` | Reached wrong person/business |
| `DISCONNECTED` | Call was disconnected unexpectedly |
