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 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
- Verify a user, send the code: POST /users/trigger-verification. Flume emails the user a 6-digit code.
- Verify a user, confirm the code: POST /users/confirm-verification returns the user access token.
- Search by photo: POST /search returns two lists and a search_session_uuid. GET /filters lists the filter options.
- Product detail: GET /products/{sku_uuid} returns photos, price, specification and shipping.
- Instant quote: POST /request-instant-quote returns the delivered price. A quote that is still pricing is read with GET /quotes/{quote_uuid}.
- Open Flume, signed in: POST /deep-links returns a one-time link into the user's Flume account.
How it fits together
Section titled “How it fits together”| Step | Call |
|---|---|
| 1. Verify a user: send the code | POST /users/trigger-verification |
| 2. Verify a user: confirm the code | POST /users/confirm-verification |
| 3. Search by photo | POST /search |
| 4. Product detail | GET /products/{sku_uuid} |
| 5. Instant quote | POST /request-instant-quote |
| 6. Open Flume, signed in | POST /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).
Two headers
Section titled “Two headers”- 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".
Before you begin
Section titled “Before you begin”- A sandbox partner key. Get a sandbox key.
- An email inbox you can read. The sandbox sends a real verification email.
curlandjq.
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"1. Verify a user: send the code
Section titled “1. Verify a user: send the code”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\"}"{ "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.
2. Verify a user: confirm the code
Section titled “2. Verify a user: confirm the code”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 emailexport 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.
3. Search by photo
Section titled “3. Search by photo”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"{ "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: truehas 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.
4. Product detail
Section titled “4. Product detail”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}'{ "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.
5. Instant quote
Section titled “5. Instant quote”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){ "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" } ] }}statusisREADY:quote.optionsholds one price per shipping option, cheapest first.statusisFAILED: readfailure_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:
statusisFAILEDandfailure_reasonisTEMPORARILY_UNAVAILABLE. Read the result withGET /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.
6. Open Flume, signed in
Section titled “6. Open Flume, signed in”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{ "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.
It worked
Section titled “It worked”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 whole flow as one script
Section titled “The whole flow as one script”The same six steps, in one file. It reads FLUME_PARTNER_KEY and EMAIL, and asks for the code.
#!/usr/bin/env bashset -euo pipefailAPI="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 codecurl -sS -X POST "$API/users/trigger-verification" "${KEY[@]}" "${JSON[@]}" \ -d "{\"external_ref\": \"$USER_REF\", \"email\": \"$EMAIL\", \"method\": \"email_code\"}" > /dev/nullread -r -p "Code from the email: " CODE
# 2. Verify a user: confirm the codeTOKEN=$(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 photoSEARCH=$(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 detailcurl -sS "$API/products/$SKU" "${KEY[@]}" "${USER[@]}" | jq -r .name
# 5. Instant quotecurl -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 incurl -sS -X POST "$API/deep-links" "${KEY[@]}" "${USER[@]}" "${JSON[@]}" \ -d "{\"search_session_uuid\": \"$SEARCH_ID\", \"sku_uuid\": \"$SKU\"}" | jq -r .urlNext steps
Section titled “Next steps”- Verify a user, Find products by photo, Get an instant quote, Open Flume signed in: each step in depth.
- Rotate keys and Handle errors.
- API reference: every endpoint, field and error.
- Want Flume to draw the screens instead? See the iframe quickstart. It is the faster way to integrate.