Transfer Destination Request
Changed onResolve 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
- 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
/
- info