Language Reference

⚡

Design communication flows with Callr Actions — a declarative, YAML-based framework inspired by GitHub Actions.

Whether you're routing calls, triggering events, or integrating with external services, Callr Actions lets you compose complex voice logic with simple building blocks.

This page describes the language of a scenario: branches, expressions, variables, secrets, conditions, goto and defaults. New to Callr Actions? Start with the Introduction. The parameters, results and examples of each action are in Actions.

ℹ️

Tip

We recommend using the Callr Console to create your Callr Actions scenarios. It features a built-in code editor with autocomplete, inline documentation, and live testing to help you build faster.


Architecture

Call flows in Callr Actions are organized around branches – sequences of actions that determine your call’s behavior. There are four special branches:

  • inbound-call: The entry point for an Inbound Call.
  • inbound-sms: The entry point for an Inbound SMS.
  • api-initiated: The entry point for actions triggered via the API.
  • hangup: Executed for Inbound Calls once the call is hung up.

You can also create additional branches to organize your logic. To jump between branches, use the built-in goto statement:

branches:
  inbound-call:
    actions:
      - # your initial actions...
      - if: '!$dial.ok'
        goto:
          branch: voicemail
          
  voicemail:
    actions:
      - action: play@v3
        params:
          tts:
            text: Sorry, we are not available at the moment.
📄

JSON Schema Reference

Callr Actions are defined by a JSON Schema, available here: YAML. You can use the schema to enable validation and auto-completion in your favorite code editor.

💡 Tip: The Callr Console includes a built-in code editor with autocomplete and live validation – no setup required.


Expressions

Expressions, enclosed in double curly braces ${{ expression }}, can be used in any params object. They are evaluated at runtime using a modern JavaScript engine.

- action: play@v3
  params:
    tts:
      text: Hello! Your number is ${{ call.fromNumber }}.

- action: play@v3
  params:
    tts:
      text: >
        I know that 1 + 1 equals ${{ 1+1 }}

- action: play@v3
  params:
    tts:
      text: >
        Your ticket ID is ${{ $ticketID.split('').join(' ') }}

User-defined variables

You can define your own variables to use in expressions. These variables must start with a dollar sign and be valid JavaScript identifiers. The variable type is inferred from the value and preserved when used in expressions.

variables:
  $errorCode: 0 # an int
  $pi: 3.14159265359 # a float
  $user: # an object
    name: John # a string
    age: 42 # an int
  $list: # an array
    - apples
    - oranges

For example, you can reference these variables in a branch:

branches:
  inbound-call:
    actions:
      - action: play@v3
        params:
          tts:
            text: >
              Hello ${{ $user.name }}!
              You look like you are ${{ $user.age/2 }} years old!

Secrets

A secret keeps an API key or a token out of your scenarios: you save its value once in your account, then a scenario uses it by its name, ${{ secrets.NAME }}. A name is made of capital letters, digits and underscores, and starts with a letter: CRM_TOKEN, ELEVENLABS_KEY.

Create your secrets in the Callr Console: with the Secrets button of the Actions page, or with Edit > Secrets in the editor. After secrets. in an expression, the editor completes the names of your secrets.

A secret is allowed in the params of an action and in defaults.textToSpeech.config (the key of your own text-to-speech engine) only. It is not allowed in variables, if, goto or in the code of javascript@v1, where it is not replaced by its value: the checks of the Console report the error SECRET_NOT_ALLOWED_HERE. The value of a secret is never shown again once saved, and never logged: it is masked in the logs of your scenarios. To change it, type a new value.

An API key or a token written in the scenario itself (an Authorization header, a secret, an auth, the key of the text-to-speech engine...) is reported by a warning of the checks of the Console, CREDENTIAL_NOT_IN_SECRETS: keep it in a secret instead.

The example below calls your API with a token and speaks with your own ElevenLabs voice:

compat:
  version: "2025-09-01"

defaults:
  language: en-US
  voice: your-elevenlabs-voice-id # your ElevenLabs voice ID
  textToSpeech:
    engine: elevenlabs
    config:
      key: ${{ secrets.ELEVENLABS_KEY }}
      modelID: eleven_flash_v2_5

branches:
  inbound-call:
    actions:
      - action: fetch@v3
        params:
          url: https://crm.example.com/api/contacts
          method: GET
          headers:
            Authorization: Bearer ${{ secrets.CRM_TOKEN }}
        result: $contact

      - action: play@v3
        params:
          tts:
            text: Hello ${{ $contact.json?.firstname }}!

The AI actions (llm@v1, anthropic@v1, openai@v4 and voicebot@v3) need no API key: Callr bills the tokens per model (see the AI section of the rates, Billing > Pricing in the Callr Console). llm@v1 is always billed by Callr; openai@v4, anthropic@v1 and voicebot@v3 can also use your own key, to have the provider bill your account: keep it in a secret, secret: ${{ secrets.OPENAI_KEY }} (engine.auth for voicebot@v3). The variable secrets is described in Pre-defined Variables.


Conditions

Use the if statement to conditionally execute actions. Conditions are expressions evaluated with a modern JavaScript engine.

- if: call.fromNumber.match(/^\+33/)
  action: play@v3
  params:
    tts:
      text: Bonjour. Comment allez-vous?
      language: fr-FR

For multiple actions under one condition, use the if-then structure:

- if: call.fromNumber == '+33199004242'
  then:
    - action: play@v3
      params:
        tts:
          text: Hello boss!
    - action: play@v3
      params:
        tts:
          text: How are you today?
    - action: play@v3
      params:
        tts:
          text: See you later!
    - action: hangup@v1

Goto & Labels

The goto lets you jump to another branch or a specific label within a branch. This is useful for looping or branching logic.

compat:
  version: "2025-09-01"

branches:
  inbound-call:
    actions:
      - label: gather # Define a label
        action: gather@v4
        params:
          maxDigits: 1
          tts:
            text: Hello! Press 1 to accept the call
        result: $digits

      - if: $digits.input == '1'
        goto:
          branch: accept # Jump to a different branch

      - action: play@v3
        params:
          tts:
            text: >
              Sorry, I didn't get your answer.

      - goto:
          label: gather # Jump back to the label

  accept: # New branch defined here
    actions:
      - action: play@v3
        params:
          tts:
            text: >
              OK! I'm going to connect you to the other party.
      - action: bridge@v3

Defaults

Defaults allow you to define common behavior at the top level of your scenario or for specific branches. These values can be overridden by branch-level defaults.

compat:
  version: "2025-09-01"

defaults:
  language: en-US        # Default language for speech (TTS/STT)
  voice: en-US-Ava:DragonHDLatest # Default voice for TTS
  autoAnswer: true       # If false, you need to use answer@v1

branches:
  voicemail:
    defaults:
      language: en-GB    # the voicemail uses a British accent
    actions:
      - action: play@v3
        params:
          tts:
            text: Sorry, we are not available at the moment.

List of actions

Each action has its own page with its parameters, its result and examples: click its name, or browse them by family in Actions.

ActionDescription
amd@v3Detects answering machines.
answer@v1Answers an Inbound Call if autoAnswer is set to false.
anthropic@v1Communicates with Anthropic's Messages API. No API key needed: Callr bills the tokens.
bridge@v3Bridges an Outbound Call with the other leg of a dial action.
conference@v1Puts the caller in a conference room. See Conference Calls.
dial@v2Dials a phone number, initiating an Outbound Call.
dtmf@v1Sends DTMF tones.
email@v1Sends an email.
fetch@v3Communicates with HTTP(s), synchronously or asynchronously.
gather@v4Waits for DTMF input.
googleAnalytics@v1Pushes events to GA4.
hangup@v1Hangs up the call.
javascript@v1Runs JavaScript in a sandbox, useful for data manipulation.
llm@v1Asks an AI model. No API key needed: Callr bills the tokens.
log@v1Logs data.
memcache@v1Stores data in a temporary memory store.
mixpanel@v1Pushes events to MixPanel.
openai@v4Communicates with OpenAI platform. No API key needed: Callr bills the tokens.
play@v3Plays a text-to-speech prompt (tts) or an uploaded audio file (audioFile). See Audio Files.
playRecordingFile@v1Plays a temporary audio recording (record@v1).
saveRecordingFile@v1Saves a temporary audio recording. See Recordings.
recognize@v3Recognizes audio (STT - Speech-to-Text).
record@v1Records audio to a temporary file.
ringing@v1Plays a ringing tone before an Inbound Call is answered (autoAnswer: false).
say@v3Alias of play@v3 (same parameters). Prefer play@v3.
sms@v1Sends an SMS message.
startRecording@v2Starts recording the call, automatically creating a Recording.
stopRecording@v2Stops the call recording. Its result is the Recording SID, in lowercase.
voicebot@v3Initiates a Voice AI Agent, with the history of what was really said on the phone. No API key needed: Callr bills the tokens.
wait@v1Pauses execution for a set duration.
waitForSilence@v1Waits until the caller stops speaking.

Older versions

Older versions are deprecated: they keep working until the date below, then they stop working. Move your scenarios to the current version before that date:

Older versionsCurrentStops working onWhat changes
say@v1, say@v2, play@v1, play@v2play@v3say@v1: March 1, 2027
the others: June 1, 2027
One way to play audio: tts (text-to-speech) or audioFile (sid). say@v3 is an alias of play@v3
gather@v1 to gather@v3gather@v4gather@v1: March 1, 2027
gather@v2, gather@v3: June 1, 2027
Prompt with tts or audioFile
recognize@v1, recognize@v2recognize@v3recognize@v1: March 1, 2027
recognize@v2: June 1, 2027
Prompt with tts or audioFile
amd@v1, amd@v2amd@v3amd@v1: March 1, 2027
amd@v2: June 1, 2027
Prompt with tts or audioFile
dial@v1dial@v2June 1, 2027cli becomes callerId, ring becomes an audioFile
bridge@v1, bridge@v2bridge@v3June 1, 2027Beep before bridging set with feedback
fetch@v1, fetch@v2fetch@v3fetch@v1: March 1, 2027
fetch@v2: June 1, 2027
audio becomes feedback
openai@v1 to openai@v3openai@v4openai@v1: March 1, 2027
openai@v2, openai@v3: June 1, 2027
secret is optional: without it, no API key needed, Callr bills the tokens. From openai@v1 and openai@v2: audio becomes feedback
startRecording@v1, stopRecording@v1startRecording@v2, stopRecording@v2June 1, 2027The result is the Recording SID in lowercase
voicebot@v1, voicebot@v2voicebot@v3June 1, 2027engine.auth and engine.model are optional, no API key needed: Callr bills the tokens. From voicebot@v1: interruptions replaces stopSpeaking, pleaseWait.delay becomes pleaseWait.delayBefore, temperature is not sent by default, the Responses API is used (engine.api)

Templates

Complete scenarios to copy, also available in the template gallery of the Callr Console. Browse them all in Templates:

  • Route calls: call forwarding, phone menus, opening hours, call screening, AI intent routing
  • Never miss a call: voicemail with transcription, missed-call SMS
  • AI agents: AI receptionist, AI personal assistant, outbound AI agent, voice AI over SIP, AI SMS agent
  • Call data: recording, transcription and AI summaries
  • Connect people: click-to-call, conference rooms, number masking
  • Reminders & alerts: appointment reminders, voice OTP, alert escalation
  • SMS: SMS auto-reply, links by SMS during a call
  • Call tracking: tracking per campaign, advanced call tracking
  • Integrations: your API, Salesforce, your own ElevenLabs voice

➡️

Prefer working programmatically?

You can also create and manage your scenarios using our API.


Did this page help you?