---
title: "API quickstart"
description: "The Flume Partner API in six steps, from a terminal. Verify a user, search by photo, read a product, get an instant quote and open Flume signed in."
url: https://docs.tryflume.ai/quickstart/api
updated: 2026-11-12
---

# API quickstart

> The Flume Partner API in six steps, from a terminal. Verify a user, search by photo, read a product, get an instant quote and open Flume signed in.

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.

**Diagram: 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.

```mermaid
sequenceDiagram
  participant backend as Your backend
  participant flume as Flume servers
  backend->>flume: POST /users/trigger-verification
  backend->>flume: POST /users/confirm-verification
  backend->>flume: POST /search
  backend->>flume: GET /products/sku_uuid
  backend->>flume: POST /request-instant-quote
  backend->>flume: POST /deep-links
```

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.

## 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

- 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

- A sandbox partner key. [Get a sandbox key](https://docs.tryflume.ai/get-a-key.md).
- An email inbox you can read. The sandbox sends a real verification email.
- `curl` and [`jq`](https://jqlang.github.io/jq/).

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

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

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

## 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`](https://docs.tryflume.ai/api/trigger-verification.md).

```sh
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)**

```json
{
  "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
The right code opens the user's Flume account and returns their user access token. It does not expire. Reference: [`POST /users/confirm-verification`](https://docs.tryflume.ai/api/confirm-verification.md).

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

## 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`](https://docs.tryflume.ai/api/search.md).

To build your filter menus first, read the options with [`GET /filters`](https://docs.tryflume.ai/api/list-filters.md):

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

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

```sh
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)**

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

## 4. Product detail
One product's photos, price, specification and shipping options. Reference: [`GET /products/{sku_uuid}`](https://docs.tryflume.ai/api/get-product.md).

```sh
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)**

```json
{
  "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
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`](https://docs.tryflume.ai/api/request-instant-quote.md).

```sh
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)**

```json
{
  "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](https://docs.tryflume.ai/guides/get-an-instant-quote.md).
- 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}`](https://docs.tryflume.ai/api/get-quote.md):

```sh
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
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`](https://docs.tryflume.ai/api/create-deep-link.md).

```sh
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)**

```json
{
  "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](https://docs.tryflume.ai/guides/open-flume-signed-in.md).

## 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

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

```sh
#!/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
```

## Next steps

- [Verify a user](https://docs.tryflume.ai/guides/verify-a-user.md), [Find products by photo](https://docs.tryflume.ai/guides/find-products-by-photo.md), [Get an instant quote](https://docs.tryflume.ai/guides/get-an-instant-quote.md), [Open Flume signed in](https://docs.tryflume.ai/guides/open-flume-signed-in.md): each step in depth.
- [Rotate keys](https://docs.tryflume.ai/guides/rotate-keys.md) and [Handle errors](https://docs.tryflume.ai/guides/handle-errors.md).
- [API reference](https://docs.tryflume.ai/api.md): every endpoint, field and error.
- Want Flume to draw the screens instead? See the [iframe quickstart](https://docs.tryflume.ai/quickstart/embed.md). It is the faster way to integrate.
