---
title: "Quickstart"
description: "Open the Flume iframe from your page with two backend calls and a short host snippet, then receive the item your user saved as a message."
url: https://docs.tryflume.ai/quickstart/embed
updated: 2026-11-11
---

# Quickstart

> Open the Flume iframe from your page with two backend calls and a short host snippet, then receive the item your user saved as a message.

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](https://docs.tryflume.ai/embed/example.md).

## Before you begin

- A sandbox partner key. [Get a sandbox key](https://docs.tryflume.ai/get-a-key.md).
- 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](https://docs.tryflume.ai/embed/security.md).

## 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**
```sh
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"
}'
```
**Node.js**
```js
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());
```
**Python**
```python
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`:

```json
{
  "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](https://docs.tryflume.ai/embed/server-calls.md#start-a-session).

Then:

- Return `iframe_url` and `session_uuid` to your page.
- Use `iframe_url` at once. Its launch code works once, within 120 seconds.
- Never build or edit `iframe_url` yourself.

## 2. Add the iframe to your page

Create the iframe with this `sandbox` attribute and no `src`. The snippet sets `src`.

```html
<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.

```css
#flume {
  position: fixed;
  inset: 0 0 0 auto;
  width: min(470px, 100vw);
  height: 100dvh;
  border: 0;
}
```

## 3. Mount it with the host snippet

Save the host snippet next to your page:

```sh
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.

```js
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 `saveItem` throws, the snippet replies `flume:item_save_failed` with reason `error`. Throw `{ reason: "storage" }` to state a reason.
- Call `stop()` whenever your page removes the iframe itself.

> **Take the origin from iframe_url:** Do not hardcode the iframe's origin. Read it with `new URL(iframe_url).origin`. It differs between the two environments, and it can change.

## 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**
```sh
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"
}'
```
**Node.js**
```js
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());
```
**Python**
```python
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`:

```json
{
  "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"
}
```

1. Check that `external_ref` is the user who is signed in to your product.
2. Store `access_token` against that user. It replaces any token you held for them.
3. Send it as `X-Flume-User-Token` on the next `POST /start-iframe-session`. The user then skips the email step.

## 5. Save the item

When the user saves, your page receives `flume:save_item`. Its payload is the [save payload](https://docs.tryflume.ai/concepts/save-payload.md).

1. Write it into your own records. Use `save_uuid` as your idempotency key.
2. Return `{ save_uuid, item_ref }` from `saveItem` on success.
3. Throw on failure. The reasons are `permission`, `conflict`, `storage` and `error`.

Reply within 60 seconds. After that the iframe treats the save as failed.

## It worked

Your page opened the iframe, the user verified their email, and one saved item reached your records.

## Next steps

- [Full example](https://docs.tryflume.ai/embed/example.md): one page and one server you can run.
- [Iframe messages](https://docs.tryflume.ai/embed/messages.md): every message and its payload.
- [Save payload](https://docs.tryflume.ai/concepts/save-payload.md): every field you can map.
- [Timeouts and expiry](https://docs.tryflume.ai/embed/timeouts.md): what to do when a session ends.
- [Security and CSP](https://docs.tryflume.ai/embed/security.md): what to allow on your page, and what to trust.
