Ring-a-Ding docs
Use Ring-a-Ding without guessing.
This page is only about using the product well: setup, how to frame calls, how the MCP and CLI flow works, what results come back, and how to recover when something fails. It is not a full app architecture reference.
The most important rule: have your agent think through the whole call before dialing. The more relevant context, personality, and prompt guidance the phone agent gets, the better it performs.
Start here
Pick the surface that matches how you actually work. OpenClaw is the shortest path. MCP is for general agent clients. The CLI is for direct local use.
OpenClaw quick-start
Best when OpenClaw is already your agent runtime.
- Install the CLI and the ring-a-ding skill.
- Add your Ring-a-Ding key and OPENAI_API_KEY.
- Start a fresh OpenClaw session before the first call.
Generic MCP client setup
Best for Claude Desktop, Claude Code, Cursor, or any MCP client.
- Add a ring-a-ding MCP server entry.
- Set RAD_API_KEY, RAD_API_URL, and OPENAI_API_KEY.
- Call make_call, then follow with wait_for_call.
CLI or scripts
Best for shell workflows, local automation, and manual debugging.
- Run rad init to save keys locally.
- Start calls with rad call.
- Poll with rad status or block with rad wait.
Call quality
Most bad calls come from thin instructions. The agent should reason through the call before it invokes the tool.
- Think through the whole call before dialing: Decide who should be called, what success looks like, what is off-limits, and what fallback behavior is acceptable if the first path fails.
- Make the purpose do real work: Purpose is not a title. It should say who to call, what needs to happen, and the exact questions or outcomes that matter. The minimum is 20 characters; strong calls usually need much more.
- Use context for facts, not vibes: Put names, dates, order numbers, budgets, addresses, and scheduling constraints in context so the phone agent can reference them naturally without exposing them unless relevant.
- Set personality intentionally: Personality changes delivery style: warmth, pacing, formality, patience, firmness. It is the easiest way to stop a call from sounding generic.
- Control the opening when first impressions matter: If the first sentence has to land well, write opening_line or openingLine yourself instead of relying on the generated greeting.
- Describe every structured field you want back: When you use output_schema or outputSchema, add a description to each property. The more context the phone agent has, the better it performs, and field descriptions are part of that context.
Thin request
Call the dentist and see what they say.
Better request
{
"to": "+15551234567",
"purpose": "Call Sunrise Dental to schedule a cleaning for Taylor next week. Ask about Tuesday or Wednesday morning, confirm Blue Cross PPO pricing, and do not book anything unless the total cost is under $200.",
"context": "Patient: Taylor Kim. Insurance: Blue Cross PPO. Preferred days: Tuesday or Wednesday morning. Budget: under $200. If they only have afternoon openings, ask for the earliest one.",
"personality": "Warm, calm, and professional. Sound like a real patient coordinator, not a scripted bot. Stay patient if put on hold.",
"caller_name": "Taylor",
"opening_line": "Hi, my name is Taylor. I'm calling to schedule a teeth cleaning for next week.",
"output_schema": {
"type": "object",
"properties": {
"appointment_date": {
"type": "string",
"description": "Confirmed appointment date and time"
},
"total_cost": {
"type": "number",
"description": "Total quoted price in dollars, including fees"
},
"accepts_insurance": {
"type": "boolean",
"description": "Whether Blue Cross PPO is accepted for this visit"
}
}
}
}outputSchema example
If you want structured data back, describe every field clearly so the phone agent knows what to collect.
{
"type": "object",
"properties": {
"appointment_date": {
"type": "string",
"description": "Confirmed appointment date and time"
},
"total_cost": {
"type": "number",
"description": "Total quoted price in dollars, including fees"
},
"accepts_insurance": {
"type": "boolean",
"description": "Whether Blue Cross PPO is accepted for this visit"
}
}
}Setup
Every working call needs a Ring-a-Ding key and an OpenAI key. After that, wire up OpenClaw or the CLI.
Ring-a-Ding API key
Authenticates Ring-a-Ding and gives the platform permission to place calls for your account.
- Issued from the hosted account and checkout flow.
- Typically starts with rad_live_.
- Rotate it from the account page if an agent loses access.
OpenAI API key
Powers the live voice model during the call. Ring-a-Ding does not replace your OpenAI account.
- Starts with sk-.
- Required for MCP and CLI calls.
- Stored in OpenClaw config or local CLI setup.
OpenClaw install
npm install -g ring-a-ding-cli openclaw skills install ring-a-ding openclaw skills check
OpenClaw config
{
"skills": {
"entries": {
"ring-a-ding": {
"enabled": true,
"apiKey": "rad_live_...",
"env": {
"OPENAI_API_KEY": "sk-..."
}
}
}
}
}Generic MCP config
{
"mcpServers": {
"ring-a-ding": {
"command": "npx",
"args": [
"-y",
"ring-a-ding-mcp"
],
"env": {
"RAD_API_KEY": "rad_live_...",
"RAD_API_URL": "https://api.ringading.ai",
"OPENAI_API_KEY": "sk-..."
}
}
}
}CLI setup
rad init rad init --api-key rad_live_... --openai-key sk-...
Smoke test
Start a fresh OpenClaw session after installation, then try a real phone task.
Use the ring-a-ding skill to call the best pizza place around me and order delivery.
MCP tools
The MCP server name is ring-a-ding. In most agent flows: make_call starts the call, wait_for_call finishes it, and get_call_result is the non-blocking fallback.
make_call
Starts the outbound call and returns a callId immediately. Use it when the task really requires a live phone conversation.
Best for: Beginning the call once your agent has already reasoned through the objective, supporting facts, tone, and desired structured output.
to, purpose, context?, personality?, caller_name?, output_schema?
After make_call, call wait_for_call. Do not treat callId alone as the result.
- MCP parameter names use snake_case such as caller_name, opening_line, and output_schema.
- Detailed purpose, context, personality, and opening_line materially improve call quality.
make_call({
"to": "+15551234567",
"purpose": "Call Sunrise Dental to ask about Tuesday morning cleaning availability next week.",
"context": "Patient: Taylor Kim. Insurance: Blue Cross PPO.",
"personality": "Warm and professional."
})wait_for_call
Blocks until the call reaches a terminal state or the timeout is reached. This is the default follow-up after make_call.
Best for: Getting the final transcript, summary, extracted data, and metadata in the same workflow without writing your own polling loop.
call_id, timeout_seconds?
- Default timeout is 660 seconds; max timeout is 1860 seconds.
- Polls internally every 5 seconds.
wait_for_call({
"call_id": "<call_id>",
"timeout_seconds": 660
})get_call_result
Returns the current call record immediately, including any transcript, summary, extracted data, and metadata available so far.
Best for: Non-blocking workflows where your agent wants to continue other work and check the call later.
call_id
- If the status is initiated, ringing, or in-progress, the call is still running.
- Use this for manual polling when blocking is not desirable.
get_call_result({
"call_id": "<call_id>"
})CLI
The CLI is the straightforward path when you want local control. Start the call, save callId, then poll or wait for the final result.
Install the CLI and OpenClaw skill
Public install path for OpenClaw users.
npm install -g ring-a-ding-cli openclaw skills install ring-a-ding openclaw skills check
- Start a fresh OpenClaw session after the skill is installed or updated.
- If OpenClaw uses a shell allowlist, permit rad.
Save keys locally with rad init
Use this for CLI-only workflows or local debugging outside OpenClaw.
rad init rad init --api-key rad_live_... --openai-key sk-...
- Config is stored in ~/.config/ring-a-ding/config.json.
- The CLI validates the Ring-a-Ding key when possible and can detect an OpenClaw install.
Start a basic call
Shortest valid CLI example.
rad call "+15551234567" "Call Sunrise Dental to ask about Tuesday or Wednesday morning cleaning availability next week."
Start a structured call from stdin
Best when the request is long or needs outputSchema.
echo '{"to":"+15551234567","purpose":"Call Northside Pizza and ask whether they deliver downtown tonight.","context":"Customer: Taylor. Address: 200 Market St. Ask for delivery estimate if yes.","personality":"Warm and concise","outputSchema":{"type":"object","properties":{"delivers":{"type":"boolean","description":"Whether the restaurant delivers downtown tonight"},"delivery_window":{"type":"string","description":"Quoted delivery estimate"}}}}' | rad call --stdin- CLI stdin uses camelCase keys such as callerName and outputSchema.
Poll a call without blocking
Fetches the latest call record.
rad status <call_id>
Block until the call finishes
Use only when blocking the current shell session is acceptable.
rad wait <call_id> --timeout 300
- Exit code 2 means the wait timed out before the call reached a terminal state.
End or cancel a call
Hangs up an active call or cancels one that has not connected yet.
rad end <call_id>
Print the bundled skill file
Useful when debugging or manually copying the OpenClaw skill.
rad skill
Call inputs
A small number of fields drive most of the behavior. MCP uses snake_case. CLI flags use kebab-case. CLI stdin uses camelCase.
Destination
to, <to>
Phone number to dial.
Use E.164 format with country code, for example +15551234567.
Required
Purpose
purpose
What the call should accomplish.
Include who is being called, what outcome you need, and any must-ask questions. Minimum 20 characters; detailed purposes consistently produce better calls.
Required
Context
context, --context
Supporting facts the phone agent may need during the call.
Put names, dates, budgets, addresses, account numbers, and constraints here. Keep tone instructions in personality instead.
Personality
personality, --personality
How the agent should sound and behave.
Use it to control warmth, pacing, formality, patience, or firmness. This changes delivery style, not the task itself.
Caller name
caller_name, callerName, --caller-name
The name used when the agent introduces itself.
Set it explicitly when identity matters. Otherwise a random caller name is chosen.
Opening line
opening_line, openingLine, --opening-line
Exact first sentence when someone answers.
Use this when the first impression matters or when the wording should be tightly controlled.
Output schema
output_schema, outputSchema, --output-schema
JSON Schema for structured data extraction after the call ends.
Add a description to every property so the agent knows what information to collect during the call.
Voice
voice, --voice
Which OpenAI voice is used during the call.
Start with marin. Try ash or cedar for warmer calls and echo for calmer ones.
Default: marin
Voicemail behavior
voicemail_action, voicemailAction, --voicemail-action
What to do if voicemail or an automated system answers.
leave_message is the default. Use hang_up when voicemail should be silent. Shared numbers cannot receive callbacks.
Default: leave_message
Max duration
max_duration_minutes, maxDurationMinutes, --max-duration
Maximum call length in minutes.
Set a lower cap for quick tasks and a higher cap for detailed multi-step conversations.
Default: 10 minutes, with a hard max of 30
Voices
Start with marin. Available voices: alloy, ash, ballad, cedar, coral, echo, marin, sage, shimmer, verse.
Results
A good workflow is simple: start the call, keep callId, then read the final record instead of assuming success.
- Start the call and store callId immediately.
- Use wait_for_call or rad wait when blocking is fine. Use get_call_result or rad status when it is not.
- Read the final record before treating the task as done.
Example final record
{
"callId": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"summary": "Sunrise Dental offered Tuesday at 10:00 AM next week for $95 with Blue Cross PPO and can hold the slot until tomorrow.",
"extractedData": {
"appointment_date": "Tuesday at 10:00 AM",
"total_cost": 95,
"accepts_insurance": true
},
"transcript": [
{
"role": "assistant",
"content": "Hi, my name is Taylor. I'm calling to schedule a teeth cleaning for next week.",
"timestamp": "2026-04-14T10:30:05Z"
},
{
"role": "user",
"content": "We have Tuesday at 10 available. With Blue Cross PPO the visit is usually around $95.",
"timestamp": "2026-04-14T10:30:18Z"
}
],
"metadata": {
"voiceUsed": "marin",
"callerNameUsed": "Taylor",
"openaiModel": "gpt-realtime-1.5",
"totalTokensUsed": null,
"callEndedBy": "assistant",
"numberUsed": "+15555551234",
"transcriptCaptured": true
},
"costCents": 84,
"startedAt": "2026-04-14T10:30:00Z",
"endedAt": "2026-04-14T10:33:04Z",
"errorMessage": null
}callId and status
callId is the stable identifier for later polling. status tells you whether the call is still running or has reached a terminal outcome.
summary
Short human-readable outcome. Use it for quick inspection, but fall back to transcript and extractedData when precision matters.
transcript
Ordered conversation turns with role and timestamp. This is the source of truth when you need to inspect exactly what happened.
extractedData
Structured JSON populated when outputSchema was provided and the conversation covered the requested data.
metadata
Includes voiceUsed, callerNameUsed, openaiModel, numberUsed, callEndedBy, and transcriptCaptured.
timing, cost, and errorMessage
Use startedAt, endedAt, durationSeconds, and costCents for observability. errorMessage explains terminal failures when one is available.
Status meanings
initiated(non-terminal): The call record exists and the outbound call flow has started.ringing(non-terminal): The destination is ringing but the conversation has not started.in-progress(non-terminal): The call connected and the live voice session is running.completed(terminal): The conversation finished normally.failed(terminal): The platform or provider failed before the task could complete.no-answer(terminal): Nobody picked up the call.busy(terminal): The destination line reported busy.canceled(terminal): The call was canceled before normal completion.
Limits
These are the defaults and constraints that matter when you plan calls or build workflows around them.
- Bring your own OpenAI key: Ring-a-Ding handles telephony, but the live voice model runs on your OpenAI account.
- Current duration limits: Calls can run from 1 to 30 minutes. The default max duration is 10 minutes.
- Current wait behavior: wait_for_call and rad wait default to 660 seconds, cap at 1860 seconds, and poll every 5 seconds.
- Shared outbound numbers: Calls go out from the managed number pool. Treat those numbers as outbound-only and do not ask for callbacks.
- Current usage caps: The shared constants declare 5 concurrent calls and 50 calls per day as the current operating limits.
- Default voice: If you do not choose a voice, the platform defaults to marin.
Troubleshooting
Most failures come from missing keys, stale sessions, bad numbers, or prompts that do not tell the agent enough.
rad is not found after install
openclaw skills check or which rad cannot find the binary after npm install -g ring-a-ding-cli.
- Open a fresh terminal and run which rad again.
- Confirm the global npm bin path is on PATH.
- Reinstall with npm install -g ring-a-ding-cli if the binary is still missing.
OpenClaw is not using the skill
The CLI is installed, but OpenClaw does not pick Ring-a-Ding for phone tasks.
- Run openclaw skills check and confirm ring-a-ding is enabled.
- Restart OpenClaw after installing the skill or changing its config.
- If OpenClaw uses a shell allowlist, permit rad.
RAD_API_KEY or OPENAI_API_KEY is missing
Calls fail before the conversation can start.
- Confirm the Ring-a-Ding key is present in OpenClaw config or local CLI config.
- Confirm OPENAI_API_KEY is present for the skill or saved locally through rad init.
- If the key was rotated, update every place where the old value is still cached.
The destination number is rejected
The API or CLI reports INVALID_INPUT for the phone number.
- Use E.164 format only, for example +19195551234.
- Do not include spaces, parentheses, or local-only formatting.
- If the number is generated dynamically, log the final string before sending it.
The call prompt is too short or too vague
The request is rejected or the call sounds generic and misses obvious questions.
- Make the purpose explicit about who to call and what outcome you need.
- Move supporting facts into context instead of keeping the request vague.
- Add personality, opening line, and output schema when the task needs more control.
The call stays initiated, ringing, or in-progress
The call appears to be running longer than expected.
- Poll again after 30 to 60 seconds with rad status or get_call_result.
- If the call is clearly stale, end it with rad end.
- Check whether the destination actually answered or whether the line is still ringing.
The call ends as failed, busy, or no-answer
The task was not completed even though the call reached a terminal state.
- Read errorMessage, summary, and transcript if they are available.
- Confirm the number, timing, and business-hours assumptions before retrying.
- Adjust the prompt or target rather than blindly running the same call again.
Transcript, summary, or extracted data is missing
The call finished but some post-call data is null or incomplete.
- Fetch the call record again after a short delay in case post-call processing is still finishing.
- Check whether the call actually connected. no-answer and early failures do not produce the same outputs.
- If you expected structured data, confirm that outputSchema was supplied on the original request.