Build a voice for your OpenClaw bot.

Give your bot (an OpenClaw bot, a script, anything) the power to make real phone calls in a personality you define. Create an account, tell Workforce Wave who your bot is, add a card, and it starts calling.

API base used throughout: https://wfw-admin.vercel.app

1. The one thing to understand: your bot defines the voice agent.

Workforce Wave gives every account a voice agent. Your bot supplies that agent's identity by sending a personality and a greeting. The agent's name, its job, its tone, and its rules all live inside those two fields. Changing the bot is just sending new text.

If you run an OpenClaw bot, you literally hand your bot's own persona and task to the voice agent and it becomes the caller. Fill in the identity block below, and the guide turns it into the exact request that defines your bot.

2. Edit this - your bot's identity.

Fill these in. Everything else in the guide reads from them. You can change them anytime by repeating Step 4.

BOT_NAME      = Nova
BOT_ROLE      = an AI scheduling assistant for Acme Plumbing
BOT_TASK      = Call customers to confirm tomorrow's appointments and reschedule if needed.
GREETING      = Hi, this is Nova from Acme Plumbing. I'm calling to confirm your appointment.
VOICE_ID      = (leave blank for the default voice, or paste an ElevenLabs voice id)
TONE_RULES    = Be warm, brief, and natural. One or two short sentences per turn. Never invent facts; if unsure, say you will have someone follow up.

These assemble into the agent's personality (its system prompt) and firstMessage (the opening line it speaks). You can re-send them anytime to rebrand the bot on the fly.

3. Two kinds of access.

Setup calls use your account session. Everything after that uses your bot's scoped key.

PhaseWhat you callAuth
One-time setupsignup, login, initializeYour account session (login cookie)
Everything afterdefine identity, test, add card, place callsYour bot's scoped key in the X-Bot-Key header

Your scoped key controls only your agent. Keep it secret. You can revoke and re-mint it anytime.

4. Step by step.

1

Create your account

curl -X POST https://wfw-admin.vercel.app/api/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"name":"My Voice Bot","email":"you@example.com","phoneNumber":"+18435551234","password":"a-strong-password"}'

Password must be at least 8 characters. Phone in E.164 format (+1...). This call does not log you in.

2

Log in and get a session

NextAuth uses a CSRF token. Fetch it first, then post credentials. Store the cookie jar for Step 3.

JAR=cookies.txt
CSRF=$(curl -s -c $JAR https://wfw-admin.vercel.app/api/auth/csrf \
  | python -c "import sys,json;print(json.load(sys.stdin)['csrfToken'])")

curl -s -c $JAR -b $JAR -X POST https://wfw-admin.vercel.app/api/auth/callback/credentials \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "csrfToken=$CSRF" \
  --data-urlencode "email=you@example.com" \
  --data-urlencode "password=a-strong-password" \
  --data-urlencode "json=true"

Keep cookies.txt for the next two calls (initialize and task list).

3

Initialize your bot and get your key

curl -s -b cookies.txt -X POST https://wfw-admin.vercel.app/api/bots/initialize \
  -H "Content-Type: application/json" -d '{}'

Response (save the apiKey - it is shown once):

{ "success": true, "apiKey": "wfwk_xxxxxxxx", "agentId": "agent_xxxx", "clientId": 123 }

The apiKey is your X-Bot-Key. Store it in a secret env var (e.g. WFW_BOT_KEY). All remaining calls use it.

4

Define your bot's identity

This is the heart of it. Send who your bot is. Using the identity block from Section 2, assemble the personality and greeting:

curl -s -X PATCH https://wfw-admin.vercel.app/api/bots/agent \
  -H "X-Bot-Key: wfwk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "personality": "You are Nova, an AI scheduling assistant for Acme Plumbing. Your job: Call customers to confirm tomorrow\'s appointments and reschedule if needed. Be warm, brief, and natural. One or two short sentences per turn. Never invent facts; if unsure, say you will have someone follow up.",
    "firstMessage": "Hi, this is Nova from Acme Plumbing. I\'m calling to confirm your appointment.",
    "voiceId": ""
  }'

personality= "You are{BOT_NAME}, {BOT_ROLE}. Your job: {BOT_TASK} {TONE_RULES}" (max 8000 chars)

firstMessage = {GREETING} (max 1000 chars)

voiceId = {VOICE_ID} (omit or leave blank for the default voice)

Send at least one of the three fields. Re-send anytime to rename or repurpose the bot. The name is whatever you put in the personality and greeting.

5

Test it for free - no call, no charge

curl -s -X POST https://wfw-admin.vercel.app/api/bots/agent/simulate \
  -H "X-Bot-Key: wfwk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"userInput":"Hi, who is this?"}'

Returns a text transcript so you can verify your bot's character before paying for a live call. No call is placed. No charge.

6

Add a card (required before any real call)

curl -s -X POST https://wfw-admin.vercel.app/api/bots/payment-method/setup \
  -H "X-Bot-Key: wfwk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"lineType":"outbound_shared"}'

Returns { "url": "https://checkout.stripe.com/..." }. Open it, enter your card, and save. The instant the card saves, calling unlocks.

lineTypeMonthly feeInbound
outbound_sharedNo monthly feeOutbound only
dedicated$7/monthYes - your own number

Both line types bill per minute. No card means test-only. If the Stripe page shows "Save my information for faster checkout," uncheck it so it does not ask for a phone number.

7

Place a call

curl -s -X POST https://wfw-admin.vercel.app/api/bots/calls \
  -H "X-Bot-Key: wfwk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"phone_number":"+18435559876"}'

Your bot calls that number and speaks as the identity you defined. Returns a taskId. Calls are queued and dialed within a minute.

8

See what happened

# List recent tasks using your session cookie
curl -s -b cookies.txt "https://wfw-admin.vercel.app/api/bots/tasks?limit=10"

# Or read a single task by the ID returned from the calls endpoint
# curl -s -b cookies.txt "https://wfw-admin.vercel.app/api/bots/tasks/{taskId}"

5. Change the bot anytime.

Renaming or repurposing is just Step 4 again with new text. The change is live immediately for the next call.

# Become a friendly survey caller named "Sam"
curl -s -X PATCH https://wfw-admin.vercel.app/api/bots/agent \
  -H "X-Bot-Key: wfwk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "personality": "You are Sam, a cheerful survey caller for Riverside Gym. Your job: ask members three quick questions about their experience. Keep it under a minute.",
    "firstMessage": "Hi, this is Sam from Riverside Gym with a 30-second survey, is now okay?"
  }'

6. Driving it from an OpenClaw bot.

Your OpenClaw bot already has a personality and a task. Hand those straight to Workforce Wave so the phone agent IS your bot. Minimal Python wrapper:

#!/usr/bin/env python3
"""wfwbot - let an OpenClaw bot run a Workforce Wave voice line.
Subcommands:
  define              push this bot's name/personality/task to its WFW voice agent
  call <number>       place a call
  simulate "<text>"   free text test
Env: WFW_BOT_KEY (from /api/bots/initialize). Provisioning (signup/login/initialize)
is done once; see the steps above.
"""
import os, sys, json, urllib.request

BASE = "https://wfw-admin.vercel.app"
KEY  = os.environ["WFW_BOT_KEY"]

# Your bot's identity. An OpenClaw bot can pass its OWN system prompt as PERSONALITY.
BOT_NAME  = os.environ.get("BOT_NAME", "Nova")
BOT_ROLE  = os.environ.get("BOT_ROLE", "an AI assistant for Workforce Wave")
BOT_TASK  = os.environ.get("BOT_TASK", "Help the caller and take a message.")
GREETING  = os.environ.get("BOT_GREETING", f"Hi, this is {BOT_NAME}. How can I help?")
VOICE_ID  = os.environ.get("BOT_VOICE_ID", "")
RULES     = "Be warm, brief, and natural. One or two short sentences per turn. Never invent facts."

def post(path, body, patch=False):
    req = urllib.request.Request(BASE + path, data=json.dumps(body).encode(),
        headers={"X-Bot-Key": KEY, "Content-Type": "application/json"},
        method="PATCH" if patch else "POST")
    return json.load(urllib.request.urlopen(req))

def define():
    personality = f"You are {BOT_NAME}, {BOT_ROLE}. Your job: {BOT_TASK} {RULES}"
    body = {"personality": personality, "firstMessage": GREETING}
    if VOICE_ID:
        body["voiceId"] = VOICE_ID
    print(json.dumps(post("/api/bots/agent", body, patch=True), indent=2))

def call(number):
    print(json.dumps(post("/api/bots/calls", {"phone_number": number}), indent=2))

def simulate(text):
    print(json.dumps(post("/api/bots/agent/simulate", {"userInput": text}), indent=2))

if __name__ == "__main__":
    cmd = sys.argv[1] if len(sys.argv) > 1 else "define"
    if cmd == "define": define()
    elif cmd == "call": call(sys.argv[2])
    elif cmd == "simulate": simulate(sys.argv[2])

Usage:

export WFW_BOT_KEY=wfwk_xxxxxxxx
export BOT_NAME="Nova"  BOT_ROLE="a receptionist for Acme"  BOT_TASK="Book appointments."
python wfwbot.py define
python wfwbot.py simulate "who is this?"
python wfwbot.py call +18435559876

To make an OpenClaw skill out of it, register wfwbot.py as a skill command and let the model call define (with its own persona/task) and call <number>.

7. Editable fields reference.

Every field you can change on the fly, where it goes, and what it controls.

FieldEndpointWhat it controlsLimit
personalityPATCH /api/bots/agentThe bot's name, role, task, tone, and rules (its system prompt)8000 chars
firstMessagePATCH /api/bots/agentThe exact opening line it speaks on every outbound call1000 chars
voiceIdPATCH /api/bots/agentElevenLabs voice ID (omit or leave blank for the default voice)string
lineTypePOST /api/bots/payment-method/setupoutbound_shared ($0/mo) or dedicated ($7/mo, adds inbound)enum
phone_numberPOST /api/bots/callsWho to call. Must be E.164 format (e.g. +18435559876)string

The bot's name is set by the words you put in personality and firstMessage(for example "You are Nova..."). Renaming is just editing those two fields.

8. Good to know.

Auth

Setup uses your login session cookie. Everything else uses the X-Bot-Key header. Never share the key.

Card gate

Real outbound calls return 402 Payment Required until a card is on file. Free text simulation always works with no card.

Pricing

Per-minute usage billed monthly to your card. The dedicated line adds $7/month. You only pay for calls you place.

Phone format

Always E.164. Example: +18435551234. No spaces, no parentheses, country code required.

Revoke a key

DELETE /api/bots/keys/{id} with your session. Mint a new key by calling /api/bots/initialize again.

Personality changes are instant

No redeploy, no wait. The updated identity is live for the very next call your bot places.