---
title: "Transfer Destination Request"
method: POST
path: "/vapi/transfer-destination"
tags: ["Vapi"]
---

# Transfer Destination Request

`POST /vapi/transfer-destination`

## Request

- Base URL: `https://api.openintake.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.openintake.ai/vapi/transfer-destination`
- Auth: HTTP bearer

Resolve and execute a Vapi dynamic transfer (the routeTransfer tool).

routeTransfer is a custom `function` tool carrying arguments, so it is
dispatched as a normal tool-calls message — NOT a transfer-destination-request
webhook. (Vapi only fires that webhook for a transferCall tool that supplies
*no* destination; a tool call carrying arguments is treated as a supplied
destination and Vapi would try to dial the argument as a phone number,
dropping the call. That is the bug this endpoint replaces.)

Flow (Vapi Live Call Control, see
https://docs.vapi.ai/calls/call-dynamic-transfers): resolve the
TransferDestination from the arguments, then POST the transfer to the call's
monitor.controlUrl. We respond with the standard {"results": [...]} custom
tool shape.

Tool arguments:
  - reason (required): a department name ("Lien Dept", "Intake Dept",
    "Settlement Dept", "Opening Dept", "Calendar Dept",
    "Client Services", "Office") maps to the customer's
    TransferDestination rows by name;
    "case" routes by the case's SmartAdvocate status rules.
  - case_number (optional): the synced case to route by when
    reason="case", copied verbatim from lookupClient.
  - caller_language (required in the tool schema, tolerated absent here):
    "english" | "spanish" | "other" — the language the call is being
    conducted in. Feeds the resolved destination's Spanish override (see
    TransferDestination.spanish_transfer_destination_id); absent or "other"
    falls back to the caller's SmartAdvocate primary_language.
  - announcement (required in the tool schema, tolerated absent here):
    the line the model SPEAKS itself, mirrored into the argument for
    logging. The system never speaks it: dev A/B on 2026-08-31 showed
    every system-spoken slot doubles — the model narrates alongside the
    tool call on about half of transfer turns (three prompt phrasings
    could not stop it), and Vapi occasionally double-played the LCC
    `content` field even on bare tool calls. Instead the prompt has the
    model speak the announcement (ending with <flush />) and then call
    this tool in the same response, and this handler holds the transfer
    POST until the announcement's TTS actually finishes. The hold is
    closed-loop, not a blind timer: it arms a TransferAnnouncementGate row
    and waits for the /webhooks/vapi speech-update events to show the
    assistant has gone quiet (see _await_announcement_quiet). The tool is
    sync (async: false), so Vapi blocks the model's next turn during the
    hold, and the in-flight announcement keeps streaming meanwhile.

Bad or missing arguments degrade to the office fallback, never to a failed
transfer. Only when no destination exists at all, or the control URL is
missing, does this return a tool error (the model then apologizes and keeps
the call going).

Message-only cases (a case-status rule of kind take_message) are answered
here with transferred: false and an instruction to take a message, and none
of the transfer machinery runs — no announcement gate, no Spanish override,
no transfer-number stamp, so the call never looks transferred in reporting.
This is only the RECOVERY path, though: the primary mechanism is the
reach_team field the lookup tools stamp on such a case (see
_apply_case_reach_team), which keeps the model from announcing a transfer at
all. By the time this handler runs the model has usually already told the
caller they are being connected — the announcement is spoken in the same
turn as the tool call, by design — so the model has to recover mid-turn.

## Request body

- VapiCustomToolRequest: Wrapper for Vapi custom tool requests.
  - `message` VapiMessage, required
    - `type` string, required
    - `toolCallList` VapiToolCall[], required
      - `id` string, required
      - `type` string
      - `function` VapiToolCallFunction, required
        - `name` string, required
        - `arguments` object, required
    - `call` VapiCallObject
      - `id` string, required
    - `artifact` object, nullable
    - `assistant` object, nullable

## Response `200`

Successful Response

- VapiCustomToolResponse: Standard Vapi custom tool response format.
  - `results` VapiToolResult[], required
    - `toolCallId` string, required
    - `result` string, required

## Other responses

- `422`: Validation Error

## Changes

- **2026-09-14** `2ad5aecbce08`: 2 info
  - added the new optional request property `message/artifact`
  - added the new optional request property `message/assistant`

[Operation history](https://skmtc.dev/openintake/apis/openintake-api/changes/vapi/transfer-destination/post.md)

---

[API](https://skmtc.dev/openintake/apis/openintake-api.md) - [All operations](https://skmtc.dev/openintake/apis/openintake-api/llms.txt) - [OpenAPI document](https://skmtc.dev/openintake/apis/openintake-api/revisions/de90a8dcbc07?raw)
