Open the Flume iframe for one item, let the user find and price an alternative, and receive the result as a message. Your backend makes two calls. Your page runs one snippet.
To run a working page first, copy the full example.
Before you begin
Section titled “Before you begin”- A sandbox partner key. Get a sandbox key.
- Your page's origin, set as an allowed origin by Flume. For local work, ask for
http://localhost:3000. - Only if you send item photos: your image host, set by Flume.
Flume sets your allowed origins and image hosts. You cannot change them yourself today. See Security and CSP.
1. Start a session from your backend
Section titled “1. Start a session from your backend”Call this when the user clicks your "Find alternatives" button. Send the user, the project and the item.
curl -X POST "https://sandbox-api.tryflume.ai/partner/v1/start-iframe-session" \ -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \ -H "Content-Type: application/json" \ -d '{ "user": { "external_ref": "u_8812", "email": "sarah.chen@example.com", "first_name": "Sarah", "last_name": "Chen" }, "project": { "external_ref": "proj_7781", "name": "Harbor Lofts - L2", "city": "McLean", "state": "VA", "destination_zip": "22102" }, "item": { "external_id": "48213", "label": "T-1", "name": "Calacatta look porcelain 24 x 48", "item_type": "Tile", "quantity": 6000, "uom": "Sq. Ft." }, "host_origin": "https://app.acme.example"}'const response = await fetch("https://sandbox-api.tryflume.ai/partner/v1/start-iframe-session", { method: "POST", headers: { "X-Flume-Partner-Key": process.env.FLUME_PARTNER_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "user": { "external_ref": "u_8812", "email": "sarah.chen@example.com", "first_name": "Sarah", "last_name": "Chen" }, "project": { "external_ref": "proj_7781", "name": "Harbor Lofts - L2", "city": "McLean", "state": "VA", "destination_zip": "22102" }, "item": { "external_id": "48213", "label": "T-1", "name": "Calacatta look porcelain 24 x 48", "item_type": "Tile", "quantity": 6000, "uom": "Sq. Ft." }, "host_origin": "https://app.acme.example" }),});if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);console.log(await response.json());import os
import requests
response = requests.post( "https://sandbox-api.tryflume.ai/partner/v1/start-iframe-session", headers={ "X-Flume-Partner-Key": os.environ["FLUME_PARTNER_KEY"], }, json={ "user": { "external_ref": "u_8812", "email": "sarah.chen@example.com", "first_name": "Sarah", "last_name": "Chen" }, "project": { "external_ref": "proj_7781", "name": "Harbor Lofts - L2", "city": "McLean", "state": "VA", "destination_zip": "22102" }, "item": { "external_id": "48213", "label": "T-1", "name": "Calacatta look porcelain 24 x 48", "item_type": "Tile", "quantity": 6000, "uom": "Sq. Ft." }, "host_origin": "https://app.acme.example" }, timeout=30,)response.raise_for_status()print(response.json())Response 201:
{ "session_uuid": "c1f0a6de-3b52-4d8e-9c47-2f8a1b6d0e93", "iframe_url": "https://<iframe-origin>?partner=acme#launch_code=launch_example&session=c1f0a6de-3b52-4d8e-9c47-2f8a1b6d0e93"}Change host_origin to the exact origin of your own page before you run the sample. Every field is on Server calls.
Then:
- Return
iframe_urlandsession_uuidto your page. - Use
iframe_urlat once. Its launch code works once, within 120 seconds. - Never build or edit
iframe_urlyourself.
2. Add the iframe to your page
Section titled “2. Add the iframe to your page”Create the iframe with this sandbox attribute and no src. The snippet sets src.
<iframe id="flume" title="Flume: find alternatives" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"></iframe>Give it a size. Without CSS an iframe is 300 by 150 pixels.
#flume { position: fixed; inset: 0 0 0 auto; width: min(470px, 100vw); height: 100dvh; border: 0;}3. Mount it with the host snippet
Section titled “3. Mount it with the host snippet”Save the host snippet next to your page:
curl -sSO "https://docs.tryflume.ai/samples/mount-flume.js"Then call it from a module script. mountFlume returns a function, stop, that removes its listener and timer.
import { mountFlume } from "./mount-flume.js";
const frame = document.querySelector("#flume");const { iframe_url, session_uuid } = await startSessionOnYourBackend();
function removeFrame() { stop(); frame.removeAttribute("src");}
const stop = mountFlume({ frame, iframeUrl: iframe_url, sessionUuid: session_uuid, flumeOrigin: new URL(iframe_url).origin, handlers: { hideSpinner() {}, showError() { removeFrame(); showRetry("Flume did not load.", openFlumeAgain); }, exchangeConnectCode(connectCode) { sendToYourBackend("/flume/connect", { connectCode }); }, async saveItem(payload) { const row = await sendToYourBackend("/flume/save", payload); return { save_uuid: payload.save_uuid, item_ref: row.id, item_label: row.label }; }, close(reason) { removeFrame(); if (reason === "expired") openFlumeAgain(); }, },});startSessionOnYourBackend, sendToYourBackend, showRetry and openFlumeAgain are your own functions. openFlumeAgain starts a new session and mounts again.
- If
saveItemthrows, the snippet repliesflume:item_save_failedwith reasonerror. Throw{ reason: "storage" }to state a reason. - Call
stop()whenever your page removes the iframe itself.
4. Exchange the connect code
Section titled “4. Exchange the connect code”The first time, the user verifies their email inside the iframe. Your page then receives flume:user_connected with a connect_code. Send it to your backend and exchange it within 120 seconds.
curl -X POST "https://sandbox-api.tryflume.ai/partner/v1/users/exchange-connect-code" \ -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \ -H "Content-Type: application/json" \ -d '{ "connect_code": "connect_example_code_0000"}'const response = await fetch("https://sandbox-api.tryflume.ai/partner/v1/users/exchange-connect-code", { method: "POST", headers: { "X-Flume-Partner-Key": process.env.FLUME_PARTNER_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "connect_code": "connect_example_code_0000" }),});if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);console.log(await response.json());import os
import requests
response = requests.post( "https://sandbox-api.tryflume.ai/partner/v1/users/exchange-connect-code", headers={ "X-Flume-Partner-Key": os.environ["FLUME_PARTNER_KEY"], }, json={ "connect_code": "connect_example_code_0000" }, timeout=30,)response.raise_for_status()print(response.json())Response 200:
{ "external_ref": "u_8812", "email": "sarah.chen@example.com", "access_token": "ut_dev_acme_example_token", "token_type": "user_token", "verified_at": "2026-10-16T09:02:11Z", "account": "LINKED"}- Check that
external_refis the user who is signed in to your product. - Store
access_tokenagainst that user. It replaces any token you held for them. - Send it as
X-Flume-User-Tokenon the nextPOST /start-iframe-session. The user then skips the email step.
5. Save the item
Section titled “5. Save the item”When the user saves, your page receives flume:save_item. Its payload is the save payload.
- Write it into your own records. Use
save_uuidas your idempotency key. - Return
{ save_uuid, item_ref }fromsaveItemon success. - Throw on failure. The reasons are
permission,conflict,storageanderror.
Reply within 60 seconds. After that the iframe treats the save as failed.
It worked
Section titled “It worked”Your page opened the iframe, the user verified their email, and one saved item reached your records.
Next steps
Section titled “Next steps”- Full example: one page and one server you can run.
- Iframe messages: every message and its payload.
- Save payload: every field you can map.
- Timeouts and expiry: what to do when a session ends.
- Security and CSP: what to allow on your page, and what to trust.