# Funnels

> The API operations about funnels, each with its input, its output and an example.

The operations about funnels. Each one is a `POST` with a JSON object as its body: [Making a request](/docs/api#start) has the rules they share.

## Create funnel

`POST /api/v1/funnels.create` · Write

Saves a funnel for one site: a name and an ordered list of steps a visitor has to do. It appears on the Funnels page of the dashboard at once. 2 to 5 steps in order, each { "type": "pageview" or "event", "value": text }. Two neighbouring steps may not be the same, and an event step may not use a name the tracker keeps for itself (pageview, 404 and the names with its own prefix). The name must be unique on the site, ignoring case (409 taken), and a site holds at most 50 funnels (409 limit_reached). A step that breaks a rule answers 400 invalid with the step named, for example steps.1. Page paths are cleaned up like recorded paths, so read the steps in the answer. Needs a token with the Write permission. To try steps without saving, call funnels.report with steps.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `name` | string, 1 to 80 characters | required | The name of the funnel, 1 to 80 characters. Unique on the site, ignoring case. |
| `steps` | array of object, 2 to 5 items | required | 2 to 5 steps in order, each { "type": "pageview" or "event", "value": text }. Two neighbouring steps may not be the same, and an event step may not use a name the tracker keeps for itself (pageview, 404 and the names with its own prefix). |

**Fields of `steps`**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "pageview" \| "event" | required | pageview for a page, event for a custom event. |
| `value` | string, 1 to 512 characters | required | For a page: its path, for example /pricing; a trailing \* matches every path that starts with it. For an event: its exact name. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The funnel id. Pass it to funnels.report as funnelId. |
| `name` | string | always | The name the funnel was saved under. |
| `steps` | array of object | always | The steps as they were saved, in order. A page step is cleaned up like a recorded path, so it may differ from what was sent. |
| `createdAt` | number | always | When the funnel was created, milliseconds since 1970 UTC. |
| `updatedAt` | number | always | When the funnel was last changed, milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.create \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**CLI**

```bash
clearsaga funnels create --input '{"siteId":"site_abc123def456","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**MCP**

```text
Tool: funnels_create
Arguments: {"siteId":"site_abc123def456","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}
```

Run `clearsaga help funnels create` for one flag per field.

## Delete funnel

`POST /api/v1/funnels.delete` · Write

Removes a saved funnel from one site for good: its name and its steps. This cannot be undone. To change a funnel and keep it, call funnels.update instead. The id comes from funnels.list. Only the definition goes. No recorded visit is touched: a report is computed from recorded hits when it is asked for, so saving the same steps again with funnels.create gives the same numbers for every past period. The alert rules that watch the funnel are removed with it, together with their delivery logs. An id the site does not have is not an error: the answer says deleted false and nothing was changed, so sending the same call twice is safe. Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `id` | string, at least 1 characters | required | The id of the funnel to remove, from funnels.list. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The id that was sent. |
| `deleted` | boolean | always | true when the funnel was there and this call removed it. false when the site has no funnel with that id, because it was removed before or the id is wrong: nothing was changed, and that is not an error. |
| `name` | string \| null | always | The name the funnel had, so it is clear what was removed. Null when deleted is false. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.delete \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","id":"FUNNEL_ID"}'
```

**CLI**

```bash
clearsaga funnels delete --input '{"siteId":"site_abc123def456","id":"FUNNEL_ID"}'
```

**MCP**

```text
Tool: funnels_delete
Arguments: {"siteId":"site_abc123def456","id":"FUNNEL_ID"}
```

Run `clearsaga help funnels delete` for one flag per field.

## Duplicate funnel

`POST /api/v1/funnels.duplicate` · Write

Saves a copy of a funnel on the same site, with the same steps and the name "Copy of" followed by the original name (a number is added when that name is taken). The id comes from funnels.list. Use it to try a variation: copy, then change the copy with funnels.update. Every call makes another copy. An unknown id answers 404, and a site that already holds 50 funnels answers 409 limit_reached. Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `id` | string, at least 1 characters | required | The id of the funnel to copy, from funnels.list. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The funnel id. Pass it to funnels.report as funnelId. |
| `name` | string | always | The name the funnel was saved under. |
| `steps` | array of object | always | The steps as they were saved, in order. A page step is cleaned up like a recorded path, so it may differ from what was sent. |
| `createdAt` | number | always | When the funnel was created, milliseconds since 1970 UTC. |
| `updatedAt` | number | always | When the funnel was last changed, milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.duplicate \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","id":"FUNNEL_ID"}'
```

**CLI**

```bash
clearsaga funnels duplicate --input '{"siteId":"site_abc123def456","id":"FUNNEL_ID"}'
```

**MCP**

```text
Tool: funnels_duplicate
Arguments: {"siteId":"site_abc123def456","id":"FUNNEL_ID"}
```

Run `clearsaga help funnels duplicate` for one flag per field.

## List funnels

`POST /api/v1/funnels.list` · Read

The saved funnels of one site, the one changed last first: each with its id, name and ordered steps. Get the siteId from sites.list first. The whole list comes at once (a site holds at most 50 funnels); there is no paging, no range and no filter. It lists definitions only. For how many visitors got through a funnel, pass its id to funnels.report as funnelId. Keeps working while the workspace is locked.

Needs a token with the Read permission. Keeps answering while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `rows` | array of object | always | The saved funnels of the site, the one changed last first: id (pass it to funnels.report as funnelId), name, the steps in order, and createdAt and updatedAt in milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.list \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga funnels list --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: funnels_list
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help funnels list` for one flag per field.

## Funnel report

`POST /api/v1/funnels.report` · Read

How many visitors got through a funnel in a period, step by step: who entered, who reached each step, where they dropped off, and the overall conversion. Send exactly one of funnelId (a saved funnel, from funnels.list or funnels.create) or steps (a list to try without saving); sending both or neither answers 400. A visitor counts for a step when they did it after the step before, on the same day. Rates are fractions from 0 to 1. The page, event, property and conversion filters cannot apply to a funnel, in any of their forms (path, path.not and path.has alike): they change nothing, and the answer names each once by its plain key in ignoredFilters. The other filters apply. An unknown funnelId answers 404, and steps that break a rule answer 400 invalid with the step named. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period to report on: one of today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace's default range is used (30d unless it was changed). For exact dates send from and to and leave range out (custom is accepted with them and means the same). A preset together with dates answers 400. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone. Send it together with to: one without the other answers 400, and so does a day that does not exist or a from after to. A day after today is read as today; window in the answer shows the period that was used. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD in the site's time zone. Send it together with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | What the period is compared with: prev (the period just before, the default), week, month, quarter or year earlier, custom (cfrom and cto are then required), or off for no comparison. The range all never has a comparison. |
| `cfrom` | string | optional | First day of a custom comparison period, YYYY-MM-DD. Required with compare set to custom, where a day that does not exist or a cfrom after cto answers 400. |
| `cto` | string | optional | Last day of a custom comparison period, inclusive, YYYY-MM-DD. Required with compare set to custom. |
| `filters` | object | optional | Narrow the numbers to matching traffic. An object such as { "country": "us", "path.has": "pricing" }. A plain key means "is"; a key ending in .not means "is not" and a key ending in .has means "contains". Filters are joined with AND. At most 10 keys, of which at most 3 may end in .has. Default: `{}`. [The filter keys are listed under Filters.](/docs/api#filters) |
| `funnelId` | string, at least 1 characters | optional | The id of a saved funnel. Send this or steps, never both. |
| `steps` | array of object, 2 to 5 items | optional | Steps to report on without saving a funnel. Send this or funnelId, never both. 2 to 5 steps in order, each { "type": "pageview" or "event", "value": text }. Two neighbouring steps may not be the same, and an event step may not use a name the tracker keeps for itself (pageview, 404 and the names with its own prefix). |

**Fields of `steps`**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "pageview" \| "event" | required | pageview for a page, event for a custom event. |
| `value` | string, 1 to 512 characters | required | For a page: its path, for example /pricing; a trailing \* matches every path that starts with it. For an event: its exact name. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period (from, to, tz, label, granularity, token) with completed, the visitors who completed the funnel in it, and totalConversion, their share of those who entered. Null when there is no comparison. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `steps` | array of object | always | One row per step, in order: visitors who reached it, cumulativeRate (share of those who entered), stepRate (share of the step before), dropoffRate and dropped (lost since the step before). Rates are fractions from 0 to 1. |
| `entered` | number | always | Visitors who did the first step. |
| `completed` | number | always | Visitors who did every step in order. |
| `totalConversion` | number \| null | always | completed divided by entered, a fraction from 0 to 1. Null when nobody entered. |
| `ignoredFilters` | array of string | always | The filters of the input that a funnel cannot apply and left out: the page, event, property and conversion filters. A filter is named once by its plain key, whichever form was sent: path.has and path.not are both listed as path. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.report \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**CLI**

```bash
clearsaga funnels report --input '{"siteId":"site_abc123def456","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**MCP**

```text
Tool: funnels_report
Arguments: {"siteId":"site_abc123def456","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}
```

Run `clearsaga help funnels report` for one flag per field.

## Update funnel

`POST /api/v1/funnels.update` · Write

Replaces the name and the steps of a saved funnel. Send both in full: a step that is left out is removed. The id comes from funnels.list or funnels.create. Reports are computed from recorded hits when they are asked for, so the change applies to every past period at once and nothing is lost by it. 2 to 5 steps in order, each { "type": "pageview" or "event", "value": text }. Two neighbouring steps may not be the same, and an event step may not use a name the tracker keeps for itself (pageview, 404 and the names with its own prefix). The name must be unique on the site, ignoring case (409 taken). A step that breaks a rule answers 400 invalid with the step named, for example steps.1, and an unknown id answers 404. Page paths are cleaned up like recorded paths, so read the steps in the answer. Sending the same input twice leaves the funnel as it is. Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `id` | string, at least 1 characters | required | The id of the funnel to change, from funnels.list. |
| `name` | string, 1 to 80 characters | required | The new name of the funnel, 1 to 80 characters. Unique on the site, ignoring case. Send the current name to keep it. |
| `steps` | array of object, 2 to 5 items | required | The new steps, replacing the old ones. 2 to 5 steps in order, each { "type": "pageview" or "event", "value": text }. Two neighbouring steps may not be the same, and an event step may not use a name the tracker keeps for itself (pageview, 404 and the names with its own prefix). |

**Fields of `steps`**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "pageview" \| "event" | required | pageview for a page, event for a custom event. |
| `value` | string, 1 to 512 characters | required | For a page: its path, for example /pricing; a trailing \* matches every path that starts with it. For an event: its exact name. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The funnel id. Pass it to funnels.report as funnelId. |
| `name` | string | always | The name the funnel was saved under. |
| `steps` | array of object | always | The steps as they were saved, in order. A page step is cleaned up like a recorded path, so it may differ from what was sent. |
| `createdAt` | number | always | When the funnel was created, milliseconds since 1970 UTC. |
| `updatedAt` | number | always | When the funnel was last changed, milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/funnels.update \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","id":"FUNNEL_ID","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**CLI**

```bash
clearsaga funnels update --input '{"siteId":"site_abc123def456","id":"FUNNEL_ID","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}'
```

**MCP**

```text
Tool: funnels_update
Arguments: {"siteId":"site_abc123def456","id":"FUNNEL_ID","name":"Signup","steps":[{"type":"pageview","value":"/pricing"},{"type":"event","value":"Signup"}]}
```

Run `clearsaga help funnels update` for one flag per field.
