Funnels
Ordered multi-step paths through the site, plus their computed drop-off statistics.
6 endpoints, all relative to https://analytics.appfor.you. Shared rules live in Conventions, Rate limits and Error codes.
List funnels
/api/v2/sites/{siteId}/funnelsscope: readDefinitions only — no computed results. Available to the site owner and accepted team members of any role.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
Query parameters
| Name | Type | Description |
|---|---|---|
| limit | integer | Rows to return. Default 50, minimum 1, maximum 500. |
| offset | integer | Rows to skip, for paging. Default 0. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels"
Example response
{
"funnels": [
{
"funnelId": "6512bb02bcf86cd7994390b2",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Checkout",
"steps": [
{ "name": "Cart", "pathname": "/cart" },
{ "name": "Payment", "pathname": "/checkout" },
{ "name": "Done", "pathname": "/thank-you" }
],
"createdAt": "2025-02-10T08:00:00.000Z",
"updatedAt": "2025-02-10T08:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}Create a funnel
/api/v2/sites/{siteId}/funnelsscope: writeEach step needs a non-empty name and pathname. Order matters — step 1 is the entry point used as the conversion baseline.
- Viewers receive 403.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Funnel display name. |
| steps | Array<{ name, pathname }> | Yes | Ordered steps. Minimum 2 items. |
Example request
curl -X POST "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Checkout", "steps": [ { "name": "Cart", "pathname": "/cart" }, { "name": "Payment", "pathname": "/checkout" }, { "name": "Done", "pathname": "/thank-you" } ] }'Example response (201)
{
"funnelId": "6512bb02bcf86cd7994390b2",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Checkout",
"steps": [
{ "name": "Cart", "pathname": "/cart" },
{ "name": "Payment", "pathname": "/checkout" },
{ "name": "Done", "pathname": "/thank-you" }
],
"createdAt": "2025-03-21T09:00:00.000Z",
"updatedAt": "2025-03-21T09:00:00.000Z"
}Get a funnel
/api/v2/sites/{siteId}/funnels/{funnelId}scope: readNo computed results here — use the funnel stats sub-resource for those.
- Returns 404 "Funnel not found" for malformed or foreign ids.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
| funnelId | string | Identifier of the funnel. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels/6512bb02bcf86cd7994390b2"
Example response
{
"funnelId": "6512bb02bcf86cd7994390b2",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Checkout",
"steps": [
{ "name": "Cart", "pathname": "/cart" },
{ "name": "Payment", "pathname": "/checkout" }
],
"createdAt": "2025-02-10T08:00:00.000Z",
"updatedAt": "2025-02-10T08:00:00.000Z"
}Update a funnel
/api/v2/sites/{siteId}/funnels/{funnelId}scope: writeAt least one field is required. `steps` REPLACES the entire array — it is not merged — and the replacement must still contain at least 2 steps.
- Viewers receive 403.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
| funnelId | string | Identifier of the funnel. |
Request body
Every field is optional, but at least one must be supplied.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | No | New funnel name. |
| steps | Array<{ name, pathname }> | No | Full replacement step list, minimum 2 items. |
Example request
curl -X PATCH "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels/6512bb02bcf86cd7994390b2" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Checkout v2" }'Example response
{
"funnelId": "6512bb02bcf86cd7994390b2",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Checkout v2",
"steps": [
{ "name": "Cart", "pathname": "/cart" },
{ "name": "Payment", "pathname": "/checkout" }
],
"updatedAt": "2025-03-21T09:25:00.000Z"
}Delete a funnel
/api/v2/sites/{siteId}/funnels/{funnelId}scope: adminDestructiveDeleting a funnel destroys the step definition and every drop-off report built on it. Pass `confirm=<funnelId>` as a query parameter or as a `confirm` field in the JSON body — it must equal the 24-character funnel id from the URL, not the funnel name.
Irreversible. Requires the admin scope and confirm={funnelId} — see Destructive operations.
- Viewers receive 403; an unknown id returns 404.
- The confirmation is checked AFTER the scope and site-access checks but BEFORE the id is validated, so a wrong `confirm` with a non-existent id returns 400, never a 404.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
| funnelId | string | Identifier of the funnel. |
Query parameters
| Name | Type | Description |
|---|---|---|
| confirm | string | Destructive-operation confirmation. Must exactly equal the `funnelId` in the URL. May be sent as a `confirm` field in the JSON body instead. |
Request body
Optional — send `{ "confirm": "<funnelId>" }` instead of the query parameter if you prefer.
| Field | Type | Required | Description |
|---|---|---|---|
| confirm | string | No | Must equal the funnelId in the URL. |
Example request
curl -X DELETE "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels/6512bb02bcf86cd7994390b2?confirm=6512bb02bcf86cd7994390b2" \ -H "Authorization: Bearer $ANALYTICS_API_KEY"
Example response
{
"deleted": true,
"funnelId": "6512bb02bcf86cd7994390b2"
}Funnel results
/api/v2/sites/{siteId}/funnels/{funnelId}/statsscope: readFor each step: the number of unique sessions that hit the step pathname, the drop-off rate versus the previous step, and the conversion rate versus step 1. `overallConversionRate` is first-step to last-step. Percentages are rounded to one decimal. Same maths as the dashboard.
- The resolved window is echoed as a FLAT `range` string alongside top-level `since` and `until`, not as a `range` object. This and `GET /goals/{goalId}` are the only two endpoints that still do.
- An unusable window (unparseable `from`/`to`, or one that ends before it starts) returns 400, matching the goal endpoints, rather than silently falling back to the `range` preset.
- Date-only `from`/`to` cover WHOLE days: `to=2025-03-21` runs through `2025-03-21T23:59:59.999Z`, so `from=X&to=X` is a full 24-hour day.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
| funnelId | string | Identifier of the funnel. |
Query parameters
| Name | Type | Description |
|---|---|---|
| range | string | Relative window: `24h`, `7d`, `30d`, `90d` or `365d`. Default `30d`. Overridden by `from`/`to`. |
| from | string (ISO-8601 or YYYY-MM-DD) | Explicit window start, overriding `range`. A date-only `YYYY-MM-DD` value means that day from `T00:00:00.000Z`; a value carrying a time is an instant and is used verbatim. Supplying only `from` gives `[from, now]`. An unparseable value, or a window that ends before it starts, returns 400. |
| to | string (ISO-8601 or YYYY-MM-DD) | Explicit window end, overriding `range`. A date-only `YYYY-MM-DD` value covers the WHOLE day, through `T23:59:59.999Z` — the final day is included, and `from=X&to=X` is a full 24 hours rather than the zero-width window it used to be. A value carrying a time is used verbatim. An end in the future is clamped to now, except where clamping would invert the window. Supplying only `to` gives `[to - range, to]`. An unparseable value, or a window that ends before it starts, returns 400. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/funnels/6512bb02bcf86cd7994390b2/stats?range=30d"
Example response
{
"funnel": { "funnelId": "6512bb02bcf86cd7994390b2", "name": "Checkout" },
"range": "30d",
"since": "2025-02-19T00:00:00.000Z",
"until": "2025-03-21T00:00:00.000Z",
"steps": [
{ "name": "Cart", "pathname": "/cart", "sessions": 1000, "dropoffRate": 0, "conversionRate": 100 },
{ "name": "Payment", "pathname": "/checkout", "sessions": 620, "dropoffRate": 38, "conversionRate": 62 },
{ "name": "Done", "pathname": "/thank-you", "sessions": 415, "dropoffRate": 33.1, "conversionRate": 41.5 }
],
"overallConversionRate": 41.5
}