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

  1. startRecording@v1 starts recording the call. It returns immediately: the next actions run while the call is recorded.
  2. The recording stops when the call ends (or when stopRecording@v1 runs).
  3. Callr builds the recording file and, if enabled, its transcript.
  4. The actions listed in events.ready run after the call, with the recording, chat and chat_text variables available.
📘

events.ready runs offline, after the caller has hung up. It accepts the same actions as the hangup branch: 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

VariableContent
recordingThe Recording object of the REST API: sid, url (MP3), duration, size, callee, caller, transcription…
chatThe transcript as a list of turns: { from: leg_a or leg_b, display: "…" }
chat_textThe same transcript as plain text, ready for an LLM prompt
transcription_leg_aWords spoken by the caller (leg A) only
transcription_leg_bWords 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.

ℹ️

A transcript is generated for each feed (leg A, leg B). chat merges them in conversation order.


✨ Extract the Intent with openai@v3

Transcripts 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@v1
description: 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@v1

Your 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

ParameterDefaultDescription
feedsallWhat to record: all (both parties), leg_a (caller only), leg_b (called party only).
transcription.enabledtrueGenerate 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@v3 with email@v1 in events.ready, with ${{ $intent.summary }} in the body.
  • Record outbound calls too: startRecording@v1 works the same in an answerOnBranch branch of a Broadcast Call.
  • Push to Excel, Google Sheets, HubSpot…: see Push Data to Your Platform.

Did this page help you?