Skip to content

API quickstart

Call the Partner API from a terminal, in the six steps of the Partner API. You finish with a delivered price and a link into the user's Flume account.

Every command runs as pasted. Values from one step are kept in shell variables for the next. Each step links to its reference page, which has the same call in Python and Node.js.

The API path: six stepsAn API integration makes these six calls, in this order. Every call carries the partner key; steps 3 to 6 also carry the user access token. The same content is listed as text under the diagram.Your backendFlume servers1POST /users/trigger-verification2POST /users/confirm-verification3POST /search4GET /products/{sku_uuid}5POST /request-instant-quote6POST /deep-linksServer call (HTTPS)

The API path: six steps. An API integration makes these six calls, in this order. Every call carries the partner key; steps 3 to 6 also carry the user access token.

Read this diagram as text
  1. Verify a user, send the code: POST /users/trigger-verification. Flume emails the user a 6-digit code.
  2. Verify a user, confirm the code: POST /users/confirm-verification returns the user access token.
  3. Search by photo: POST /search returns two lists and a search_session_uuid. GET /filters lists the filter options.
  4. Product detail: GET /products/{sku_uuid} returns photos, price, specification and shipping.
  5. Instant quote: POST /request-instant-quote returns the delivered price. A quote that is still pricing is read with GET /quotes/{quote_uuid}.
  6. Open Flume, signed in: POST /deep-links returns a one-time link into the user's Flume account.
StepCall
1. Verify a user: send the codePOST /users/trigger-verification
2. Verify a user: confirm the codePOST /users/confirm-verification
3. Search by photoPOST /search
4. Product detailGET /products/{sku_uuid}
5. Instant quotePOST /request-instant-quote
6. Open Flume, signed inPOST /deep-links

Supporting calls: GET /filters (filter options), GET /quotes/{quote_uuid} (a quote that is still pricing), DELETE /users/{external_ref}/token (revoke a user's token), and GET /keys, POST /keys, DELETE /keys/{key_id} (your own keys).

  • Every call carries your partner key as X-Flume-Partner-Key.
  • Steps 3 to 6 also carry the user access token as X-Flume-User-Token. Neither works alone.
  • Start with a sandbox key (sk_dev_…), then a user access token once you have one.
  • Prices are US dollars, sent as text with 2 decimals, for example "6.40".
  • A sandbox partner key. Get a sandbox key.
  • An email inbox you can read. The sandbox sends a real verification email.
  • curl and jq.

Set three variables. Use your own key and your own email address.

export FLUME_PARTNER_KEY="paste-your-sandbox-key"
export EMAIL="you@yourcompany.com"
export USER_REF="quickstart-user-1"

Download a sample photo of a tile. Any JPEG, PNG or WebP up to 6,000,000 bytes works.

curl -sSO "https://docs.tryflume.ai/samples/tile.jpg"

Your first call for a new user. Flume emails them a 6-digit code. external_ref is your own permanent id for the user. Reference: POST /users/trigger-verification.

curl -sS -X POST "https://sandbox-api.tryflume.ai/partner/v1/users/trigger-verification" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d "{\"external_ref\": \"$USER_REF\", \"email\": \"$EMAIL\", \"method\": \"email_code\"}"
Response: 202
{
"external_ref": "u_8812",
"masked_email": "s···@example.com",
"expires_in_s": 600,
"resend_after_s": 30
}

Carry forward: nothing. Read the code from the email.

The right code opens the user's Flume account and returns their user access token. It does not expire. Reference: POST /users/confirm-verification.

CODE="123456" # the digits from the email
export FLUME_USER_TOKEN=$(curl -sS -X POST "https://sandbox-api.tryflume.ai/partner/v1/users/confirm-verification" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d "{\"email\": \"$EMAIL\", \"code\": \"$CODE\"}" | jq -r .access_token)
echo "${FLUME_USER_TOKEN:0:12}..."
  • The token is returned once. In your product, store it as a secret, against the user.
  • If the output is null..., the code was wrong or expired. Run step 1 again.

Carry forward: FLUME_USER_TOKEN, on every call from here on. Keep it against the user: it does not expire.

A photo and the user's filters give two lists, strong matches and other similar options, plus a search_session_uuid. Reference: POST /search.

To build your filter menus first, read the options with GET /filters:

curl -sS "https://sandbox-api.tryflume.ai/partner/v1/filters" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" | jq '.tile | keys'

Then search. Send the photo and a JSON payload as multipart/form-data. category is required.

SEARCH=$(curl -sS -X POST "https://sandbox-api.tryflume.ai/partner/v1/search" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" \
-F "image=@tile.jpg" \
-F 'payload={"category": "TILE"}')
echo "$SEARCH" | jq '{search_session_uuid, total, first: .strong_matches[0].name}'

Keep the search's id, and pick the first product that has an instant price.

SEARCH_ID=$(echo "$SEARCH" | jq -r .search_session_uuid)
SKU=$(echo "$SEARCH" | jq -r '[.strong_matches[], .other_similar_options[]]
| map(select(.price_on_request == false)) | .[0].sku_uuid')
echo "search=$SEARCH_ID product=$SKU"
Response: 200
{
"search_session_uuid": "5f0c1d2e-8a7b-4c3d-9e1f-2a3b4c5d6e7f",
"total": 31,
"strong_match_count": 9,
"other_similar_count": 22,
"page": 1,
"page_size": 24,
"has_more": true,
"strong_matches": [
{
"sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
"name": "Porcelain 24×48″ Matte",
"image_url": "https://sandbox-api.tryflume.ai/partner/v1/images/example-image-main",
"more_image_urls": [
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-2",
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-3"
],
"size": "24×48 in",
"thickness": "9 mm",
"material": "Porcelain",
"as_low_as_per_sqft": "6.40",
"price_on_request": false,
"lead_times": [
{
"source": "US_WAREHOUSE",
"lead_time_min_days": 14,
"lead_time_max_days": 30
},
{
"source": "OVERSEAS_DIRECT",
"lead_time_min_days": 90,
"lead_time_max_days": 120
}
],
"suitable_application_areas": [
{
"code": "INTERIOR_DRY_FLOOR",
"label": "Interior Dry Floor"
},
{
"code": "INTERIOR_WALL_BACKSPLASH",
"label": "Interior Wall / Backsplash"
}
],
"saving_vs_spec": null
}
],
"other_similar_options": [
{
"sku_uuid": "0b6f9d34-2e71-4c5a-8f10-3d9a7e2b6c41",
"name": "Porcelain 24×48″ Natural",
"image_url": "https://sandbox-api.tryflume.ai/partner/v1/images/example-image-main",
"more_image_urls": [
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-2",
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-3"
],
"size": "24×48 in",
"thickness": "9 mm",
"material": "Porcelain",
"as_low_as_per_sqft": "5.90",
"price_on_request": false,
"lead_times": [
{
"source": "US_WAREHOUSE",
"lead_time_min_days": 14,
"lead_time_max_days": 30
},
{
"source": "OVERSEAS_DIRECT",
"lead_time_min_days": 90,
"lead_time_max_days": 120
}
],
"suitable_application_areas": [
{
"code": "INTERIOR_DRY_FLOOR",
"label": "Interior Dry Floor"
},
{
"code": "INTERIOR_WALL_BACKSPLASH",
"label": "Interior Wall / Backsplash"
}
],
"saving_vs_spec": null
}
],
"project": {
"external_ref": "proj_7781",
"status": "FILED",
"reason": null,
"missing": [],
"message": null
}
}
  • A product with price_on_request: true has no instant quote. The filter above skips those.
  • If product=null, no result has an instant price. Try another photo.
  • What a search returns depends on the photo. The sandbox has no fixed test results.

Carry forward: SEARCH_ID and SKU.

One product's photos, price, specification and shipping options. Reference: GET /products/{sku_uuid}.

curl -sS "https://sandbox-api.tryflume.ai/partner/v1/products/$SKU" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" | jq '{name, as_low_as_per_sqft, price_on_request}'
Response: 200
{
"sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
"category": "TILE",
"name": "Porcelain 24×48″ Matte",
"as_low_as_per_sqft": "6.40",
"price_on_request": false,
"image_urls": [
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-main",
"https://sandbox-api.tryflume.ai/partner/v1/images/example-image-2"
],
"at_a_glance": [
{
"label": "Size",
"value": "24×48″"
},
{
"label": "Thickness",
"value": "9 mm"
},
{
"label": "Finish",
"value": "Matte"
}
],
"specification": [
{
"group": "Slip resistance",
"rows": [
{
"label": "DCOF",
"value": "> 0.42",
"test_method": "ANSI A326.3"
}
]
}
],
"shipping_options": [
{
"source": "US_WAREHOUSE",
"lead_time_min_days": 14,
"lead_time_max_days": 30,
"ships_from_country": "US",
"minimum_order_sqft": 1000
},
{
"source": "OVERSEAS_DIRECT",
"lead_time_min_days": 90,
"lead_time_max_days": 120,
"ships_from_country": "ES",
"minimum_order_sqft": 5000
}
],
"suitable_application_areas": [
{
"code": "INTERIOR_DRY_FLOOR",
"label": "Interior Dry Floor"
}
]
}

Carry forward: nothing new.

The delivered price for a quantity and a ZIP code: here 6,000 square feet to ZIP code 22102. Idempotency-Key is required. Reference: POST /request-instant-quote.

QUOTE=$(curl -sS -X POST "https://sandbox-api.tryflume.ai/partner/v1/request-instant-quote" \
--max-time 75 \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "{\"sku_uuid\": \"$SKU\", \"quantity_sqft\": 6000, \"destination_zip\": \"22102\", \"search_session_uuid\": \"$SEARCH_ID\"}")
echo "$QUOTE" | jq '{status, failure_reason, quote_uuid}'
QUOTE_ID=$(echo "$QUOTE" | jq -r .quote_uuid)
Response: 200
{
"status": "READY",
"failure_reason": null,
"quote_uuid": "9c1e4b7a-2f3d-4e5b-8a6c-7d8e9f0a1b2c",
"quote": {
"quote_uuid": "9c1e4b7a-2f3d-4e5b-8a6c-7d8e9f0a1b2c",
"quote_number": "Q-7KD2-M4PA",
"sku_uuid": "7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30",
"destination_zip": "22102",
"currency": "USD",
"created_at": "2026-10-16T09:14:22Z",
"valid_until": "2026-10-23T09:14:22Z",
"options": [
{
"source": "OVERSEAS_DIRECT",
"lead_time_min_days": 90,
"lead_time_max_days": 120,
"ships_from_country": "ES",
"quantity_sqft": "6000.00",
"price_per_sqft": "6.40",
"subtotal": "38400.00",
"sales_tax": "3168.00",
"total": "41568.00"
},
{
"source": "US_WAREHOUSE",
"lead_time_min_days": 14,
"lead_time_max_days": 30,
"ships_from_country": "US",
"quantity_sqft": "6000.00",
"price_per_sqft": "8.10",
"subtotal": "48600.00",
"sales_tax": "4009.50",
"total": "52609.50"
}
]
}
}
  • status is READY: quote.options holds one price per shipping option, cheapest first.
  • status is FAILED: read failure_reason. See Get an instant quote.
  • The call can hold for up to 60 seconds. Keep the read timeout at 75 seconds.
  • The quote can still be pricing when the call returns: status is FAILED and failure_reason is TEMPORARILY_UNAVAILABLE. Read the result with GET /quotes/{quote_uuid}:
curl -sS "https://sandbox-api.tryflume.ai/partner/v1/quotes/$QUOTE_ID" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" | jq '{status, failure_reason}'

Carry forward: nothing. The quote is in the user's Flume account.

A one-time link into the user's Flume account, where every search and quote you made for them is waiting. Reference: POST /deep-links.

curl -sS -X POST "https://sandbox-api.tryflume.ai/partner/v1/deep-links" \
-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
-H "X-Flume-User-Token: $FLUME_USER_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"search_session_uuid\": \"$SEARCH_ID\", \"sku_uuid\": \"$SKU\"}" | jq
Response: 201
{
"url": "https://app.tryflume.ai/?utm_source=acme&utm_medium=partner#/l/dlk_q3Vh8yT…",
"expires_at": "2026-10-23T09:14:22Z"
}

Create the link when the user clicks, and open it at once. It works once. See Open Flume signed in.

You verified a user, searched by photo, read a product, priced it and made a link into Flume: the six steps of the Partner API.

The same six steps, in one file. It reads FLUME_PARTNER_KEY and EMAIL, and asks for the code.

#!/usr/bin/env bash
set -euo pipefail
API="https://sandbox-api.tryflume.ai/partner/v1"
KEY=(-H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY")
JSON=(-H "Content-Type: application/json")
USER_REF="${USER_REF:-quickstart-user-1}"
# 1. Verify a user: send the code
curl -sS -X POST "$API/users/trigger-verification" "${KEY[@]}" "${JSON[@]}" \
-d "{\"external_ref\": \"$USER_REF\", \"email\": \"$EMAIL\", \"method\": \"email_code\"}" > /dev/null
read -r -p "Code from the email: " CODE
# 2. Verify a user: confirm the code
TOKEN=$(curl -sS -X POST "$API/users/confirm-verification" "${KEY[@]}" "${JSON[@]}" \
-d "{\"email\": \"$EMAIL\", \"code\": \"$CODE\"}" | jq -r .access_token)
USER=(-H "X-Flume-User-Token: $TOKEN")
# 3. Search by photo
SEARCH=$(curl -sS -X POST "$API/search" "${KEY[@]}" "${USER[@]}" \
-F "image=@tile.jpg" -F 'payload={"category": "TILE"}')
SEARCH_ID=$(echo "$SEARCH" | jq -r .search_session_uuid)
SKU=$(echo "$SEARCH" | jq -r '[.strong_matches[], .other_similar_options[]]
| map(select(.price_on_request == false)) | .[0].sku_uuid')
# 4. Product detail
curl -sS "$API/products/$SKU" "${KEY[@]}" "${USER[@]}" | jq -r .name
# 5. Instant quote
curl -sS -X POST "$API/request-instant-quote" --max-time 75 \
"${KEY[@]}" "${USER[@]}" "${JSON[@]}" -H "Idempotency-Key: $(uuidgen)" \
-d "{\"sku_uuid\": \"$SKU\", \"quantity_sqft\": 6000, \"destination_zip\": \"22102\", \"search_session_uuid\": \"$SEARCH_ID\"}" | jq '{status, failure_reason, quote_uuid}'
# 6. Open Flume, signed in
curl -sS -X POST "$API/deep-links" "${KEY[@]}" "${USER[@]}" "${JSON[@]}" \
-d "{\"search_session_uuid\": \"$SEARCH_ID\", \"sku_uuid\": \"$SKU\"}" | jq -r .url