---
title: "Save payload"
description: "Saving an item gives you one versioned JSON payload for the product a user chose. This page covers the save flow and every field you can map."
url: https://docs.tryflume.ai/concepts/save-payload
updated: 2026-11-11
---

# Save payload

> Saving an item gives you one versioned JSON payload for the product a user chose. This page covers the save flow and every field you can map.

Saving an item gives you one JSON object, the **save payload**, for one product the user chose. You write it into your own records and tell Flume whether that worked.

## What is the flow?

**Diagram: Save an item.** The user saves in the iframe, your side writes the record, and your page answers. Your backend calls Flume at no point.

```mermaid
sequenceDiagram
  participant user as User
  participant iframe as Flume iframe
  participant page as Your page
  participant backend as Your backend
  participant flume as Flume servers
  user->>iframe: confirms a save
  iframe->>flume: build the save payload
  iframe-->>page: flume:save_item
  page->>backend: save payload
  Note over backend: write once per save_uuid
  backend->>page: your record id
  page-->>iframe: flume:item_saved
  iframe->>flume: record the result
  iframe->>user: saved screen
```

1. The user confirms a save in the iframe.
2. The iframe asks Flume for the save payload. If that fails, the iframe shows its own error and your page hears nothing.
3. The iframe posts flume:save_item with the save payload and its save_uuid.
4. Your page sends the payload to your backend.
5. Your backend writes one record per save_uuid, and copies the photo bytes if you keep a photo.
6. Your backend answers with your record, or with a failure.
7. Your page replies flume:item_saved with the same save_uuid, within 60 seconds. On a failed write it replies flume:item_save_failed instead.
8. The iframe tells Flume how the save ended. You make no call for this.
9. The iframe shows the saved screen.

| Step | In the iframe | With the API |
|---|---|---|
| 1. Build the payload | The iframe calls Flume | You call `POST /save-item` |
| 2. Receive it | Your page gets `flume:save_item` | It is the response body |
| 3. Write your record | Your page or backend | Your backend |
| 4. Report the result | Reply `flume:item_saved` or `flume:item_save_failed` | Call `POST /save-item/{save_uuid}/result` |

The payload is the same object on both paths. Step by step with the API: [Save an item](https://docs.tryflume.ai/concepts/save-payload.md).

## Why is save_uuid important?

`save_uuid` identifies one save. A retry sends the same `save_uuid` and gets the payload Flume stored the first time.

- Treat `save_uuid` as your idempotency key.
- If you already hold a record for it, return success without writing again.
- A `save_uuid` that belongs to another user returns `409` `SAVE_CONFLICT`.
- The stored payload keeps its first image links. They may have expired by the time of a retry. Read fresh links with `GET /products/{sku_uuid}`.

## How do you carry pricing?

- Send `quote_uuid` to include an instant quote's prices.
- Send `option_source` to pick the shipping option. Without it, Flume picks the recommended one.
- Leave `quote_uuid` out and `pricing` is `null`.
- The quote must be `READY`, the user's own, not expired, and at or above the option's minimum order. Otherwise the call returns `422` [`QUOTE_NOT_READY`](https://docs.tryflume.ai/api/errors.md#quote_not_ready).

## What must you do with the payload?

- **Copy image bytes.** Photo links expire after about 24 hours. In the sandbox they can stop working earlier.
- **Store `product_url`.** It is the durable link back to Flume. It does not expire.
- **Render text as text.** Escape every string on output. See [Text rules](#text-rules).
- **Reply within 60 seconds** in the iframe. Silence counts as a failure.
- **Check prices on your server** if they matter. See [Can you trust the save payload?](https://docs.tryflume.ai/embed/security.md#can-you-trust-the-save-payload)

A save also files the product in the user's Flume project, when the search has one.

## Example

```json
{
  "schema_version": "1.0",
  "save_uuid": "2b9d6f1c-7a34-4e58-b0c2-9d1e3f5a7b6c",
  "saved_at": "2026-10-16T09:20:05Z",
  "source": {
    "item_ref": "48213",
    "item_label": "T-1",
    "project_ref": "proj_7781"
  },
  "user": {
    "external_ref": "u_8812",
    "flume_user_uuid": "a4d2c7e1-6b39-4f80-8e15-3c9b7a2d6f04"
  },
  "project": {
    "external_ref": "proj_7781",
    "flume_project_uuid": "e7b1f3a9-2c64-4d05-a8f7-6b0e9d4c1a28"
  },
  "product_url": "https://app.tryflume.ai/?utm_source=acme&utm_medium=partner&save_uuid=2b9d6f1c-7a34-4e58-b0c2-9d1e3f5a7b6c#/product/7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
  "product": {
    "sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
    "name": "Porcelain 24×48″ Matte",
    "category": "tile",
    "material": "Porcelain",
    "finish": "Matte",
    "size_label": "24×48″",
    "glance": [
      {
        "label": "Size",
        "value": "24×48″"
      },
      {
        "label": "Thickness",
        "value": "9 mm"
      },
      {
        "label": "Finish",
        "value": "Matte"
      },
      {
        "label": "Slip resistance",
        "value": "DCOF over 0.42"
      }
    ],
    "spec_rows": [
      {
        "group": "Slip resistance",
        "rows": [
          {
            "label": "DCOF",
            "value": "over 0.42",
            "method": "ANSI A326.3",
            "note": null
          }
        ]
      }
    ],
    "gallery": [
      {
        "urls": {
          "640": "https://sandbox-api.tryflume.ai/partner/v1/images/example-image-main"
        }
      }
    ],
    "lanes": [
      {
        "supply_source_type": "OVERSEAS_DIRECT",
        "on_request": false,
        "minimum_order_sqft": 5000,
        "ships_from": {
          "country": "ES",
          "label": "Ships from Spain"
        },
        "lead_time": {
          "min_days": 90,
          "max_days": 120,
          "label": "Delivered in 90 to 120 days"
        }
      },
      {
        "supply_source_type": "US_WAREHOUSE",
        "on_request": false,
        "minimum_order_sqft": 1000,
        "ships_from": {
          "country": "US",
          "label": "Ships from the US"
        },
        "lead_time": {
          "min_days": 14,
          "max_days": 30,
          "label": "Delivered in 14 to 30 days"
        }
      }
    ],
    "suitable_application_areas": [
      {
        "code": "INTERIOR_DRY_FLOOR",
        "label": "Interior Dry Floor"
      }
    ]
  },
  "pricing": {
    "quote_uuid": "9c1e4b7a-2f3d-4e5b-8a6c-7d8e9f0a1b2c",
    "quote_number": "Q-7KD2-M4PA",
    "status": "READY",
    "created_at": "2026-10-16T09:14:22Z",
    "valid_until": "2026-10-23T09:14:22Z",
    "destination_zip": "22102",
    "currency": "USD",
    "picked": {
      "sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
      "supply_source_type": "OVERSEAS_DIRECT",
      "ships_from_country": "ES",
      "is_recommended": true,
      "quantity": "6000.00",
      "quantity_unit": "SF",
      "material_total": "38400.00",
      "tax_amount": "3168.00",
      "total_excl_tax": "38400.00",
      "total_incl_tax": "41568.00",
      "unit_landed_price_excl_tax": "6.40",
      "saving_vs_other_lane": "10200.00"
    },
    "lead_time": {
      "min_days": 90,
      "max_days": 120,
      "label": "Delivered in 90 to 120 days"
    },
    "items": [
      {
        "sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
        "supply_source_type": "OVERSEAS_DIRECT",
        "ships_from_country": "ES",
        "is_recommended": true,
        "quantity": "6000.00",
        "quantity_unit": "SF",
        "material_total": "38400.00",
        "tax_amount": "3168.00",
        "total_excl_tax": "38400.00",
        "total_incl_tax": "41568.00",
        "unit_landed_price_excl_tax": "6.40",
        "saving_vs_other_lane": "10200.00"
      },
      {
        "sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
        "supply_source_type": "US_WAREHOUSE",
        "ships_from_country": "US",
        "is_recommended": false,
        "quantity": "6000.00",
        "quantity_unit": "SF",
        "material_total": "48600.00",
        "tax_amount": "4009.50",
        "total_excl_tax": "48600.00",
        "total_incl_tax": "52609.50",
        "unit_landed_price_excl_tax": "8.10",
        "saving_vs_other_lane": null
      }
    ]
  },
  "match": {
    "search_session_uuid": "5f0c1d2e-8a7b-4c3d-9e1f-2a3b4c5d6e7f",
    "filters": {
      "material": [
        {
          "code": "PORCELAIN",
          "label": "Porcelain"
        }
      ],
      "finish": [
        {
          "code": "MATTE",
          "label": "Matte"
        }
      ]
    },
    "section": "strong",
    "rank": 1,
    "matched_on": [
      "finish",
      "material"
    ]
  }
}
```

## Top level

| Field | Type | Notes |
|---|---|---|
| `schema_version` | string | `"1.0"` |
| `save_uuid` | string (UUID) | Identifies the save. Your idempotency key |
| `saved_at` | string | UTC time |
| `source` | object or null | Your own ids. `null` when you call the API yourself |
| `user` | object | Who saved |
| `project` | object or null | The project, when Flume holds one |
| `product_url` | string | A durable link to the product in the Flume app |
| `product` | object | What was saved |
| `pricing` | object or null | The quote's prices. `null` when the save has no quote |
| `match` | object | How the product was found |

## source

| Field | Type | Notes |
|---|---|---|
| `item_ref` | string | The `item.external_id` you sent |
| `item_label` | string or null | The `item.label` you sent |
| `project_ref` | string | The `project.external_ref` you sent |

## user

| Field | Type | Notes |
|---|---|---|
| `external_ref` | string | Your id for the user |
| `flume_user_uuid` | string (UUID) | The user's id in Flume |

## project

| Field | Type | Notes |
|---|---|---|
| `external_ref` | string | Your id for the project |
| `flume_project_uuid` | string (UUID) | The project's id in Flume |

## product_url

- Opens the product in the Flume app.
- The user is not signed in by the link.
- It does not expire. Store it.
- Do not parse it. Its host depends on the environment.

## product

| Field | Type | Notes |
|---|---|---|
| `sku_uuid` | string (UUID) | The product's id |
| `name` | string | Written by Flume from the product's specification |
| `category` | string | `tile` or `vinyl` |
| `material` | string or null | For example `Porcelain` |
| `finish` | string or null | For example `Matte` |
| `size_label` | string or null | For example `24×48″` |
| `glance` | array | Key facts: `label`, `value` |
| `spec_rows` | array | The full specification, in groups |
| `gallery` | array | Every product photo the product page shows, in order |
| `lanes` | array | The shipping options |
| `suitable_application_areas` | array | Where the product can be used: `code`, `label` |

> **Two spellings of the category:** The save payload and `POST /detect-category` say `tile` or `vinyl`. `POST /search` and `GET /products/{sku_uuid}` say `TILE` or `FLOORING`. `vinyl` and `FLOORING` are the same category.

`spec_rows` entries:

| Field | Type | Notes |
|---|---|---|
| `group` | string | The group's heading |
| `rows[].label` | string | A label can repeat within a group |
| `rows[].value` | string | The property's value |
| `rows[].method` | string or null | The test standard, when one is stated |
| `rows[].note` | string or null | An extra note, when there is one |

Each `rows` entry has `label`, `value`, `method` and `note`.

`gallery` entries:

| Field | Type | Notes |
|---|---|---|
| `urls` | object | Image links by size. They expire. Copy the bytes |

`lanes` entries, one per shipping option:

| Field | Type | Notes |
|---|---|---|
| `supply_source_type` | string | `US_WAREHOUSE` or `OVERSEAS_DIRECT` |
| `on_request` | boolean | `true` when Flume's sales team prices this option |
| `minimum_order_sqft` | integer | The smallest order for this option |
| `ships_from` | object or null | `country` (two letters) and `label` |
| `lead_time` | object or null | The delivery window the product page shows: `min_days`, `max_days`, `label`. `null` when the option is priced on request |

## pricing

Present when the save names a quote.

| Field | Type | Notes |
|---|---|---|
| `quote_uuid` | string (UUID) | The quote's id |
| `quote_number` | string | As the user sees it in Flume, for example `Q-7KD2-M4PA` |
| `status` | string | Always `READY` |
| `created_at`, `valid_until` | string | UTC. Prices hold until `valid_until` |
| `destination_zip` | string | The delivery ZIP code |
| `currency` | string | `USD` |
| `picked` | object | The option the user chose. Same fields as an `items` entry |
| `lead_time` | object | For the picked option: `min_days`, `max_days`, `label` |
| `items` | array | Every option of the quote for this product, cheapest first |

`picked` and each `items` entry:

| Field | Type | Notes |
|---|---|---|
| `sku_uuid` | string (UUID) | The product priced |
| `supply_source_type` | string | `US_WAREHOUSE` or `OVERSEAS_DIRECT` |
| `ships_from_country` | string or null | Two letters |
| `is_recommended` | boolean | The option Flume led with |
| `quantity` | decimal string | The quantity the user asked for |
| `quantity_unit` | string | The unit of `quantity` |
| `unit_landed_price_excl_tax` | decimal string | Delivered price per unit, before tax |
| `total_excl_tax` | decimal string | Delivered price, before tax |
| `material_total` | decimal string | Always equal to `total_excl_tax` |
| `tax_amount` | decimal string or null | Sales tax |
| `total_incl_tax` | decimal string | `total_excl_tax` plus `tax_amount` |
| `saving_vs_other_lane` | decimal string or null | On the recommended option: how much less it costs than the other, before tax |

Money is a decimal string. Do not parse it as a float.

## How do the quote and the payload name the same thing?

A quote from the API and the `pricing` block describe the same shipping option with different field names.

| Meaning | `quote.options[]` (API) | `pricing.items[]` (save payload) |
|---|---|---|
| How it ships | `source` | `supply_source_type` |
| Where it ships from | `ships_from_country` | `ships_from_country` |
| Quantity | `quantity_sqft` | `quantity` |
| Delivered price per unit, before tax | `price_per_sqft` | `unit_landed_price_excl_tax` |
| Delivered price, before tax | `subtotal` | `total_excl_tax` |
| Sales tax | `sales_tax` | `tax_amount` |
| Delivered price, with tax | `total` | `total_incl_tax` |
| Delivery time | `lead_time_min_days`, `lead_time_max_days` | `pricing.lead_time`, for the picked option |

## match

| Field | Type | Notes |
|---|---|---|
| `search_session_uuid` | string (UUID) | The search that found the product |
| `filters` | object | The filters that search ran with. List values carry `code` and `label` |
| `section` | string or null | Where the product appeared. `strong` for a strong match |
| `rank` | integer or null | Its position in the results |
| `matched_on` | array of strings | For a strong match: the filters it met. Otherwise empty |

## Text rules

Flume cleans every string before it sends the payload.

- No control characters.
- No `<` or `>`. A comparison before a number becomes a word: `> 0.42` reads `over 0.42`.
- Labels are cut at 200 characters. Values are cut at 500.
- Links are not cut.

Quotes and `&` are not removed. Escape every string on output. Never build HTML, or an HTML attribute, by joining these strings.

## Versioning

`schema_version` is `"1.0"`. Read it before you map the payload, and log a value you do not know.

> **Not in the OpenAPI document:** The OpenAPI document types the top-level fields only. The blocks are described on this page.
