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 and website chats live in one place, told apart by channel (phone or web_chat).
List conversations, newest first.
| Query param | Type | Notes |
|---|---|---|
channel | string | phone or web_chat |
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, 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.
{
"id": 7, "phone_number": "+15551234567", "subscription_type": "growth",
"agent_name": "Ava", "voice": "friendly",
"greeting_message": "Thanks for calling Acme — how can I help?",
"agent_goal": "Qualify leads and book consultations.",
"business_name": "Acme Plumbing", "business_website": "https://acme.com",
"business_services": "Emergency plumbing, water heaters, drain cleaning.",
"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"},
"features": {"web_chat_enabled": true, "live_context_enabled": false,
"webhook_enabled": false, "webhook_url": null},
"is_suspended": false, "created_at": "2026-01-12T09:30:00Z"
}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 |
faq | 50 entries · question 200 · answer 500 | A {question: answer} map |
Voice, agent name, tier flags, webhooks, Conversion API and SMS settings are managed in the dashboard, not the API.
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.
For AI agents & tools
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.