What is an action binding?
An action binding lets a playground @key invoke one platform-tool action (e.g. Slack post message, Stripe create charge) or AI model call (e.g. OpenAI summarise) directly — without building a workflow around it. Useful when a single button needs to run a single action.
- platform_tool_action — runs one action from a connected platform tool, using the team connection you select.
- ai_tool_action — runs one AI model call. Use a team API connection (BYOK) or leave the connection empty to use the platform default key when your plan and the registry both support it.
Phase 1 limits: synchronous only (~25 s budget), no media-generating AI models (image/video/audio), no file-output actions.
Creating a binding
- Open the bottom Keys drawer and add a key. Pick type Platform action or AI action in the per-key toggle.
- Click the gear icon on that row and choose Configure action.
- Pick the Platform + Action (or AI registry + Model) and the team Connection the action should run with. Use the refresh icon next to Connection to pull a fresh list, or the open-in-new-tab icon to add a connection.
- For each declared input the action exposes, pick a source: From @key (snapshot a registered input/text/js_value @key at call time), Custom JS (a JS expression evaluated in the iframe before sending — same scope as your script, so it can read globals / DOM / top-level let/const bindings), Manual (you pass the value yourself when you invoke the action — from page JS or from a server function), Static value (you bake in a literal value), or Skip (only allowed for optional inputs).
- Each declared output is returned to the iframe by default. Uncheck any you want to hide from the public response.
- Save. An invocation code is minted — this is the bearer credential the iframe SDK uses; rotate it if it leaks.
Wiring an input to the snapshot
When you pick From @key for an action input, the binding declares a client-facing alias. By default the alias is the action's field key (e.g. text). The iframe must have a matching input / text / js_value @key with the same name so the SDK can snapshot a value into it.
If two bindings on the same playground both declare an input named text, give each one a unique alias in the drawer (e.g. summarise_text / classify_text) and create matching @keys for each.
Custom JS source
For values that aren't tied to a single @key — current timestamp, a computed property, the URL of the embedding page, a value derived from multiple fields — pick Custom JS in the source dropdown and write a JS expression like Date.now(), document.title, or cart.subtotal * 1.08. The SDK evaluates the expression in your script's lexical scope at call time (same path as js_value @keys), so it sees the top-level let / const bindings in your playground JS. The result is sent as the value of the declared input.
The expression runs every invocation — there's no caching. Use it for dynamic values; for constants use Static value instead.
Manual source
Pick Manual when you want to supply the value yourself at the moment you invoke the action, rather than tying it to a snapshot @key. It works the same whether the action runs from the page or from your server code:
- From page JS:
flowfn.invoke('send_email', { subject: 'Hi', html: body }) - From a server function:
ctx.tools.invoke('send_email', { subject: 'Hi', html: body })
The drawer lets you name the input the caller passes (e.g. html). Manual inputs are never auto-filled, so if you invoke the action without passing one, it's simply left out of the call. This is the natural choice for actions you drive entirely from code — for example building an email body on the server and sending it through a bound email action.
Auto-fire on click
Tag a button as a platform_tool_action / ai_tool_action @key (via Inspect, or via a selector in the Keys drawer). Clicks fire flowfn.invoke automatically with the snapshot values matching the binding's declared inputs. Listen for results via:
flowfn.onInvokeResult((res) => {
if (res.ok) console.log(res.key, res.outputs);
else console.error(res.key, res.error);
});
Invoking from JavaScript
Call flowfn.invoke(keyName, inputs) when you need full control — e.g. running an action on a non-click event or transforming inputs before sending. Returns a Promise:
const result = await flowfn.invoke('summarise', { text: 'hello world' });
// → { status: 'completed', key: 'summarise', outputs: { message: '...' } }
Unknown input names are rejected by the server. Missing required inputs trigger an invalid_inputs error. The full handler error is server-side only — the iframe sees a sanitised message.
Security
- The invocation code is server-resolved — the iframe never sees which action runs, only its public alias. Treat it like any bearer credential; don't commit it to a public repo.
- Inputs are validated against the binding's allowlist; extra fields are stripped silently.
- Each (slug, IP) burst is rate-limited like other public surfaces. Persistent abuse trips the same 429s as form / trigger submissions.
- If you delete a team connection that an action binding references, the dashboard refuses the delete and points you at the playground.