> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clickbase.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Funnels

> Manage the funnels defined on one of the current team's sites.

Manage the funnels defined on one of the current team's sites. A funnel is an ordered sequence of **2–8 inline steps** — each step is its own match definition (`match_type` + `match_value`/`scroll_threshold`), the same shape a [goal](/api/goals) uses, but stored on the funnel itself rather than referencing a saved goal. Deleting or editing a goal never affects a funnel, and duplicate steps are allowed since they aren't shared records. A site is addressed by `{siteKey}` (UUID or domain). All endpoints require `Authorization: Bearer {token}`.

### List funnels

```http theme={null}
GET /api/sites/{siteKey}/funnels
```

List a site's funnels, ordered by name, each with its step count. Not paginated — flat JSON array.

**Path parameters**

| Parameter | Type   | Required | Description          |
| --------- | ------ | -------- | -------------------- |
| `siteKey` | string | Yes      | Site UUID or domain. |

**Response** — flat JSON array of:

| Field          | Type                      | Description                                |
| -------------- | ------------------------- | ------------------------------------------ |
| `id`           | string (uuid)             | Funnel id.                                 |
| `name`         | string                    | Funnel name.                               |
| `strict_order` | bool                      | Whether steps must be completed in order.  |
| `steps_count`  | int                       | Number of steps (present on list results). |
| `created_at`   | string (ISO 8601) \| null | When the funnel was created.               |

Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl https://clickbase.so/api/sites/example.com/funnels \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/funnels', {
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  })
  ```

  ```python title="Python" theme={null}
  requests.get(
      'https://clickbase.so/api/sites/example.com/funnels',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
  )
  ```
</CodeGroup>

### Create a funnel

```http theme={null}
POST /api/sites/{siteKey}/funnels
```

Create a funnel from an ordered list of 2–8 inline steps. Array position is the step order (1-indexed).

**Body parameters**

| Parameter      | Type   | Required | Description                                                                                          |
| -------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |
| `name`         | string | Yes      | Funnel name. Max 255 chars. Must be unique on the site.                                              |
| `steps`        | array  | Yes      | Ordered list of **2–8** step objects, each `{match_type, match_value, scroll_threshold}`. See below. |
| `strict_order` | bool   | No       | Whether steps must be completed in the given order. Default `false`.                                 |

Each step object:

| Field              | Type   | Required    | Description                                                                                                                                                                                                                                              |
| ------------------ | ------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `match_type`       | string | Yes         | One of `page`, `event`, `scroll`, `outbound`, `download`, `button`, `form`, `copy` (same set as a goal's `match_type`).                                                                                                                                  |
| `match_value`      | string | Conditional | Page path (`page`/`scroll`), custom event name (`event`), or selector (autocapture). Required for `page`/`scroll`/`event`. A path gets a leading `/` if missing. Max 2048 chars. An `event` value cannot be a [reserved name](/api/goals#create-a-goal). |
| `scroll_threshold` | int    | Conditional | Scroll depth 0–100. Required for a `scroll` step, rejected on any other type.                                                                                                                                                                            |

A step's match operator is not settable through this endpoint — it always uses the type-dependent default (`is` for an `event` step, `matches_pattern` for every other type), same as an unset goal `match_operator`.

The `CreateFunnel` Action enforces the 2–8 step bound, that each step's value satisfies its type's rules, and funnel-name uniqueness — each violation returns `422`. Unlike goal ids, duplicate step definitions within the same funnel are allowed.

**Response** — the created funnel, including its ordered `steps`:

| Field          | Type                      | Description                                                                               |
| -------------- | ------------------------- | ----------------------------------------------------------------------------------------- |
| `id`           | string (uuid)             | Funnel id.                                                                                |
| `name`         | string                    | Funnel name.                                                                              |
| `strict_order` | bool                      | Whether steps must be completed in order.                                                 |
| `steps`        | array                     | Ordered `{match_type, match_value, scroll_threshold}` objects (present on create/update). |
| `created_at`   | string (ISO 8601) \| null | When the funnel was created.                                                              |

Status: `201 Created`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST https://clickbase.so/api/sites/example.com/funnels \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"name": "Checkout", "steps": [{"match_type": "event", "match_value": "signup"}, {"match_type": "event", "match_value": "purchase"}], "strict_order": true}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/funnels', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      name: 'Checkout',
      steps: [
        { match_type: 'event', match_value: 'signup' },
        { match_type: 'event', match_value: 'purchase' },
      ],
      strict_order: true,
    }),
  })
  ```

  ```python title="Python" theme={null}
  requests.post(
      'https://clickbase.so/api/sites/example.com/funnels',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={
          'name': 'Checkout',
          'steps': [
              {'match_type': 'event', 'match_value': 'signup'},
              {'match_type': 'event', 'match_value': 'purchase'},
          ],
          'strict_order': True,
      },
  )
  ```
</CodeGroup>

### Update a funnel

```http theme={null}
PATCH /api/sites/{siteKey}/funnels/{funnel}
```

Update a funnel. In practice this is a **full replace, not a partial patch** — `name` and `steps` are re-validated the same way as create (a missing/empty `name` and a `steps` array outside 2–8 both return `422`), and `strict_order` is not merged with the current value: any request that omits it resets it to `false`. Always send the funnel's complete definition — name, all steps, and the intended `strict_order` — on every update, not just the field you changed.

**Path parameters**

| Parameter | Type   | Required | Description                                    |
| --------- | ------ | -------- | ---------------------------------------------- |
| `siteKey` | string | Yes      | Site UUID or domain.                           |
| `funnel`  | string | Yes      | Funnel id. A foreign/unknown id returns `404`. |

**Body parameters**

| Parameter      | Type   | Required | Description                                                                     |
| -------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `name`         | string | Yes      | Funnel name. Must stay unique on the site.                                      |
| `steps`        | array  | Yes      | Replacement ordered list of 2–8 step objects (same shape and rules as create).  |
| `strict_order` | bool   | No       | Whether steps must be completed in order. **Omitting it resets it to `false`.** |

**Response** — the updated funnel (with `steps`). Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X PATCH https://clickbase.so/api/sites/example.com/funnels/{funnel} \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"name": "Checkout v2", "steps": [{"match_type": "event", "match_value": "signup"}, {"match_type": "event", "match_value": "purchase"}], "strict_order": true}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch(`https://clickbase.so/api/sites/example.com/funnels/${funnelId}`, {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      name: 'Checkout v2',
      steps: [
        { match_type: 'event', match_value: 'signup' },
        { match_type: 'event', match_value: 'purchase' },
      ],
      strict_order: true,
    }),
  })
  ```

  ```python title="Python" theme={null}
  requests.patch(
      f'https://clickbase.so/api/sites/example.com/funnels/{funnel_id}',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={
          'name': 'Checkout v2',
          'steps': [
              {'match_type': 'event', 'match_value': 'signup'},
              {'match_type': 'event', 'match_value': 'purchase'},
          ],
          'strict_order': True,
      },
  )
  ```
</CodeGroup>

### Evaluate a funnel definition

```http theme={null}
POST /api/sites/{siteKey}/funnels/evaluate
```

Evaluate an ad hoc, **unsaved** funnel definition over a date range — the live preview the funnel builder uses while you're still editing steps. Never persists a `Funnel` row; read-only.

**Body parameters**

| Parameter      | Type   | Required | Description                                                                                |
| -------------- | ------ | -------- | ------------------------------------------------------------------------------------------ |
| `steps`        | array  | Yes      | Ordered list of **2–8** step objects (same shape and rules as [create](#create-a-funnel)). |
| `strict_order` | bool   | No       | Whether steps must be completed in order. Default `false`.                                 |
| `range`        | string | No       | Preset date range. See [Conventions](/api/conventions#date-ranges).                        |
| `from`         | string | No       | Custom range start, `Y-m-d`. Requires `to`; takes precedence over `range`.                 |
| `to`           | string | No       | Custom range end, `Y-m-d`. Requires `from`.                                                |

Dashboard filter dimensions (`country`, `path`, `utm_source`, etc. — see [Conventions](/api/conventions#filters)) are read from the **query string**, not the body. A `goal` or `prop_key`/`prop_value` filter is accepted but ignored: a funnel is already its own set of match conditions, so goal/property filtering on top of it is dropped before evaluation.

**Response** — computed, not persisted:

| Field                                | Type  | Description                                                         |
| ------------------------------------ | ----- | ------------------------------------------------------------------- |
| `steps`                              | array | Per-step breakdown, in order (see below).                           |
| `entering_visitors`                  | int   | Visitors who reached step 1.                                        |
| `never_entering_visitors`            | int   | Visitors active in range who never reached step 1.                  |
| `entering_visitors_percentage`       | float | `entering_visitors` as a percentage of all visitors in range.       |
| `never_entering_visitors_percentage` | float | `never_entering_visitors` as a percentage of all visitors in range. |

Each `steps[]` entry:

| Field                  | Type   | Description                                                                |
| ---------------------- | ------ | -------------------------------------------------------------------------- |
| `label`                | string | Display label for the step.                                                |
| `visitors`             | int    | Visitors who reached this step.                                            |
| `dropoff`              | int    | Visitors lost between the previous step and this one.                      |
| `dropoff_percentage`   | float  | `dropoff` as a percentage.                                                 |
| `conversion_rate`      | float  | Cumulative conversion rate from step 1 to this step.                       |
| `conversion_rate_step` | float  | Conversion rate from the previous step to this one.                        |
| `revenue`              | int    | Revenue (minor units) attributed at this step.                             |
| `value`                | int    | Goal value attributed at this step.                                        |
| `sources`              | array  | Top traffic sources reaching this step: `{label, domain, count, percent}`. |
| `countries`            | array  | Top countries reaching this step: `{label, count, percent}`.               |

Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST "https://clickbase.so/api/sites/example.com/funnels/evaluate?country=US" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"steps": [{"match_type": "event", "match_value": "signup"}, {"match_type": "event", "match_value": "purchase"}], "range": "last_30_days"}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/funnels/evaluate?country=US', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      steps: [
        { match_type: 'event', match_value: 'signup' },
        { match_type: 'event', match_value: 'purchase' },
      ],
      range: 'last_30_days',
    }),
  })
  ```

  ```python title="Python" theme={null}
  requests.post(
      'https://clickbase.so/api/sites/example.com/funnels/evaluate',
      params={'country': 'US'},
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={
          'steps': [
              {'match_type': 'event', 'match_value': 'signup'},
              {'match_type': 'event', 'match_value': 'purchase'},
          ],
          'range': 'last_30_days',
      },
  )
  ```
</CodeGroup>

### Funnel conversion (saved funnel)

```http theme={null}
GET /api/sites/{siteKey}/stats/funnels/{funnel}
```

Compute the conversion breakdown for a **saved** funnel over a date range — the same numbers the dashboard's funnel report shows. The funnel is resolved **through** the team's site, so a foreign/unknown funnel id returns `404`. Where [Evaluate](#evaluate-a-funnel-definition) runs an unsaved definition from the body, this reads a persisted `Funnel` by id.

**Path parameters**

| Parameter | Type   | Required | Description          |
| --------- | ------ | -------- | -------------------- |
| `siteKey` | string | Yes      | Site UUID or domain. |
| `funnel`  | string | Yes      | Saved funnel UUID.   |

**Query parameters** — the shared date range (`range`, or `from`/`to`) and dashboard filters, see [Conventions](/api/conventions#date-ranges) and [filters](/api/conventions#filters). As with evaluate, a `goal` or `prop_key`/`prop_value` filter is accepted but ignored.

**Response** — identical shape to [Evaluate a funnel definition](#evaluate-a-funnel-definition) (the `steps[]` breakdown plus the entering/never-entering visitor totals). Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl "https://clickbase.so/api/sites/example.com/stats/funnels/{funnel}?range=last_30_days" \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/stats/funnels/{funnel}?range=last_30_days', {
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  })
  ```

  ```python title="Python" theme={null}
  requests.get(
      f'https://clickbase.so/api/sites/example.com/stats/funnels/{funnel_id}',
      params={'range': 'last_30_days'},
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
  )
  ```
</CodeGroup>

### Delete a funnel

```http theme={null}
DELETE /api/sites/{siteKey}/funnels/{funnel}
```

Delete a funnel and its steps. Cannot be undone. A foreign/unknown funnel id returns `404`. Status: `204 No Content`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X DELETE https://clickbase.so/api/sites/example.com/funnels/{funnel} \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch(`https://clickbase.so/api/sites/example.com/funnels/${funnelId}`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  })
  ```

  ```python title="Python" theme={null}
  requests.delete(
      f'https://clickbase.so/api/sites/example.com/funnels/{funnel_id}',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
  )
  ```
</CodeGroup>
