> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formo.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Profiles

> Search and filter wallet profiles by address, socials, labels, net worth, chain activity, and more. Returns paginated results with full profile data.

Search and filter wallet profiles across your project with advanced filtering capabilities. You can look up an exact wallet address, run a free-text search across addresses and social fields, and combine that with structured filters in the request body.

## Authentication

This endpoint requires authentication using a **Workspace API Key** with `profiles:read` permission. Include the API key in your request headers:

```bash theme={null}
Authorization: Bearer <your_workspace_api_key>
```

<Note>
  The API key needs to have the **read permission for profiles** in the API settings. You can configure this in your workspace API key settings.
</Note>

## Query Parameters

### `address`

Filter by a specific wallet address (exact match). Accepts an EVM address, a Solana address, or an ENS name (e.g. `vitalik.eth`) which is resolved to an address before filtering. An unresolvable ENS name or an otherwise invalid value returns a `400 BAD_REQUEST`.

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  "https://api.formo.so/v0/profiles?address=0x1234..."

# ENS names are resolved automatically
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  "https://api.formo.so/v0/profiles?address=vitalik.eth"
```

### `search`

Case-insensitive free-text search across wallet addresses and all supported social fields. This includes values from both global wallet profiles and project-scoped identify overrides.

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  "https://api.formo.so/v0/profiles?search=bob_override"
```

### `expand`

Comma-separated list of optional sections to include in each profile response.

Supported values:

* `apps` - DeFi app interactions and balances
* `chains` - Per-chain activity metrics
* `tokens` - Token holdings
* `labels` - Wallet labels

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  "https://api.formo.so/v0/profiles?expand=apps,chains,labels"
```

<Note>
  Expanding fields like `chains`, `tokens`, or `apps` increases response size and latency. The collections are capped at **50 items** each.
</Note>

### `order_by`

Field to sort results by. Supported values:

* `last_onchain` (default) - Last on-chain activity timestamp
* `first_onchain` - First on-chain activity timestamp
* `net_worth_usd` - Total net worth
* `updated_at` - Last profile update timestamp
* `tx_count` - Total transaction count
* `first_seen` - First seen timestamp in your app
* `last_seen` - Last seen timestamp in your app
* `num_sessions` - Number of sessions
* `revenue` - Total revenue
* `volume` - Total volume
* `points` - Total points

### `order_dir`

Sort direction:

* `desc` (default) - Descending order
* `asc` - Ascending order

### `page`

1-indexed page number to return.

* Default: `1`

### `size`

Page size - number of profiles per page.

* Default: `100`
* Maximum: `1000`

## Request Body (Filters)

The search endpoint supports rich filtering capabilities through a JSON request body. The body contains a filter object with `conditions` and `logic`.

### Filter Schema

```json theme={null}
{
  "conditions": [
    {
      "field": "users.net_worth_usd",
      "op": "gt",
      "value": 1000
    }
  ],
  "logic": "and"
}
```

| Field        | Type   | Description                                                                                |
| ------------ | ------ | ------------------------------------------------------------------------------------------ |
| `conditions` | array  | Array of filter conditions                                                                 |
| `logic`      | string | How to combine conditions: `and` (all must match) or `or` (any must match). Default: `and` |

### Filter Condition

Each condition in the `conditions` array has:

| Field   | Type                                 | Description                                                                                                       |
| ------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `field` | string                               | The field path to filter on (see Field Reference below)                                                           |
| `op`    | string                               | The comparison operator                                                                                           |
| `value` | string \| number \| boolean \| array | The value to compare against                                                                                      |
| `scope` | string                               | (Optional) Only for token filters: `any` (wallet + protocols) or `protocol` (specific protocol only)              |
| `appId` | string                               | (Optional) Only for token filters with `scope: "protocol"`. The DeFi protocol ID (e.g., `aave-v3`, `compound-v3`) |

### Operators

| Operator   | Description                                             | Example                                                                    |
| ---------- | ------------------------------------------------------- | -------------------------------------------------------------------------- |
| `eq`       | Equals                                                  | `{"field": "users.device", "op": "eq", "value": "Desktop"}`                |
| `neq`      | Not equals                                              | `{"field": "users.os", "op": "neq", "value": "Windows"}`                   |
| `gt`       | Greater than                                            | `{"field": "users.net_worth_usd", "op": "gt", "value": 1000}`              |
| `gte`      | Greater than or equal                                   | `{"field": "users.volume", "op": "gte", "value": 100}`                     |
| `lt`       | Less than                                               | `{"field": "users.net_worth_usd", "op": "lt", "value": 50000}`             |
| `lte`      | Less than or equal                                      | `{"field": "chains.1.balance", "op": "lte", "value": 10000}`               |
| `in`       | In array                                                | `{"field": "users.lifecycle", "op": "in", "value": ["New", "Power user"]}` |
| `nin`      | Not in array                                            | `{"field": "users.location", "op": "nin", "value": ["US", "UK"]}`          |
| `contains` | Case-insensitive substring match for social fields only | `{"field": "users.twitter", "op": "contains", "value": "vitalik"}`         |

***

## Field Reference

### User Fields (`users.*`)

Filter by user engagement and profile data. Use the format `users.{attribute}`.

#### Profile Fields

| Field                 | Type   | Description            |
| --------------------- | ------ | ---------------------- |
| `users.net_worth_usd` | number | Total net worth in USD |
| `users.volume`        | number | Total trading volume   |
| `users.revenue`       | number | Total revenue          |
| `users.points`        | number | Total points           |

**Example - Find users with net worth > \$10,000:**

```json theme={null}
{
  "conditions": [
    { "field": "users.net_worth_usd", "op": "gt", "value": 10000 }
  ],
  "logic": "and"
}
```

**Example - Find high-volume users:**

```json theme={null}
{
  "conditions": [
    { "field": "users.volume", "op": "gt", "value": 5000 }
  ],
  "logic": "and"
}
```

#### Engagement Fields

| Field            | Type   | Description                                 |
| ---------------- | ------ | ------------------------------------------- |
| `users.device`   | string | Device type (e.g., `Desktop`, `Mobile`)     |
| `users.browser`  | string | Browser name (e.g., `Chrome`, `Safari`)     |
| `users.os`       | string | Operating system (e.g., `macOS`, `Windows`) |
| `users.location` | string | Country code (e.g., `US`, `NG`)             |

**Example - Find mobile users from the US:**

```json theme={null}
{
  "conditions": [
    { "field": "users.device", "op": "eq", "value": "Mobile" },
    { "field": "users.location", "op": "eq", "value": "US" }
  ],
  "logic": "and"
}
```

#### UTM & Referral Fields

| Field                      | Type   | Description             |
| -------------------------- | ------ | ----------------------- |
| `users.first_utm_source`   | string | First UTM source        |
| `users.last_utm_source`    | string | Last UTM source         |
| `users.first_utm_medium`   | string | First UTM medium        |
| `users.last_utm_medium`    | string | Last UTM medium         |
| `users.first_utm_campaign` | string | First UTM campaign      |
| `users.last_utm_campaign`  | string | Last UTM campaign       |
| `users.first_utm_content`  | string | First UTM content       |
| `users.last_utm_content`   | string | Last UTM content        |
| `users.first_utm_term`     | string | First UTM term          |
| `users.last_utm_term`      | string | Last UTM term           |
| `users.first_referrer`     | string | First referrer domain   |
| `users.last_referrer`      | string | Last referrer domain    |
| `users.first_referrer_url` | string | First referrer full URL |
| `users.last_referrer_url`  | string | Last referrer full URL  |
| `users.first_ref`          | string | First referral code     |
| `users.last_ref`           | string | Last referral code      |

**Example - Find users from Google Ads campaign:**

```json theme={null}
{
  "conditions": [
    { "field": "users.first_utm_source", "op": "eq", "value": "google" },
    { "field": "users.first_utm_medium", "op": "eq", "value": "cpc" }
  ],
  "logic": "and"
}
```

#### Lifecycle Filter

Filter by user lifecycle stage. Valid values: `At Risk`, `Churned`, `New`, `Power user`, `Resurrected`, `Returning`.

| Field             | Type   | Description          |
| ----------------- | ------ | -------------------- |
| `users.lifecycle` | string | User lifecycle stage |

**Example - Find new and power users:**

```json theme={null}
{
  "conditions": [
    { "field": "users.lifecycle", "op": "in", "value": ["New", "Power user"] }
  ],
  "logic": "and"
}
```

#### Social Fields

Social fields support two modes:

* Presence checks: use an empty string value to match profiles where the field is present.
* Value matching: use `eq`, `neq`, or `contains` with a non-empty string to match the actual social value.

| Field             | Description               |
| ----------------- | ------------------------- |
| `users.ens`       | ENS name                  |
| `users.farcaster` | Farcaster username        |
| `users.lens`      | Lens handle               |
| `users.basenames` | Base names                |
| `users.linea`     | Linea identifier          |
| `users.discord`   | Discord username          |
| `users.telegram`  | Telegram username         |
| `users.website`   | Website URL               |
| `users.twitter`   | Twitter/X handle          |
| `users.github`    | GitHub username           |
| `users.linkedin`  | LinkedIn profile          |
| `users.email`     | Email address             |
| `users.instagram` | Instagram handle          |
| `users.facebook`  | Facebook profile          |
| `users.tiktok`    | TikTok handle             |
| `users.youtube`   | YouTube channel or handle |
| `users.reddit`    | Reddit username           |

**Example - Find users with any email present:**

```json theme={null}
{
  "conditions": [
    { "field": "users.email", "op": "eq", "value": "" }
  ],
  "logic": "and"
}
```

**Example - Find users with both ENS and Farcaster present:**

```json theme={null}
{
  "conditions": [
    { "field": "users.ens", "op": "eq", "value": "" },
    { "field": "users.farcaster", "op": "eq", "value": "" }
  ],
  "logic": "and"
}
```

**Example - Find users whose Twitter contains `bob`:**

```json theme={null}
{
  "conditions": [
    { "field": "users.twitter", "op": "contains", "value": "bob" }
  ],
  "logic": "and"
}
```

**Example - Find an exact email match:**

```json theme={null}
{
  "conditions": [
    { "field": "users.email", "op": "eq", "value": "alice@formo.so" }
  ],
  "logic": "and"
}
```

***

### Chain Filters (`chains.*`)

Filter by per-chain net worth. Supports filtering across all chains or specific chains.

#### All Chains

Use `chains.balance` to filter across all chains (returns profiles where **any** chain matches).

**Example - Find users with >\$1,000 on any chain:**

```json theme={null}
{
  "conditions": [
    { "field": "chains.balance", "op": "gt", "value": 1000 }
  ],
  "logic": "and"
}
```

#### Specific Chain

Use `chains.{chain_id}.balance` to filter on a specific chain.

Common chain IDs:

* `1` - Ethereum Mainnet
* `137` - Polygon
* `42161` - Arbitrum One
* `10` - Optimism
* `8453` - Base
* `56` - BNB Chain
* `43114` - Avalanche

**Example - Find users with >\$5,000 on Ethereum:**

```json theme={null}
{
  "conditions": [
    { "field": "chains.1.balance", "op": "gt", "value": 5000 }
  ],
  "logic": "and"
}
```

**Example - Find users with >\$1,000 on both Ethereum and Polygon:**

```json theme={null}
{
  "conditions": [
    { "field": "chains.1.balance", "op": "gt", "value": 1000 },
    { "field": "chains.137.balance", "op": "gt", "value": 1000 }
  ],
  "logic": "and"
}
```

***

### App Filters (`apps.*`)

Filter by DeFi app interactions and balances.

#### All Chains

Use `apps.{app_id}.balance` to filter by app balance across all chains.

**Example - Find users with >\$1,000 in Uniswap:**

```json theme={null}
{
  "conditions": [
    { "field": "apps.uniswap.balance", "op": "gt", "value": 1000 }
  ],
  "logic": "and"
}
```

#### Specific Chain

Use `apps.{chain_id}.{app_id}.balance` to filter by app balance on a specific chain.

**Example - Find users with >\$500 in Aave on Ethereum:**

```json theme={null}
{
  "conditions": [
    { "field": "apps.1.aave.balance", "op": "gt", "value": 500 }
  ],
  "logic": "and"
}
```

***

### Token Filters (`tokens.*`)

Filter by token holdings. Supports optional `scope` and `appId` parameters.

#### Token Filter Parameters

| Parameter | Type   | Description                                                                                                 |
| --------- | ------ | ----------------------------------------------------------------------------------------------------------- |
| `scope`   | string | `any` (default) = wallet + all protocols, `protocol` = specific protocol only                               |
| `appId`   | string | Only for `scope: "protocol"`. Specifies the DeFi protocol ID (e.g., `aave-v3`, `compound-v3`, `uniswap-v3`) |

#### All Chains

Use `tokens.{token_address}.balance` to filter by token balance across all chains.

**Example - Find users holding >1000 USDC (any chain):**

```json theme={null}
{
  "conditions": [
    {
      "field": "tokens.0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.balance",
      "op": "gt",
      "value": 1000
    }
  ],
  "logic": "and"
}
```

#### Specific Chain

Use `tokens.{chain_id}.{token_address}.balance` to filter by token balance on a specific chain.

**Example - Find users with >500 USDC on Ethereum:**

```json theme={null}
{
  "conditions": [
    {
      "field": "tokens.1.0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.balance",
      "op": "gt",
      "value": 500
    }
  ],
  "logic": "and"
}
```

#### Protocol-Specific Token Filters

Use `scope: "protocol"` with `appId` to filter for tokens deposited in a specific DeFi protocol.

**Example - Find users with USDC deposited in any protocol:**

```json theme={null}
{
  "conditions": [
    {
      "field": "tokens.0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.balance",
      "op": "gt",
      "value": 0,
      "scope": "protocol"
    }
  ],
  "logic": "and"
}
```

**Example - Find users with USDC deposited in Aave V3:**

```json theme={null}
{
  "conditions": [
    {
      "field": "tokens.0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.balance",
      "op": "gt",
      "value": 1000,
      "scope": "protocol",
      "appId": "aave-v3"
    }
  ],
  "logic": "and"
}
```

**Example - Find users with ETH staked in Lido:**

```json theme={null}
{
  "conditions": [
    {
      "field": "tokens.0x0000000000000000000000000000000000000000.balance",
      "op": "gt",
      "value": 1,
      "scope": "protocol",
      "appId": "lido"
    }
  ],
  "logic": "and"
}
```

***

### Label Filters (`labels.*`)

Filter by wallet labels. Use the format `labels.{tag_id}`.

Common label IDs:

* `coinbase.verified_account` - Coinbase verified account (boolean)
* `coinbase.verified_country` - Coinbase verified country code
* `coinbase.verified_coinbase_one` - Coinbase One membership (boolean)
* `sanctions.designated` - Sanctioned address (boolean)
* `passport.models_aggregate_score` - Passport aggregate score (0-100)
* `passport.unique_humanity_score` - Passport uniqueness score

**Example - Find Coinbase verified users:**

```json theme={null}
{
  "conditions": [
    { "field": "labels.coinbase.verified_account", "op": "eq", "value": "true" }
  ],
  "logic": "and"
}
```

**Example - Find US-verified Coinbase users:**

```json theme={null}
{
  "conditions": [
    { "field": "labels.coinbase.verified_country", "op": "eq", "value": "US" }
  ],
  "logic": "and"
}
```

**Example - Find users with high Passport score:**

```json theme={null}
{
  "conditions": [
    { "field": "labels.passport.models_aggregate_score", "op": "gte", "value": "50" }
  ],
  "logic": "and"
}
```

***

## Combined Filter Examples

### High-Value Web3 Users

Find users with >\$10,000 net worth, ENS name, and activity on Ethereum:

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": [
      { "field": "users.net_worth_usd", "op": "gt", "value": 10000 },
      { "field": "users.ens", "op": "eq", "value": true },
      { "field": "chains.1.balance", "op": "gt", "value": 0 }
    ],
    "logic": "and"
  }' \
  "https://api.formo.so/v0/profiles?expand=chains,labels"
```

### Active DeFi Users

Find users with activity in Uniswap or Aave:

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": [
      { "field": "apps.uniswap.balance", "op": "gt", "value": 0 },
      { "field": "apps.aave.balance", "op": "gt", "value": 0 }
    ],
    "logic": "or"
  }' \
  "https://api.formo.so/v0/profiles?expand=apps"
```

### Verified Power Users

Find Coinbase-verified power users from specific countries:

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": [
      { "field": "labels.coinbase.verified_account", "op": "eq", "value": "true" },
      { "field": "labels.coinbase.verified_country", "op": "in", "value": ["US", "UK", "DE"] },
      { "field": "users.lifecycle", "op": "eq", "value": "Power user" }
    ],
    "logic": "and"
  }' \
  "https://api.formo.so/v0/profiles"
```

### Multi-Chain Whales

Find users with significant holdings across multiple chains:

```bash theme={null}
curl -sS -X GET \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": [
      { "field": "chains.1.balance", "op": "gt", "value": 10000 },
      { "field": "chains.137.balance", "op": "gt", "value": 5000 },
      { "field": "chains.42161.balance", "op": "gt", "value": 5000 }
    ],
    "logic": "and"
  }' \
  "https://api.formo.so/v0/profiles?expand=chains&order_by=net_worth_usd&order_dir=desc"
```

***

## Response

The response is a [paginated envelope](/api/overview#pagination) of wallet profiles.

```json theme={null}
{
  "data": [
    {
      "address": "0x9CC3cB28cd94eB4423B15cdA73346e204f59a407",
      "net_worth_usd": 125000.5,
      "ens": "example.eth",
      "tx_count": 1234,
      "first_onchain": "2024-01-15T10:30:00Z",
      "last_onchain": "2025-01-20T14:22:00Z",
      "lifecycle": "Returning"
    }
  ],
  "total": 150,
  "page": 1,
  "size": 100,
  "has_more": true
}
```

### Response Fields

| Field      | Type    | Description                                                     |
| ---------- | ------- | --------------------------------------------------------------- |
| `data`     | array   | Array of wallet profiles matching the search criteria           |
| `total`    | integer | Total number of profiles matching the criteria across all pages |
| `page`     | integer | 1-indexed page number echoed from the request                   |
| `size`     | integer | Page size echoed from the request                               |
| `has_more` | boolean | `true` if there are pages after the current one                 |

### Profile Fields

Each profile in the `data` array includes:

| Field           | Type            | Description                                                                                 |
| --------------- | --------------- | ------------------------------------------------------------------------------------------- |
| `address`       | string          | Wallet address                                                                              |
| `net_worth_usd` | number          | Total net worth in USD                                                                      |
| `tx_count`      | integer         | Total transaction count                                                                     |
| `first_onchain` | string          | First on-chain activity timestamp (ISO 8601)                                                |
| `last_onchain`  | string          | Last on-chain activity timestamp (ISO 8601)                                                 |
| `lifecycle`     | string          | User lifecycle stage: `At Risk`, `Churned`, `New`, `Power user`, `Resurrected`, `Returning` |
| `ens`           | string \| null  | ENS name                                                                                    |
| `farcaster`     | string \| null  | Farcaster username                                                                          |
| `twitter`       | string \| null  | Twitter/X handle                                                                            |
| `discord`       | string \| null  | Discord username                                                                            |
| `telegram`      | string \| null  | Telegram username                                                                           |
| `email`         | string \| null  | Email address                                                                               |
| `first_seen`    | string \| null  | First seen timestamp in your app                                                            |
| `last_seen`     | string \| null  | Last seen timestamp in your app                                                             |
| `num_sessions`  | integer \| null | Total number of sessions                                                                    |
| `revenue`       | number \| null  | Total revenue                                                                               |
| `volume`        | number \| null  | Total volume                                                                                |
| `points`        | number \| null  | Total points                                                                                |
| `chains`        | array \| null   | Per-chain data (when expanded)                                                              |
| `apps`          | array \| null   | DeFi app data (when expanded)                                                               |
| `tokens`        | array \| null   | Token holdings (when expanded)                                                              |
| `labels`        | array \| null   | Wallet labels (when expanded)                                                               |

## Error Responses

| Status | Code                    | Description                                                |
| ------ | ----------------------- | ---------------------------------------------------------- |
| 400    | `BAD_REQUEST`           | Invalid query parameters or malformed JSON in request body |
| 401    | `UNAUTHORIZED`          | Missing or invalid API key                                 |
| 403    | `FORBIDDEN`             | API key does not have `profiles:read` permission           |
| 500    | `INTERNAL_SERVER_ERROR` | Failed to fetch wallet profile data                        |


## OpenAPI

````yaml GET /v0/profiles
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.


    **Auth.** All endpoints require a workspace API key with the appropriate
    scopes (see `x-api-scopes`).


    **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.


    **Errors.** Every non-2xx response uses the `Error` envelope: `{ 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).


    **Idempotency.** Pass an `Idempotency-Key` header on POST/PUT/PATCH/DELETE
    to make retries safe; the response is cached for 24 h and replayed on
    duplicate keys.
  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: []
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)
paths:
  /v0/profiles:
    get:
      tags:
        - Profiles
      summary: Search wallet profiles
      operationId: searchProfiles
      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: 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
            minimum: 1
            default: 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:
                  conditions:
                    - field: users.net_worth_usd
                      op: gt
                      value: 10000
                  logic: and
              chainFilter:
                summary: Users active on Ethereum with >$1k balance
                value:
                  conditions:
                    - field: chains.1.balance
                      op: gt
                      value: 1000
                  logic: and
              labelFilter:
                summary: Coinbase verified users
                value:
                  conditions:
                    - field: labels.coinbase.verified_account
                      op: eq
                      value: 'true'
                  logic: and
              tokenFilter:
                summary: Users holding USDC in any protocol
                value:
                  conditions:
                    - field: >-
                        tokens.0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.balance
                      op: gt
                      value: 0
                      scope: any
                  logic: and
      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'
components:
  schemas:
    ProfileFilter:
      type: object
      description: Filter conditions for profile search
      properties:
        conditions:
          type: array
          items:
            $ref: '#/components/schemas/FilterCondition'
        logic:
          type: string
          enum:
            - and
            - or
          default: and
    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`).
    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
            - Resurrected
            - At Risk
            - Churned
        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
        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
    FilterCondition:
      type: object
      required:
        - field
        - op
        - value
      properties:
        field:
          type: string
          description: >-
            Field path. Profile: users.net_worth_usd, users.volume,
            users.revenue, users.points. Engagement: users.device,
            users.browser, users.os, users.location. Lifecycle: users.lifecycle.
            Socials: users.ens, users.farcaster, etc. Chains: chains.balance,
            chains.{chain_id}.balance. Apps: apps.{app_id}.balance. Tokens:
            tokens.{address}.balance. Labels: labels.{tag_id}
        op:
          type: string
          enum:
            - eq
            - neq
            - gt
            - gte
            - lt
            - lte
            - in
            - nin
        value:
          description: Filter value (string, number, boolean, or array)
        scope:
          type: string
          enum:
            - any
            - protocol
          description: 'For token filters: any=wallet+protocol, protocol=specific app'
        appId:
          type: string
          description: For token scope=protocol (e.g. aave-v3)
    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
    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
  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
    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
  securitySchemes:
    WorkspaceApiKey:
      type: http
      scheme: bearer
      description: >-
        Workspace API key (e.g. `formo_xxx`). Create one in the Formo dashboard
        under Team Settings > API Keys.

````