Vapi

Transfer Destination Request

Changed on

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.

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

Request body

Response

Successful Response

Changes

    • info

      added the new optional request property /

    • info

      added the new optional request property /