> ## Documentation Index
> Fetch the complete documentation index at: https://koreai-content-gov.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# DTMF support in Artemis voice agents

Dual-Tone Multi-Frequency (DTMF) enables callers and voice agents to communicate using telephone keypad tones during an active voice call.

Artemis provides built-in support for both inbound DTMF (caller-to-agent) and outbound DTMF (agent-to-external IVR), allowing developers to build complete IVR experiences without implementing custom telephony logic. It also supports **secure DTMF collection** for sensitive workflows such as payment processing.

## DTMF architecture

DTMF is supported for pipeline voice agents through the Kore Voice Gateway. Currently, it is not available for realtime voice agents. The platform exposes DTMF functionality through three built-in system tools:

| Tool | Direction | Purpose |
| - | - | - |
| `ivr_menu` | Inbound | Present a menu and collect a single keypad selection. |
| `ivr_digit_input` | Inbound | Collect multi-digit keypad input such as account numbers or PINs. |
| `send_dtmf` | Outbound | Send keypad tones into an active call to control an external IVR. |

These tools are implemented by the platform runtime and require no HTTP endpoint or custom tool implementation.

## How DTMF works

When a caller presses a key during a voice call, the tone is detected by the voice gateway and delivered directly to the Artemis runtime. The process follows these steps:

```text theme={null}
Caller presses a key
       │
       ▼
KoreVG detects DTMF
       │
       ▼
KoreVG sends an event containing the digit data (data.digits) to the runtime
       │
       ▼
Runtime processes the event
       │
       ▼
Digit string delivered to the agent
       |
  ▼
Redacted status recorded in traces / eval artifacts / Studio inspection
```

Unlike speech recognition, DTMF input does not use Automatic Speech Recognition (ASR). Every valid keypress is considered deterministic and is assigned a confidence score of 1.0.

## DTMF capabilities

### Enable DTMF capabilities

As discussed above, the DTMF feature is built-in and can be implemented by the system tools. To use these tools within your agent, add the tools definition to the Tools section of the ABL file.

## DTMF Tools

### Presenting an IVR Menu

Use `ivr_menu` when callers need to select from a numbered menu.

The tool plays a prompt, waits for a single keypad press, and maps the selected digit to a workflow step or intent.

**Tool Definition**

```yaml theme={null}
TOOLS:
  ivr_menu(
    prompt: string,
    dtmfMappings: object[],
    noInputConfig: object,
    noMatchConfig: object,
    bargeIn: boolean = false,
    language: string = "en"
  ) -> {digit: string, matched: boolean, mappedIntent: string, branch: string}
    description: "Present a DTMF menu to the voice caller. The caller presses a digit to select an option. Use for voice IVR flows with numbered choices."
```

**Input Parameters**

| Parameter | Type | Required | Description |
| - | - | - | - |
| `prompt` | string | Yes | TTS message played to the caller |
| `dtmfMappings` | array | Yes | 1–12 mappings of `{key, nextStep, intent?}`. Keys: 0–9, \*, # |
| `noInputConfig` | object | Yes | `{timeout: 1-120s, maxRetries: 0-10, message: string}` |
| `noMatchConfig` | object | Yes | `{maxRetries: 0-10, message: string}` |
| `bargeIn` | boolean | No | Allow caller to press key while prompt plays. Default: false |
| `language` | string | No | Language code for TTS playback (for example, `en`, `es`) |

**Example**

```yaml expandable=true theme={null}
CALL: ivr_menu
WITH:
  prompt: "Press 1 for Billing, 2 for Support, or 0 for an agent."

  dtmfMappings:
    - key: "1"
      nextStep: billing
      intent: billing

    - key: "2"
      nextStep: support
      intent: support

    - key: "0"
      nextStep: escalate
      intent: agent
```

### Collect multi-digit input

Use `ivr_digit_input` when callers must enter a sequence of digits.

The tool continues collecting digits until one of the following occurs:

* Maximum digits reached
* Ending key pressed
* Inter-digit timeout expires

**Tool definition**

```yaml expandable=true theme={null}
ivr_digit_input(
    prompt: string,
    maxDigits?: number,
    endingKeyPress?: string,
    interDigitTimeout?: number,
    noInputConfig: object,
    noMatchConfig: object,
    language?: string
) -> object
description: "Collect multiple DTMF digits from the caller."
```

**Input Parameters**

| Field | Type | Required | Description |
| - | - | - | - |
| `prompt` | string | Yes | TTS message played to the caller asking them to enter digits. |
| `maxDigits` | number (integer) | No | Maximum digits to collect before finalizing. Default: 10. If `endingKeyPress` is also set, internally increments by 1 to account for the terminator key. |
| `endingKeyPress` | string | No | Key the caller presses to signal they're done (for example, #). Collection stops early when this key is pressed. |
| `interDigitTimeout` | number (integer, milliseconds) | No | How long to wait after each keypress before assuming input is complete. Default: 2000 ms. |
| `noInputConfig` | object | Yes | What to do when the caller presses nothing. |
| `noMatchConfig` | object | Yes | What to do when the input doesn't satisfy conditions. |
| `language` | string | No | BCP 47 language code for TTS rendering (for example, "en" or "es"). |

**noInputConfig Sub-fields**

| Field | Type | Required | Description |
| - | - | - | - |
| `timeout` | number (integer, seconds) | Yes | Seconds to wait for the first digit before triggering no-input. |
| `maxRetries` | number (integer) | Yes | How many times to re-play the prompt on silence. |
| `message` | string | Yes | TTS message played when the caller doesn't press anything. |

**noMatchConfig Sub-fields**

| Field | Type | Required | Description |
| - | - | - | - |
| `maxRetries` | number (integer) | Yes | How many times to re-play the prompt when input is invalid. |
| `message` | string | Yes | TTS message played when input doesn't match expectations. |

**Example**

```yaml expandable=true theme={null}
CALL: ivr_digit_input
WITH:
   prompt: "Please enter your 6-digit PIN followed by the pound key."
    maxDigits: 6
    endingKeyPress: "#"
    interDigitTimeout: 3000
    noInputConfig:
      timeout: 15
      maxRetries: 2
      message: "I didn't receive any input. Please enter your PIN."
    noMatchConfig:
      maxRetries: 1
      message: "Entry incomplete. Please enter your full PIN."
    language: "en"
```

### Send DTMF to external IVRs

Use `send_dtmf` when Artemis needs to navigate another IVR after a call has been established.

Typical examples include:

* Selecting a department
* Navigating hospital booking systems
* Banking IVRs
* Telecom IVRs
* Legacy call routing systems

Unlike the inbound tools, `send_dtmf` transmits keypad tones rather than collecting them.

**How it works**

During an active voice call, the agent can decide to send DTMF digits, for example after recognizing it has reached an IVR menu:

```text theme={null}
Agent reaches IVR menu
         ↓
Agent sends DTMF digits
         ↓
Platform validates digits and call state
         ↓
Digits dispatched in order to the voice provider
         ↓
Redacted status recorded in traces / eval artifacts / Studio inspection
```

Digits are dispatched one at a time, in order, with a configurable pause between digits. Raw digits are never written to logs, traces, eval artifacts, or Studio evidence — only a digit count and a status are recorded.

**Tool Definition**

```yaml theme={null}
TOOLS:
  send_dtmf(
      digits: string,
      durationMs?: number,
      interDigitDelayMs?: number,
      reason?: string
  ) -> object
```

Once declared, `send_dtmf` behaves like any other tool available to the LLM: during an active call, the model decides when to call it and what `digits` to pass, based on the conversation so far and your agent's `GOAL`, `PERSONA`, and `INSTRUCTIONS`. For example, an `INSTRUCTIONS` line such as "When the IVR menu asks you to select a department, call `send_dtmf` with the digit for that option" steers the model without hardcoding the digits at author time.

**Example**

```yaml theme={null}
CALL: send_dtmf
WITH:
  digits: "{{booking_code}}"
  durationMs: 300
  interDigitDelayMs: 500
  reason: "Selecting doctor queue"
```

The runtime validates the request before dispatching digits sequentially to the voice provider.

**Input Parameters**

| Field | Required | Notes |
| - | - | - |
| `digits` | Yes | Only 0-9, \*, and #. 1–64 characters. |
| `durationMs` | No | Tone duration per digit, in milliseconds. Range: 50-5000. Default: 250. |
| `interDigitDelayMs` | No | Delay between digits, in milliseconds. Range: 50-5000. Default: 300. |
| `reason` | No | Up to 256 characters. Do not include PINs, account numbers, or the |

<Note>Pauses inside a call flow (for example, waiting for an IVR menu to finish playing before sending digits) are handled as separate wait steps, not by embedding non-DTMF characters in \`digits.</Note>

**Call-State Behavior**

| Call State | Result |
| - | - |
| Active, connected call | Digits dispatch immediately |
| Active playback/agent speaking | Digits dispatch immediately, without queuing behind playback |
| Inactive, closed, on hold without media, dialing, or already transferred | Fails closed before dispatch |
| Channel/provider without outbound DTMF support (VXML IVR, Genesys Audio Connector, AudioCodes) | Fails closed before dispatch |
| Invalid digits or out-of-range timing | Fails closed before dispatch |

**Status Codes**

| Code | Meaning |
| - | - |
| `VOICE_DTMF_DISPATCHED` | Digits were sent to the provider in order. |
| `VOICE_DTMF_INVALID` | `digits` failed validation (empty, too long, or disallowed characters), or `durationMs`/`interDigitDelayMs` was out of range. |
| `VOICE_DTMF_UNSUPPORTED` | The active channel/provider does not support outbound DTMF. |
| `VOICE_DTMF_INACTIVE` | The call was not in a state that allows dispatch (see table above). |
| `VOICE_DTMF_PROVIDER_ERROR` | The platform could not deliver a digit command to the provider. |

A successful `VOICE_DTMF_DISPATCHED` result means the platform sent the tones — it does not confirm that the far-end IVR accepted or acted on them. There is currently no delivery acknowledgment or automatic retry.

## Privacy and evidence

DTMF digits can carry sensitive information such as PINs or account numbers. Runtime traces, eval run artifacts, and Studio inspection show only a redacted digit count and status — never the raw digits sent during a call.

## Secure DTMF input

Use Secure DTMF Input when collecting sensitive information, such as payment card numbers, PINs, or other confidential data that must not be processed through speech recognition.

During secure input collection, platform temporarily disables voice input and accepts only DTMF key presses. Spoken audio is not forwarded to the Automatic Speech Recognition (ASR) service, ensuring that sensitive information is not transcribed or processed by language models. After the secure input flow completes, normal voice interaction resumes automatically.

This capability is intended for PCI-compliant payment and authentication workflows.

**Example**

```json expandable=true theme={null}
AGENT: SecurePaymentDemo
GOAL: "Collect a card payment (PAN, expiry, CVV) via PCI SECURE_INPUT and charge it, without ever exposing the raw digits to the model or any log/trace/session surface."

TOOLS:
  validate_card(pan)
  charge_card(pan, exp, cvc)

FLOW:
  welcome -> offer_payment -> collect_card -> thank_you

  welcome:
    REASONING: false
    RESPOND: "Welcome. I can take your card payment securely over the phone. Your details are handled securely."
    THEN: offer_payment

  offer_payment:
    REASONING: false
    GATHER:
      - ready_to_pay:
          type: string
          required: true
          infer: true
          prompt_mode: ask
          description: "The caller's acknowledgement to begin payment. Set this to the caller's response verbatim. ANY non-empty caller utterance or key press satisfies this — 'ready', 'yes', 'ok', 'sure', 'go ahead', 'yeah', a number, or a single word all count. Only leave it empty if the caller said nothing at all."
          prompt: "When you're ready to pay, say 'ready' or press any key. I'll then ask for your card number, expiry, and security code."
    THEN: collect_card

  collect_card:
    REASONING: false
    SECURE_INPUT:
      MODE: [dtmf, speech]
      FIELDS:
        - card_number:
            PROMPT: { en: "Please enter or say your card number followed by the pound key.", es: "Por favor ingrese o diga el numero de su tarjeta seguido de la tecla numeral." }
            COLLECT: { digits: 13..19, finishOnKey: "#", interDigitTimeoutMs: 5000, asrTimeoutMs: 5000 }
            VALIDATE: [ luhn, { length: 13..19 }, CALL validate_card WITH { pan: "{{secure.card_number}}" } ]
        - expiry:
            PROMPT: { en: "Please enter the 4 digit expiry date, month then year, followed by the pound key." }
            COLLECT: { digits: 4..4, finishOnKey: "#", interDigitTimeoutMs: 3000, asrTimeoutMs: 2500 }
            VALIDATE: [ { date_mmYY: not_past }, { length: 4..4 } ]
        - cvv:
            PROMPT: { en: "Please enter your card security code, followed by the pound key." }
            COLLECT: { digits: 3..4, finishOnKey: "#", interDigitTimeoutMs: 3000, asrTimeoutMs: 2500 }
            VALIDATE: [ { length: 3..4 }, { regex: "^[0-9]{3,4}$" } ]
      MAX_ATTEMPTS: 3
      CANCEL: { dtmf_key: "*", keywords: [ "cancel", "stop" ] }
      CALL charge_card WITH: { pan: "{{secure.card_number}}", exp: "{{secure.expiry}}", cvc: "{{secure.cvv}}" }
      ON_SUCCESS: thank_you
      ON_TIMEOUT: human_agent
      ON_CANCEL: cancelled
      ON_FAIL: human_agent
    THEN: thank_you

  thank_you:
    REASONING: false
    RESPOND: "Thank you. I've securely captured your card details and your payment has been processed successfully. Is there anything else I can help you with?"

  human_agent:
    REASONING: false
    RESPOND: "I'm sorry, I wasn't able to complete that. Let me transfer you to a human agent for help with your payment."

  cancelled:
    REASONING: false
    RESPOND: "Okay, I've cancelled the payment collection. Is there anything else I can help with?"

```

[Learn More](/agent-platform/abl/reference/flow#secure_input).

## Known limitations

* Kore.ai Voice Gateway (KoreVG) is the only supported provider.
* No provider delivery acknowledgment or automatic retry is available for this method.
* No pause syntax inside `digits`; model pauses as separate wait steps.

**Example**

The following workflow combines inbound and outbound DTMF.

```text theme={null}
Caller
   │
   ▼
ivr_menu
   │
Caller presses 1
   │
booking_code = "1"
   │
   ▼
Connect to hospital IVR
   │
   ▼
send_dtmf("1")
   │
   ▼
Hospital routes caller
```

This pattern is commonly used for appointment booking and external IVR automation.

## Best practices

* Use `ivr_menu` for single-choice menus.
* Use `ivr_digit_input` for collecting structured numeric data.
* Use `send_dtmf` only after the external IVR is ready to receive keypad input.
* Enable barge-in where faster caller interaction is desired.
* Validate all tool results before continuing the workflow.
* Always provide a fallback or escalation path for invalid input or DTMF failures.
* Include DTMF tools only in voice-enabled agents.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.