Funnels & pathsGET /api/v1/analytics/funnelsPOST /api/v1/analytics/funnels/queryPOST /api/v1/analytics/paths/query

Three endpoints over behavioral analytics: list the funnels saved in the dashboard, run one (or an ad-hoc definition you compose on the fly), and run a path (Sankey) query showing what people do after a starting event.

List saved funnels

Saved funnels are shared config across live and test mode, so this endpoint returns the same list regardless of which key prefix you use.

curl https://www.vevee.org/api/v1/analytics/funnels \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
{
  "ok": true,
  "data": {
    "funnels": [
      {
        "id": "fnl_signup_to_paid",
        "appId": "app_9f2c1a",
        "name": "Signup to paid",
        "definition": {
          "steps": [
            { "events": [{ "kind": "event", "name": "signed_up" }] },
            { "events": [{ "kind": "event", "name": "onboarding_completed" }] },
            { "events": [{ "kind": "event", "name": "paywall_shown" }] },
            { "events": [{ "kind": "event", "name": "checkout_completed" }] }
          ],
          "conversionWindowHours": 168,
          "range": { "kind": "last_30_days" }
        },
        "createdAt": "2026-06-02T09:14:00.000Z",
        "updatedAt": "2026-07-18T11:40:12.000Z"
      }
    ]
  }
}

Run a funnel

Pass exactly one of funnelId (a saved funnel from the list above) or definition (an ad-hoc funnel spec, never persisted). Passing both, or neither, returns invalid_request.

By saved funnel id

curl https://www.vevee.org/api/v1/analytics/funnels/query \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "funnelId": "fnl_signup_to_paid" }'

Ad-hoc definition

curl https://www.vevee.org/api/v1/analytics/funnels/query \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "definition": {
      "steps": [
        { "events": [{ "kind": "event", "name": "paywall_shown" }] },
        { "events": [{ "kind": "event", "name": "checkout_completed" }] }
      ],
      "conversionWindowHours": 24,
      "range": { "kind": "last_7_days" }
    }
  }'

The response wraps the result with the funnel's name (null for ad-hoc definitions):

{
  "ok": true,
  "data": {
    "name": "Signup to paid",
    "result": {
      "totalEntered": 1240,
      "totalConverted": 301,
      "overallConversion": 0.2427,
      "steps": [
        {
          "step": 1,
          "label": "signed_up",
          "optional": false,
          "reachedCount": 1240,
          "conversionFromStart": 1,
          "conversionFromPrev": 1,
          "avgTimeFromPrevSec": null
        },
        {
          "step": 2,
          "label": "onboarding_completed",
          "optional": false,
          "reachedCount": 812,
          "conversionFromStart": 0.6548,
          "conversionFromPrev": 0.6548,
          "avgTimeFromPrevSec": 431
        },
        {
          "step": 3,
          "label": "paywall_shown",
          "optional": false,
          "reachedCount": 790,
          "conversionFromStart": 0.6371,
          "conversionFromPrev": 0.9729,
          "avgTimeFromPrevSec": 96
        },
        {
          "step": 4,
          "label": "checkout_completed",
          "optional": false,
          "reachedCount": 301,
          "conversionFromStart": 0.2427,
          "conversionFromPrev": 0.381,
          "avgTimeFromPrevSec": 218
        }
      ]
    }
  }
}

Run a path query

Starting from one event, walk each person's timeline forward up to maxSteps events within windowHours, and aggregate the transitions into a layered node/edge graph. Paths are stateless - there is no pathId to save; every call is ad-hoc.

curl https://www.vevee.org/api/v1/analytics/paths/query \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "start": { "name": "onboarding_completed" },
    "maxSteps": 4,
    "windowHours": 48,
    "compressRepeats": true,
    "minEdgeShare": 0.02,
    "range": { "kind": "last_30_days" }
  }'

Parameters

NameTypeDescription
start.namerequiredstringCaptured analytics event name to start every person's path from.
maxStepsrequirednumberTotal nodes in the path including the start. Range 2 to 10.
windowHoursrequirednumberMax wall-clock distance from the start event to still count. Range 1 to 168.
compressRepeatsrequiredbooleanCollapse consecutive repeats of the same event name while walking.
minEdgeSharerequirednumberDrop edges whose share of totalStarters falls below this. Range 0 to 1.
rangerequiredFunnelRangeSame relative/custom date range shape as funnels.
{
  "ok": true,
  "data": {
    "totalStarters": 812,
    "totalCompleters": 214,
    "nodes": [
      { "depth": 0, "event": "onboarding_completed", "count": 812, "share": 1 },
      { "depth": 1, "event": "paywall_shown", "count": 601, "share": 0.7401 },
      { "depth": 1, "event": "__dropoff__", "count": 211, "share": 0.2599 },
      { "depth": 2, "event": "checkout_completed", "count": 214, "share": 0.2635 },
      { "depth": 2, "event": "__dropoff__", "count": 387, "share": 0.4766 }
    ],
    "edges": [
      { "depth": 0, "from": "onboarding_completed", "to": "paywall_shown", "count": 601, "share": 0.7401 },
      { "depth": 0, "from": "onboarding_completed", "to": "__dropoff__", "count": 211, "share": 0.2599 },
      { "depth": 1, "from": "paywall_shown", "to": "checkout_completed", "count": 214, "share": 0.2635 },
      { "depth": 1, "from": "paywall_shown", "to": "__dropoff__", "count": 387, "share": 0.4766 }
    ]
  }
}

Errors

  • invalid_key (401) - bad or revoked API key.
  • requires_secret_key (403) - a pk_* key was used.
  • invalid_request (400) - malformed body, or both/neither of funnelId and definition supplied.
  • not_found (404) - funnelId does not match a saved funnel on this app.
i
Related: the MCP server exposes the same three operations as list_funnels, run_funnel, and run_path_query - see MCP server. Funnel and path definitions are covered in depth in the Analytics & funnels guide.