Build and run voice agents with the API
Create assistants, start calls, attach tools and knowledge, read results and receive events through the JSON REST API.
Bearer keys
Private keys for servers; public keys for browsers, limited to starting calls and reading assistants.
Plain JSON
Resources are JSON documents. List endpoints return results and metadata.
Webhooks
Status updates, tool calls and end-of-call reports are posted to your server with a shared secret.
Rate limits
Per organization, reported in X-RateLimit-Limit and X-RateLimit-Remaining.
Your first call in three requests
Create a private key in the dashboard under API keys. It is shown once; Ryffio stores only a hash.
1. Create an assistant
An assistant is a prompt, a first message, and the speech, model and voice providers it uses.
curl https://app.ryffio.com/assistant \
-H "Authorization: Bearer $RYFFIO_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Front desk",
"firstMessage": "Thanks for calling. How can I help?",
"model": { "provider": "openai", "model": "gpt-4.1-mini",
"messages": [{ "role": "system",
"content": "You answer calls for a dental clinic." }] },
"transcriber": { "provider": "openai", "model": "gpt-transcribe" },
"voice": { "provider": "openai", "voiceId": "alloy" }
}'2. Start a web call
The reply carries a single-use webCallUrl. Over WebSocket it takes audio frames; for WebRTC pass "transport": { "provider": "webrtc" } and post your SDP offer to the returned URL.
A public key (safe in a browser) can start web calls but can't change anything.
curl https://app.ryffio.com/call/web \
-H "Authorization: Bearer $RYFFIO_KEY" \
-H "Content-Type: application/json" \
-d '{ "assistantId": "<assistant id>" }'
# => { "id": "...", "status": "queued",
# "webCallUrl": "wss://app.ryffio.com/call/.../ws?token=..." }3. Read the result
When the call ends the record holds the transcript, messages, summary, cost breakdown, any structured outputs and the end reason. Poll it, or have it posted to your server.
curl https://app.ryffio.com/call/<call id> \
-H "Authorization: Bearer $RYFFIO_KEY"
# => { "status": "ended", "endedReason": "assistant-ended-call",
# "summary": "...", "cost": 0.0412,
# "costBreakdown": { "stt": ..., "llm": ..., "tts": ..., "total": ... },
# "artifact": { "structuredOutputs": { ... } } }What the API covers
| Resource | What it does | Endpoints |
|---|---|---|
| Assistants | Prompt, voice, model, transcriber, limits; versions and traffic splits | /assistant |
| Calls | Web, WebRTC and phone calls; records, recordings, control of a live call | /call/web /call /call/{id} |
| Tools | Function, API request, transfer and end-call tools | /tool |
| Knowledge | Knowledge bases, files and indexing status | /v2/knowledge-base /file |
| Structured outputs | Schemas extracted after each call | /structured-output |
| Squads | Several assistants in one call with handoffs | /squad |
| Phone numbers | Twilio numbers pointed at an assistant or squad | /phone-number |
| Campaigns | Outbound dialing, results per customer, do-not-call list | /campaign /dnc |
| Evals and simulations | Test suites, eval runs, simulated callers | /eval /test-suite |
| Analytics | Queries, boards, insights, cost reconciliation | /analytics /reporting/* |
| Organization | Members, API keys, credentials, audit log, data export | /org/{id}/* /token /credential |
Events, signed with your secret
Set a server URL on the assistant (or the organization) and choose which events you want. Each is a JSON POST carrying your secret in X-Ryffio-Secret. Use the same endpoint for tool calls and answer with { "results": [{ "toolCallId", "result" }] }.
status-update: the call started or endedtool-calls: the agent wants your server to run somethingend-of-call-report: transcript, summary, cost, structured outputs
Requests to your URLs must be public HTTPS; private and internal addresses are refused.
{
"message": {
"type": "end-of-call-report",
"endedReason": "customer-ended-call",
"summary": "Caller moved Thursday to Friday.",
"cost": 0.0412,
"artifact": { "transcript": "..." }
}
}Call your tools
A function tool tells the model what it can ask your server to do. Ryffio posts the call, waits, and feeds the reply back into the conversation.
{
"type": "function",
"function": {
"name": "find_slot",
"description": "Find an open appointment slot.",
"parameters": { "type": "object",
"properties": { "day": { "type": "string" } },
"required": ["day"] }
},
"server": { "url": "https://api.example.com/slots", "secret": "..." }
}Get the fields you need, every call
Define a JSON schema, list its id in the assistant's artifactPlan.structuredOutputIds, and the extracted object appears in artifact.structuredOutputs on the call record.
{
"name": "Appointment request",
"schema": {
"type": "object",
"properties": {
"intent": { "type": "string" },
"preferredDay": { "type": "string" }
}
}
}API behavior and errors
Errors
Failures return { "message", "error", "statusCode" } with an honest status code: 400 for bad input, 401 for a bad key, 403 for missing permission, 429 when rate-limited.
Keys and roles
Private keys act for the organization. Members have admin, editor or viewer roles. Sensitive actions are recorded in the audit log.
Draining deploys
During a restart, new calls get 503 with Retry-After while live calls finish, so retrying is safe.
Make your first request
Create an account, generate a key, and send the three requests above.