Record and Transcribe Calls
Record a call with startRecording@v1, get the transcript when it is ready, extract the intent with openai@v3 and push everything to your platform.
One action records the whole conversation. When the recording and its transcript are ready, your scenario runs again with the transcript in hand: push it to your CRM, email a summary, or ask an LLM what the call was about.
⚙️ How It Works
startRecording@v1starts recording the call. It returns immediately: the next actions run while the call is recorded.- The recording stops when the call ends (or when
stopRecording@v1runs). - Callr builds the recording file and, if enabled, its transcript.
- The actions listed in
events.readyrun after the call, with therecording,chatandchat_textvariables available.
events.readyruns offline, after the caller has hung up. It accepts the same actions as thehangupbranch:fetch@v3,openai@v3,email@v1,sms@v1,javascript@v1,log@v1… but no audio action.
🚀 Record a Forwarded Call
Announce the recording, record, forward the call. When the recording is ready, push it to your server.
description: Record a forwarded call and push the recording to my server
compat:
version: "2025-09-01"
defaults:
language: en-US
variables:
$url: https://api.example.com/callr/recordings
$dial: null
branches:
inbound-call:
actions:
- action: play@v3
params:
tts:
text: This call may be recorded for quality purposes.
- action: startRecording@v1
params:
transcription:
enabled: true
language: en-US
events:
ready:
- action: fetch@v3
params:
url: ${{ $url }}
method: POST
headers:
content-type: application/json
body: |
{
"call": ${{ JSON.stringify(call) }},
"recording": ${{ JSON.stringify(recording) }},
"transcript": ${{ JSON.stringify(chat_text) }}
}
- action: dial@v2
params:
targets:
- number: "+33199001020"
timeout: 30
result: $dial
- action: hangup@v1📦 What You Get in events.ready
events.ready| Variable | Content |
|---|---|
recording | The Recording object of the REST API: sid, url (MP3), duration, size, callee, caller, transcription… |
chat | The transcript as a list of turns: { from: leg_a or leg_b, display: "…" } |
chat_text | The same transcript as plain text, ready for an LLM prompt |
transcription_leg_a | Words spoken by the caller (leg A) only |
transcription_leg_b | Words spoken by the called party (leg B) only |
The full recording object is documented in Pre-defined Variables. Recordings are also listed, downloaded and deleted with the Recordings API.
✨ Extract the Intent with openai@v3
openai@v3Transcripts are long and nobody reads them. Ask an LLM for the few fields your business cares about: intent, outcome, next step. openai@v3 calls the OpenAI API with your key, and response_format: json_object forces a JSON answer you can push as-is. Another LLM provider? The Mistral tab calls the Mistral API directly with fetch@v3, the same pattern works with any OpenAI-compatible API.
description: Record, transcribe, extract the intent with OpenAI and push to my CRM
compat:
version: "2025-09-01"
defaults:
language: en-US
variables:
$url: https://api.example.com/callr/calls
$openaiKey: <openai-key>
$dial: null
$messages: null
$openai: null
$intent: null
branches:
inbound-call:
actions:
- action: play@v3
params:
tts:
text: This call may be recorded for quality purposes.
- action: startRecording@v1
params:
transcription:
enabled: true
language: en-US
events:
ready:
# 1. Build the prompt from the transcript
- action: javascript@v1
params:
code: |
$messages = [
{ role: "system", content: "You classify phone calls to a company. Answer ONLY with a JSON object with these keys: intent (one of: sales, support, billing, other), summary (one sentence), callback_needed (boolean)." },
{ role: "user", content: chat_text }
]
# 2. Ask OpenAI
- action: openai@v3
params:
secret: ${{ $openaiKey }}
endpoint: chat/completions
properties:
model: gpt-5.4-mini
messages: ${{ $messages }}
temperature: 0
response_format:
type: json_object
timeout: 60
result: $openai
# 3. Parse the answer
- action: javascript@v1
params:
code: |
$intent = JSON.parse($openai?.choices?.[0]?.message?.content || "{}")
# 4. Push the call, the recording and the intent
- action: fetch@v3
params:
url: ${{ $url }}
method: POST
headers:
content-type: application/json
body: |
{
"call": ${{ JSON.stringify(call) }},
"recordingUrl": ${{ JSON.stringify(recording.url) }},
"duration": ${{ JSON.stringify(recording.duration) }},
"intent": ${{ JSON.stringify($intent) }}
}
- action: dial@v2
params:
targets:
- number: "+33199001020"
timeout: 30
result: $dial
- action: hangup@v1description: Record, transcribe, extract the intent with Mistral (via fetch@v3) and push to my CRM
compat:
version: "2025-09-01"
defaults:
language: en-US
variables:
$url: https://api.example.com/callr/calls
$mistralKey: <mistral-key>
$dial: null
$request: null
$mistral: null
$intent: null
branches:
inbound-call:
actions:
- action: play@v3
params:
tts:
text: This call may be recorded for quality purposes.
- action: startRecording@v1
params:
transcription:
enabled: true
language: en-US
events:
ready:
# 1. Build the Mistral request from the transcript
# (fetch@v3 bodies are limited to 10 KB: the transcript is truncated)
- action: javascript@v1
params:
code: |
$request = {
model: "mistral-small-latest",
temperature: 0,
response_format: { type: "json_object" },
messages: [
{ role: "system", content: "You classify phone calls to a company. Answer ONLY with a JSON object with these keys: intent (one of: sales, support, billing, other), summary (one sentence), callback_needed (boolean)." },
{ role: "user", content: (chat_text || "").slice(0, 8000) }
]
}
# 2. Call the Mistral API with fetch@v3
- action: fetch@v3
params:
url: https://api.mistral.ai/v1/chat/completions
method: POST
headers:
content-type: application/json
authorization: Bearer ${{ $mistralKey }}
body: ${{ JSON.stringify($request) }}
responseFormat: json
timeout: 60
result: $mistral
# 3. Parse the answer
- action: javascript@v1
params:
code: |
$intent = $mistral?.ok
? JSON.parse($mistral.json?.choices?.[0]?.message?.content || "{}")
: { error: $mistral?.status }
# 4. Push the call, the recording and the intent
- action: fetch@v3
params:
url: ${{ $url }}
method: POST
headers:
content-type: application/json
body: |
{
"call": ${{ JSON.stringify(call) }},
"recordingUrl": ${{ JSON.stringify(recording.url) }},
"duration": ${{ JSON.stringify(recording.duration) }},
"intent": ${{ JSON.stringify($intent) }}
}
- action: dial@v2
params:
targets:
- number: "+33199001020"
timeout: 30
result: $dial
- action: hangup@v1Your server receives something like:
{
"call": { "fromNumber": "+33639980042", "toNumber": "+33939140303", "...": "..." },
"recordingUrl": "https://media.callr.com/recordings/<ACCOUNT-SID>/<RECORDING-SID>.mp3",
"duration": 184,
"intent": {
"intent": "support",
"summary": "The caller cannot log in since the last update and wants a password reset.",
"callback_needed": true
}
}
Ask several questions at once and get a richer object: see Extract Call Data with AI for a complete, email-based version with a list of questions.
🎚️ Options
| Parameter | Default | Description |
|---|---|---|
feeds | all | What to record: all (both parties), leg_a (caller only), leg_b (called party only). |
transcription.enabled | true | Generate a transcript. |
transcription.language | — | Language of the conversation (en-US, fr-FR…). Improves accuracy. |
Recording a call is regulated in most countries: announce it, and check what applies to you and to the called party. Recordings contain personal data: delete them with the Recordings API when you no longer need them.
💡 Going Further
- Voicemail messages: to record a message rather than a conversation, use
record@v1+saveRecordingFile@v1. See Voicemail & Answering Machine Detection. - Email the summary: replace
fetch@v3withemail@v1inevents.ready, with${{ $intent.summary }}in the body. - Record outbound calls too:
startRecording@v1works the same in ananswerOnBranchbranch of a Broadcast Call. - Push to Excel, Google Sheets, HubSpot…: see Push Data to Your Platform.
Updated about 1 hour ago
