Skip to main content
The integration_call node calls an OAuth-backed third-party integration directly from inside a flow — no tools row required. The node carries the integration config (provider, account, action, args) on itself; the runtime resolves each entry in args_template per its declared mode (literal / ai_extract) — interpolating {{node.arg}} and {{metadata.key}} mustache tokens — and dispatches against the integration. The result routes deterministically the same way a tool-call node does (success / error / custom JSONPath branches, exactly one out-edge per fire). Use an integration_call node when the action is a first-class capability of a managed integration — booking a calendar event, sending email — and you don’t want to hand-author a webhook URL + payload schema in the tools table for it. For the /integrations connect / list / disconnect surface that owns the OAuth handshake, see the Integrations section.

Schema

integration_call nodes do not support is_global. Like tool_call, they’re deterministic dispatch nodes; only conversation, transfer, and end nodes can be marked global.

Arg modes

Every entry in args_template is an ArgValue — a discriminated union with two writable shapes plus a string shorthand for literals:

Token interpolation

Both literal.value and ai_extract.description strings are scanned for mustache tokens at dispatch time. Two namespaces are supported:
  • {{<node_id>.<arg_name>}} — resolves to the value an earlier node AI-extracted from the conversation. Both integration_call AND tool_call source nodes are addressable. For an integration_call source the referenced arg must be declared in ai_extract mode in that node’s args_template. For a tool_call source the referenced arg name must match an entry in the linked tool’s config.payload_config.extraction_parameters — all extraction_parameters are AI-extracted at runtime, so any of them work as a token source. The referenced node must exist in the same flow.
  • {{metadata.<key>}} — resolves against per-call metadata. Built-in keys (always available): id, direction, agent_number, user_number, agent_name. User-defined keys come from the metadata dict passed at call dispatch (POST /calls body.metadata) — declare which custom keys your flow expects in flow_config.metadata.custom_metadata_keys so the dashboard can surface them.
Direction details for the built-in metadata keys: for inbound calls the caller is the user and the callee is the agent; for outbound the caller is the agent. agent_number / user_number hide that distinction so you don’t have to special-case direction. Missing metadata.<key> resolves to an empty string — it is not an error. The caller is responsible for passing the value at call time. Save-time validation only catches dangling {{node.arg}} references where the referenced node or arg doesn’t exist or isn’t in ai_extract mode (args_template_dangling_reference). Example — include the caller’s phone number in the email body so the support team can call back without context-switching, and reuse the recipient that an earlier node already extracted:
Example — pull a custom caller-supplied key ({{metadata.CustomerEmail}}) declared in flow_config.metadata.custom_metadata_keys:
If the caller forgets to pass CustomerEmail at dispatch time, the to field resolves to an empty string and the integration’s own validation will surface the issue at runtime via the node’s error branch.

Action catalog

The runtime knows the following actions per provider. Anything outside this catalog returns action_invalid at save time.

Google Calendar (provider: "google_calendar")

start_time / end_time are ISO-8601 strings (e.g. "2026-05-12T10:00:00+02:00"). attendees is an array of email strings. calendar_id accepts a Google calendar id or "primary" (default). cancel_event does not expose calendar_id — the runtime auto-resolves which calendar a given event lives on (tries primary first; falls back to scanning the user’s other writable calendars on a 404). One extra API call only when the event lives on a non-primary calendar. time_zone is an IANA name (e.g. "Asia/Jerusalem"). When set, Google’s API response is pinned to that zone AND the event being created is stamped with it. When blank, the calendar’s default timezone is used. The dashboard ships an IANA picker; over the API any valid zone string works.

Response post-processing — wall-clock dateTimes

Calendar action responses (create_event, list_events, check_availability) are post-processed before the voice agent receives them, because Gemini Live’s ISO 8601 parser ignores offsets unreliably. The runtime:
  1. Strips the offset and seconds from each event’s start.dateTime / end.dateTime, leaving wall-clock format ("2026-05-10 16:30").
  2. Removes the per-event start.timeZone / end.timeZone fields (they document the authoring timezone — not the wall-clock zone — and contradict the wall-clock anchor).
  3. Adds top-level timeZone + timeZone_note (“Event times below are wall-clock values in <tz> …”) so Live has one explicit anchor.
The <tz> quoted in the note is whatever you passed in time_zone, or — if blank — whatever timezone Google reported (the calendar’s primary). The agent never sees raw ISO offsets for these actions. Concrete before/after for a list_events response in Israel (UTC+3):
The raw, untouched Google response is preserved separately on the call event as raw_response_preview, viewable in the dashboard’s call-detail sheet alongside the agent-facing view (see “Tool result events” below). The flow agent itself only ever sees the sanitized version. JSONPath custom transitions (transitions.custom[].jsonpath) match against the sanitized view — so $.items[0].start.dateTime matches "2026-05-10 16:30", not Google’s ISO.

Gmail (provider: "gmail")

to, cc, bcc accept either a single email string or an array of email strings. body is plain text; pass html: true to mark body as HTML.

Examples

Calendar — create an event

Calendar — check availability with a custom branch

Gmail — send a confirmation, reusing the email captured earlier

The to address was already extracted by the create_event node above, so this node splices it through with a {{create_event.attendees}} token instead of asking the caller again:

Transitions

Routing on an integration_call node is deterministic — no LLM is involved. Exactly one out-edge fires per dispatch:
  1. error_next_step_id fires only on hard failures: 4xx/5xx from the provider, network timeout, integration disconnected, integration belongs to a different company, missing required arg.
  2. Otherwise the dispatcher walks custom[] top-to-bottom — first branch whose jsonpath extracts a value == equals (after JSON stringification) wins, loop returns, success is NOT also taken.
  3. If no custom matched → success_next_step_id fires.
This is the same semantics as a tool_call node — see the flow composition guide for the full JSONPath subset and stringification rules. Always design an error branch. The result of the action is injected as a <tool_result> block into the next node’s LLM context, so a single success → conversation node usually handles both happy-path and soft-fail cases gracefully via prompt instructions. Reach for custom[] only when the next node should be structurally different for that result shape.

Validation errors

Saves that include a malformed integration_call node return a 400 FLOW_INVALID response with one or more issues from this list: The full save-validation contract — codes that apply to other node types too, the response shape, and the global terminal/reachability rules — lives on the agent update endpoint.

Setting up the integration

Before you can use an integration_call node, the integration itself has to exist:
  1. Connect Google Calendar / Gmail from the dashboard’s Integrations page. The OAuth handshake (popup → Google consent → callback) is dashboard-only; the public API does not expose a connect endpoint.
  2. List your integrations with GET /integrations and grab the id of the row whose provider matches what you want to call.
  3. Plug that id into your node’s integration_id.
If the integration is later disconnected, calls hitting that node fail with error (the runtime returns integration_disconnected) until you reconnect from the dashboard or update the node to point at a different integration_id.