Build on your RingReady data
A simple REST API to read your call & web-chat activity and manage your AI receptionists — designed for your own scripts and for AI agents like Claude acting on your behalf.
Bearer-token auth · JSON · stable /api/v1
Quickstart
1. Create an API key in Profile → API keys. Copy it — the full key is shown only once.
2. Call the API with the key as a bearer token. This first request confirms your key works:
import requests
BASE = "https://www.ring-ready.com"
API_KEY = "rr_live_xxxxxxxx" # from Profile → API keys
headers = {"Authorization": f"Bearer {API_KEY}"}
r = requests.get(f"{BASE}/api/v1/whoami", headers=headers)
print(r.json())
# {'user_id': 142, 'email': 'you@example.com', 'auth_method': 'api_key',
# 'api_key_prefix': 'rr_live_8f3a', 'scopes': ['read', 'write']}Authentication
Every request is authenticated with an API key sent as a bearer token:
Authorization: Bearer rr_live_xxxxxxxxCreate and revoke keys in Profile → API keys. Each key only ever sees your own data. The full key is shown once at creation — we store only a hash, so if you lose it, revoke it and create a new one.
Scopes
Keys carry coarse scopes. Read endpoints need read; updating an agent needs write.
| Scope | Grants |
|---|---|
read | List and read conversations and agents. |
write | Update agent metadata (PATCH /agents/{id}). |
Conventions
- Base URL:
https://www.ring-ready.com— all paths under/api/v1. - Format: JSON request and response bodies.
- Timestamps: UTC, ISO-8601 with a trailing
Z. - Scoping: every response is limited to the key owner's data.
Errors
Errors return {"detail": "..."} with an HTTP status:
| Status | Meaning |
|---|---|
401 | Missing, invalid, or revoked key. |
403 | Valid key, but missing the required scope. |
404 | Resource not found or not yours. |
422 | Invalid request body (e.g. a blank required field). |
Rate limits
None are enforced during the experimental phase. Per-key limits will be added before general availability.
Conversations
Phone calls, website chats, and inbound texts live in one place, told apart by channel (phone, web_chat, or sms). Rows include callback_number when the receptionist captured one (or recovered it from caller ID).
List conversations, newest first.
| Query param | Type | Notes |
|---|---|---|
channel | string | phone, web_chat, or sms |
agent_id | int | Filter to one receptionist |
since | string | ISO-8601; conversations at/after this time |
before_id | int | Pagination cursor (the previous page's next_before_id) |
limit | int | 1–200, default 50 |
r = requests.get(f"{BASE}/api/v1/conversations",
headers=headers,
params={"channel": "web_chat", "limit": 20})
data = r.json()
for c in data["conversations"]:
print(c["started_at"], "·", c["label"], "·", c["summary"])
# next page:
if data["next_before_id"]:
more = requests.get(f"{BASE}/api/v1/conversations", headers=headers,
params={"before_id": data["next_before_id"]}).json()Response:
{
"conversations": [
{
"id": 184, "channel": "web_chat", "agent_id": 7,
"from": null, "to": null,
"caller_name": "Sam", "label": "Demo Request",
"summary": "Visitor asked for a demo and pricing.",
"started_at": "2026-06-09T14:03:00Z",
"ended_at": "2026-06-09T14:07:11Z",
"duration_seconds": 251, "end_reason": "visitor_ended",
"is_archived": false, "is_viewed": false,
"web_chat": {"device_type": "desktop", "origin_url": "https://acme.com/pricing"}
}
],
"next_before_id": 184,
"limit": 20
}A single conversation with its full transcript. Adds transcript (an array of {role, content, elapsed_s}), recording_url, caller_address, comments (the owner's notes), appointment_link_sent, and — for web chats — web_chat.visitor_ip.
convo = requests.get(f"{BASE}/api/v1/conversations/184", headers=headers).json()
for turn in convo["transcript"]:
print(f"{turn['role']}: {turn['content']}")Agents (receptionists)
List your receptionists.
agents = requests.get(f"{BASE}/api/v1/agents", headers=headers).json()["agents"]
# [{"id": 7, "phone_number": "+15551234567", "agent_name": "Ava",
# "business_name": "Acme Plumbing", "subscription_type": "growth",
# "created_at": "2026-01-12T09:30:00Z"}]Read one receptionist's full configuration. goal_source tells you which goal is active: "prompt" (free-text agent_goal) or "list" (the structured collect-list, shown read-only as agent_goal_list). The entitlements block reports, per plan-gated feature, whether it's configured, included in the plan, and therefore actually active — config saved on a lower plan stays dormant until an upgrade. CAPI secrets are write-only: reads return has_access_token / has_api_secret flags, never the values.
{
"id": 7, "phone_number": "+15551234567", "subscription_type": "growth",
"plan": {"subscription_type": "growth", "effective_tier": "growth"},
"agent_name": "Ava", "voice": "friendly",
"greeting_message": "Thanks for calling Acme — how can I help?",
"goal_source": "prompt",
"agent_goal": "Qualify leads and book consultations.",
"agent_goal_list": null,
"supported_languages": ["en", "es"],
"business_name": "Acme Plumbing", "business_website": "https://acme.com",
"business_services": "Emergency plumbing, water heaters, drain cleaning.",
"business_address": "12 Main St, Denver, CO",
"business_service_areas": "Denver metro",
"business_hours": {"mon": [{"open": "08:00", "close": "17:00"}]},
"timezone": "America/Denver",
"faq": {"Do you offer emergency service?": "Yes, 24/7.",
"Do you give free estimates?": "Yes, on every job."},
"appointment_booking": {"sms_enabled": true, "sms_url": "https://book.acme.com", "sms_purpose": "booking"},
"notifications": {"email": null, "calls_disabled": false, "sms_enabled": true,
"sms_number_masked": "***4567", "sms_number_verified": true},
"recording_enabled": true,
"capi": {"meta": {"enabled": false, "pixel": null, "has_access_token": false, "events": []},
"google": {"enabled": true, "pixel": {"measurement_id": "G-XXXX"},
"has_api_secret": true, "events": [{"event_name": "conv", "always": true}]},
"tiktok": {"enabled": false, "pixel": null, "has_access_token": false, "events": []}},
"features": {"web_chat_enabled": true, "live_context_enabled": false,
"webhook_enabled": false, "webhook_url": null},
"entitlements": {
"call_recording": {"configured": true, "plan_includes": true, "active": true, "required_tier": "growth"},
"capi_google": {"configured": true, "plan_includes": false, "active": false, "required_tier": "scale"},
"webhooks": {"configured": false, "plan_includes": true, "active": false, "required_tier": "professional"}
},
"is_suspended": false, "created_at": "2026-01-12T09:30:00Z"
}The voice catalog. Each entry's key is what you send in PATCH {"voice": ...}; agent_name is the receptionist name that voice speaks with (the name always derives from the voice — it can't be set directly).
Partial update — only the fields you send change. Writable fields:
| Field | Max length | Notes |
|---|---|---|
agent_goal | 10,000 chars | Required — can't be blanked |
greeting_message | 500 chars | Optional |
business_name | 100 chars | Required — can't be blanked |
business_services | 30,000 chars | Required — can't be blanked |
business_website | 400 chars | Optional |
business_address | 512 chars | Optional |
business_service_areas | 1,024 chars | Optional |
business_hours | — | Per-day ranges object; null clears |
timezone | — | IANA name, e.g. America/New_York |
faq | 50 entries · question 200 · answer 500 | A {question: answer} map |
goal_source | — | prompt or list; list mode needs a dashboard-built collect-list |
voice | — | A key from GET /voices; renames the receptionist (response returns the new agent_name) |
agent_notification_email | 255 chars | Where summaries email; empty = account email. Triggers a security notice to the owner |
webhook_enabled / webhook_url | URL 500 chars | Enabling requires a URL. Triggers a security notice |
recording_enabled | — | Boolean. Active on Growth+; callers hear a consent notice |
capi | — | {meta|google|tiktok: {enabled, pixel, events}}. Omitted channels untouched; a pixel sent without its token keeps the stored token. Active on Scale |
Configured ≠ active. Tier-gated config (recording, webhooks, CAPI) saves on any plan; the runtime only activates it when the plan includes the feature. Any PATCH touching one echoes an activation block — e.g. {"capi_google": {"configured": true, "plan_includes": false, "active": false, "required_tier": "scale"}} — so your integration can tell the customer an upgrade is needed rather than assuming it went live.
Goal modes. If goal_source is "list", the free-text goal is inactive and PATCH agent_goal returns 409. goal_source is writable: send {"goal_source": "prompt", "agent_goal": "..."} to switch a list-mode agent to prompt mode and set the goal in one request. Switching to "list" requires a collect-list already built in the dashboard (its content isn't editable via the API).
Still dashboard-only: the structured goal list, phone number & forwarding, the SMS notification number (verify-first), SMS booking, web chat, live context, plan & billing.
r = requests.patch(f"{BASE}/api/v1/agents/7",
headers=headers,
json={
"agent_goal": "Qualify the caller, then book a consultation.",
"greeting_message": "Thanks for calling Acme Plumbing — how can I help?",
"faq": {
"Do you offer free estimates?": "Yes — free, no-obligation estimates on every job.",
"What are your hours?": "We answer 24/7, including weekends and holidays.",
"Do you handle emergencies?": "Yes — we dispatch same-day for emergencies.",
},
})
print(r.json())
# {'id': 7, 'updated': ['agent_goal', 'greeting_message', 'faq']}Full example: improve a receptionist from its conversations
The pattern this API is built for — read what callers actually asked, have an LLM find the gaps, and feed the answers back into the agent's FAQ:
import requests, json
from anthropic import Anthropic
BASE = "https://www.ring-ready.com"
RR = {"Authorization": "Bearer rr_live_xxxxxxxx"}
claude = Anthropic()
AGENT_ID = 7
# 1. Pull the last 50 conversations for this agent
convos = requests.get(f"{BASE}/api/v1/conversations", headers=RR,
params={"agent_id": AGENT_ID, "limit": 50}).json()["conversations"]
# 2. Fetch each transcript
transcripts = []
for c in convos:
full = requests.get(f"{BASE}/api/v1/conversations/{c['id']}", headers=RR).json()
transcripts.append(full["transcript"])
# 3. Ask Claude for recurring questions the agent answered poorly,
# with a drafted FAQ answer for each
prompt = (
f"Here are {len(transcripts)} receptionist transcripts. "
"Find the 5 most common questions callers asked that the agent did NOT answer well. "
'Return JSON: [{"question": ..., "answer": ...}].\n\n'
+ json.dumps(transcripts)[:50000]
)
reply = claude.messages.create(
model="claude-sonnet-4-6", max_tokens=1500,
messages=[{"role": "user", "content": prompt}],
)
additions = json.loads(reply.content[0].text)
# 4. Merge into the agent's FAQ and write it back
agent = requests.get(f"{BASE}/api/v1/agents/{AGENT_ID}", headers=RR).json()
faq = agent.get("faq") or {}
for item in additions:
faq[item["question"]] = item["answer"]
requests.patch(f"{BASE}/api/v1/agents/{AGENT_ID}", headers=RR, json={"faq": faq})
print(f"Added {len(additions)} answers to the FAQ.")Run it on a schedule and your receptionist keeps getting better at the questions it actually gets.
Prefer to just talk to Claude? The same loop with no script to deploy — paste one prompt into Claude Code or Codex and let it read your calls and propose the changes, across every client. See the conversational walkthrough →
For AI agents & tools
Coding agents (Claude Code, Codex, Gemini CLI) — start here: point your agent at ring-ready.com/agent.md. It's a purpose-built operator's guide — the workflow, the editable-vs-locked fields, the secure-key & skill best practices, and a link to the spec below. See it in action →
Machine-readable spec: the whole API is described in OpenAPI at /api/v1/openapi.json — import it as tools in the Anthropic or OpenAI SDKs, a ChatGPT custom GPT (Actions), LangChain or n8n, or generate an MCP server from it.
Site overview for LLMs: llms.txt.
Hand an agent a key and the spec, and it can read conversations and tune receptionists on its own. Because every key is scoped to one account, an agent only ever touches that customer's data.
Ready to build?
Create a key and make your first call in two minutes.