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?
Section titled “What is the flow?”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.
Read this diagram as text
- The user confirms a save in the iframe.
- The iframe asks Flume for the save payload. If that fails, the iframe shows its own error and your page hears nothing.
- The iframe posts flume:save_item with the save payload and its save_uuid.
- Your page sends the payload to your backend.
- Your backend writes one record per save_uuid, and copies the photo bytes if you keep a photo.
- Your backend answers with your record, or with a failure.
- 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.
- The iframe tells Flume how the save ended. You make no call for this.
- 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.
Why is save_uuid important?
Section titled “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_uuidas your idempotency key. - If you already hold a record for it, return success without writing again.
- A
save_uuidthat belongs to another user returns409SAVE_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?
Section titled “How do you carry pricing?”- Send
quote_uuidto include an instant quote's prices. - Send
option_sourceto pick the shipping option. Without it, Flume picks the recommended one. - Leave
quote_uuidout andpricingisnull. - The quote must be
READY, the user's own, not expired, and at or above the option's minimum order. Otherwise the call returns422QUOTE_NOT_READY.
What must you do with the payload?
Section titled “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.
- 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?
A save also files the product in the user's Flume project, when the search has one.
Example
Section titled “Example”{ "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
Section titled “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
Section titled “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 |
| Field | Type | Notes |
|---|---|---|
external_ref | string | Your id for the user |
flume_user_uuid | string (UUID) | The user's id in Flume |
project
Section titled “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
Section titled “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
Section titled “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 |
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
Section titled “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?
Section titled “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 |
| 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
Section titled “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.42readsover 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
Section titled “Versioning”schema_version is "1.0". Read it before you map the payload, and log a value you do not know.