REST API
Funnels
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 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
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.
curl https://clickbase.so/api/sites/example.com/funnels \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/funnels', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.get(
'https://clickbase.so/api/sites/example.com/funnels',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Create a funnel
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. |
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.
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}'
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,
}),
})
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,
},
)
Update a funnel
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.
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}'
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,
}),
})
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,
},
)
Evaluate a funnel definition
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). |
strict_order |
bool | No | Whether steps must be completed in order. Default false. |
range |
string | No | Preset date range. See Conventions. |
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) 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.
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"}'
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',
}),
})
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',
},
)
Funnel conversion (saved funnel)
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 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 and filters. As with evaluate, a goal or prop_key/prop_value filter is accepted but ignored.
Response — identical shape to Evaluate a funnel definition (the steps[] breakdown plus the entering/never-entering visitor totals). Status: 200 OK.
curl "https://clickbase.so/api/sites/example.com/stats/funnels/{funnel}?range=last_30_days" \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/stats/funnels/{funnel}?range=last_30_days', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
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'},
)
Delete a funnel
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.
curl -X DELETE https://clickbase.so/api/sites/example.com/funnels/{funnel} \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch(`https://clickbase.so/api/sites/example.com/funnels/${funnelId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.delete(
f'https://clickbase.so/api/sites/example.com/funnels/{funnel_id}',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)