{
  "openapi": "3.1.0",
  "info": {
    "title": "Formo Public API",
    "description": "REST API for managing Formo projects, analytics, alerts, boards, charts, contracts, segments, and AI chat.\n\n**Auth.** Every endpoint on `api.formo.so` requires a workspace API key with the appropriate scopes (see `x-api-scopes`). The one exception is `POST /v0/raw_events`, which runs on `events.formo.so` and authenticates with the project SDK write key instead.\n\n**Response shape.** Successful responses return the resource directly (or `{ data: [...], total, page, size, has_more }` for paginated lists). HTTP status carries success/failure; there is no envelope wrapping success bodies.\n\n**Errors.** Non-2xx responses from `api.formo.so` use the `Error` envelope, with two exceptions: some rate-limit rejections reply in plain text, and `POST /v0/raw_events` returns a plain `{ \"error\": \"...\" }` object. Treat the HTTP status as authoritative and parse defensively: `{ error: { code, message, doc_url, param?, details? } }`. Branch on the machine-readable `code` (see `ErrorCode` enum) and follow `doc_url` to the matching section of the [errors reference](https://docs.formo.so/api/errors).\n\n**Idempotency.** Pass an `Idempotency-Key` header on write requests to alerts, boards, charts, contracts, segments, and import to make retries safe; the response is cached for 24 h and replayed on duplicate keys. Profile writes and `POST /v0/query` do not support it.",
    "version": "0.1.0",
    "contact": {
      "name": "Formo",
      "url": "https://formo.so"
    }
  },
  "servers": [
    {
      "url": "https://api.formo.so",
      "description": "API Server (boards, alerts, contracts, segments, profiles, query, import)"
    },
    {
      "url": "https://events.formo.so",
      "description": "Events Server (event ingestion)"
    }
  ],
  "security": [
    {
      "WorkspaceApiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "WorkspaceApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Workspace API key (e.g. `formo_xxx`). Create one in the Formo dashboard under Team Settings > API."
      },
      "X402Payment": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PAYMENT",
        "description": "x402 payment payload generated after the gateway returns a 402 challenge."
      },
      "MPPPayment": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "MPP payment authorization header, formatted as `Payment <payload>`, generated after the gateway returns a 402 challenge."
      },
      "SdkWriteKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Project SDK write key (a JWT), sent as `Authorization: Bearer <write key>`. This is NOT the `formo_...` workspace API key used on api.formo.so. Find it in the Formo dashboard under the project’s settings."
      }
    },
    "schemas": {
      "PaginatedListMeta": {
        "type": "object",
        "description": "Pagination cursor returned alongside `data` on every paginated list endpoint. Use these to walk pages: `has_more` is true while `page * size < total`. Combine with the matching `Page` and `Size` query parameters to request the next page.",
        "required": ["page", "size", "total", "has_more"],
        "properties": {
          "page": {
            "type": "integer",
            "description": "1-indexed page number echoed from the request."
          },
          "size": {
            "type": "integer",
            "description": "Page size echoed from the request."
          },
          "total": {
            "type": "integer",
            "description": "Total row count across all pages."
          },
          "has_more": {
            "type": "boolean",
            "description": "True when more pages remain (`page * size < total`)."
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Standard error envelope returned by every public API endpoint for any non-2xx response. The HTTP status code carries success/failure; the body provides a machine-readable `code`, a human-readable `message`, and a `doc_url` pointing at the matching section of the docs so agents can fetch context on the fly.",
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "doc_url"],
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ErrorCode"
              },
              "message": {
                "type": "string",
                "description": "Human-readable error description. Wording may change between releases, so branch on `code`, not `message`."
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "description": "Link to the matching section of the errors reference at https://docs.formo.so/api/errors."
              },
              "param": {
                "type": "string",
                "description": "When the error pertains to a specific request field, the dotted path to that field (e.g. `body.trigger_filters.0.value`)."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Code-specific extra context. For `INVALID_VALIDATION_REQUEST` this is a `{ fieldPath: message }` map of every Zod validation failure."
              }
            }
          }
        },
        "required": ["error"]
      },
      "ErrorCode": {
        "type": "string",
        "description": "Stable, enumerated error codes. New codes may be added in any release; clients should treat unknown codes as the closest matching HTTP status family.",
        "enum": [
          "INTERNAL_SERVER_ERROR",
          "INVALID_VALIDATION_REQUEST",
          "UNAUTHORIZED",
          "BAD_REQUEST",
          "FORBIDDEN",
          "NOT_FOUND",
          "CONFLICT",
          "INVALID_CHAIN_ID",
          "CONTEXT_LIMIT_EXCEEDED",
          "SERVICE_UNAVAILABLE",
          "TOO_MANY_REQUESTS",
          "IDEMPOTENCY_IN_PROGRESS",
          "INVALID_IDEMPOTENCY_KEY"
        ]
      },
      "Alert": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "trigger_type": {
            "type": "string",
            "enum": ["event", "user"]
          },
          "status": {
            "type": "string",
            "enum": ["active", "inactive"]
          },
          "trigger_filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlertFilter"
            }
          },
          "recipient": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": ["email", "slack", "webhook"]
                },
                "value": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": ["type", "value"]
            }
          },
          "project_id": {
            "type": "string"
          },
          "has_secret": {
            "type": "boolean"
          }
        },
        "required": ["id", "name", "trigger_type", "status", "project_id"]
      },
      "Board": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "project_id": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether the board is publicly accessible"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          }
        },
        "required": ["id", "project_id", "enabled"]
      },
      "StepFilterCondition": {
        "type": "object",
        "description": "A canonical filter on a funnel, flow, retention, or user-path step.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Column or property targeted by this filter."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "lt",
              "gte",
              "lte",
              "in",
              "nin",
              "startsWith",
              "endsWith",
              "contains",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Canonical comparison operator token."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. Omit for `notEmpty` and `isEmpty`. For `in` / `nin`, pass a non-empty array or a pipe-delimited string. Array string members cannot contain a literal `|`, which is reserved as the Tinybird membership separator."
          }
        },
        "required": ["field", "op"]
      },
      "FunnelStepEvent": {
        "type": "object",
        "description": "One alternative inside a funnel step's OR group. `filters` uses the same canonical `{field, op, value}` envelope as `FunnelStep.filters` and binds to this member only: `(A AND filtersA) OR (B AND filtersB)`.",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["event", "track", "decoded_log"]
          },
          "event": {
            "type": "string",
            "minLength": 1
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StepFilterCondition"
            },
            "description": "Member-scoped predicates, AND-joined within this member."
          }
        },
        "required": ["type", "event"]
      },
      "FunnelStep": {
        "type": "object",
        "description": "A single funnel or user-path step. Event-property predicates are stored in `filters`, using the same canonical `{field, op, value}` envelope as every other filter surface. Funnel steps (not user-path anchors or retention filters) may also carry `events`, an OR group: the step then matches any listed event. `type`/`event` stay the step's primary event and are always part of the group; `filters` apply to the whole group.",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["event", "track", "decoded_log"],
            "description": "`event`: built-in page/connect/transaction events; `track`: custom tracked events; `decoded_log`: decoded smart-contract events."
          },
          "event": {
            "type": "string",
            "minLength": 1,
            "description": "Event name (e.g. `page`, `connect`, `transaction`, or a custom track event name)."
          },
          "events": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/FunnelStepEvent"
            },
            "description": "Optional OR group (funnel steps only, max 10). When present and non-empty the step matches any of these events, e.g. `[{\"type\":\"track\",\"event\":\"Swap Initiated\"},{\"type\":\"track\",\"event\":\"Limit Order Placed\"}]`. The primary `type`/`event` is always included."
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StepFilterCondition"
            }
          }
        },
        "required": ["type", "event"],
        "additionalProperties": true
      },
      "ConversionWindow": {
        "type": "object",
        "description": "Time window within which a user must complete all funnel steps (measured from Step 1). Defaults to 2 hours if omitted.",
        "properties": {
          "value": {
            "type": "integer",
            "minimum": 1,
            "description": "Number of time units."
          },
          "unit": {
            "type": "string",
            "enum": ["hour", "day", "week"],
            "description": "Time unit. `week` = 7 days."
          }
        },
        "required": ["value", "unit"]
      },
      "AnalyticsFilterCondition": {
        "type": "object",
        "description": "A single condition in an analytics `filters` query parameter. Multiple entries are combined with implicit AND. Use `in` / `nin` with a pipe-delimited string value (e.g. `\"a|b|c\"`) for multi-value matches.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Column to filter on. Page path: `page`. Standard session columns: `device`, `browser`, `os`, `location`, `referrer`, `ref`, `origin`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`. Track-event columns: `event`, `type`. Numeric event properties: `volume`, `revenue`, `points`."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "lt",
              "gte",
              "lte",
              "in",
              "nin",
              "startsWith",
              "endsWith",
              "contains",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Comparison operator. `startsWith` / `endsWith` / `contains` are substring matches on text columns (e.g. `referrer`, `ref`, `utm_*`, `origin`, `builder_codes`). `notEmpty` matches rows where the column has any non-empty value, and `isEmpty` is its complement (the column is empty/unset); both ignore `value` (pass any placeholder). Existence checks are rejected on the numeric event columns `volume`/`revenue`/`points`."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. Omit for `notEmpty` and `isEmpty`. For `in` / `nin`, pass a non-empty array or a pipe-delimited string. Array string members cannot contain a literal `|`, which is reserved as the Tinybird membership separator."
          },
          "filters": {
            "type": "array",
            "description": "Optional predicates on properties of the selected event. Nested entries use the same canonical envelope and are leaves; deeper nesting is not supported.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsNestedFilterCondition"
            }
          }
        },
        "required": ["field", "op"]
      },
      "AnalyticsNestedFilterCondition": {
        "type": "object",
        "description": "A leaf predicate on a property of the event selected by an analytics filter. This canonical envelope cannot contain further nested filters.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Event property targeted by this filter."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "lt",
              "gte",
              "lte",
              "in",
              "nin",
              "startsWith",
              "endsWith",
              "contains",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Canonical comparison operator token."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. Omit for `notEmpty` and `isEmpty`. For `in` / `nin`, pass a non-empty array or a pipe-delimited string. Array string members cannot contain a literal `|`, which is reserved as the Tinybird membership separator."
          }
        },
        "required": ["field", "op"]
      },
      "SegmentFilterCondition": {
        "type": "object",
        "additionalProperties": false,
        "description": "A condition in a saved segment. Segment entries use the canonical `{field, op, value}` envelope and are combined with implicit AND. Nested event-property predicates are not supported by saved segments.",
        "properties": {
          "field": {
            "type": "string",
            "minLength": 1
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "lt",
              "gte",
              "lte",
              "in",
              "nin",
              "startsWith",
              "endsWith",
              "contains",
              "notEmpty",
              "isEmpty"
            ]
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. Omit for `notEmpty` and `isEmpty`. Membership arrays must be non-empty. Array string members cannot contain a literal `|`, which is reserved as the Tinybird membership separator."
          }
        },
        "required": ["field", "op"]
      },
      "RetentionUserFilter": {
        "type": "object",
        "description": "A user-level retention cohort filter using the canonical `{field, op, value}` envelope.",
        "properties": {
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ],
            "description": "The value to compare against. Omit for `notEmpty` and `isEmpty`."
          },
          "field": {
            "type": "string",
            "description": "The user property to filter on (e.g. `device`, `browser`, `os`, `location`, `utm_source`, `utm_medium`, `utm_campaign`)."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "in",
              "nin",
              "gt",
              "gte",
              "lt",
              "lte",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Comparison operator. Only the canonical terse tokens are accepted (the retired long forms `greater`/`greaterOrEqual`/`less`/`lessOrEqual` are rejected). `notEmpty` (\"is not empty\") and `isEmpty` (\"is empty\") are value-less existence checks on a string user property; the `value` is ignored. Substring operators (`startsWith` / `endsWith` / `contains`) are not supported on retention user filters."
          }
        },
        "required": ["field", "op"]
      },
      "RetentionLabelSignal": {
        "type": "object",
        "description": "The label predicate for label-based retention. The cohort is wallets grouped by the week they FIRST crossed this predicate on their label value; a wallet is retained in a later week if its latest value as of that week's end still satisfies it (as-of / carry-forward, evaluated against label history).",
        "properties": {
          "field": {
            "type": "string",
            "description": "The label tag to evaluate (the value of `tag_id` set via `POST /v0/profiles/:address/labels`)."
          },
          "op": {
            "type": "string",
            "enum": ["gt", "gte", "lt", "lte", "eq"],
            "default": "gt",
            "description": "Comparison operator. Numeric operators coerce both sides via toFloat64OrZero, so a numeric op on a non-numeric value yields no match (not an error)."
          },
          "value": {
            "type": "string",
            "description": "The threshold/value to compare against. Always a string; numeric ops coerce it."
          },
          "chain_id": {
            "type": "string",
            "default": "",
            "description": "Optional chain scope. Empty string matches across all chains."
          }
        },
        "required": ["field", "op", "value"]
      },
      "RetentionCohortLabelFilter": {
        "type": "object",
        "description": "A label predicate that restricts an event-based retention cohort. Uses the canonical filter envelope.",
        "properties": {
          "field": {
            "type": "string",
            "description": "The label tag to evaluate."
          },
          "op": {
            "type": "string",
            "enum": ["eq", "neq", "contains", "gt", "gte", "lt", "lte"],
            "default": "eq"
          },
          "value": {
            "type": "string"
          },
          "chain_id": {
            "type": "string",
            "default": "",
            "description": "Optional chain scope. Empty string matches across all chains."
          }
        },
        "required": ["field", "op", "value"]
      },
      "ChartSettings": {
        "type": "object",
        "description": "Chart-type-specific configuration. The fields that apply depend on `chart_type`:\n\n- **funnel**: `funnelType`, `conversionWindow`, `breakdown`\n- **user_paths**: `anchors`, `maxSteps`, `nodesPerStep`, `conversionWindow`, `filters`\n- **retention**: `entryFilter`, `retentionFilter`, `retentionUserFilters`, `retentionSignalType`, `retentionLabelSignal`\n\nFields are optional at the schema level except where the selected chart type requires them. User Paths require at least one `anchors` entry. Retention requests must explicitly include `entryFilter`; use `null` for any event.",
        "properties": {
          "funnelType": {
            "type": "string",
            "enum": ["closed", "open"],
            "default": "closed",
            "description": "**Funnel only.** `closed`: users must complete steps in strict order with no intervening events. `open`: users may complete steps in order but other events may occur between steps."
          },
          "conversionWindow": {
            "$ref": "#/components/schemas/ConversionWindow",
            "description": "**Funnel & user_paths.** Maximum time from Step 1 for a user to complete all steps."
          },
          "breakdown": {
            "type": "string",
            "enum": [
              "device",
              "browser",
              "os",
              "location",
              "referrer",
              "ref",
              "utm_source",
              "utm_medium",
              "utm_campaign",
              "utm_term",
              "utm_content",
              "builder_codes"
            ],
            "description": "**Funnel only.** Split each funnel bar by this dimension. The top categories are shown individually; the rest are collapsed into 'Others'."
          },
          "anchors": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/FunnelStep"
            },
            "description": "**user_paths (required).** Ordered path anchors. The first entry starts the flow; the last entry ends it. A single entry creates an open-ended flow."
          },
          "maxSteps": {
            "type": "integer",
            "minimum": 2,
            "maximum": 5,
            "default": 3,
            "description": "**user_paths.** Maximum number of steps to show in the flow (2 to 5). Values above 5 are clamped to 5."
          },
          "nodesPerStep": {
            "type": "integer",
            "minimum": 2,
            "maximum": 8,
            "default": 5,
            "description": "**user_paths.** Maximum number of unique event nodes visible per step (2 to 8). Values above 8 are clamped to 8."
          },
          "filters": {
            "type": "string",
            "description": "**user_paths.** JSON-encoded string of additional filters applied to the path query."
          },
          "retentionFilter": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FunnelStep"
              },
              {
                "type": "null"
              }
            ],
            "description": "**retention.** Event that qualifies a returning visit as 'retained'. If `null`, any event counts as a return."
          },
          "entryFilter": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FunnelStep"
              },
              {
                "type": "null"
              }
            ],
            "description": "**retention (required).** Event that places a user into the cohort. If `null`, any event counts as cohort entry. The key must be present even when its value is `null`."
          },
          "retentionUserFilters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RetentionUserFilter"
            },
            "description": "**retention.** Zero or more user-segment filters that narrow the cohort (e.g. only desktop users, only users from a specific UTM source)."
          },
          "retentionSignalType": {
            "type": "string",
            "enum": ["event", "label"],
            "default": "event",
            "description": "**retention.** `event` (default): cohort and retention are driven by events. `label`: driven by a label value over time (see `retentionLabelSignal`); when `label`, the event fields are ignored."
          },
          "retentionLabelSignal": {
            "$ref": "#/components/schemas/RetentionLabelSignal",
            "description": "**retention.** The label predicate when `retentionSignalType` is `label`."
          },
          "retentionCohortLabelFilters": {
            "type": "array",
            "description": "**retention.** Label predicates that restrict an event-based cohort.",
            "items": {
              "$ref": "#/components/schemas/RetentionCohortLabelFilter"
            }
          }
        }
      },
      "CreateChartRequest": {
        "type": "object",
        "description": "Request body for creating a chart.",
        "properties": {
          "projectId": {
            "type": "string",
            "description": "Project the chart belongs to."
          },
          "query": {
            "type": "string",
            "minLength": 1,
            "description": "SQL query that powers the chart.\n\n- **`funnel`**: pass `\"SELECT 1\"`; the actual query is auto-generated from `steps`.\n- **`user_paths`**: omit it; the query is generated from `settings.anchors`.\n- **`retention`**: omit it; data is fetched automatically.\n- **All other types**: required; must be a valid SQL string."
          },
          "chart_type": {
            "type": "string",
            "enum": [
              "table",
              "number",
              "funnel",
              "bar",
              "line",
              "area",
              "pie",
              "stacked",
              "user_paths",
              "retention"
            ],
            "description": "Visualization type. Determines which other fields are required:\n\n| `chart_type` | Extra required fields |\n|---|---|\n| `table` | `query` |\n| `number` | `query` (must return 1 row × 1 column) |\n| `bar` | `query`, `x_axis`, `y_axis` (≥ 1) |\n| `line` | `query`, `x_axis`, `y_axis` (≥ 1) |\n| `area` | `query`, `x_axis`, `y_axis` (≥ 1; exactly 1 if `group_by` set) |\n| `pie` | `query`, `y_axis` (exactly 1) |\n| `stacked` | `query`, `x_axis`, `y_axis` (exactly 1), `group_by` |\n| `funnel` | `steps` (≥ 2), `query` placeholder `\"SELECT 1\"` |\n| `user_paths` | `settings.anchors` (≥ 1); query is generated |\n| `retention` | `settings.entryFilter` (key required; `null` means any event) |"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Display name shown on the chart and board."
          },
          "description": {
            "type": "string",
            "description": "Optional description."
          },
          "x_axis": {
            "type": "string",
            "description": "Column name for the X axis. Required for `bar`, `line`, and `stacked`."
          },
          "y_axis": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Column name(s) used as Y axis metrics.\n\n- `bar` / `line`: at least 1 element required.\n- `pie` / `stacked`: exactly 1 element required."
          },
          "group_by": {
            "type": "string",
            "description": "Column to group / stack series by. Required for `stacked`."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FunnelStep"
            },
            "minItems": 2,
            "description": "Ordered list of funnel steps. Required for `funnel` (minimum 2 steps).\n\nEach element is a `FunnelStep`; add property predicates in its canonical `filters` array (e.g. `\"filters\": [{ \"field\": \"rdns\", \"op\": \"eq\", \"value\": \"io.metamask\" }]`)."
          },
          "settings": {
            "$ref": "#/components/schemas/ChartSettings"
          }
        },
        "required": ["projectId", "chart_type", "title"]
      },
      "UpdateChartRequest": {
        "type": "object",
        "description": "Request body for updating an existing chart.",
        "properties": {
          "chartId": {
            "type": "string",
            "description": "ID of the chart to update."
          },
          "projectId": {
            "type": "string",
            "description": "Project the chart belongs to."
          },
          "query": {
            "type": "string",
            "minLength": 1,
            "description": "SQL query that powers the chart.\n\n- **`funnel`**: pass `\"SELECT 1\"`; the actual query is auto-generated from `steps`.\n- **`user_paths`**: omit it; the query is generated from `settings.anchors`.\n- **`retention`**: omit it; data is fetched automatically.\n- **All other types**: required; must be a valid SQL string."
          },
          "chart_type": {
            "type": "string",
            "enum": [
              "table",
              "number",
              "funnel",
              "bar",
              "line",
              "area",
              "pie",
              "stacked",
              "user_paths",
              "retention"
            ],
            "description": "Visualization type. Determines which other fields are required:\n\n| `chart_type` | Extra required fields |\n|---|---|\n| `table` | `query` |\n| `number` | `query` (must return 1 row × 1 column) |\n| `bar` | `query`, `x_axis`, `y_axis` (≥ 1) |\n| `line` | `query`, `x_axis`, `y_axis` (≥ 1) |\n| `area` | `query`, `x_axis`, `y_axis` (≥ 1; exactly 1 if `group_by` set) |\n| `pie` | `query`, `y_axis` (exactly 1) |\n| `stacked` | `query`, `x_axis`, `y_axis` (exactly 1), `group_by` |\n| `funnel` | `steps` (≥ 2), `query` placeholder `\"SELECT 1\"` |\n| `user_paths` | `settings.anchors` (≥ 1); query is generated |\n| `retention` | `settings.entryFilter` (key required; `null` means any event) |"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Display name shown on the chart and board."
          },
          "description": {
            "type": "string",
            "description": "Optional description."
          },
          "x_axis": {
            "type": "string",
            "description": "Column name for the X axis. Required for `bar`, `line`, and `stacked`."
          },
          "y_axis": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Column name(s) used as Y axis metrics.\n\n- `bar` / `line`: at least 1 element required.\n- `pie` / `stacked`: exactly 1 element required."
          },
          "group_by": {
            "type": "string",
            "description": "Column to group / stack series by. Required for `stacked`."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FunnelStep"
            },
            "minItems": 2,
            "description": "Ordered list of funnel steps. Required for `funnel` (minimum 2 steps).\n\nEach element is a `FunnelStep`; add property predicates in its canonical `filters` array (e.g. `\"filters\": [{ \"field\": \"rdns\", \"op\": \"eq\", \"value\": \"io.metamask\" }]`)."
          },
          "settings": {
            "$ref": "#/components/schemas/ChartSettings"
          }
        },
        "required": ["chartId", "projectId", "chart_type", "title"]
      },
      "Chart": {
        "type": "object",
        "description": "A saved chart attached to a board.",
        "properties": {
          "id": {
            "type": "string"
          },
          "chart_type": {
            "type": "string",
            "enum": [
              "table",
              "number",
              "funnel",
              "bar",
              "line",
              "area",
              "pie",
              "stacked",
              "user_paths",
              "retention"
            ],
            "description": "Visualization type."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "query": {
            "type": "string",
            "description": "SQL query powering the chart. For `funnel` and `retention` charts this is a system-managed placeholder."
          },
          "project_id": {
            "type": "string"
          },
          "board_id": {
            "type": "string"
          },
          "x_axis": {
            "type": "string",
            "nullable": true,
            "description": "Column used as the X axis."
          },
          "y_axis": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Column(s) used as Y axis metric(s)."
          },
          "group_by": {
            "type": "string",
            "nullable": true,
            "description": "Column used to group/stack series."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FunnelStep"
            },
            "nullable": true,
            "description": "Ordered list of funnel steps. Only present when `chart_type` is `funnel`."
          },
          "settings": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ChartSettings"
              },
              {
                "type": "null"
              }
            ],
            "description": "Type-specific configuration. See `ChartSettings` for all fields."
          }
        },
        "required": [
          "id",
          "chart_type",
          "title",
          "query",
          "project_id",
          "board_id"
        ]
      },
      "Contract": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "chain": {
            "type": "integer"
          },
          "address": {
            "type": "string"
          },
          "start_block": {
            "type": "integer"
          },
          "abi": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "anonymous": {
                  "type": "boolean"
                },
                "inputs": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              },
              "required": ["anonymous", "inputs", "name", "type"]
            }
          },
          "include_in_pipeline": {
            "type": "boolean",
            "description": "Whether the contract is configured for the project contract-events pipeline. This is the desired membership, not live deployment state: use the `deploy` sidecar on the contract list to see what is currently deployed."
          }
        },
        "required": [
          "name",
          "chain",
          "address",
          "abi",
          "events",
          "include_in_pipeline"
        ]
      },
      "Segment": {
        "type": "object",
        "description": "A saved segment. `filters` is an array of canonical filter objects combined with implicit AND.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SegmentFilterCondition"
            }
          }
        },
        "required": ["id", "title", "filters"]
      },
      "Profile": {
        "type": "object",
        "description": "Comprehensive wallet profile with onchain and offchain data",
        "properties": {
          "address": {
            "type": "string"
          },
          "net_worth_usd": {
            "type": "number"
          },
          "tx_count": {
            "type": "integer"
          },
          "first_onchain": {
            "type": "string",
            "description": "First onchain activity date"
          },
          "last_onchain": {
            "type": "string",
            "description": "Last onchain activity date"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "ens": {
            "type": "string",
            "nullable": true
          },
          "farcaster": {
            "type": "string",
            "nullable": true
          },
          "lens": {
            "type": "string",
            "nullable": true
          },
          "basenames": {
            "type": "string",
            "nullable": true
          },
          "linea": {
            "type": "string",
            "nullable": true
          },
          "avatar": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "discord": {
            "type": "string",
            "nullable": true
          },
          "telegram": {
            "type": "string",
            "nullable": true
          },
          "twitter": {
            "type": "string",
            "nullable": true
          },
          "github": {
            "type": "string",
            "nullable": true
          },
          "linkedin": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "instagram": {
            "type": "string",
            "nullable": true
          },
          "facebook": {
            "type": "string",
            "nullable": true
          },
          "website": {
            "type": "string",
            "nullable": true
          },
          "reddit": {
            "type": "string",
            "nullable": true
          },
          "youtube": {
            "type": "string",
            "nullable": true
          },
          "tiktok": {
            "type": "string",
            "nullable": true
          },
          "first_seen": {
            "type": "string",
            "nullable": true,
            "description": "First seen in project"
          },
          "last_seen": {
            "type": "string",
            "nullable": true,
            "description": "Last seen in project"
          },
          "lifecycle": {
            "type": "string",
            "nullable": true,
            "enum": [
              "New",
              "Returning",
              "Power user",
              "At Risk",
              "Churned",
              "Resurrected"
            ]
          },
          "num_sessions": {
            "type": "integer",
            "nullable": true
          },
          "revenue": {
            "type": "number",
            "nullable": true
          },
          "volume": {
            "type": "number",
            "nullable": true
          },
          "points": {
            "type": "number",
            "nullable": true
          },
          "device": {
            "type": "string",
            "nullable": true
          },
          "browser": {
            "type": "string",
            "nullable": true
          },
          "os": {
            "type": "string",
            "nullable": true
          },
          "location": {
            "type": "string",
            "nullable": true
          },
          "first_utm_source": {
            "type": "string",
            "nullable": true
          },
          "first_utm_medium": {
            "type": "string",
            "nullable": true
          },
          "first_utm_campaign": {
            "type": "string",
            "nullable": true
          },
          "first_referrer": {
            "type": "string",
            "nullable": true
          },
          "last_utm_source": {
            "type": "string",
            "nullable": true
          },
          "last_utm_medium": {
            "type": "string",
            "nullable": true
          },
          "last_utm_campaign": {
            "type": "string",
            "nullable": true
          },
          "last_referrer": {
            "type": "string",
            "nullable": true
          },
          "first_referrer_url": {
            "type": "string",
            "nullable": true,
            "description": "First referrer full URL"
          },
          "last_referrer_url": {
            "type": "string",
            "nullable": true,
            "description": "Last referrer full URL"
          },
          "first_ref": {
            "type": "string",
            "nullable": true,
            "description": "First referral code"
          },
          "last_ref": {
            "type": "string",
            "nullable": true,
            "description": "Last referral code"
          },
          "first_utm_content": {
            "type": "string",
            "nullable": true,
            "description": "First UTM content"
          },
          "last_utm_content": {
            "type": "string",
            "nullable": true,
            "description": "Last UTM content"
          },
          "first_utm_term": {
            "type": "string",
            "nullable": true,
            "description": "First UTM term"
          },
          "last_utm_term": {
            "type": "string",
            "nullable": true,
            "description": "Last UTM term"
          },
          "first_paid_source": {
            "type": "string",
            "nullable": true,
            "description": "First-touch acquiring ad network (google, meta, microsoft, tiktok, twitter, linkedin, reddit, or paid_utm for pricing-medium UTMs with no click ID)"
          },
          "last_paid_source": {
            "type": "string",
            "nullable": true,
            "description": "Last-touch acquiring ad network (same value set as first_paid_source)"
          },
          "first_click_id": {
            "type": "string",
            "nullable": true,
            "description": "First-touch raw ad-platform click ID (gclid, fbclid, msclkid, ttclid, twclid, li_fat_id, or rdt_cid)"
          },
          "last_click_id": {
            "type": "string",
            "nullable": true,
            "description": "Last-touch raw ad-platform click ID"
          },
          "last_type": {
            "type": "string",
            "nullable": true,
            "description": "Last event type"
          },
          "last_event": {
            "type": "string",
            "nullable": true,
            "description": "Last event name"
          },
          "last_properties": {
            "type": "string",
            "nullable": true,
            "description": "Last event properties (JSON string)"
          },
          "activity_dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            },
            "nullable": true,
            "description": "Array of activity dates (YYYY-MM-DD format)"
          },
          "chains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WalletChain"
            },
            "description": "Requires expand=chains"
          },
          "apps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WalletApp"
            },
            "description": "Requires expand=apps"
          },
          "tokens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WalletToken"
            },
            "description": "Requires expand=tokens"
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WalletLabel"
            },
            "description": "Requires expand=labels"
          }
        }
      },
      "EventContext": {
        "type": "object",
        "description": "Contextual information about the event environment",
        "properties": {
          "user_agent": {
            "type": "string"
          },
          "locale": {
            "type": "string",
            "description": "e.g. en-US"
          },
          "timezone": {
            "type": "string",
            "description": "e.g. America/New_York"
          },
          "page_url": {
            "type": "string"
          },
          "page_path": {
            "type": "string"
          },
          "page_title": {
            "type": "string"
          },
          "page_query": {
            "type": "string"
          },
          "page_hash": {
            "type": "string"
          },
          "referrer_url": {
            "type": "string"
          },
          "referrer": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          },
          "utm_source": {
            "type": "string"
          },
          "utm_medium": {
            "type": "string"
          },
          "utm_campaign": {
            "type": "string"
          },
          "utm_term": {
            "type": "string"
          },
          "utm_content": {
            "type": "string"
          },
          "browser": {
            "type": "string"
          },
          "device": {
            "type": "string",
            "enum": ["desktop", "mobile", "tablet"]
          },
          "os": {
            "type": "string"
          },
          "screen_width": {
            "type": "integer"
          },
          "screen_height": {
            "type": "integer"
          },
          "screen_density": {
            "type": "number",
            "description": "Pixel density of the device screen (devicePixelRatio)"
          },
          "viewport_width": {
            "type": "integer",
            "description": "Width of the browser viewport in pixels"
          },
          "viewport_height": {
            "type": "integer",
            "description": "Height of the browser viewport in pixels"
          },
          "location": {
            "type": "string",
            "description": "Geographic location country code (e.g., US, NG)"
          },
          "library_name": {
            "type": "string"
          },
          "library_version": {
            "type": "string"
          }
        }
      },
      "EventProperties": {
        "type": "object",
        "description": "Event-specific properties. Can contain any key-value pairs relevant to the event.",
        "additionalProperties": true
      },
      "Event": {
        "type": "object",
        "description": "A single analytics event",
        "required": ["type", "anonymous_id", "version", "channel"],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "page",
              "connect",
              "disconnect",
              "chain",
              "signature",
              "transaction",
              "track",
              "decoded_log",
              "detect",
              "identify"
            ]
          },
          "channel": {
            "type": "string",
            "enum": ["web", "mobile", "server", "api", "import"],
            "description": "Source of the event. The Formo Web SDK uses `web`; mobile SDK uses `mobile`; server SDK uses `server`. Use `api` for direct HTTP submissions and `import` for backfills."
          },
          "version": {
            "type": "string",
            "description": "SDK schema version. Web SDK 1.x emits `1`; legacy clients emit `0`.",
            "example": "1"
          },
          "anonymous_id": {
            "type": "string",
            "description": "Anonymous visitor identifier"
          },
          "user_id": {
            "type": "string",
            "nullable": true,
            "description": "Identified user ID"
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Wallet address"
          },
          "event": {
            "type": "string",
            "nullable": true,
            "description": "Event name (for track events)"
          },
          "context": {
            "$ref": "#/components/schemas/EventContext"
          },
          "properties": {
            "$ref": "#/components/schemas/EventProperties"
          },
          "original_timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "message_id": {
            "type": "string",
            "description": "Unique ID for deduplication. Optional; a UUID is generated server-side when omitted."
          }
        }
      },
      "WalletChain": {
        "type": "object",
        "properties": {
          "chain_id": {
            "type": "string"
          },
          "net_worth_usd": {
            "type": "number"
          },
          "tx_count": {
            "type": "integer"
          },
          "first_onchain": {
            "type": "string"
          },
          "last_onchain": {
            "type": "string"
          }
        }
      },
      "WalletApp": {
        "type": "object",
        "properties": {
          "chain_id": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "img": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "balance_usd": {
            "type": "number"
          }
        }
      },
      "WalletToken": {
        "type": "object",
        "properties": {
          "chain_id": {
            "type": "string"
          },
          "token_address": {
            "type": "string"
          },
          "app_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "symbol": {
            "type": "string"
          },
          "img": {
            "type": "string",
            "nullable": true
          },
          "decimals": {
            "type": "integer"
          },
          "price": {
            "type": "number"
          },
          "balance": {
            "type": "string"
          },
          "balance_usd": {
            "type": "number"
          }
        }
      },
      "WalletLabel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. coinbase.verified_account"
          },
          "value": {
            "type": "string"
          },
          "chain_id": {
            "type": "string"
          },
          "source": {
            "type": "string"
          }
        }
      },
      "UserLabelInput": {
        "type": "object",
        "required": ["tag_id"],
        "properties": {
          "tag_id": {
            "type": "string",
            "description": "Label identifier (lowercased on write). e.g. vip, airdrop_eligible, coinbase.verified_account"
          },
          "value": {
            "type": "string",
            "description": "Optional label value (e.g. tier name, country code)"
          },
          "chain_id": {
            "type": "string",
            "description": "Optional chain identifier the label applies to"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Optional ISO-8601 event-time for the label. When provided, the label is recorded at this time instead of the server's current time; used to backfill historical values (e.g. an `open_interest` reading from a past week) so label-based retention can evaluate them at the right point in time. Must not be in the future. Defaults to server time when omitted."
          },
          "_is_deleted": {
            "type": "integer",
            "enum": [0, 1],
            "description": "Optional tombstone flag for backfilled removals. `1` records the row as a soft-delete (label removed) instead of a live value; pair it with a past `timestamp` to express \"label removed at past time T\" so point-in-time retention drops the wallet from that week. The future-timestamp guard still applies. Defaults to `0` (a live label) when omitted."
          }
        }
      },
      "UserLabel": {
        "type": "object",
        "description": "Canonical user-label resource. Echoed back from upsert calls so callers can cache the normalised entry without a follow-up read.",
        "required": [
          "address",
          "tag_id",
          "value",
          "chain_id",
          "source",
          "timestamp"
        ],
        "properties": {
          "address": {
            "type": "string",
            "description": "Wallet address the label applies to"
          },
          "tag_id": {
            "type": "string",
            "description": "Label identifier (lowercased + trimmed on write)"
          },
          "value": {
            "type": "string",
            "description": "Label value, empty string when omitted"
          },
          "chain_id": {
            "type": "string",
            "description": "Chain identifier, empty string when omitted"
          },
          "source": {
            "type": "string",
            "description": "Origin of the label (e.g. 'import')"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Event-time of the label: the caller-supplied `timestamp` when provided (retrospective backfill), otherwise the server's write time."
          }
        }
      },
      "UpdateUserPropertiesResponse": {
        "type": "object",
        "description": "Echo of the wallet identity after the merge-update applied. Synthesised from the request payload; analytics ingestion is eventually consistent, so the response reflects the applied write rather than a follow-up read.",
        "required": ["address", "updated_at"],
        "properties": {
          "address": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "user_id": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "farcaster": {
            "type": "string",
            "nullable": true
          },
          "discord": {
            "type": "string",
            "nullable": true
          },
          "twitter": {
            "type": "string",
            "nullable": true
          },
          "telegram": {
            "type": "string",
            "nullable": true
          },
          "instagram": {
            "type": "string",
            "nullable": true
          },
          "website": {
            "type": "string",
            "nullable": true
          },
          "github": {
            "type": "string",
            "nullable": true
          },
          "linkedin": {
            "type": "string",
            "nullable": true
          },
          "facebook": {
            "type": "string",
            "nullable": true
          },
          "tiktok": {
            "type": "string",
            "nullable": true
          },
          "youtube": {
            "type": "string",
            "nullable": true
          },
          "reddit": {
            "type": "string",
            "nullable": true
          },
          "avatar": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "location": {
            "type": "string",
            "nullable": true
          },
          "ens": {
            "type": "string",
            "nullable": true
          },
          "lens": {
            "type": "string",
            "nullable": true
          },
          "basenames": {
            "type": "string",
            "nullable": true
          },
          "linea": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "BatchWriteResponse": {
        "type": "object",
        "description": "Acknowledgement for a batch write. `successful_rows` counts rows accepted and forwarded to ingest after a 2xx (ingestion is async/eventually-consistent; there is no follow-up read). `quarantined_rows` counts rows skipped for an invalid address or no valid keys; `errors` (omitted when nothing was quarantined) maps each skipped row back to its request index.",
        "required": ["successful_rows", "quarantined_rows"],
        "properties": {
          "successful_rows": {
            "type": "integer",
            "description": "Number of rows accepted and forwarded to ingest."
          },
          "quarantined_rows": {
            "type": "integer",
            "description": "Number of rows skipped (invalid address, or no valid keys)."
          },
          "errors": {
            "type": "array",
            "description": "One entry per quarantined row. Omitted when quarantined_rows is 0.",
            "items": {
              "type": "object",
              "required": ["index", "reason"],
              "properties": {
                "index": {
                  "type": "integer",
                  "description": "Zero-based index of the row in the request array."
                },
                "address": {
                  "type": "string",
                  "description": "The address as submitted, echoed back."
                },
                "reason": {
                  "type": "string",
                  "description": "Why the row was quarantined."
                }
              }
            }
          }
        }
      },
      "ProfileFilter": {
        "type": "object",
        "description": "A profile-search filter group. `filters` contains canonical `{field, op, value}` leaves and `logic` combines them at this level.",
        "properties": {
          "logic": {
            "type": "string",
            "enum": ["and", "or"],
            "default": "and"
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilterCondition"
            }
          }
        },
        "required": ["filters"]
      },
      "FilterCondition": {
        "type": "object",
        "additionalProperties": false,
        "required": ["field", "op"],
        "properties": {
          "field": {
            "type": "string",
            "description": "Stable profile filter field. User/profile/social fields use user.* or users.*. Resource metrics use chains.balance, apps.balance, tokens.balance, or labels.value; resource identifiers belong in named qualifier properties and must not be embedded in the field path."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "gte",
              "lt",
              "lte",
              "in",
              "nin",
              "contains",
              "startsWith",
              "endsWith",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Comparison operator. Only the canonical terse tokens (`eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `endsWith`, `notEmpty`, `isEmpty`) are accepted; the retired long-form spellings (`equals`, `notEquals`, `greater`, `greaterOrEqual`, `less`, `lessOrEqual`, `notIn`, `includes`) are rejected with a 400 naming the token. **Substring operators:** `contains`, `startsWith` and `endsWith` are supported on routable string user attributes (`users.device`, `users.os`, `users.referrer`, `users.utm_*`, `users.click_id`, and the `first_*`/`last_*` attribution variants), where they match **case-sensitively**. Social fields (e.g. `users.twitter`, `users.email`) additionally support `contains`, which matches **case-insensitively** there; `startsWith`/`endsWith` are rejected on social fields. `contains` is also supported on `labels.value` and matches case-insensitively; `startsWith` and `endsWith` remain unsupported on labels. All three substring operators are rejected on numeric, chain/app/token, and lifecycle fields. `notEmpty` (\"is not empty\" / \"is set\") is a string existence check for user string attributes (device/os/utm_*/referrer/…) and social fields; `isEmpty` (\"is empty\" / \"is not set\") is its complement, supported for user string attributes only (not social fields). Both ignore `value`. `users.lifecycle` supports only `eq` (a single stage) and `in` (a list of stages)."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              }
            ],
            "description": "Filter value (string, number, boolean, or non-empty array). Required for every operator except the existence checks `notEmpty` and `isEmpty`, which ignore it. Resource balance fields (`chains.balance`, `apps.balance`, and `tokens.balance`) require a JSON number; empty strings and numeric strings are rejected. `labels.value` also rejects an empty string operand."
          },
          "scope": {
            "type": "string",
            "enum": ["any", "protocol"],
            "description": "Required for tokens.balance: any includes wallet and protocol balances; protocol requires app_id."
          },
          "chain_id": {
            "type": "string",
            "minLength": 1,
            "description": "Optional chain qualifier. Omit to compare across all chains."
          },
          "app_id": {
            "type": "string",
            "minLength": 1,
            "description": "Required for apps.balance and for tokens.balance with scope=protocol."
          },
          "token_address": {
            "type": "string",
            "minLength": 1,
            "description": "Required token address for tokens.balance."
          },
          "tag_id": {
            "type": "string",
            "minLength": 1,
            "description": "Required label tag for labels.value."
          }
        }
      },
      "RestApiError": {
        "$ref": "#/components/schemas/Error",
        "description": "Deprecated alias for `Error`. Existing endpoint specs reference this name; new specs should reference `Error` directly."
      },
      "AnalyticsResponse": {
        "type": "object",
        "description": "Analytics endpoint response. The `data` array contains the rows; the exact row shape depends on the endpoint. `meta` carries column type information for rendering, `rows` is the row count, and `statistics` holds query timing metadata.",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "meta": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              }
            }
          },
          "rows": {
            "type": "integer"
          },
          "rows_before_limit_at_least": {
            "type": "integer"
          },
          "statistics": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "AlertFilter": {
        "type": "object",
        "description": "A project-alert trigger filter using the canonical envelope.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Column or property targeted by this filter."
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "lt",
              "gte",
              "lte",
              "in",
              "nin",
              "startsWith",
              "endsWith",
              "contains",
              "notEmpty",
              "isEmpty"
            ],
            "description": "Canonical comparison operator token."
          },
          "value": {
            "type": "string"
          },
          "numericThreshold": {
            "type": "string"
          }
        },
        "required": ["field", "op", "value"]
      },
      "SourceFilterCondition": {
        "type": "object",
        "description": "Filters the raw event source. `field` must be `source` on user/lifecycle endpoints; Overview endpoint `filters` use the raw `channel` field for the same Web/Mobile/API/Import/Server/Onchain values.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Column or property targeted by this filter.",
            "enum": ["source"]
          },
          "op": {
            "type": "string",
            "enum": ["eq", "neq", "in", "nin"],
            "description": "Canonical comparison operator token."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. Raw event source values are `web`, `mobile`, `api`, `import`, `server`, and `onchain`. For `in` / `nin`, pass a non-empty array or a pipe-delimited string."
          }
        },
        "required": ["field", "op"]
      },
      "ChannelFilterCondition": {
        "type": "object",
        "description": "Filters the classified acquisition channel. `field` must be `channel_type`.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Column or property targeted by this filter.",
            "enum": ["channel_type"]
          },
          "op": {
            "type": "string",
            "enum": ["eq", "neq", "in", "nin"],
            "description": "Canonical comparison operator token."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[^|]*$"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Value to compare against. For `in` / `nin`, pass a non-empty array or a pipe-delimited string."
          }
        },
        "required": ["field", "op"]
      },
      "ChartSummary": {
        "type": "object",
        "required": [
          "id",
          "project_id",
          "board_id",
          "chart_type",
          "title",
          "position",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "board_id": {
            "type": "string"
          },
          "chart_type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": ["string", "null"]
          },
          "position": {
            "type": "integer",
            "description": "Display position of the chart on its board."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Optional unique value (e.g. a UUID v4) that lets you safely retry POST/PUT/PATCH/DELETE requests. The first request runs normally; subsequent requests with the same key replay the stored response (status + body) for 24 hours, so retries can never double-create or double-charge. Two concurrent requests with the same key return `409 IDEMPOTENCY_IN_PROGRESS`. Generate a fresh key per logical operation."
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        },
        "description": "1-indexed page number. Defaults to 1."
      },
      "Size": {
        "name": "size",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 100
        },
        "description": "Page size. Defaults to 100, capped at 200."
      },
      "AnalyticsDateFrom": {
        "name": "date_from",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Inclusive start date (YYYY-MM-DD). Defaults to 7 days before date_to."
      },
      "AnalyticsDateTo": {
        "name": "date_to",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "Inclusive end date (YYYY-MM-DD). Defaults to today."
      },
      "AnalyticsFilters": {
        "name": "filters",
        "in": "query",
        "description": "Array of filter conditions, JSON-encoded in the query string. Entries use `{ field, op, value }` and are combined with implicit AND. For example, filter traffic referred by Google with `[{\"field\":\"referrer\",\"op\":\"contains\",\"value\":\"google\"}]`, or filter a page with `[{\"field\":\"page\",\"op\":\"eq\",\"value\":\"/pricing\"}]`. For `in` / `nin`, pass a non-empty array value such as `[\"chrome\",\"firefox\"]` or a pipe-delimited string such as `\"chrome|firefox\"`. Array string members cannot contain a literal `|`, which is reserved as the Tinybird membership separator. Optional one-level nested `filters` use the same canonical envelope and membership rule. On the user-aggregate endpoints (lifecycle, frequency) the same array also carries profile-family entries: profile metrics (net_worth_usd, volume, revenue, points), social identity fields, a lifecycle entry ({\"field\":\"lifecycle\",\"op\":\"in\",\"value\":\"New|Power user\"}), and resource entries using the stable fields chains.balance, apps.balance, tokens.balance, and labels.value with named qualifier properties (chain_id, app_id, token_address, scope, tag_id). The retired per-family parameters (socials, chain_filters, app_filters, token_filters, label_filters, profile_filters, lifecycle_filter) are rejected. Overview also accepts raw Data source filters with `field` set to `channel`, ops `eq`/`neq`/`in`/`nin`, and values `web`, `mobile`, `api`, `import`, `server`, `onchain`; this is distinct from acquisition Channel (`channel_type`) and the two can be combined.",
        "schema": {
          "type": "string"
        },
        "example": "[{\"field\":\"referrer\",\"op\":\"contains\",\"value\":\"google\"}]"
      },
      "AnalyticsPageScope": {
        "name": "page_scope",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": ["page", "session"],
          "default": "page"
        },
        "description": "Controls how a `page` filter is interpreted. `page` (default) scopes metrics to activity on the filtered page. On `/v0/kpis`, `pageviews` counts only views of the filtered page, while `bounce_rate` and `avg_session_sec` are entry-page metrics for sessions that landed there. `session` preserves the legacy session scope: metrics include all relevant activity in any session that viewed the page. `sessions` and `visitors` are unchanged between scopes. This parameter only affects requests that include a `page` filter. It is distinct from `/v0/top_pages` `mode`, which selects the all, entry, or exit page-flow view.",
        "example": "page"
      },
      "AnalyticsLimit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 50,
          "minimum": 1,
          "maximum": 1000
        },
        "description": "Maximum results to return (default 50, max 1000)"
      },
      "AnalyticsOffset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 0,
          "minimum": 0,
          "maximum": 100000
        },
        "description": "Number of results to skip for pagination (default 0)"
      },
      "AnalyticsIncludePreviousPeriod": {
        "name": "include_previous_period",
        "in": "query",
        "schema": {
          "type": "boolean"
        },
        "description": "When `true`, returns both current and previous period metrics for week-over-week comparison. The previous period is non-overlapping and equal in length to the current range."
      },
      "LifecycleNewWindowDays": {
        "name": "new_window_days",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: a wallet is New if first seen within this many days of the reference date (default 30). Caller value wins over the project's saved setting."
      },
      "LifecycleChurnWindowDays": {
        "name": "churn_window_days",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: a wallet is Churned once last seen this many days before the reference date (default 30)."
      },
      "LifecyclePowerUserMinActiveDays": {
        "name": "power_user_min_active_days",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: distinct active days within the power-user window required to qualify as Power user (default 5)."
      },
      "LifecyclePowerUserWindowDays": {
        "name": "power_user_window_days",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: trailing window in which active days are counted for Power user qualification (default 30)."
      },
      "LifecycleResurrectedGapDays": {
        "name": "resurrected_gap_days",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: minimum inactivity gap that marks a re-engaging wallet as Resurrected (default 30)."
      },
      "LifecycleAtRiskMinDaysInactive": {
        "name": "at_risk_min_days_inactive",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: minimum days since last activity for an established, still-active wallet to be At Risk (default 14; must be < churn_window_days)."
      },
      "LifecycleAtRiskPriorActiveDaysThreshold": {
        "name": "at_risk_prior_active_days_threshold",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "description": "Override: minimum active days a wallet had in the window before the recent quiet stretch to qualify as At Risk (default 1)."
      },
      "AnalyticsBehaviorFilters": {
        "name": "behavior_filters",
        "in": "query",
        "description": "JSON array filtering users by event-based behavior with counts and either an absolute date range or a window relative to the user's first event. Each entry is `{ event, op, times, date_from?, date_to?, event_type?, relative_to?, window? }`. When `relative_to: \"first_seen\"` is set, the matching events must occur within `window.value` `window.unit` (`hour` | `day`) after the user's first event; `date_from`/`date_to` are ignored. `event_type` narrows matching to a specific event-type column (`page`, `connect`, `disconnect`, `chain`, `signature`, `transaction`, `track`, `decoded_log`, `detect`, `identify`); for `track` and `decoded_log` the `event` field is the named identifier; for other types only the `type` column is matched.",
        "schema": {
          "type": "string"
        },
        "example": "[{\"event\":\"swap\",\"event_type\":\"track\",\"op\":\"gte\",\"times\":1,\"relative_to\":\"first_seen\",\"window\":{\"value\":24,\"unit\":\"hour\"}}]"
      },
      "AnalyticsSourceFilter": {
        "name": "source_filter",
        "in": "query",
        "description": "JSON array filtering users by raw event source. Entries use the canonical envelope and `field` must be `source`; accepted values are `web`, `mobile`, `api`, `import`, `server`, and `onchain`. Distinct from `channel_filter`, which matches the classified acquisition channel. Overview endpoints carry the same raw event source through the general `filters` parameter as `field: \"channel\"`.",
        "schema": {
          "type": "string"
        },
        "example": "[{\"field\":\"source\",\"op\":\"in\",\"value\":\"web|mobile\"}]"
      },
      "AnalyticsChannelFilter": {
        "name": "channel_filter",
        "in": "query",
        "description": "JSON array filtering users by classified acquisition channel. Entries use the canonical envelope and `field` must be `channel_type`. Distinct from `source_filter`, which matches the raw event source.",
        "schema": {
          "type": "string"
        },
        "example": "[{\"field\":\"channel_type\",\"op\":\"in\",\"value\":\"Organic Search|Paid Social\"}]"
      },
      "AnalyticsExclude": {
        "name": "exclude",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated list of event types to exclude from results (e.g. `identify,detect`).",
        "example": "identify,detect"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was rejected. `code` is either `INVALID_VALIDATION_REQUEST` (Zod schema mismatch; `details` carries a `{ fieldPath: message }` map) or `BAD_REQUEST` (semantic validation failure outside Zod, e.g. mismatched IDs, business-rule violations). Branch on `code`, not status, to tell the two apart.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "validation": {
                "summary": "Zod schema mismatch",
                "value": {
                  "error": {
                    "code": "INVALID_VALIDATION_REQUEST",
                    "message": "Invalid request data",
                    "doc_url": "https://docs.formo.so/api/errors#invalid_validation_request",
                    "details": {
                      "body.name": "String must contain at least 1 character(s)"
                    }
                  }
                }
              },
              "semantic": {
                "summary": "Semantic validation failure",
                "value": {
                  "error": {
                    "code": "BAD_REQUEST",
                    "message": "Target board must be different from the current board",
                    "doc_url": "https://docs.formo.so/api/errors#bad_request"
                  }
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "UNAUTHORIZED",
                "message": "Invalid API key",
                "doc_url": "https://docs.formo.so/api/errors#unauthorized"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key is valid but lacks the required scope for this endpoint.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "FORBIDDEN",
                "message": "API key missing required scope: alerts:write",
                "doc_url": "https://docs.formo.so/api/errors#forbidden"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The requested resource does not exist or is not visible to this API key's workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "NOT_FOUND",
                "message": "Alert not found",
                "doc_url": "https://docs.formo.so/api/errors#not_found"
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with current resource state, or an `Idempotency-Key` request with the same key is currently in flight.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "IDEMPOTENCY_IN_PROGRESS",
                "message": "A request with this Idempotency-Key is already in progress. Retry shortly.",
                "doc_url": "https://docs.formo.so/api/errors#idempotency_in_progress"
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Per-workspace rate limit exceeded. Inspect the `RateLimit-*` response headers and back off.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "TOO_MANY_REQUESTS",
                "message": "Too many requests",
                "doc_url": "https://docs.formo.so/api/errors#too_many_requests"
              }
            }
          }
        }
      },
      "InternalServerError": {
        "description": "An unexpected error occurred on the server. The error has been logged; retry with exponential backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INTERNAL_SERVER_ERROR",
                "message": "Internal Server Error",
                "doc_url": "https://docs.formo.so/api/errors#internal_server_error"
              }
            }
          }
        }
      }
    }
  },
  "x-api-scopes": {
    "description": "API key scopes control access to endpoints. Create keys with the required scopes in Team Settings > API.",
    "scopes": {
      "alerts:read": "List and get alerts",
      "alerts:write": "Create, update, delete alerts (requires alerts:read)",
      "boards:read": "List and get boards and charts",
      "boards:write": "Create, update, delete boards and charts (requires boards:read)",
      "contracts:read": "List contracts",
      "contracts:write": "Create, update, delete contracts (requires contracts:read)",
      "segments:read": "List segments",
      "segments:write": "Create and delete segments (requires segments:read)",
      "profiles:read": "Search and get wallet profiles",
      "profiles:write": "Update wallet properties, manage labels, and import addresses (requires profiles:read)",
      "query:read": "Execute SQL analytics queries and read pre-built analytics endpoints (KPIs, top pages, lifecycle, retention, revenue, etc.)"
    }
  },
  "paths": {
    "/v0/alerts": {
      "get": {
        "operationId": "listAlerts",
        "summary": "List alerts",
        "description": "List alerts for the project scoped to the API key. Paginated: see `page` and `size` query params; the response carries `total` and `has_more` so callers can walk pages.",
        "tags": ["Alerts"],
        "x-required-scope": "alerts:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Size"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of alerts",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedListMeta"
                    },
                    {
                      "type": "object",
                      "required": ["data"],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Alert"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "alrt_4f8e2c1a9b3d4e5f",
                      "project_id": "proj_abc123",
                      "name": "Daily revenue drop",
                      "trigger_type": "event",
                      "status": "active",
                      "trigger_filters": [
                        {
                          "value": "transaction",
                          "field": "event",
                          "op": "eq"
                        },
                        {
                          "value": "1000",
                          "numericThreshold": "sum",
                          "field": "revenue",
                          "op": "lt"
                        }
                      ],
                      "recipient": [
                        {
                          "type": "email",
                          "value": ["alerts@myapp.com"]
                        },
                        {
                          "type": "slack",
                          "value": ["C0123456789"]
                        }
                      ],
                      "has_secret": false,
                      "created_at": "2026-04-12T09:32:18.000Z",
                      "updated_at": "2026-04-25T14:01:55.000Z"
                    }
                  ],
                  "page": 1,
                  "size": 100,
                  "total": 1,
                  "has_more": false
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAlert",
        "summary": "Create alert",
        "description": "Create a new alert for the project.",
        "tags": ["Alerts"],
        "x-required-scope": "alerts:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "trigger_type": {
                    "type": "string",
                    "enum": ["event", "user"]
                  },
                  "trigger_filters": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AlertFilter"
                    }
                  },
                  "recipient": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": ["email", "slack", "webhook"]
                        },
                        "value": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "secret": {
                    "type": "string",
                    "description": "Webhook signing secret"
                  }
                },
                "required": ["name", "trigger_type", "trigger_filters"]
              },
              "examples": {
                "eventAlertWithWebhook": {
                  "summary": "Alert on swap events from mobile devices with webhook",
                  "value": {
                    "name": "Mobile swap alert",
                    "trigger_type": "event",
                    "trigger_filters": [
                      {
                        "value": "swap",
                        "field": "type",
                        "op": "eq"
                      },
                      {
                        "value": "mobile",
                        "field": "device",
                        "op": "eq"
                      },
                      {
                        "value": "1000",
                        "numericThreshold": "1000",
                        "field": "volume",
                        "op": "gt"
                      }
                    ],
                    "recipient": [
                      {
                        "type": "webhook",
                        "value": ["https://hooks.myapp.com/formo-alerts"]
                      },
                      {
                        "type": "email",
                        "value": ["alerts@myapp.com"]
                      }
                    ],
                    "secret": "whsec_mysigningsecret123"
                  }
                },
                "eventAlertWithUtmFilter": {
                  "summary": "Alert on events from specific UTM campaigns",
                  "value": {
                    "name": "Paid campaign activity",
                    "trigger_type": "event",
                    "trigger_filters": [
                      {
                        "value": "purchase",
                        "field": "type"
                      },
                      {
                        "value": "google",
                        "field": "utm_source",
                        "op": "eq"
                      },
                      {
                        "value": "cpc",
                        "field": "utm_medium",
                        "op": "eq"
                      }
                    ],
                    "recipient": [
                      {
                        "type": "slack",
                        "value": [
                          "#marketing-alerts|https://hooks.slack.com/services/T00/B00/xxx"
                        ]
                      }
                    ]
                  }
                },
                "userAlertWithLocationFilter": {
                  "summary": "Alert when new users spike from a specific country",
                  "value": {
                    "name": "US user spike",
                    "trigger_type": "user",
                    "trigger_filters": [
                      {
                        "value": "US",
                        "field": "location",
                        "op": "eq"
                      },
                      {
                        "value": "10000",
                        "numericThreshold": "10000",
                        "field": "net_worth_usd",
                        "op": "gte"
                      }
                    ],
                    "recipient": [
                      {
                        "type": "email",
                        "value": ["team@myapp.com"]
                      }
                    ]
                  }
                },
                "eventAlertWithNumericThreshold": {
                  "summary": "Alert when users hold >5 tokens in a specific app",
                  "value": {
                    "name": "High token holder alert",
                    "trigger_type": "event",
                    "trigger_filters": [
                      {
                        "value": "uniswap",
                        "numericThreshold": "5",
                        "field": "apps",
                        "op": "gt"
                      },
                      {
                        "value": "1",
                        "numericThreshold": "3",
                        "field": "chains",
                        "op": "gte"
                      }
                    ],
                    "recipient": [
                      {
                        "type": "webhook",
                        "value": ["https://hooks.myapp.com/token-alerts"]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Alert created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v0/alerts/{alertId}": {
      "get": {
        "operationId": "getAlert",
        "summary": "Get alert",
        "tags": ["Alerts"],
        "parameters": [
          {
            "name": "alertId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Alert details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                },
                "example": {
                  "id": "alrt_4f8e2c1a9b3d4e5f",
                  "project_id": "proj_abc123",
                  "name": "Daily revenue drop",
                  "trigger_type": "event",
                  "status": "active",
                  "trigger_filters": [
                    {
                      "value": "transaction",
                      "field": "event",
                      "op": "eq"
                    },
                    {
                      "value": "1000",
                      "numericThreshold": "sum",
                      "field": "revenue",
                      "op": "lt"
                    }
                  ],
                  "recipient": [
                    {
                      "type": "email",
                      "value": ["alerts@myapp.com"]
                    },
                    {
                      "type": "slack",
                      "value": ["C0123456789"]
                    }
                  ],
                  "has_secret": false,
                  "created_at": "2026-04-12T09:32:18.000Z",
                  "updated_at": "2026-04-25T14:01:55.000Z"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "alerts:read"
      },
      "put": {
        "operationId": "updateAlert",
        "summary": "Update alert",
        "tags": ["Alerts"],
        "parameters": [
          {
            "name": "alertId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "trigger_type": {
                    "type": "string",
                    "enum": ["event", "user"]
                  },
                  "trigger_filters": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AlertFilter"
                    }
                  },
                  "recipient": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "secret": {
                    "type": "string"
                  }
                },
                "required": ["name", "trigger_type", "trigger_filters"]
              },
              "examples": {
                "updateRecipients": {
                  "summary": "Update alert recipients",
                  "value": {
                    "name": "Mobile swap alert (updated)",
                    "trigger_type": "event",
                    "trigger_filters": [
                      {
                        "value": "swap",
                        "field": "type"
                      },
                      {
                        "value": "mobile",
                        "field": "device"
                      }
                    ],
                    "recipient": [
                      {
                        "type": "webhook",
                        "value": ["https://hooks.myapp.com/v2/alerts"]
                      },
                      {
                        "type": "slack",
                        "value": [
                          "#alerts|https://hooks.slack.com/services/T00/B00/new"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Alert updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "alerts:write"
      },
      "delete": {
        "operationId": "deleteAlert",
        "summary": "Delete alert",
        "tags": ["Alerts"],
        "parameters": [
          {
            "name": "alertId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Alert deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "number"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "alerts:write"
      },
      "patch": {
        "operationId": "toggleAlertStatus",
        "summary": "Toggle alert status",
        "tags": ["Alerts"],
        "parameters": [
          {
            "name": "alertId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": ["active", "inactive"]
                  }
                },
                "required": ["status"]
              },
              "examples": {
                "activate": {
                  "summary": "Activate alert",
                  "value": {
                    "status": "active"
                  }
                },
                "deactivate": {
                  "summary": "Deactivate alert",
                  "value": {
                    "status": "inactive"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Alert status updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "alerts:write"
      }
    },
    "/v0/boards": {
      "get": {
        "operationId": "listBoards",
        "summary": "List boards",
        "description": "List boards for the project scoped to the API key. Paginated.",
        "tags": ["Boards"],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Size"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of boards",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedListMeta"
                    },
                    {
                      "type": "object",
                      "required": ["data"],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Board"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "brd_a1b2c3d4e5f6",
                      "project_id": "proj_abc123",
                      "title": "Revenue Dashboard",
                      "description": "Weekly revenue, conversion, and retention metrics.",
                      "enabled": false,
                      "created_at": "2026-03-04T11:22:08.000Z",
                      "updated_at": "2026-04-22T08:14:31.000Z"
                    },
                    {
                      "id": "brd_g7h8i9j0k1l2",
                      "project_id": "proj_abc123",
                      "title": "Marketing Funnel",
                      "description": "Acquisition channel performance.",
                      "enabled": true,
                      "created_at": "2026-02-18T16:09:44.000Z",
                      "updated_at": "2026-04-19T10:55:02.000Z"
                    }
                  ],
                  "page": 1,
                  "size": 100,
                  "total": 2,
                  "has_more": false
                }
              }
            }
          }
        },
        "x-required-scope": "boards:read"
      },
      "post": {
        "operationId": "createBoard",
        "summary": "Create board",
        "description": "Create a new board with the given title.",
        "tags": ["Boards"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title"],
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Human-readable board name. Required."
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional longer description."
                  },
                  "isPublic": {
                    "type": "boolean",
                    "description": "Whether the board is publicly viewable via its share URL. Defaults to false.",
                    "default": false
                  }
                }
              },
              "example": {
                "title": "Weekly KPIs",
                "description": "Headline metrics for the team standup.",
                "isPublic": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Board created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v0/boards/{boardId}": {
      "get": {
        "operationId": "getBoard",
        "summary": "Get board",
        "tags": ["Boards"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Board details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                },
                "example": {
                  "id": "brd_a1b2c3d4e5f6",
                  "project_id": "proj_abc123",
                  "title": "Revenue Dashboard",
                  "description": "Weekly revenue, conversion, and retention metrics.",
                  "enabled": false,
                  "created_at": "2026-03-04T11:22:08.000Z",
                  "updated_at": "2026-04-22T08:14:31.000Z"
                }
              }
            }
          }
        },
        "x-required-scope": "boards:read"
      },
      "patch": {
        "operationId": "updateBoard",
        "summary": "Update board",
        "tags": ["Boards"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "isPublic": {
                    "type": "boolean",
                    "description": "Whether the board is publicly accessible"
                  }
                }
              },
              "examples": {
                "updateTitle": {
                  "summary": "Update board title",
                  "value": {
                    "title": "Weekly Dashboard"
                  }
                },
                "makePublic": {
                  "summary": "Make board publicly accessible",
                  "value": {
                    "isPublic": true
                  }
                },
                "fullUpdate": {
                  "summary": "Update all fields",
                  "value": {
                    "title": "Revenue Dashboard",
                    "description": "Weekly revenue metrics and KPIs",
                    "isPublic": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Board updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write",
        "description": "Update board title, description, and/or public visibility."
      },
      "delete": {
        "operationId": "deleteBoard",
        "summary": "Delete board",
        "description": "Delete a board and all its charts.",
        "tags": ["Boards"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Board deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts": {
      "get": {
        "operationId": "listCharts",
        "summary": "List charts for a board",
        "description": "List charts in a board. By default returns lightweight chart summaries (no queries executed). Pass `include=results` to execute each chart’s stored query server-side and receive full charts with `results`, the parent `board`, and an optional `warnings` sidecar carrying per-chart query failures (failing charts are excluded from `data` so the page renders cleanly).",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": ["results", "meta"]
            },
            "description": "Response detail. Omitted (or `meta`): lightweight summaries only, fast, with no query execution. `results`: execute every chart’s stored query and include `results`, `board`, and `warnings`."
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Size"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated chart summaries (default), or full charts with results when `include=results`",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "Chart summaries (default)",
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/PaginatedListMeta"
                        },
                        {
                          "type": "object",
                          "required": ["data"],
                          "properties": {
                            "data": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/ChartSummary"
                              }
                            }
                          }
                        }
                      ]
                    },
                    {
                      "title": "Full charts with results (include=results)",
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/PaginatedListMeta"
                        },
                        {
                          "type": "object",
                          "required": ["data", "board"],
                          "properties": {
                            "data": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Chart"
                              }
                            },
                            "board": {
                              "$ref": "#/components/schemas/Board"
                            },
                            "warnings": {
                              "type": "object",
                              "description": "Present only if some charts failed to execute. Failures are excluded from `data`.",
                              "properties": {
                                "failedCharts": {
                                  "type": "integer"
                                },
                                "errors": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "chartId": {
                                        "type": "string"
                                      },
                                      "error": {
                                        "type": "string"
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "x-required-scope": "boards:read"
      },
      "post": {
        "operationId": "createChart",
        "summary": "Create chart",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChartRequest"
              },
              "examples": {
                "funnelBasic": {
                  "summary": "3-step closed funnel (7-day window)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT 1",
                    "chart_type": "funnel",
                    "title": "Onboarding Funnel",
                    "steps": [
                      {
                        "type": "event",
                        "event": "page"
                      },
                      {
                        "type": "event",
                        "event": "connect"
                      },
                      {
                        "type": "event",
                        "event": "transaction"
                      }
                    ],
                    "settings": {
                      "funnelType": "closed",
                      "conversionWindow": {
                        "value": 7,
                        "unit": "day"
                      }
                    }
                  }
                },
                "funnelWithPropertyFilters": {
                  "summary": "Funnel with per-step property filters (MetaMask on mainnet)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT 1",
                    "chart_type": "funnel",
                    "title": "MetaMask Conversion Funnel",
                    "steps": [
                      {
                        "type": "event",
                        "event": "page"
                      },
                      {
                        "type": "event",
                        "event": "connect",
                        "filters": [
                          {
                            "field": "rdns",
                            "op": "eq",
                            "value": "io.metamask"
                          },
                          {
                            "field": "chain_id",
                            "op": "eq",
                            "value": "1"
                          }
                        ]
                      },
                      {
                        "type": "event",
                        "event": "transaction"
                      }
                    ],
                    "settings": {
                      "funnelType": "closed",
                      "conversionWindow": {
                        "value": 7,
                        "unit": "day"
                      }
                    }
                  }
                },
                "funnelWithBreakdown": {
                  "summary": "Funnel with device breakdown",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT 1",
                    "chart_type": "funnel",
                    "title": "Onboarding Funnel by Device",
                    "steps": [
                      {
                        "type": "event",
                        "event": "page"
                      },
                      {
                        "type": "event",
                        "event": "connect"
                      },
                      {
                        "type": "event",
                        "event": "signature"
                      }
                    ],
                    "settings": {
                      "funnelType": "closed",
                      "conversionWindow": {
                        "value": 30,
                        "unit": "day"
                      },
                      "breakdown": "device"
                    }
                  }
                },
                "funnelOpenMultiValue": {
                  "summary": "Open funnel with multi-value `in` filter and breakdown",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT 1",
                    "chart_type": "funnel",
                    "title": "Mobile Onboarding Funnel",
                    "steps": [
                      {
                        "type": "event",
                        "event": "page",
                        "filters": [
                          {
                            "field": "device",
                            "op": "eq",
                            "value": "mobile"
                          }
                        ]
                      },
                      {
                        "type": "event",
                        "event": "connect",
                        "filters": [
                          {
                            "field": "provider_name",
                            "op": "in",
                            "value": ["metamask", "rainbow", "coinbase"]
                          }
                        ]
                      },
                      {
                        "type": "event",
                        "event": "transaction"
                      }
                    ],
                    "settings": {
                      "funnelType": "open",
                      "conversionWindow": {
                        "value": 30,
                        "unit": "day"
                      },
                      "breakdown": "device"
                    }
                  }
                },
                "barChart": {
                  "summary": "Daily active users (bar chart)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT toDate(timestamp) AS date, countDistinct(address) AS users FROM events GROUP BY date ORDER BY date",
                    "chart_type": "bar",
                    "title": "Daily Active Users",
                    "x_axis": "date",
                    "y_axis": ["users"]
                  }
                },
                "lineChart": {
                  "summary": "DAU last 30 days (line chart)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT toDate(timestamp) AS date, countDistinct(address) AS daily_active_users FROM events GROUP BY date ORDER BY date DESC LIMIT 30",
                    "chart_type": "line",
                    "title": "Daily Active Users",
                    "x_axis": "date",
                    "y_axis": ["daily_active_users"]
                  }
                },
                "pieChart": {
                  "summary": "Sessions by device (pie chart)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT device, COUNT(*) AS session_count FROM (SELECT session_id, argMinMerge(device) AS device FROM sessions GROUP BY session_id) GROUP BY device ORDER BY session_count DESC LIMIT 10",
                    "chart_type": "pie",
                    "title": "Sessions by Device",
                    "x_axis": "device",
                    "y_axis": ["session_count"]
                  }
                },
                "stackedChart": {
                  "summary": "Sessions by device grouped by browser (stacked chart)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT device, browser, COUNT(*) AS session_count FROM (SELECT session_id, argMinMerge(device) AS device, argMinMerge(browser) AS browser FROM sessions GROUP BY session_id) GROUP BY device, browser ORDER BY session_count DESC",
                    "chart_type": "stacked",
                    "title": "Sessions by Device and Browser",
                    "x_axis": "device",
                    "y_axis": ["session_count"],
                    "group_by": "browser"
                  }
                },
                "numberChart": {
                  "summary": "Total connected wallets (number / KPI card)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT COUNT(DISTINCT address) FROM events WHERE type = 'connect'",
                    "chart_type": "number",
                    "title": "Total Connected Wallets"
                  }
                },
                "tableChart": {
                  "summary": "Recent events (table)",
                  "value": {
                    "projectId": "proj_abc",
                    "query": "SELECT * FROM events ORDER BY timestamp DESC LIMIT 10",
                    "chart_type": "table",
                    "title": "Recent Events"
                  }
                },
                "userPathsChart": {
                  "summary": "User flow from connect (max 5 steps)",
                  "value": {
                    "projectId": "proj_abc",
                    "chart_type": "user_paths",
                    "title": "Post-Connect User Flow",
                    "settings": {
                      "anchors": [
                        {
                          "type": "event",
                          "event": "connect"
                        },
                        {
                          "type": "event",
                          "event": "transaction"
                        }
                      ],
                      "maxSteps": 5,
                      "conversionWindow": {
                        "value": 2,
                        "unit": "week"
                      }
                    }
                  }
                },
                "userPathsOpenEnded": {
                  "summary": "Open-ended user flow from page view",
                  "value": {
                    "projectId": "proj_abc",
                    "chart_type": "user_paths",
                    "title": "User Discovery Paths",
                    "settings": {
                      "anchors": [
                        {
                          "type": "event",
                          "event": "page"
                        }
                      ],
                      "maxSteps": 5,
                      "nodesPerStep": 8
                    }
                  }
                },
                "retentionFiltered": {
                  "summary": "Weekly retention: desktop users, transaction event",
                  "value": {
                    "projectId": "proj_abc",
                    "chart_type": "retention",
                    "title": "Weekly Retention: Desktop",
                    "settings": {
                      "retentionFilter": {
                        "type": "event",
                        "event": "transaction"
                      },
                      "entryFilter": {
                        "type": "event",
                        "event": "transaction"
                      },
                      "retentionUserFilters": [
                        {
                          "value": "desktop",
                          "field": "device",
                          "op": "eq"
                        }
                      ]
                    }
                  }
                },
                "retentionUnfiltered": {
                  "summary": "Overall retention (no filters)",
                  "value": {
                    "projectId": "proj_abc",
                    "chart_type": "retention",
                    "title": "Overall Retention",
                    "settings": {
                      "retentionFilter": null,
                      "entryFilter": null
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Chart created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chart"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts/reorder": {
      "put": {
        "operationId": "reorderCharts",
        "summary": "Reorder charts",
        "description": "Sets the display order of a board's charts to the given id list. Charts omitted from `chartIds` keep their relative order after the listed ones. Duplicate or unknown chart ids are rejected.",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["chartIds"],
                "properties": {
                  "chartIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Chart ids in the desired display order."
                  }
                }
              },
              "example": {
                "chartIds": ["cht_x1y2z3a4b5c6", "cht_q7r8s9t0u1v2"]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Charts reordered"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts/{chartId}": {
      "get": {
        "operationId": "getChart",
        "summary": "Get chart",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chart details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chart"
                },
                "example": {
                  "id": "cht_x1y2z3a4b5c6",
                  "project_id": "proj_abc123",
                  "board_id": "brd_a1b2c3d4e5f6",
                  "chart_type": "line",
                  "title": "Daily revenue (last 30 days)",
                  "description": null,
                  "query": "SELECT toDate(timestamp) AS day, sum(revenue) AS revenue FROM events WHERE event = 'transaction' AND timestamp >= now() - INTERVAL 30 DAY GROUP BY day ORDER BY day",
                  "x_axis": "day",
                  "y_axis": ["revenue"],
                  "group_by": null,
                  "steps": null,
                  "settings": null
                }
              }
            }
          }
        },
        "x-required-scope": "boards:read"
      },
      "put": {
        "operationId": "editChart",
        "summary": "Edit chart",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chart updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "description": "Chart ID"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write"
      },
      "delete": {
        "operationId": "deleteChart",
        "summary": "Delete chart",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Chart deleted"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts/{chartId}/move": {
      "put": {
        "operationId": "moveChart",
        "summary": "Move chart to another board",
        "description": "Moves the chart to a different board in the same project. The target board must differ from the current one.",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["targetBoardId"],
                "properties": {
                  "targetBoardId": {
                    "type": "string",
                    "description": "The board to move the chart to."
                  }
                }
              },
              "example": {
                "targetBoardId": "brd_f6e5d4c3b2a1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved chart",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chart"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts/{chartId}/duplicate": {
      "post": {
        "operationId": "duplicateChart",
        "summary": "Duplicate chart",
        "description": "Creates a copy of the chart on the same board and returns the new chart id.",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Id of the newly created chart",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                },
                "example": "cht_n3w1d2e3f4a5"
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:write"
      }
    },
    "/v0/boards/{boardId}/charts/{chartId}/query": {
      "get": {
        "operationId": "queryChartWithDateRange",
        "summary": "Execute saved chart query",
        "description": "Runs the saved chart's query with `{{date_from}}`/`{{date_to}}` substituted from the query parameters; the chart's query must contain both template variables. Dune integration is currently unavailable. Request access at https://formo.so/support to discuss re-enabling it; a configured API key alone does not enable Dune-backed charts.",
        "tags": ["Charts"],
        "parameters": [
          {
            "name": "boardId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chartId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start date (YYYY-MM-DD), substituted for `{{date_from}}`."
          },
          {
            "name": "dateTo",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End date (YYYY-MM-DD), substituted for `{{date_to}}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Query result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["result"],
                  "properties": {
                    "result": {
                      "type": "string",
                      "description": "JSON-encoded query result: `{ meta, data, rows }`."
                    }
                  }
                },
                "example": {
                  "result": "{\"meta\":[{\"name\":\"day\",\"type\":\"Date\"},{\"name\":\"revenue\",\"type\":\"Float64\"}],\"data\":[{\"day\":\"2026-08-01\",\"revenue\":1204.5}],\"rows\":1}"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "boards:read"
      }
    },
    "/v0/contracts": {
      "get": {
        "operationId": "listContracts",
        "summary": "List contracts",
        "description": "List monitored contracts. Paginated; the canonical `data` array carries the contracts for the current page. The `deploy` sidecar reports the project-wide deploy state (always reflects ALL contracts, not just this page) so callers can render \"X contracts pending deploy\" without a second request.",
        "tags": ["Contracts"],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Size"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of contracts plus deploy-state sidecar",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedListMeta"
                    },
                    {
                      "type": "object",
                      "required": ["data", "deploy"],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Contract"
                          }
                        },
                        "deploy": {
                          "type": "object",
                          "required": ["last_deployed_at", "diff"],
                          "properties": {
                            "last_deployed_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "Timestamp of the project's last successful deploy, or null if no deploy has run yet."
                            },
                            "diff": {
                              "type": "array",
                              "description": "Difference between the contracts currently registered for the project and what's actually deployed. Drives the \"contracts pending deploy\" UI.",
                              "items": {
                                "type": "object"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "name": "USD Coin",
                      "chain": 1,
                      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                      "start_block": 6082465,
                      "abi": "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]",
                      "events": [
                        {
                          "anonymous": false,
                          "name": "Transfer",
                          "type": "event",
                          "inputs": [
                            {
                              "indexed": true,
                              "name": "from",
                              "type": "address"
                            },
                            {
                              "indexed": true,
                              "name": "to",
                              "type": "address"
                            },
                            {
                              "indexed": false,
                              "name": "value",
                              "type": "uint256"
                            }
                          ]
                        }
                      ],
                      "include_in_pipeline": false
                    },
                    {
                      "name": "WETH (Base)",
                      "chain": 8453,
                      "address": "0x4200000000000000000000000000000000000006",
                      "start_block": 1,
                      "abi": "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]",
                      "events": [
                        {
                          "anonymous": false,
                          "name": "Transfer",
                          "type": "event",
                          "inputs": [
                            {
                              "indexed": true,
                              "name": "from",
                              "type": "address"
                            },
                            {
                              "indexed": true,
                              "name": "to",
                              "type": "address"
                            },
                            {
                              "indexed": false,
                              "name": "value",
                              "type": "uint256"
                            }
                          ]
                        }
                      ],
                      "include_in_pipeline": false
                    }
                  ],
                  "page": 1,
                  "size": 100,
                  "total": 2,
                  "has_more": false,
                  "deploy": {
                    "last_deployed_at": "2026-04-22T08:14:31.000Z",
                    "diff": []
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "contracts:read"
      },
      "post": {
        "operationId": "createContract",
        "summary": "Create contract",
        "description": "Add a blockchain contract to monitor.",
        "tags": ["Contracts"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "EVM contract address"
                  },
                  "chain": {
                    "type": "integer",
                    "description": "Chain ID"
                  },
                  "name": {
                    "type": "string"
                  },
                  "abi": {
                    "type": "string",
                    "description": "JSON-stringified ABI"
                  },
                  "events": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "properties": {
                        "anonymous": {
                          "type": "boolean"
                        },
                        "inputs": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "name": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        }
                      },
                      "required": ["anonymous", "inputs", "name", "type"]
                    }
                  },
                  "start_block": {
                    "type": "integer",
                    "default": 0,
                    "description": "Block height recorded on the contract. Note: the events pipeline currently opens every source at the chain head, so this does not backfill historical events. Defaults to 0.",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "include_in_pipeline": {
                    "type": "boolean",
                    "default": false,
                    "description": "Whether to include this contract in the project contract-events pipeline. When false the ABI is still cached for transaction decoding, but no events are indexed. Defaults to false (decode-only)."
                  }
                },
                "required": ["address", "chain", "name", "abi", "events"]
              },
              "examples": {
                "erc20Token": {
                  "summary": "Monitor ERC-20 Transfer events",
                  "value": {
                    "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                    "chain": 1,
                    "name": "USDC",
                    "abi": "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]",
                    "events": [
                      {
                        "anonymous": false,
                        "inputs": [
                          {
                            "indexed": true,
                            "internalType": "address",
                            "name": "from",
                            "type": "address"
                          },
                          {
                            "indexed": true,
                            "internalType": "address",
                            "name": "to",
                            "type": "address"
                          },
                          {
                            "indexed": false,
                            "internalType": "uint256",
                            "name": "value",
                            "type": "uint256"
                          }
                        ],
                        "name": "Transfer",
                        "type": "event"
                      }
                    ],
                    "start_block": 18000000,
                    "include_in_pipeline": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contract created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contract"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "contracts:write",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v0/contracts/{chain}/{address}": {
      "get": {
        "operationId": "getContract",
        "summary": "Get contract",
        "description": "Fetch a single monitored contract by chain ID and address. Returns the bare `Contract` resource (no envelope).",
        "tags": ["Contracts"],
        "parameters": [
          {
            "name": "chain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "EVM chain ID (e.g. `1` for Ethereum mainnet, `8453` for Base)."
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "description": "Contract address (0x-prefixed, 40 hex chars)."
          }
        ],
        "responses": {
          "200": {
            "description": "Contract found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contract"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "contracts:read"
      },
      "put": {
        "operationId": "updateContract",
        "summary": "Update contract",
        "tags": ["Contracts"],
        "parameters": [
          {
            "name": "chain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string"
                  },
                  "chain": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "abi": {
                    "type": "string"
                  },
                  "events": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object"
                    }
                  },
                  "start_block": {
                    "type": "integer",
                    "description": "Block height recorded on the contract. Omit to preserve the stored value. Note: the events pipeline currently opens every source at the chain head, so this does not backfill historical events.",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "include_in_pipeline": {
                    "type": "boolean",
                    "description": "Whether to include this contract in the project contract-events pipeline. Omit to preserve the stored value; send false to keep the ABI cached for transaction decoding without indexing events."
                  }
                },
                "required": ["address", "chain", "name", "abi", "events"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contract updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contract"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "contracts:write"
      },
      "delete": {
        "operationId": "deleteContract",
        "summary": "Delete contract",
        "tags": ["Contracts"],
        "parameters": [
          {
            "name": "chain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Contract deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "contracts:write"
      }
    },
    "/v0/segments": {
      "get": {
        "operationId": "listSegments",
        "summary": "List segments",
        "description": "List user segments for the project scoped to the API key. Paginated.",
        "tags": ["Segments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Size"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of segments",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedListMeta"
                    },
                    {
                      "type": "object",
                      "required": ["data"],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Segment"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "seg_e3f4g5h6i7j8",
                      "projectId": "proj_abc123",
                      "title": "High net worth desktop users",
                      "filters": [
                        {
                          "field": "device",
                          "op": "eq",
                          "value": "desktop"
                        },
                        {
                          "field": "net_worth_usd",
                          "op": "gte",
                          "value": "100000"
                        }
                      ]
                    }
                  ],
                  "page": 1,
                  "size": 100,
                  "total": 1,
                  "has_more": false
                }
              }
            }
          }
        },
        "x-required-scope": "segments:read"
      },
      "post": {
        "operationId": "createSegment",
        "summary": "Create segment",
        "tags": ["Segments"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1
                  },
                  "filters": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/SegmentFilterCondition"
                    },
                    "description": "Canonical filter objects combined with implicit AND."
                  }
                },
                "required": ["title", "filters"]
              },
              "examples": {
                "highValueMobileUsers": {
                  "summary": "High-value mobile users from paid campaigns",
                  "value": {
                    "title": "High-value mobile users",
                    "filters": [
                      {
                        "field": "device",
                        "op": "eq",
                        "value": "mobile"
                      },
                      {
                        "field": "net_worth_usd",
                        "op": "gte",
                        "value": "10000"
                      },
                      {
                        "field": "utm_source",
                        "op": "eq",
                        "value": "paid_ads"
                      }
                    ]
                  }
                },
                "multiDeviceChromeSafari": {
                  "summary": "Chrome or Safari users (multi-value filter)",
                  "value": {
                    "title": "Chrome/Safari users",
                    "filters": [
                      {
                        "field": "browser",
                        "op": "in",
                        "value": "Chrome|Safari"
                      }
                    ]
                  }
                },
                "excludeUSUsers": {
                  "summary": "All users except from the US",
                  "value": {
                    "title": "Non-US users",
                    "filters": [
                      {
                        "field": "location",
                        "op": "neq",
                        "value": "US"
                      }
                    ]
                  }
                },
                "powerUsers": {
                  "summary": "Power users with high net worth from organic traffic",
                  "value": {
                    "title": "Organic power users",
                    "filters": [
                      {
                        "field": "net_worth_usd",
                        "op": "gt",
                        "value": "1000"
                      },
                      {
                        "field": "utm_source",
                        "op": "nin",
                        "value": "google_ads|facebook_ads"
                      },
                      {
                        "field": "lifecycle",
                        "op": "eq",
                        "value": "power"
                      }
                    ]
                  }
                },
                "multiChainWhaleUsers": {
                  "summary": "Users active on 3+ chains with high net worth",
                  "value": {
                    "title": "Multi-chain whales",
                    "filters": [
                      {
                        "field": "chains",
                        "op": "gte",
                        "value": "3"
                      },
                      {
                        "field": "net_worth_usd",
                        "op": "gt",
                        "value": "100000"
                      },
                      {
                        "field": "apps",
                        "op": "gt",
                        "value": "5"
                      }
                    ]
                  }
                },
                "behavioralFilterSimple": {
                  "summary": "Users who performed 'signature' event at least once in last 30 days",
                  "description": "Behavior filters use the `events` field. Its value is a base64-encoded JSON array of event-count predicates; property predicates inside each event use canonical `filters` leaves.",
                  "value": {
                    "title": "Recent signers",
                    "filters": [
                      {
                        "field": "events",
                        "op": "eq",
                        "value": "W3siZXZlbnQiOiJzaWduYXR1cmUiLCJvcGVyYXRvciI6Imd0ZSIsInRpbWVzIjoxLCJwcmVzZXQiOiJsYXN0XzMwZCJ9XQ=="
                      }
                    ]
                  }
                },
                "behavioralFilterWithProperties": {
                  "summary": "Users who connected via MetaMask on Ethereum mainnet + from India",
                  "description": "Combines an event-count filter and a demographic filter. Event property filters use canonical `{field, op, value}` leaves in the behavior step `filters` array.",
                  "value": {
                    "title": "Indian MetaMask users",
                    "filters": [
                      {
                        "field": "events",
                        "op": "eq",
                        "value": "W3siZXZlbnQiOiJjb25uZWN0Iiwib3BlcmF0b3IiOiJndGUiLCJ0aW1lcyI6MSwicHJlc2V0IjoibGFzdF8zMGQiLCJmaWx0ZXJzIjpbeyJmaWVsZCI6InJkbnMiLCJvcCI6ImVxIiwidmFsdWUiOiJpby5tZXRhbWFzayJ9LHsiZmllbGQiOiJjaGFpbl9pZCIsIm9wIjoiZXEiLCJ2YWx1ZSI6IjEifV19XQ=="
                      },
                      {
                        "field": "location",
                        "op": "eq",
                        "value": "IN"
                      }
                    ]
                  }
                },
                "behavioralFilterMultipleEvents": {
                  "summary": "Users with 5+ page views last week AND at least 1 transaction last month",
                  "description": "Multiple behavior steps in one base64-encoded `events` filter value. All steps in the array must match.",
                  "value": {
                    "title": "Active transactors",
                    "filters": [
                      {
                        "field": "events",
                        "op": "eq",
                        "value": "W3siZXZlbnQiOiJwYWdlIiwib3BlcmF0b3IiOiJndGUiLCJ0aW1lcyI6NSwicHJlc2V0IjoibGFzdF83ZCJ9LHsiZXZlbnQiOiJ0cmFuc2FjdGlvbiIsIm9wZXJhdG9yIjoiZ3RlIiwidGltZXMiOjEsInByZXNldCI6Imxhc3RfMzBkIn1d"
                      },
                      {
                        "field": "device",
                        "op": "eq",
                        "value": "desktop"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Segment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Segment"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "segments:write",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v0/segments/{segmentId}": {
      "delete": {
        "operationId": "deleteSegment",
        "summary": "Delete segment",
        "tags": ["Segments"],
        "parameters": [
          {
            "name": "segmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Segment deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "segments:write"
      }
    },
    "/v0/profiles/{address}": {
      "get": {
        "operationId": "getProfile",
        "summary": "Get wallet profile",
        "description": "Get a single wallet profile. Agents can call the same path through the paid x402 or MPP gateway hosts without a caller-supplied Formo API key; Paysponge uses Formo's workspace API key behind the scenes. Paid gateway callers only provide the protocol payment header returned by the gateway challenge.",
        "tags": ["Profiles"],
        "servers": [
          {
            "url": "https://api.formo.so",
            "description": "Standard Formo API; authenticate with `Authorization: Bearer formo_...`."
          },
          {
            "url": "https://formo.x402.paysponge.com",
            "description": "x402 paid gateway; authenticate with `X-PAYMENT`."
          },
          {
            "url": "https://formo.mpp.paysponge.com",
            "description": "MPP paid gateway; authenticate with `Authorization: Payment ...`."
          }
        ],
        "security": [
          {
            "WorkspaceApiKey": []
          },
          {
            "X402Payment": []
          },
          {
            "MPPPayment": []
          }
        ],
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Wallet address. Accepts an EVM (0x...) or Solana address, or an ENS name (e.g. vitalik.eth) which is resolved to an address."
          },
          {
            "name": "expand",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: apps, chains, tokens, labels"
          },
          {
            "name": "timestamp",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Optional point in time for wallet-enrichment data. Returns the stored base snapshot closest to this instant instead of the latest snapshot; an exact-distance tie chooses the later snapshot. Expanded chains, apps, and tokens come from the profiling batch associated with that selected snapshot. Project engagement fields, project-defined identity overrides, and labels remain current. Must be ISO-8601 with a timezone."
          }
        ],
        "responses": {
          "200": {
            "description": "Profile details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                },
                "example": {
                  "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                  "ens": "vitalik.eth",
                  "display_name": "vitalik.eth",
                  "avatar": "https://euc.li/vitalik.eth",
                  "description": "mi pinxe lo crino tcati",
                  "location": "Earth",
                  "net_worth_usd": 1581720.97,
                  "tx_count": 18743,
                  "first_onchain": "2015-09-28T08:24:43.000Z",
                  "last_onchain": "2026-04-01T03:34:47.000Z",
                  "twitter": "vitalikbuterin",
                  "farcaster": "vitalik.eth",
                  "lens": "vitalik.lens",
                  "basenames": "vb62831.base.eth",
                  "linea": null,
                  "github": "vbuterin",
                  "reddit": null,
                  "linkedin": null,
                  "telegram": null,
                  "discord": null,
                  "email": null,
                  "website": "vitalik.ca",
                  "youtube": null,
                  "tiktok": null,
                  "instagram": null,
                  "facebook": null,
                  "first_seen": "2025-09-14T03:11:42.000Z",
                  "last_seen": "2026-04-26T18:09:53.000Z",
                  "lifecycle": "Returning",
                  "num_sessions": 12,
                  "revenue": 0,
                  "volume": 482.55,
                  "points": 0,
                  "device": "desktop",
                  "browser": "brave",
                  "os": "macOS",
                  "first_utm_source": "twitter",
                  "first_utm_medium": "social",
                  "first_utm_campaign": "devcon-launch",
                  "first_utm_content": null,
                  "first_utm_term": null,
                  "first_referrer": "twitter.com",
                  "first_referrer_url": "https://twitter.com/vitalikbuterin/status/1234567890",
                  "first_ref": null,
                  "last_utm_source": "direct",
                  "last_utm_medium": null,
                  "last_utm_campaign": null,
                  "last_utm_content": null,
                  "last_utm_term": null,
                  "last_referrer": null,
                  "last_referrer_url": null,
                  "last_ref": null,
                  "last_type": "track",
                  "last_event": "Swap Confirmed",
                  "last_properties": "{\"chain_id\":1,\"volume\":482.55}",
                  "activity_dates": [
                    "2026-04-22",
                    "2026-04-23",
                    "2026-04-25",
                    "2026-04-26"
                  ],
                  "chains": [
                    {
                      "chain_id": "1",
                      "net_worth_usd": 1491971.89,
                      "tx_count": 1655,
                      "first_onchain": "2015-09-28T08:24:43.000Z",
                      "last_onchain": "2026-04-01T03:34:47.000Z"
                    },
                    {
                      "chain_id": "8453",
                      "net_worth_usd": 41871.88,
                      "tx_count": 16,
                      "first_onchain": "2023-07-30T12:40:39.000Z",
                      "last_onchain": "2026-02-10T22:48:51.000Z"
                    },
                    {
                      "chain_id": "56",
                      "net_worth_usd": 21108.72,
                      "tx_count": 8,
                      "first_onchain": "2022-10-21T13:52:11.000Z",
                      "last_onchain": "2025-12-20T16:00:26.000Z"
                    },
                    {
                      "chain_id": "10",
                      "net_worth_usd": 16668.39,
                      "tx_count": 40,
                      "first_onchain": "2021-12-17T15:15:45.000Z",
                      "last_onchain": "2026-01-13T07:42:51.000Z"
                    }
                  ],
                  "apps": [
                    {
                      "chain_id": "1",
                      "id": "uniswap-v3",
                      "name": "Uniswap V3",
                      "img": "https://cdn.formo.so/apps/uniswap.png",
                      "url": "https://app.uniswap.org",
                      "balance_usd": 124820.41
                    },
                    {
                      "chain_id": "1",
                      "id": "aave-v3",
                      "name": "Aave V3",
                      "img": "https://cdn.formo.so/apps/aave.png",
                      "url": "https://app.aave.com",
                      "balance_usd": 88421.07
                    }
                  ],
                  "tokens": [
                    {
                      "chain_id": "1",
                      "token_address": "0x0000000000000000000000000000000000000000",
                      "app_id": "",
                      "name": "Ethereum",
                      "symbol": "ETH",
                      "img": "https://cdn.formo.so/tokens/eth.png",
                      "decimals": 18,
                      "price": 3290.42,
                      "balance": "184.732910421054811234",
                      "balance_usd": 607823.55
                    },
                    {
                      "chain_id": "1",
                      "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                      "app_id": "",
                      "name": "USD Coin",
                      "symbol": "USDC",
                      "img": "https://cdn.formo.so/tokens/usdc.png",
                      "decimals": 6,
                      "price": 1,
                      "balance": "125804.520000",
                      "balance_usd": 125804.52
                    }
                  ],
                  "labels": [
                    {
                      "id": "ethereum.founder",
                      "value": "",
                      "chain_id": "-",
                      "source": "system"
                    },
                    {
                      "id": "whale",
                      "value": "",
                      "chain_id": "-",
                      "source": "formo"
                    },
                    {
                      "id": "coinbase.verified_account",
                      "value": "true",
                      "chain_id": "1",
                      "source": "coinbase"
                    }
                  ],
                  "updated_at": "2026-04-27T01:00:00.000Z"
                }
              }
            }
          },
          "202": {
            "description": "Wallet not yet profiled. Returned for a first-time lookup while the profile is still being built; retry after the given delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": ["processing"]
                    },
                    "address": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retrying."
                    }
                  }
                },
                "example": {
                  "status": "processing",
                  "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                  "message": "Wallet profile is being generated. Retry shortly.",
                  "retry_after": 3
                }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        },
        "x-required-scope": "profiles:read"
      }
    },
    "/v0/profiles": {
      "get": {
        "operationId": "searchProfiles",
        "summary": "Search wallet profiles",
        "tags": ["Profiles"],
        "responses": {
          "200": {
            "description": "Paginated wallet profile search results.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedListMeta"
                    },
                    {
                      "type": "object",
                      "required": ["data"],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Profile"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                      "net_worth_usd": 12345.67
                    }
                  ],
                  "page": 1,
                  "size": 100,
                  "total": 1,
                  "has_more": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:read",
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by wallet address. Accepts an EVM (0x...) or Solana address, or an ENS name (e.g. vitalik.eth) which is resolved to an address."
          },
          {
            "name": "expand",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: apps, chains, tokens, labels"
          },
          {
            "name": "timestamp",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Optional point in time for wallet-enrichment data. Requires the address query parameter; unbounded historical collection scans are rejected. Returns the wallet's stored base snapshot closest to this instant instead of its latest snapshot; an exact-distance tie chooses the later snapshot. Expanded chains, apps, and tokens come from the selected snapshot's profiling batch. Project engagement fields, project-defined identity overrides, and labels remain current. Must be ISO-8601 with a timezone."
          },
          {
            "name": "order_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "net_worth_usd",
                "tx_count",
                "first_onchain",
                "last_onchain",
                "updated_at",
                "first_seen",
                "last_seen",
                "num_sessions",
                "revenue",
                "volume",
                "points"
              ]
            },
            "description": "Sort field"
          },
          {
            "name": "order_dir",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["asc", "desc"]
            },
            "description": "Sort direction"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "1-indexed page number (default 1)."
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            },
            "description": "Page size (default 100, max 1000)."
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Optional filter conditions",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileFilter"
              },
              "examples": {
                "highNetWorth": {
                  "summary": "Users with >$10k net worth",
                  "value": {
                    "logic": "and",
                    "filters": [
                      {
                        "field": "users.net_worth_usd",
                        "op": "gt",
                        "value": 10000
                      }
                    ]
                  }
                },
                "chainFilter": {
                  "summary": "Users active on Ethereum with >$1k balance",
                  "value": {
                    "logic": "and",
                    "filters": [
                      {
                        "field": "chains.balance",
                        "op": "gt",
                        "value": 1000,
                        "chain_id": "1"
                      }
                    ]
                  }
                },
                "labelFilter": {
                  "summary": "Coinbase verified users",
                  "value": {
                    "logic": "and",
                    "filters": [
                      {
                        "field": "labels.value",
                        "op": "eq",
                        "value": "true",
                        "tag_id": "coinbase.verified_account"
                      }
                    ]
                  }
                },
                "tokenFilter": {
                  "summary": "Users holding USDC in any protocol",
                  "value": {
                    "logic": "and",
                    "filters": [
                      {
                        "field": "tokens.balance",
                        "op": "gt",
                        "value": 0,
                        "scope": "any",
                        "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v0/profiles/{address}/properties": {
      "put": {
        "operationId": "updateUserProperties",
        "summary": "Update user properties",
        "description": "Set or unset first-party properties for a wallet profile. Override display name, email, socials, avatar, location, and other identity fields. Send `null` as a value to delete (unset) that property: the field then reads as null everywhere, including over any globally-enriched fallback value, until a new value is set. `user_id` cannot be unset (it participates in identity stitching). Set-and-unset compose in a single call. Note: profile enrichment snapshots are versioned, but first-party property overrides (and their deletions) are always current; a point-in-time read (`timestamp`) reflects the latest overrides, so a deletion also masks historical reads.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Wallet address. Accepts an EVM (0x...) or Solana address, or an ENS name (e.g. vitalik.eth) which is resolved to an address."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Merge-update of profile properties. Only the listed keys are accepted; unknown keys are rejected. At least one key must be provided. A `null` value unsets the property (not allowed for `user_id`). The literal string `_is_deleted` is a reserved internal tombstone and is rejected as a value.",
                "minProperties": 1,
                "additionalProperties": false,
                "properties": {
                  "user_id": {
                    "type": "string"
                  },
                  "display_name": {
                    "type": ["string", "null"]
                  },
                  "email": {
                    "type": ["string", "null"]
                  },
                  "farcaster": {
                    "type": ["string", "null"]
                  },
                  "discord": {
                    "type": ["string", "null"]
                  },
                  "twitter": {
                    "type": ["string", "null"]
                  },
                  "telegram": {
                    "type": ["string", "null"]
                  },
                  "instagram": {
                    "type": ["string", "null"]
                  },
                  "website": {
                    "type": ["string", "null"]
                  },
                  "github": {
                    "type": ["string", "null"]
                  },
                  "linkedin": {
                    "type": ["string", "null"]
                  },
                  "facebook": {
                    "type": ["string", "null"]
                  },
                  "tiktok": {
                    "type": ["string", "null"]
                  },
                  "youtube": {
                    "type": ["string", "null"]
                  },
                  "reddit": {
                    "type": ["string", "null"]
                  },
                  "avatar": {
                    "type": ["string", "null"]
                  },
                  "description": {
                    "type": ["string", "null"]
                  },
                  "location": {
                    "type": ["string", "null"]
                  },
                  "ens": {
                    "type": ["string", "null"]
                  },
                  "lens": {
                    "type": ["string", "null"]
                  },
                  "basenames": {
                    "type": ["string", "null"]
                  },
                  "linea": {
                    "type": ["string", "null"]
                  }
                }
              },
              "examples": {
                "updateProperties": {
                  "summary": "Set display name and email",
                  "value": {
                    "display_name": "alice.eth",
                    "email": "alice@example.com",
                    "twitter": "alice"
                  }
                },
                "unsetProperties": {
                  "summary": "Unset email, set a new display name",
                  "value": {
                    "display_name": "alice.eth",
                    "email": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Properties updated. Returns the merged identity (the keys actually set, plus address + updated_at) so callers can cache without a follow-up read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateUserPropertiesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:write"
      }
    },
    "/v0/profiles/properties": {
      "post": {
        "operationId": "batchUpdateUserProperties",
        "summary": "Batch update user properties",
        "description": "Set first-party properties for up to 100 wallets in one request. Each item is a flat object with a required `address` (literal EVM `0x...` or Solana; ENS names are NOT resolved here) plus any of the allowed profile keys. A `null` value unsets (deletes) that property (`user_id` cannot be unset); the reserved tombstone string `_is_deleted` is rejected as a value. Unknown keys are ignored; a row left with no valid keys, or with an invalid address, is quarantined (skipped and reported) rather than failing the batch. A request where every row is invalid returns 400. Requires profiles:write scope.",
        "tags": ["Profiles"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "minItems": 1,
                "maxItems": 100,
                "items": {
                  "type": "object",
                  "description": "Flat { address, ...keys } object. `address` is required; only the listed profile keys are persisted (string-valued, or null to unset; user_id cannot be unset). Unknown keys are accepted but ignored.",
                  "required": ["address"],
                  "additionalProperties": true,
                  "properties": {
                    "address": {
                      "type": "string",
                      "description": "Wallet address. Literal EVM (0x...) or Solana address only; ENS names are not resolved in batch requests."
                    },
                    "user_id": {
                      "type": "string"
                    },
                    "display_name": {
                      "type": ["string", "null"]
                    },
                    "email": {
                      "type": ["string", "null"]
                    },
                    "farcaster": {
                      "type": ["string", "null"]
                    },
                    "discord": {
                      "type": ["string", "null"]
                    },
                    "twitter": {
                      "type": ["string", "null"]
                    },
                    "telegram": {
                      "type": ["string", "null"]
                    },
                    "instagram": {
                      "type": ["string", "null"]
                    },
                    "website": {
                      "type": ["string", "null"]
                    },
                    "github": {
                      "type": ["string", "null"]
                    },
                    "linkedin": {
                      "type": ["string", "null"]
                    },
                    "facebook": {
                      "type": ["string", "null"]
                    },
                    "tiktok": {
                      "type": ["string", "null"]
                    },
                    "youtube": {
                      "type": ["string", "null"]
                    },
                    "reddit": {
                      "type": ["string", "null"]
                    },
                    "avatar": {
                      "type": ["string", "null"]
                    },
                    "description": {
                      "type": ["string", "null"]
                    },
                    "location": {
                      "type": ["string", "null"]
                    },
                    "ens": {
                      "type": ["string", "null"]
                    },
                    "lens": {
                      "type": ["string", "null"]
                    },
                    "basenames": {
                      "type": ["string", "null"]
                    },
                    "linea": {
                      "type": ["string", "null"]
                    }
                  }
                }
              },
              "examples": {
                "batchProperties": {
                  "summary": "Set properties for multiple wallets",
                  "value": [
                    {
                      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "display_name": "alice.eth",
                      "email": "alice@example.com"
                    },
                    {
                      "address": "EPjFWaYbrgqCC2Qbg4EV4FjUreEMKwMn1zNbiboXXKV",
                      "twitter": "bob"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed. Returns counts of forwarded vs quarantined rows, with a per-row `errors` entry for each quarantined row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchWriteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:write"
      }
    },
    "/v0/profiles/{address}/labels": {
      "post": {
        "operationId": "upsertUserLabel",
        "summary": "Add or update user labels",
        "description": "Upsert one or more labels for a wallet. Accepts either a single label object or an array of labels. Labels with the same tag_id (and chain_id, if provided) are overwritten. Requires profiles:write scope.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Wallet address. Accepts an EVM (0x...) or Solana address, or an ENS name (e.g. vitalik.eth) which is resolved to an address."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/UserLabelInput"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/UserLabelInput"
                    },
                    "minItems": 1
                  }
                ]
              },
              "examples": {
                "singleLabel": {
                  "summary": "Upsert a single label",
                  "value": {
                    "tag_id": "vip",
                    "value": "tier-1"
                  }
                },
                "multipleLabels": {
                  "summary": "Upsert multiple labels",
                  "value": [
                    {
                      "tag_id": "vip",
                      "value": "tier-1"
                    },
                    {
                      "tag_id": "airdrop_eligible",
                      "value": "season-2",
                      "chain_id": "1"
                    }
                  ]
                },
                "historicalLabel": {
                  "summary": "Backfill a label at a past timestamp",
                  "description": "Set `timestamp` to record the label at a historical point in time instead of the server's write time; used to backfill values from another source so label-based retention evaluates them at the correct point. ISO-8601 datetime (UTC `Z` or with a timezone offset); future values are rejected with 400.",
                  "value": {
                    "tag_id": "open_interest",
                    "value": "high",
                    "timestamp": "2024-01-15T00:00:00.000Z"
                  }
                },
                "historicalTombstone": {
                  "summary": "Backfill a label removal at a past timestamp",
                  "description": "Set `_is_deleted` to 1 together with a past `timestamp` to record that the label was removed at a historical point in time. Used by history imports to express \"label removed at past time T\" so point-in-time retention drops the wallet from that week, rather than only from now. Future timestamps are rejected with 400.",
                  "value": {
                    "tag_id": "open_interest",
                    "_is_deleted": 1,
                    "timestamp": "2024-03-15T00:00:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Labels upserted. Single label in → bare `UserLabel`; array in → array of `UserLabel`. Echoes the normalised entries (lowercased tag_id; the caller-supplied `timestamp` when present, otherwise the server write time).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/UserLabel"
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserLabel"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:write"
      },
      "delete": {
        "operationId": "deleteUserLabel",
        "summary": "Delete a user label",
        "description": "Delete a label from a wallet. Pass chain_id to scope the deletion to a specific chain; omit it to match labels without a chain scope. Requires profiles:write scope.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Wallet address. Accepts an EVM (0x...) or Solana address, or an ENS name (e.g. vitalik.eth) which is resolved to an address."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["tag_id"],
                "properties": {
                  "tag_id": {
                    "type": "string",
                    "description": "Label identifier to delete"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Optional chain identifier to scope the deletion"
                  }
                }
              },
              "examples": {
                "deleteLabel": {
                  "summary": "Delete a label",
                  "value": {
                    "tag_id": "vip"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Label deleted. No body; the caller already knows the new state."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:write"
      }
    },
    "/v0/profiles/labels": {
      "post": {
        "operationId": "batchUpsertUserLabels",
        "summary": "Batch add or update user labels",
        "description": "Upsert up to 100 labels across many wallets in one request; each item carries its own `address`. Modelled on the events ingest API: rows with an invalid address (ENS names are NOT resolved here; pass a literal EVM `0x...` or Solana address) are quarantined (skipped and reported in the response) rather than failing the whole batch. A request where every row is invalid returns 400. Requires profiles:write scope. Batch delete: send rows with `_is_deleted: 1` to tombstone labels in bulk; omit `timestamp` to delete as of now, or pair it with a past `timestamp` to record a historical removal (the equivalent of DELETE /v0/profiles/{address}/labels, one row per wallet/label).",
        "tags": ["Profiles"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "minItems": 1,
                "maxItems": 100,
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/UserLabelInput"
                    },
                    {
                      "type": "object",
                      "required": ["address"],
                      "properties": {
                        "address": {
                          "type": "string",
                          "description": "Wallet address the label applies to. Literal EVM (0x...) or Solana address only; ENS names are not resolved in batch requests."
                        }
                      }
                    }
                  ]
                }
              },
              "examples": {
                "batchLabels": {
                  "summary": "Upsert labels for multiple wallets",
                  "value": [
                    {
                      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "tag_id": "vip",
                      "value": "tier-1"
                    },
                    {
                      "address": "EPjFWaYbrgqCC2Qbg4EV4FjUreEMKwMn1zNbiboXXKV",
                      "tag_id": "airdrop_eligible",
                      "chain_id": "1"
                    }
                  ]
                },
                "historicalLabels": {
                  "summary": "Backfill a label time series ending in a removal",
                  "description": "Each row may carry a `timestamp` to record the label at a historical point in time instead of now; used to import a time series of a label's value so label-based retention evaluates it at the correct point. Set `_is_deleted` to 1 on a row to backfill a removal at that timestamp (here the label is removed on 2024-03-15). ISO-8601 datetime (UTC `Z` or with a timezone offset); a future value rejects the whole batch with 400.",
                  "value": [
                    {
                      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "tag_id": "open_interest",
                      "value": "high",
                      "timestamp": "2024-01-15T00:00:00.000Z"
                    },
                    {
                      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "tag_id": "open_interest",
                      "value": "low",
                      "timestamp": "2024-02-15T00:00:00.000Z"
                    },
                    {
                      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "tag_id": "open_interest",
                      "_is_deleted": 1,
                      "timestamp": "2024-03-15T00:00:00.000Z"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed. Returns counts of forwarded vs quarantined rows, with a per-row `errors` entry for each quarantined row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchWriteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "profiles:write"
      }
    },
    "/v0/import": {
      "post": {
        "operationId": "importWallets",
        "summary": "Import wallet addresses",
        "description": "Import wallet addresses into the project as identified users. `addresses` is always required and must be non-empty; add `rows` to attach first-party properties to the same wallets. Imported wallets count toward monthly active user billing. Requires profiles:write scope and Scale/Enterprise plan.",
        "tags": ["Profiles"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "addresses": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Wallet addresses to import. Required and must be non-empty, even when `rows` is supplied: send the same addresses in both fields. EVM (0x...) and Solana addresses are accepted; invalid entries are skipped.",
                    "minItems": 1
                  },
                  "rows": {
                    "type": "array",
                    "description": "Optional per-wallet first-party properties. When present and non-empty, the import uses these rows instead of `addresses` for the payload, so list the same wallets in both fields. Only the allowed profile keys are persisted (string values); unknown keys are ignored. Rows with an invalid address are skipped, and a request where no row has a valid address returns 400.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "address": {
                          "type": "string",
                          "description": "EVM (0x...) or Solana wallet address"
                        },
                        "properties": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          },
                          "description": "Optional first-party profile properties for this wallet"
                        }
                      },
                      "required": ["address"]
                    }
                  }
                },
                "required": ["addresses"]
              },
              "examples": {
                "importWallets": {
                  "summary": "Import wallet addresses",
                  "value": {
                    "addresses": [
                      "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                      "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
                    ]
                  }
                },
                "importWalletsWithProperties": {
                  "summary": "Import wallet addresses with profile properties",
                  "value": {
                    "addresses": ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"],
                    "rows": [
                      {
                        "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                        "properties": {
                          "display_name": "alice.eth",
                          "email": "alice@example.com"
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Wallets imported successfully"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-required-scope": "profiles:write",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v0/query": {
      "post": {
        "operationId": "executeQuery",
        "summary": "Execute SQL query",
        "description": "Execute a SQL query against the project's analytics data. Only SELECT and WITH statements are allowed. LIMIT is capped at 1,000,000. Forbidden keywords: INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE, etc.",
        "tags": ["Query"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "SQL query to execute"
                  }
                },
                "required": ["query"]
              },
              "examples": {
                "dailyActiveUsers": {
                  "summary": "Daily active users over last 30 days",
                  "value": {
                    "query": "SELECT toDate(timestamp) as date, uniq(anonymous_id) as dau FROM events WHERE timestamp >= now() - interval 30 day GROUP BY date ORDER BY date"
                  }
                },
                "topEvents": {
                  "summary": "Top 10 events by count",
                  "value": {
                    "query": "SELECT event, count() as total FROM events WHERE timestamp >= now() - interval 7 day GROUP BY event ORDER BY total DESC LIMIT 10"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Offset-paginated query results. The server doesn't own pagination here; `LIMIT` and `OFFSET` come from your SQL string and are echoed back. `total` is the row count before `LIMIT` was applied; `has_more` is true when there are additional rows beyond the current window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data", "total", "limit", "offset", "has_more"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "Result rows."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total rows before LIMIT was applied."
                    },
                    "limit": {
                      "type": "integer",
                      "description": "Applied LIMIT (parsed from your SQL; defaults to the server cap if absent)."
                    },
                    "offset": {
                      "type": "integer",
                      "description": "Applied OFFSET (parsed from your SQL; 0 if absent)."
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "True when `offset + data.length < total`; i.e. there's another page to fetch by re-running with a higher OFFSET."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "day": "2026-04-21",
                      "users": 1284
                    },
                    {
                      "day": "2026-04-22",
                      "users": 1352
                    },
                    {
                      "day": "2026-04-23",
                      "users": 1411
                    }
                  ],
                  "total": 7,
                  "limit": 100,
                  "offset": 0,
                  "has_more": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-required-scope": "query:read"
      }
    },
    "/v0/kpis": {
      "get": {
        "operationId": "getAnalyticsKpis",
        "summary": "Get KPIs (sessions, pageviews, bounce rate, session duration)",
        "description": "Time-series traffic KPIs with optional dimension breakdown. Returns `sessions` (session count), pageviews, bounce rate and average session length. When `include_previous_period=true` without `group_by`, the response also includes `visitors` (unique visitors), `visitors_current` and `visitors_previous`.",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "referrer",
                "location",
                "device",
                "browser",
                "os",
                "utm_source",
                "utm_medium",
                "utm_campaign"
              ]
            },
            "description": "Dimension to break down by"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "include_previous_period",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Return current and previous period for WoW comparison"
          }
        ],
        "responses": {
          "200": {
            "description": "KPI time-series",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "date",
                      "type": "Date"
                    },
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "pageviews",
                      "type": "UInt64"
                    },
                    {
                      "name": "bounce_rate",
                      "type": "Float64"
                    },
                    {
                      "name": "avg_session_sec",
                      "type": "Float64"
                    }
                  ],
                  "data": [
                    {
                      "date": "2025-10-15",
                      "project_id": "proj_abc",
                      "sessions": 412,
                      "pageviews": 1187,
                      "bounce_rate": 0.31,
                      "avg_session_sec": 142.6
                    },
                    {
                      "date": "2025-10-16",
                      "project_id": "proj_abc",
                      "sessions": 487,
                      "pageviews": 1402,
                      "bounce_rate": 0.28,
                      "avg_session_sec": 156.2
                    },
                    {
                      "date": "2025-10-17",
                      "project_id": "proj_abc",
                      "sessions": 533,
                      "pageviews": 1610,
                      "bounce_rate": 0.26,
                      "avg_session_sec": 168.4
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 3,
                  "statistics": {
                    "elapsed": 0.041,
                    "rows_read": 12340,
                    "bytes_read": 482310
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_pages": {
      "get": {
        "operationId": "getAnalyticsTopPages",
        "summary": "Get top pages by sessions or visitors, optionally restricted to entry or exit pages",
        "description": "Returns the most visited pages with traffic metrics. Pass `mode=entry` to restrict to the first page of each session (with `bounce_rate`) or `mode=exit` for the last page (with `exit_rate`). Default `mode=all` returns project-wide page metrics with anon-per-session visitor counts.",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["all", "entry", "exit"],
              "default": "all"
            },
            "description": "Page-flow mode. `all` (default) returns project-wide page metrics with anon-per-session visitor counts (`sessions`, `visitors`, `hits`). `entry` returns landing pages with `bounce_rate`. `exit` returns exit pages with `exit_rate`. The visitor count is omitted in `entry`/`exit` modes since those metrics are session-scoped."
          }
        ],
        "responses": {
          "200": {
            "description": "Top pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "origin",
                      "type": "String"
                    },
                    {
                      "name": "pathname",
                      "type": "String"
                    },
                    {
                      "name": "hits",
                      "type": "UInt64"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "visitors",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "origin": "app.example.com",
                      "pathname": "/",
                      "hits": 1456,
                      "sessions": 1284,
                      "visitors": 982
                    },
                    {
                      "project_id": "proj_abc",
                      "origin": "app.example.com",
                      "pathname": "/trade",
                      "hits": 1024,
                      "sessions": 612,
                      "visitors": 487
                    },
                    {
                      "project_id": "proj_abc",
                      "origin": "app.example.com",
                      "pathname": "/earn",
                      "hits": 587,
                      "sessions": 412,
                      "visitors": 318
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 24
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_sources": {
      "get": {
        "operationId": "getAnalyticsTopSources",
        "summary": "Get top traffic sources (referrers / utm)",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "name": "metric_column",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "referrer",
                "referrer_url",
                "ref",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_term",
                "utm_content",
                "origin",
                "device",
                "browser",
                "os",
                "channel",
                "paid_source"
              ]
            },
            "description": "Column to break down sources by. Use `channel` for the 13-channel acquisition classifier (see the [channel classification table](https://docs.formo.so/features/attribution/key-metrics#channels)). Use `paid_source` for the session-entry acquiring ad network (paid sessions only; see [Ad attribution](https://docs.formo.so/features/attribution/ads))."
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Top sources",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "referrer",
                      "type": "String"
                    },
                    {
                      "name": "hits",
                      "type": "UInt64"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "visitors",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "referrer": "Direct",
                      "hits": 1024,
                      "sessions": 612,
                      "visitors": 487
                    },
                    {
                      "project_id": "proj_abc",
                      "referrer": "google.com",
                      "hits": 542,
                      "sessions": 312,
                      "visitors": 268
                    },
                    {
                      "project_id": "proj_abc",
                      "referrer": "twitter.com",
                      "hits": 312,
                      "sessions": 184,
                      "visitors": 154
                    },
                    {
                      "project_id": "proj_abc",
                      "referrer": "farcaster.xyz",
                      "hits": 167,
                      "sessions": 98,
                      "visitors": 86
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 18
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_locations": {
      "get": {
        "operationId": "getAnalyticsTopLocations",
        "summary": "Get top countries",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Top countries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "location",
                      "type": "String"
                    },
                    {
                      "name": "hits",
                      "type": "UInt64"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "visitors",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "location": "US",
                      "hits": 824,
                      "sessions": 482,
                      "visitors": 387
                    },
                    {
                      "project_id": "proj_abc",
                      "location": "GB",
                      "hits": 312,
                      "sessions": 184,
                      "visitors": 156
                    },
                    {
                      "project_id": "proj_abc",
                      "location": "DE",
                      "hits": 268,
                      "sessions": 142,
                      "visitors": 124
                    },
                    {
                      "project_id": "proj_abc",
                      "location": "JP",
                      "hits": 187,
                      "sessions": 98,
                      "visitors": 86
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 32
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_wallets": {
      "get": {
        "operationId": "getAnalyticsTopWallets",
        "summary": "Get top wallet types",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Top wallets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "rdns",
                      "type": "String"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "visitors",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "rdns": "io.metamask",
                      "sessions": 412,
                      "visitors": 318
                    },
                    {
                      "project_id": "proj_abc",
                      "rdns": "com.coinbase.wallet",
                      "sessions": 184,
                      "visitors": 142
                    },
                    {
                      "project_id": "proj_abc",
                      "rdns": "me.rainbow",
                      "sessions": 98,
                      "visitors": 76
                    },
                    {
                      "project_id": "proj_abc",
                      "rdns": "app.phantom",
                      "sessions": 87,
                      "visitors": 64
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 12
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_chains": {
      "get": {
        "operationId": "getAnalyticsTopChains",
        "summary": "Get top blockchain chains",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Top chains",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "chain_id",
                      "type": "String"
                    },
                    {
                      "name": "sessions",
                      "type": "UInt64"
                    },
                    {
                      "name": "visitors",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "chain_id": "1",
                      "sessions": 482,
                      "visitors": 387
                    },
                    {
                      "project_id": "proj_abc",
                      "chain_id": "8453",
                      "sessions": 312,
                      "visitors": 268
                    },
                    {
                      "project_id": "proj_abc",
                      "chain_id": "42161",
                      "sessions": 184,
                      "visitors": 154
                    },
                    {
                      "project_id": "proj_abc",
                      "chain_id": "137",
                      "sessions": 142,
                      "visitors": 118
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 9
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/top_events": {
      "get": {
        "operationId": "getAnalyticsTopEvents",
        "summary": "Get top events by frequency",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["custom"]
            },
            "description": "Pass 'custom' to filter to custom track events only (events with type='track' and a non-empty event name). Omit for all events."
          }
        ],
        "responses": {
          "200": {
            "description": "Top events",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "type",
                      "type": "String"
                    },
                    {
                      "name": "event",
                      "type": "String"
                    },
                    {
                      "name": "hits",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "type": "page",
                      "event": "",
                      "hits": 4128
                    },
                    {
                      "project_id": "proj_abc",
                      "type": "identify",
                      "event": "",
                      "hits": 1284
                    },
                    {
                      "project_id": "proj_abc",
                      "type": "track",
                      "event": "wallet_connect",
                      "hits": 612
                    },
                    {
                      "project_id": "proj_abc",
                      "type": "track",
                      "event": "swap",
                      "hits": 248
                    },
                    {
                      "project_id": "proj_abc",
                      "type": "track",
                      "event": "stake",
                      "hits": 86
                    }
                  ],
                  "rows": 5,
                  "rows_before_limit_at_least": 14
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        },
        "description": "Most frequent events in the project. By default returns all event categories (page / track / identify / decoded_log / etc.). Pass `type=custom` to filter to custom track events only."
      }
    },
    "/v0/revenue_by_metric": {
      "get": {
        "operationId": "getAnalyticsRevenueByMetric",
        "summary": "Get revenue grouped by a chosen column",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "name": "metric_column",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "pathname",
                "origin",
                "channel",
                "paid_source",
                "referrer",
                "referrer_url",
                "ref",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_content",
                "utm_term",
                "builder_codes",
                "location",
                "device",
                "browser",
                "os",
                "rdns",
                "provider_name",
                "chain_id",
                "event"
              ]
            },
            "description": "Column to group revenue by. Use `channel` for the 13-channel acquisition classifier (see the [channel classification table](https://docs.formo.so/features/attribution/key-metrics#channels)). Use `paid_source` for the acquiring ad network (per-event sticky attribution; paid rows only, see [Ad attribution](https://docs.formo.so/features/attribution/ads)). Unknown values return zero rows."
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Revenue by metric",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "pathname",
                      "type": "String"
                    },
                    {
                      "name": "sum_revenue",
                      "type": "Float64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/trade",
                      "sum_revenue": 8412.55
                    },
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/earn",
                      "sum_revenue": 2184.3
                    },
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/swap",
                      "sum_revenue": 1287.2
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 8
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/volume_by_metric": {
      "get": {
        "operationId": "getAnalyticsVolumeByMetric",
        "summary": "Get transaction volume grouped by a chosen column",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "name": "metric_column",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "pathname",
                "origin",
                "channel",
                "paid_source",
                "referrer",
                "referrer_url",
                "ref",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_content",
                "utm_term",
                "builder_codes",
                "location",
                "device",
                "browser",
                "os",
                "rdns",
                "provider_name",
                "chain_id",
                "event"
              ]
            },
            "description": "Column to group volume by. Use `channel` for the 13-channel acquisition classifier (see the [channel classification table](https://docs.formo.so/features/attribution/key-metrics#channels)). Use `paid_source` for the acquiring ad network (per-event sticky attribution; paid rows only, see [Ad attribution](https://docs.formo.so/features/attribution/ads)). Unknown values return zero rows."
          },
          {
            "$ref": "#/components/parameters/AnalyticsLimit"
          },
          {
            "$ref": "#/components/parameters/AnalyticsOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Volume by metric",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "pathname",
                      "type": "String"
                    },
                    {
                      "name": "sum_volume",
                      "type": "Float64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/trade",
                      "sum_volume": 2812400
                    },
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/earn",
                      "sum_volume": 612300
                    },
                    {
                      "project_id": "proj_abc",
                      "pathname": "app.example.com/swap",
                      "sum_volume": 412800
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 8
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/revenue_overview": {
      "get": {
        "operationId": "getAnalyticsRevenueOverview",
        "summary": "Get revenue and transaction volume time-series",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsPageScope"
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Breakdown dimension. Valid options: referrer, location, device, browser, os, utm_source, utm_medium, utm_campaign, utm_content, utm_term, ref, builder_codes, channel_type, paid_source. Unknown values are ignored (ungrouped totals). `paid_source` is the acquiring ad network (per-event sticky attribution; empty bucket is 'None'; see [Ad attribution](https://docs.formo.so/features/attribution/ads))."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "rank_by",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_previous_period",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Return current and previous period for WoW comparison"
          }
        ],
        "responses": {
          "200": {
            "description": "Revenue and volume time-series",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "date",
                      "type": "Date"
                    },
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "revenue",
                      "type": "Float32"
                    },
                    {
                      "name": "volume",
                      "type": "Float32"
                    }
                  ],
                  "data": [
                    {
                      "date": "2025-10-15",
                      "project_id": "proj_abc",
                      "revenue": 1284.55,
                      "volume": 412800
                    },
                    {
                      "date": "2025-10-16",
                      "project_id": "proj_abc",
                      "revenue": 1567.2,
                      "volume": 506100
                    },
                    {
                      "date": "2025-10-17",
                      "project_id": "proj_abc",
                      "revenue": 1893.75,
                      "volume": 612400
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 3
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/revenue_timeseries": {
      "get": {
        "operationId": "getAnalyticsRevenueTimeseries",
        "summary": "Get per-event revenue and volume trend for a single wallet",
        "description": "Returns daily per-event revenue and volume rows for the given wallet `address`. Scoped per wallet; there is no project-wide aggregate mode on this endpoint.",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Wallet address to scope the timeseries to. Required.",
            "example": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-event revenue/volume time-series for the requested wallet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "date",
                      "type": "Date"
                    },
                    {
                      "name": "event",
                      "type": "String"
                    },
                    {
                      "name": "page_path",
                      "type": "String"
                    },
                    {
                      "name": "referrer",
                      "type": "String"
                    },
                    {
                      "name": "utm_source",
                      "type": "String"
                    },
                    {
                      "name": "revenue",
                      "type": "Float32"
                    },
                    {
                      "name": "volume",
                      "type": "Float32"
                    }
                  ],
                  "data": [
                    {
                      "date": "2025-10-15",
                      "event": "swap",
                      "page_path": "/trade",
                      "referrer": "google.com",
                      "utm_source": "organic",
                      "revenue": 215.3,
                      "volume": 68000
                    },
                    {
                      "date": "2025-10-15",
                      "event": "stake",
                      "page_path": "/earn",
                      "referrer": "",
                      "utm_source": "",
                      "revenue": 42.1,
                      "volume": 12500
                    },
                    {
                      "date": "2025-10-16",
                      "event": "swap",
                      "page_path": "/trade",
                      "referrer": "twitter.com",
                      "utm_source": "twitter",
                      "revenue": 318.75,
                      "volume": 102400
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 3
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/event_timeseries": {
      "get": {
        "operationId": "getAnalyticsEventTimeseries",
        "summary": "Get event count time-series",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsExclude"
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "channel_type",
                "device",
                "browser",
                "os",
                "location",
                "referrer",
                "ref",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_content",
                "utm_term",
                "builder_codes"
              ]
            },
            "description": "Optional breakdown dimension. When set, `event_key` carries the dimension's value (empty → `Direct`) instead of the event type/name, so the series splits by that dimension. Top 100 values + `Others`."
          }
        ],
        "responses": {
          "200": {
            "description": "Event time-series",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "date",
                      "type": "Date"
                    },
                    {
                      "name": "event_key",
                      "type": "String"
                    },
                    {
                      "name": "count",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "date": "2025-10-15",
                      "event_key": "page",
                      "count": 1187
                    },
                    {
                      "date": "2025-10-15",
                      "event_key": "wallet_connect",
                      "count": 124
                    },
                    {
                      "date": "2025-10-15",
                      "event_key": "swap",
                      "count": 38
                    },
                    {
                      "date": "2025-10-15",
                      "event_key": "identify",
                      "count": 412
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 4
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/lifecycle": {
      "get": {
        "operationId": "getAnalyticsLifecycle",
        "summary": "Get user lifecycle stages (New / Returning / Power user / At Risk / Churned / Resurrected)",
        "description": "Counts wallet users by lifecycle stage based on activity within the date range. The reference date is `date_to`.\n\nDefault stage thresholds can be overridden per-request with the lifecycle threshold query params (`new_window_days`, `churn_window_days`, `power_user_min_active_days`, `power_user_window_days`, `resurrected_gap_days`, `at_risk_min_days_inactive`, `at_risk_prior_active_days_threshold`); each is an optional integer 1 to 90. When omitted, the project's saved Settings → Lifecycle values apply, then the platform defaults.",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsIncludePreviousPeriod"
          },
          {
            "$ref": "#/components/parameters/AnalyticsBehaviorFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsSourceFilter"
          },
          {
            "$ref": "#/components/parameters/AnalyticsChannelFilter"
          },
          {
            "$ref": "#/components/parameters/LifecycleNewWindowDays"
          },
          {
            "$ref": "#/components/parameters/LifecycleChurnWindowDays"
          },
          {
            "$ref": "#/components/parameters/LifecyclePowerUserMinActiveDays"
          },
          {
            "$ref": "#/components/parameters/LifecyclePowerUserWindowDays"
          },
          {
            "$ref": "#/components/parameters/LifecycleResurrectedGapDays"
          },
          {
            "$ref": "#/components/parameters/LifecycleAtRiskMinDaysInactive"
          },
          {
            "$ref": "#/components/parameters/LifecycleAtRiskPriorActiveDaysThreshold"
          }
        ],
        "responses": {
          "200": {
            "description": "Lifecycle counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "user_type",
                      "type": "String"
                    },
                    {
                      "name": "user_count",
                      "type": "UInt64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "user_type": "New",
                      "user_count": 184
                    },
                    {
                      "project_id": "proj_abc",
                      "user_type": "Returning",
                      "user_count": 62
                    },
                    {
                      "project_id": "proj_abc",
                      "user_type": "Power user",
                      "user_count": 23
                    },
                    {
                      "project_id": "proj_abc",
                      "user_type": "At Risk",
                      "user_count": 14
                    },
                    {
                      "project_id": "proj_abc",
                      "user_type": "Churned",
                      "user_count": 142
                    },
                    {
                      "project_id": "proj_abc",
                      "user_type": "Resurrected",
                      "user_count": 11
                    }
                  ],
                  "rows": 6,
                  "rows_before_limit_at_least": 6
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/frequency": {
      "get": {
        "operationId": "getAnalyticsFrequency",
        "summary": "Get visit-frequency distribution",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsIncludePreviousPeriod"
          },
          {
            "$ref": "#/components/parameters/AnalyticsBehaviorFilters"
          },
          {
            "$ref": "#/components/parameters/AnalyticsSourceFilter"
          },
          {
            "$ref": "#/components/parameters/AnalyticsChannelFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "Visit frequency distribution",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "project_id",
                      "type": "String"
                    },
                    {
                      "name": "session_bucket",
                      "type": "String"
                    },
                    {
                      "name": "user_count",
                      "type": "UInt64"
                    },
                    {
                      "name": "avg_session_per_user",
                      "type": "Float64"
                    }
                  ],
                  "data": [
                    {
                      "project_id": "proj_abc",
                      "session_bucket": "1 session",
                      "user_count": 412,
                      "avg_session_per_user": 1
                    },
                    {
                      "project_id": "proj_abc",
                      "session_bucket": "2 - 10 sessions",
                      "user_count": 287,
                      "avg_session_per_user": 4.6
                    },
                    {
                      "project_id": "proj_abc",
                      "session_bucket": "10 - 30 sessions",
                      "user_count": 76,
                      "avg_session_per_user": 16.2
                    },
                    {
                      "project_id": "proj_abc",
                      "session_bucket": "30 - 50 sessions",
                      "user_count": 18,
                      "avg_session_per_user": 38.4
                    },
                    {
                      "project_id": "proj_abc",
                      "session_bucket": "> 50 sessions",
                      "user_count": 5,
                      "avg_session_per_user": 84
                    }
                  ],
                  "rows": 5,
                  "rows_before_limit_at_least": 5
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/retention": {
      "get": {
        "operationId": "getAnalyticsRetention",
        "summary": "Get user retention cohorts",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/AnalyticsDateFrom"
          },
          {
            "$ref": "#/components/parameters/AnalyticsDateTo"
          },
          {
            "$ref": "#/components/parameters/AnalyticsFilters"
          },
          {
            "name": "id_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["address", "anonymous_id"],
              "default": "address"
            },
            "description": "User identifier to cohort by. `address` (default) groups by wallet; `anonymous_id` groups by anonymous session ID."
          },
          {
            "name": "event_type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Restrict the cohort-defining event to a single type (e.g. `page`, `connect`, `track`, `transaction`).",
            "example": "track"
          },
          {
            "name": "event_name",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Restrict the cohort-defining event to a custom event name. Combine with `event_type=track`.",
            "example": "swap"
          },
          {
            "name": "min_users",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 0
            },
            "description": "Minimum cohort size to include (default 10). Ignored when `user_filters` is set; all weeks are returned regardless of size."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 12,
              "minimum": 1,
              "maximum": 52
            },
            "description": "Number of weekly cohorts to return (default 12). Also drives the default date range when `date_from`/`date_to` are omitted."
          },
          {
            "name": "user_filters",
            "in": "query",
            "description": "JSON array filtering users by profile attributes (`device`, `browser`, `os`, `location`, `volume`, `revenue`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`). When set, all weekly cohorts are returned regardless of `min_users`.",
            "schema": {
              "type": "string"
            },
            "example": "[{\"field\":\"device\",\"op\":\"eq\",\"value\":\"desktop\"}]"
          }
        ],
        "responses": {
          "200": {
            "description": "Retention cohorts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "metric",
                      "type": "Tuple(project_id String, user_count UInt64, day7_retained UInt64, day30_retained UInt64, day90_retained UInt64, day7_retention_rate Float64, day30_retention_rate Float64, day90_retention_rate Float64)"
                    },
                    {
                      "name": "user",
                      "type": "Array(Tuple(project_id String, cohort_week Date, num_users UInt64, week_0 Float64, week_1 Float64, week_2 Float64, week_3 Float64, week_4 Float64, week_5 Float64, week_6 Float64, week_7 Float64, week_8 Float64, week_9 Float64, week_10 Float64, week_11 Float64, week_12 Float64))"
                    }
                  ],
                  "data": [
                    {
                      "metric": [
                        "proj_abc",
                        1284,
                        412,
                        198,
                        76,
                        0.32,
                        0.154,
                        0.059
                      ],
                      "user": [
                        [
                          "proj_abc",
                          "2025-07-28",
                          142,
                          100,
                          48,
                          32,
                          22,
                          18,
                          14,
                          12,
                          11,
                          9,
                          8,
                          7,
                          6,
                          null,
                          null
                        ],
                        [
                          "proj_abc",
                          "2025-08-04",
                          178,
                          100,
                          52,
                          36,
                          26,
                          20,
                          16,
                          14,
                          12,
                          10,
                          9,
                          8,
                          null,
                          null,
                          null
                        ],
                        [
                          "proj_abc",
                          "2025-08-11",
                          165,
                          100,
                          50,
                          34,
                          24,
                          19,
                          15,
                          13,
                          11,
                          null,
                          null,
                          null,
                          null,
                          null,
                          null
                        ]
                      ]
                    }
                  ],
                  "rows": 1,
                  "rows_before_limit_at_least": 1
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/funnel": {
      "get": {
        "operationId": "getAnalyticsFunnel",
        "summary": "Get multi-step conversion funnel",
        "description": "Multi-step conversion funnel. For an ordered list of step specs, returns one row per step with the unique-user count, conversion ratios against step 1 and the previous step, drop-off ratio, and median time-to-convert (from-previous and from-start, in seconds).\n\n**Closed vs open:** `funnel_type=closed` (default) requires the user to fire the steps in order within `window_seconds`. `funnel_type=open` counts whoever fired step k regardless of order, and adds a `dropped_off_users` column for each step.\n\n**Breakdown:** when `group_by` is set to a column from the breakdown allowlist (`device`, `browser`, `os`, `location`, `referrer`, `ref`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `builder_codes`), the response gains a `breakdown` column and is grouped per (step, top-N category + 'Others', up to `limit` which defaults to 5). First-touch attribution is used by default; pass `attribution=last_touch` to bucket by each user's latest value.\n\n**Steps:** the `steps` query param is a JSON-encoded array of 2 to 10 step specs of shape `{type, event, name, filters?: [{field, op, value}]}`. Use `name` (e.g. `\"page::0\"`) as a unique step id so the same event re-used at different steps can be disambiguated in the response. `op` accepts `eq | neq | in | nin | gt | lt | gte | lte | startsWith | endsWith | contains | notEmpty | isEmpty` (canonical tokens only; the retired long-form spellings are rejected, `notEmpty`/`isEmpty` are value-less existence checks, rejected on the numeric event columns `volume`/`revenue`/`points`). `field` may target a standard event column or a JSON property on `properties`.",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "name": "date_from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive ISO date for the start of the funnel window (YYYY-MM-DD). The events scan extends past `date_to` by `window_seconds` so a user who fires step 1 just before `date_to` can still complete the funnel inside their conversion window.",
            "example": "2026-04-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive ISO date for the end of the start-event window (YYYY-MM-DD).",
            "example": "2026-04-30"
          },
          {
            "name": "steps",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "JSON-encoded array of 2 to 10 step specs. Each step is `{type, event, name, filters?: [{field, op, value}]}`. `type` is the event type (e.g. `event` for page views, `track` for custom events, `transaction`, `signature`, `decoded_log`). `event` is the event name. `name` is the unique step id (use `\"<event>::<index>\"` to disambiguate repeated events).\n\n**Filter operators:** `eq`, `neq`, `in`, `nin`, `gt`, `lt`, `gte`, `lte`, `startsWith`, `endsWith`, `contains`, `notEmpty`, `isEmpty` (canonical tokens only; the retired long-form spellings `equals`/`greater`/`includes`/… are rejected, `notEmpty`/`isEmpty` are value-less existence checks, rejected on numeric event columns `volume`/`revenue`/`points`). For `in`/`nin`, pass the values as a `|`-separated string in `value` (e.g. `\"ethereum|polygon|base\"`).\n\n**Standard columns** (rendered via direct column access): `origin`, `device`, `browser`, `os`, `location`, `referrer`, `direct`, `ref`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `builder_codes`, `version`, `locale`, `timezone`, `page_path`. Anything else is treated as a JSON property and read from `properties` via `JSONExtractString` (or `JSONExtractFloat` for numeric comparators).",
            "examples": {
              "simple": {
                "summary": "Simple 3-step funnel: page → connect → swap",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[]},{\"type\":\"track\",\"event\":\"connect\",\"name\":\"connect::1\",\"filters\":[]},{\"type\":\"track\",\"event\":\"swap\",\"name\":\"swap::2\",\"filters\":[]}]"
              },
              "standardColumnFilter": {
                "summary": "Step filter on a standard column (mobile-only first step)",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[{\"value\":\"mobile\",\"field\":\"device\",\"op\":\"eq\"}]},{\"type\":\"track\",\"event\":\"signup\",\"name\":\"signup::1\",\"filters\":[]}]"
              },
              "utmSourceIn": {
                "summary": "Step filter using `in` on a standard column (UTM-attributed sessions)",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[{\"value\":\"twitter|farcaster|telegram\",\"field\":\"utm_source\",\"op\":\"in\"}]},{\"type\":\"track\",\"event\":\"connect\",\"name\":\"connect::1\",\"filters\":[]}]"
              },
              "pagePathStartsWith": {
                "summary": "Step filter using `startsWith` on `page_path`",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[{\"value\":\"/swap\",\"field\":\"page_path\",\"op\":\"startsWith\"}]},{\"type\":\"track\",\"event\":\"swap\",\"name\":\"swap::1\",\"filters\":[]}]"
              },
              "jsonPropertyFilter": {
                "summary": "Step property filter (JSON property `provider_name=MetaMask`)",
                "value": "[{\"type\":\"track\",\"event\":\"connect\",\"name\":\"connect::0\",\"filters\":[{\"value\":\"MetaMask\",\"field\":\"provider_name\",\"op\":\"eq\"}]},{\"type\":\"track\",\"event\":\"swap\",\"name\":\"swap::1\",\"filters\":[]}]"
              },
              "jsonNumericFilter": {
                "summary": "Step property filter using a numeric comparator (price > 100 → JSONExtractFloat)",
                "value": "[{\"type\":\"track\",\"event\":\"swap\",\"name\":\"swap::0\",\"filters\":[{\"value\":\"100\",\"field\":\"price\",\"op\":\"gt\"}]},{\"type\":\"track\",\"event\":\"refund\",\"name\":\"refund::1\",\"filters\":[]}]"
              },
              "combinedFilters": {
                "summary": "Step with multiple filters (standard column + JSON property)",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[{\"value\":\"desktop\",\"field\":\"device\",\"op\":\"eq\"},{\"value\":\"/earn\",\"field\":\"page_path\",\"op\":\"startsWith\"}]},{\"type\":\"track\",\"event\":\"deposit\",\"name\":\"deposit::1\",\"filters\":[{\"value\":\"1\",\"field\":\"chain_id\",\"op\":\"eq\"},{\"value\":\"USDC|USDT|DAI\",\"field\":\"asset\",\"op\":\"in\"}]}]"
              },
              "transactionStatus": {
                "summary": "Onchain step (`transaction` type, success-only via JSON property)",
                "value": "[{\"type\":\"event\",\"event\":\"page\",\"name\":\"page::0\",\"filters\":[]},{\"type\":\"transaction\",\"event\":\"swap\",\"name\":\"swap::1\",\"filters\":[{\"value\":\"success\",\"field\":\"status\",\"op\":\"eq\"}]}]"
              }
            }
          },
          {
            "name": "window_seconds",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 7200,
              "minimum": 1
            },
            "description": "Conversion window length in seconds. Defaults to 7,200 (2 hours). For closed funnels this is the in-order completion cap; for both variants the events scan is extended by this amount past `date_to`.",
            "example": 86400
          },
          {
            "name": "funnel_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["closed", "open"],
              "default": "closed"
            },
            "description": "`closed` (default): ordered, in-window. `open`: unordered per-step; emits an extra `dropped_off_users` column."
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "device",
                "browser",
                "os",
                "location",
                "referrer",
                "ref",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_content",
                "utm_term",
                "builder_codes"
              ]
            },
            "description": "Optional dimension to break each step down by. Defaults to first-touch attribution; pass `attribution=last_touch` to bucket by each user's latest value. When set, the response gains a `breakdown` column."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 5,
              "minimum": 1
            },
            "description": "Top-N breakdown categories to keep (by user count), used only with `group_by`. Remaining categories are bucketed as `Others`. Defaults to 5."
          },
          {
            "name": "attribution",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["first_touch", "last_touch"],
              "default": "first_touch"
            },
            "description": "Per-user attribution for the `group_by` dimension. `first_touch` (default) buckets each user by their earliest value; `last_touch` by their latest. Ignored unless `group_by` is set."
          }
        ],
        "responses": {
          "200": {
            "description": "Per-step funnel results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "step",
                      "type": "UInt64"
                    },
                    {
                      "name": "event",
                      "type": "String"
                    },
                    {
                      "name": "users",
                      "type": "UInt64"
                    },
                    {
                      "name": "total",
                      "type": "UInt64"
                    },
                    {
                      "name": "conversion_from_start",
                      "type": "Nullable(Float64)"
                    },
                    {
                      "name": "conversion_from_previous",
                      "type": "Nullable(Float64)"
                    },
                    {
                      "name": "dropoff_from_previous",
                      "type": "Nullable(Float64)"
                    },
                    {
                      "name": "median_seconds_from_previous",
                      "type": "Nullable(Float64)"
                    },
                    {
                      "name": "median_seconds_from_start",
                      "type": "Nullable(Float64)"
                    }
                  ],
                  "data": [
                    {
                      "step": 1,
                      "event": "page::0",
                      "users": 1240,
                      "total": 1240,
                      "conversion_from_start": 1,
                      "conversion_from_previous": null,
                      "dropoff_from_previous": null,
                      "median_seconds_from_previous": null,
                      "median_seconds_from_start": null
                    },
                    {
                      "step": 2,
                      "event": "connect::1",
                      "users": 482,
                      "total": 482,
                      "conversion_from_start": 0.3887,
                      "conversion_from_previous": 0.3887,
                      "dropoff_from_previous": 0.6113,
                      "median_seconds_from_previous": 38,
                      "median_seconds_from_start": 38
                    },
                    {
                      "step": 3,
                      "event": "swap::2",
                      "users": 187,
                      "total": 187,
                      "conversion_from_start": 0.1508,
                      "conversion_from_previous": 0.388,
                      "dropoff_from_previous": 0.612,
                      "median_seconds_from_previous": 124,
                      "median_seconds_from_start": 162
                    }
                  ],
                  "rows": 3,
                  "rows_before_limit_at_least": 3
                }
              }
            }
          },
          "400": {
            "description": "Invalid `steps`, `window_seconds`, or other query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/flow": {
      "get": {
        "operationId": "getAnalyticsFlow",
        "summary": "Get session-scoped user-flow transitions (Sankey)",
        "description": "Session-scoped user-flow transitions for Sankey-style charts. Given a required `start_step`, optional `end_step`, date window, conversion window, and max-step cap, returns one row per `(step, source, target)` transition with counts and per-step percentages.\n\n**How it works:** for each session that fires the start step inside `[date_from, date_to]`, the endpoint rebuilds the ordered sequence of subsequent events within the conversion window (`window_seconds`), normalises each event into a flow-node label (page path for `page`, event name for `track`/`decoded_log`, prefixed for `signature`/`transaction`), truncates at the first end-step match (when `end_step` is set), and slices to `max_steps + 1` nodes. Adjacent nodes are then exploded into `(step, source, target)` edges with counts and percentages.\n\n**Converter-only mode:** when `end_step` is set, only sessions that actually reached the end event within the window are kept, and the matched event is suffixed with `__END_MATCH__`. This mirrors PostHog's `pathsFilter.endPoint` behaviour; every visible link is part of a converting path.\n\n**Step shape:** `start_step` and `end_step` are JSON objects of shape `{type, event, resolved_event, filters?: [...], status_type?, status_value?}`. `resolved_event` is the value used to match against the events table (event name, page path, or the sentinel `__ALL_PAGE_VIEWS__` for any page view). `filters` and `global_filters` accept `{field, op, value, values?}` (use `values` for `in`/`nin`).",
        "tags": ["Query"],
        "x-required-scope": "query:read",
        "parameters": [
          {
            "name": "date_from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive ISO date for the start of the start-event window (YYYY-MM-DD).",
            "example": "2026-04-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive ISO date for the end of the start-event window (YYYY-MM-DD).",
            "example": "2026-04-30"
          },
          {
            "name": "start_step",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "JSON-encoded start-step spec of shape `{type, event, resolved_event, filters?: [...], status_type?, status_value?}`. Use `\"resolved_event\":\"__ALL_PAGE_VIEWS__\"` to match any page view; pass a page path for a specific landing page; or pass an event name for `track`/`decoded_log` start events. `filters` use `{field, op, value, values?}` (use `values` for `in`/`nin`).\n\n**Filter operators:** `eq`, `neq`, `in`, `nin`, `gt`, `lt`, `gte`, `lte`, `startsWith`, `endsWith`, `contains`, `notEmpty`, `isEmpty` (canonical tokens only; the retired long-form spellings `equals`/`greater`/`includes`/… are rejected, `notEmpty`/`isEmpty` are value-less existence checks, rejected on numeric event columns `volume`/`revenue`/`points`). Standard columns (`device`, `browser`, `os`, `location`, `referrer`, `utm_*`, `page_path`, etc.) are read directly; everything else is read from `properties` via `JSONExtractString` (or `JSONExtractFloat` for numeric comparators).",
            "examples": {
              "anyPageView": {
                "summary": "Match any page view as the start step",
                "value": "{\"type\":\"event\",\"event\":\"page\",\"resolved_event\":\"__ALL_PAGE_VIEWS__\",\"filters\":[]}"
              },
              "specificPagePath": {
                "summary": "Match a specific landing page",
                "value": "{\"type\":\"event\",\"event\":\"page\",\"resolved_event\":\"/swap\",\"filters\":[]}"
              },
              "trackEvent": {
                "summary": "Custom track event as the start step",
                "value": "{\"type\":\"track\",\"event\":\"signup\",\"resolved_event\":\"signup\",\"filters\":[]}"
              },
              "withStandardFilter": {
                "summary": "Step filter on a standard column (mobile sessions only)",
                "value": "{\"type\":\"event\",\"event\":\"page\",\"resolved_event\":\"__ALL_PAGE_VIEWS__\",\"filters\":[{\"value\":\"mobile\",\"field\":\"device\",\"op\":\"eq\"}]}"
              },
              "withInValuesFilter": {
                "summary": "Step filter using `in` with the `values` array",
                "value": "{\"type\":\"event\",\"event\":\"page\",\"resolved_event\":\"__ALL_PAGE_VIEWS__\",\"filters\":[{\"value\":\"twitter\",\"values\":[\"twitter\",\"farcaster\",\"telegram\"],\"field\":\"utm_source\",\"op\":\"in\"}]}"
              },
              "withJsonPropertyFilter": {
                "summary": "Step property filter (JSON property `provider_name=MetaMask`)",
                "value": "{\"type\":\"track\",\"event\":\"connect\",\"resolved_event\":\"connect\",\"filters\":[{\"value\":\"MetaMask\",\"field\":\"provider_name\",\"op\":\"eq\"}]}"
              },
              "transactionWithStatus": {
                "summary": "Onchain transaction matched via `status_type` + `status_value`",
                "value": "{\"type\":\"transaction\",\"event\":\"swap\",\"resolved_event\":\"swap\",\"status_type\":\"transaction\",\"status_value\":\"success\",\"filters\":[]}"
              }
            }
          },
          {
            "name": "end_step",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional JSON-encoded end-step spec, same shape as `start_step`. When present, paths are truncated at the first end-event match (suffixed `__END_MATCH__`) and only converting sessions are returned.",
            "examples": {
              "trackEvent": {
                "summary": "Custom track event as the conversion goal",
                "value": "{\"type\":\"track\",\"event\":\"swap\",\"resolved_event\":\"swap\",\"filters\":[]}"
              },
              "endWithFilter": {
                "summary": "Conversion goal with a JSON property filter (premium plan only)",
                "value": "{\"type\":\"track\",\"event\":\"upgrade\",\"resolved_event\":\"upgrade\",\"filters\":[{\"value\":\"premium\",\"field\":\"plan\",\"op\":\"eq\"}]}"
              },
              "transactionEnd": {
                "summary": "Successful onchain transaction as the end step",
                "value": "{\"type\":\"transaction\",\"event\":\"swap\",\"resolved_event\":\"swap\",\"status_type\":\"transaction\",\"status_value\":\"success\",\"filters\":[]}"
              }
            }
          },
          {
            "name": "global_filters",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional JSON-encoded array of `{field, op, value, values?}` filters applied to both the start-session scan and the relevant-events scan. Useful for restricting the entire flow to a specific cohort (e.g. desktop visitors, a UTM source, a wallet provider).",
            "examples": {
              "deviceFilter": {
                "summary": "Restrict to desktop sessions",
                "value": "[{\"value\":\"desktop\",\"field\":\"device\",\"op\":\"eq\"}]"
              },
              "utmCohort": {
                "summary": "Restrict to paid UTM cohorts using `in`",
                "value": "[{\"value\":\"google\",\"values\":[\"google\",\"twitter\",\"farcaster\"],\"field\":\"utm_source\",\"op\":\"in\"}]"
              },
              "referrer": {
                "summary": "Restrict the flow to Google-referred sessions",
                "value": "[{\"value\":\"google\",\"field\":\"referrer\",\"op\":\"contains\"}]"
              },
              "jsonProperty": {
                "summary": "Filter by a JSON property (MetaMask wallet only)",
                "value": "[{\"value\":\"MetaMask\",\"field\":\"provider_name\",\"op\":\"eq\"}]"
              },
              "combined": {
                "summary": "Combine standard column + JSON property filters",
                "value": "[{\"value\":\"desktop\",\"field\":\"device\",\"op\":\"eq\"},{\"value\":\"1\",\"field\":\"chain_id\",\"op\":\"eq\"}]"
              }
            }
          },
          {
            "name": "window_seconds",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 7200,
              "minimum": 1
            },
            "description": "Conversion window length in seconds. Defaults to 7,200 (2 hours).",
            "example": 7200
          },
          {
            "name": "max_steps",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 4,
              "minimum": 2,
              "maximum": 10
            },
            "description": "Maximum number of transitions per session (Sankey depth). Clamped to 2..10.",
            "example": 4
          }
        ],
        "responses": {
          "200": {
            "description": "Per-step Sankey transitions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResponse"
                },
                "example": {
                  "meta": [
                    {
                      "name": "step",
                      "type": "UInt32"
                    },
                    {
                      "name": "source",
                      "type": "String"
                    },
                    {
                      "name": "target",
                      "type": "String"
                    },
                    {
                      "name": "transitions",
                      "type": "UInt64"
                    },
                    {
                      "name": "percentage",
                      "type": "Float64"
                    }
                  ],
                  "data": [
                    {
                      "step": 1,
                      "source": "/",
                      "target": "/swap",
                      "transitions": 412,
                      "percentage": 47.83
                    },
                    {
                      "step": 1,
                      "source": "/",
                      "target": "/earn",
                      "transitions": 248,
                      "percentage": 28.79
                    },
                    {
                      "step": 2,
                      "source": "/swap",
                      "target": "connect",
                      "transitions": 198,
                      "percentage": 38.6
                    },
                    {
                      "step": 3,
                      "source": "connect",
                      "target": "swap__END_MATCH__",
                      "transitions": 112,
                      "percentage": 56.57
                    }
                  ],
                  "rows": 4,
                  "rows_before_limit_at_least": 4
                }
              }
            }
          },
          "400": {
            "description": "Invalid `start_step`, `end_step`, or other query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v0/raw_events": {
      "post": {
        "operationId": "ingestEvents",
        "summary": "Ingest events",
        "description": "Send analytics events to Formo. This endpoint runs on events.formo.so (not api.formo.so). Authenticate with your project's SDK write key.",
        "tags": ["Events"],
        "servers": [
          {
            "url": "https://events.formo.so"
          }
        ],
        "security": [
          {
            "SdkWriteKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Event"
                }
              },
              "examples": {
                "pageView": {
                  "summary": "Track a page view",
                  "value": [
                    {
                      "type": "page",
                      "channel": "web",
                      "version": "1",
                      "anonymous_id": "e397c4e7-5f0a-45d6-a06c-f34a809d8b82",
                      "user_id": "",
                      "address": "",
                      "event": "",
                      "context": {
                        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
                        "locale": "en-US",
                        "timezone": "America/Los_Angeles",
                        "location": "US",
                        "ref": "",
                        "referrer": "",
                        "utm_campaign": "",
                        "utm_content": "",
                        "utm_medium": "",
                        "utm_source": "",
                        "utm_term": "",
                        "page_title": "Dashboard | MyApp",
                        "page_url": "https://myapp.com/swap/ethereum#/swap",
                        "page_path": "/swap/ethereum",
                        "library_name": "Formo Web SDK",
                        "library_version": "1.27.0",
                        "browser": "chrome",
                        "device": "desktop",
                        "os": "Windows",
                        "screen_width": 1280,
                        "screen_height": 720,
                        "screen_density": 1.5,
                        "viewport_width": 1280,
                        "viewport_height": 604
                      },
                      "properties": {
                        "url": "https://myapp.com/swap/ethereum#/swap",
                        "path": "/swap/ethereum",
                        "hash": "#/swap",
                        "query": ""
                      },
                      "original_timestamp": "2026-04-28T02:08:30.000Z",
                      "sent_at": "2026-04-28T02:09:00.000Z",
                      "message_id": "263434374239d12b797bf571c6045d6f9f9000a70f19f94d75ca485536606b27"
                    }
                  ]
                },
                "walletConnect": {
                  "summary": "Track a wallet connection",
                  "value": [
                    {
                      "type": "connect",
                      "channel": "web",
                      "version": "1",
                      "anonymous_id": "66b81795-cf59-43d1-80ab-ef48098b6e06",
                      "user_id": "",
                      "address": "0xA39260F25D6ebBEAE4595977bDE410623A96E7Af",
                      "event": "",
                      "context": {
                        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
                        "locale": "en-US",
                        "timezone": "Europe/London",
                        "location": "GB",
                        "page_title": "MyApp - Swap & Bridge",
                        "page_url": "https://myapp.com/earn/positions",
                        "library_name": "Formo Web SDK",
                        "library_version": "1.27.0",
                        "browser": "chrome",
                        "device": "desktop",
                        "os": "Windows"
                      },
                      "properties": {
                        "rdns": "io.metamask",
                        "chain_id": 43114,
                        "provider_name": "MetaMask"
                      },
                      "original_timestamp": "2026-04-27T20:04:54.000Z",
                      "sent_at": "2026-04-27T20:05:00.000Z",
                      "message_id": "b0a1dc19c494df191e2cb0c56460f7472113e5858440b3d4f3798a070265f631"
                    }
                  ]
                },
                "trackEvent": {
                  "summary": "Track a custom event",
                  "value": [
                    {
                      "type": "track",
                      "channel": "web",
                      "version": "1",
                      "anonymous_id": "66b81795-cf59-43d1-80ab-ef48098b6e06",
                      "address": "0xA39260F25D6ebBEAE4595977bDE410623A96E7Af",
                      "event": "Chain Switched",
                      "context": {
                        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
                        "locale": "en-US",
                        "timezone": "Europe/London",
                        "location": "GB",
                        "page_url": "https://myapp.com/swap",
                        "library_name": "Formo Web SDK",
                        "library_version": "1.27.0",
                        "browser": "chrome",
                        "device": "desktop",
                        "os": "Windows"
                      },
                      "properties": {
                        "old_network": "Arbitrum",
                        "new_network": "BNB Chain"
                      },
                      "original_timestamp": "2026-04-27T23:05:38.000Z",
                      "sent_at": "2026-04-27T23:05:42.000Z",
                      "message_id": "f4b2e8c1a59d3e7f6c8b9a02d5e4f1c3b8a7e6d5c4b3a291807e6d5c4b3a2918"
                    }
                  ]
                },
                "identify": {
                  "summary": "Identify a wallet",
                  "value": [
                    {
                      "type": "identify",
                      "channel": "web",
                      "version": "1",
                      "anonymous_id": "66b81795-cf59-43d1-80ab-ef48098b6e06",
                      "user_id": "usr_42a91c2b",
                      "address": "0xA39260F25D6ebBEAE4595977bDE410623A96E7Af",
                      "event": null,
                      "context": {
                        "library_name": "Formo Web SDK",
                        "library_version": "1.27.0",
                        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
                        "locale": "en-US",
                        "timezone": "Europe/London",
                        "location": "GB",
                        "page_url": "https://myapp.com/dashboard",
                        "browser": "chrome",
                        "device": "desktop",
                        "os": "Windows"
                      },
                      "properties": {
                        "rdns": "io.metamask",
                        "provider_name": "MetaMask",
                        "email": "alice@example.com",
                        "display_name": "alice.eth"
                      },
                      "original_timestamp": "2026-04-27T20:05:10.000Z",
                      "sent_at": "2026-04-27T20:05:11.000Z",
                      "message_id": "7e2a4b1c8d3f6e9a0b5c2d1e4f7a8b6c9d0e3f2a1b4c5d6e7f8a9b0c1d2e3f4a"
                    }
                  ]
                },
                "transaction": {
                  "summary": "Track an onchain transaction",
                  "value": [
                    {
                      "type": "transaction",
                      "channel": "web",
                      "version": "1",
                      "anonymous_id": "66b81795-cf59-43d1-80ab-ef48098b6e06",
                      "user_id": "",
                      "address": "0xA39260F25D6ebBEAE4595977bDE410623A96E7Af",
                      "event": "",
                      "context": {
                        "library_name": "Formo Web SDK",
                        "library_version": "1.27.0",
                        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
                        "locale": "en-US",
                        "timezone": "Europe/London",
                        "location": "GB",
                        "page_url": "https://myapp.com/swap",
                        "browser": "chrome",
                        "device": "desktop",
                        "os": "Windows"
                      },
                      "properties": {
                        "status": "confirmed",
                        "chain_id": 1,
                        "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                        "value": "0",
                        "transaction_hash": "0x6f4c1f2c3a8b9e0d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e",
                        "function_name": "transfer",
                        "function_args": {
                          "to": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
                          "amount": "1000000000"
                        },
                        "revenue": 250.5,
                        "currency": "usd"
                      },
                      "original_timestamp": "2026-04-27T22:14:03.000Z",
                      "sent_at": "2026-04-27T22:14:04.000Z",
                      "message_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch accepted. The body carries an ingestion summary when one is available, otherwise `{}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {}
              }
            }
          },
          "202": {
            "description": "Batch accepted for asynchronous processing. Returns `{ \"queued\": true }` when the batch was buffered for retry, an ingestion summary when one is available, otherwise `{}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "queued": true
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing. A body that is present but not valid JSON returns 500 instead.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "error": "Invalid request body"
                }
              }
            }
          },
          "401": {
            "description": "The request could not be authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "error": "Unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "Missing or invalid SDK write key. The body for this status is not the shape shown here.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "Message": "User is not authorized to access this resource with an explicit deny"
                }
              }
            }
          },
          "500": {
            "description": "The batch could not be ingested, or the request body was not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "error": "Internal Server Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Alerts",
      "description": "Manage project alerts and notifications"
    },
    {
      "name": "Boards",
      "description": "Manage dashboard boards"
    },
    {
      "name": "Charts",
      "description": "Manage charts within boards"
    },
    {
      "name": "Contracts",
      "description": "Manage blockchain contract monitoring"
    },
    {
      "name": "Segments",
      "description": "Manage user segments"
    },
    {
      "name": "Profiles",
      "description": "Wallet profiles and import"
    },
    {
      "name": "Query",
      "description": "Execute SQL queries and call pre-built analytics endpoints (KPIs, top pages, lifecycle, retention, revenue). Requires the query:read scope."
    },
    {
      "name": "Events",
      "description": "Event ingestion API (events.formo.so)"
    }
  ]
}
