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

# Goals

> Manage the goals defined on one of the current team's sites.

Manage the goals defined on one of the current team's sites. A goal has a **match type** (`match_type`) and, for most types, a **match value** (`match_value`) that scopes it:

* **Page goal** (`page`) — a `match_value` page path.
* **Scroll goal** (`scroll`) — a page path plus a `scroll_threshold`.
* **Event goal** (`event`) — a custom event name.
* **Autocapture goal** (`outbound`, `download`, `button`, `form`, `copy`) — an optional selector; omit `match_value` to match any event of that kind.

A site is addressed by `{siteKey}` (UUID or domain). All endpoints require `Authorization: Bearer {token}`.

### List goals

```http theme={null}
GET /api/sites/{siteKey}/goals
```

List a site's goals, ordered by display name. This is a **paginated** endpoint — it keeps the `data` / `meta` / `links` envelope. See [Conventions](/api/conventions) for the shared pagination envelope every paginated endpoint uses.

**Query parameters**

| Parameter  | Type   | Required | Description                                                          |
| ---------- | ------ | -------- | -------------------------------------------------------------------- |
| `search`   | string | No       | Case-insensitive match against the goal display name. Max 255 chars. |
| `per_page` | int    | No       | Page size, 1–100. Default 25.                                        |

**Response** — each item in `data`:

| Field              | Type                      | Description                                                                                                            |
| ------------------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id`               | string (uuid)             | Goal id.                                                                                                               |
| `display_name`     | string                    | Human-readable label.                                                                                                  |
| `type`             | string                    | One of `page`, `event`, `scroll`, `outbound`, `download`, `button`, `form`, `copy`.                                    |
| `match_value`      | string \| null            | The path, event name, or selector the goal matches; `null` for an autocapture goal that matches any event of its kind. |
| `match_operator`   | string                    | How `match_value` is compared, e.g. `is`, `matches_pattern`.                                                           |
| `scroll_threshold` | int                       | Scroll depth percentage; `-1` when not a scroll goal.                                                                  |
| `currency`         | string \| null            | Reporting currency for a revenue event goal.                                                                           |
| `custom_props`     | object                    | Map of custom event property name → value.                                                                             |
| `created_at`       | string (ISO 8601) \| null | When the goal was created.                                                                                             |

Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl "https://clickbase.so/api/sites/example.com/goals?per_page=50" \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  const params = new URLSearchParams({ per_page: '50' });
  await fetch(`https://clickbase.so/api/sites/example.com/goals?${params}`, {
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  })
  ```

  ```python title="Python" theme={null}
  requests.get(
      'https://clickbase.so/api/sites/example.com/goals',
      params={'per_page': 50},
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
  )
  ```
</CodeGroup>

```json theme={null}
{
    "data": [
        {
            "id": "1f0a...",
            "display_name": "Signup",
            "type": "event",
            "match_value": "Signup",
            "match_operator": "is",
            "scroll_threshold": -1,
            "currency": null,
            "custom_props": {},
            "created_at": "2026-07-11T12:00:00.000000Z"
        }
    ],
    "links": { "first": "...", "last": "...", "prev": null, "next": null },
    "meta": { "current_page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}
```

### Create a goal

```http theme={null}
POST /api/sites/{siteKey}/goals
```

Create a goal. Set `match_type`, and a `match_value` to scope it — required for `page`, `scroll`, and `event` types, optional for the autocapture types.

**Body parameters**

| Parameter          | Type   | Required    | Description                                                                                                                                                                                                                                           |
| ------------------ | ------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `match_type`       | string | Yes         | One of `page`, `event`, `scroll`, `outbound`, `download`, `button`, `form`, `copy`.                                                                                                                                                                   |
| `match_value`      | string | Conditional | Page path (`page`/`scroll`), custom event name (`event`), or selector (autocapture). Required for `page`/`scroll`/`event`. A path gets a leading `/` if missing. Max 2048 chars. An `event` value cannot be a reserved name (see below).              |
| `match_operator`   | string | No          | How `match_value` is compared. One of `is`, `is_not`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `matches_pattern`, `matches_regex`. Defaults to `is` for an `event` goal, `matches_pattern` (legacy wildcard) for every other type. |
| `scroll_threshold` | int    | Conditional | Scroll depth 0–100. Required for a `scroll` goal, rejected on any other type.                                                                                                                                                                         |
| `display_name`     | string | No          | Label. Max 255 chars. Derived from the match type and value when omitted. Must be unique on the site.                                                                                                                                                 |
| `currency`         | string | No          | ISO 4217 currency for a revenue event goal. Only valid on an `event` goal, and must match the site's reporting currency.                                                                                                                              |
| `custom_props`     | object | No          | Map of property name → value. Only valid on an `event` goal. Max 3 properties; keys ≤ 300 chars, values 1–2000 chars.                                                                                                                                 |

An `event` goal cannot use a reserved name (each returns `422`): `pageview`, `engagement`, `performance`, `web-vitals`, and the Stripe lifecycle goals `payment`, `free_trial`, `trial_started`, `trial_converted`, `subscription_started`, `subscription_renewed`, `subscription_upgraded`, `subscription_downgraded`, `subscription_cancel_scheduled`, `subscription_reactivated`, `subscription_ended` are written only by Clickbase. Those Stripe names appear automatically as filterable goals after Connect — you do not create them as custom goals. See [Track revenue](/track-revenue) for why they are off-limits.

Additional invariants enforced by the `CreateGoal` Action (each returns `422`): an identical goal config (same type, value, operator, scroll threshold, and custom props) already exists; the display name is taken; the site has reached its 1000-goal ceiling.

**Response** — the created goal (same fields as the list). Status: `201 Created`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST https://clickbase.so/api/sites/example.com/goals \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"match_type": "scroll", "match_value": "/pricing", "scroll_threshold": 75}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/goals', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({ match_type: 'scroll', match_value: '/pricing', scroll_threshold: 75 }),
  })
  ```

  ```python title="Python" theme={null}
  requests.post(
      'https://clickbase.so/api/sites/example.com/goals',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={'match_type': 'scroll', 'match_value': '/pricing', 'scroll_threshold': 75},
  )
  ```
</CodeGroup>

### Update a goal

```http theme={null}
PATCH /api/sites/{siteKey}/goals/{goal}
```

Update a goal's **full definition** — this replaces the goal's whole record, the same edit the web settings form performs. It is not a rename-only or partial-patch endpoint: it accepts the exact same body as create, re-validated the same way (`match_type` is required again, `match_value` is required again for `page`/`scroll`/`event`), and every field you omit is reset rather than left alone — omitting `currency` clears it, omitting `custom_props` clears the property filters, and omitting `display_name` re-derives it from the (possibly new) match type/value rather than keeping the old label. Always send the goal's complete definition on update, not just the field you changed.

**Path parameters**

| Parameter | Type   | Required | Description                                  |
| --------- | ------ | -------- | -------------------------------------------- |
| `siteKey` | string | Yes      | Site UUID or domain.                         |
| `goal`    | string | Yes      | Goal id. A foreign/unknown id returns `404`. |

**Body parameters** — identical set and rules as [create](#create-a-goal): `match_type`, `match_value`, `match_operator`, `scroll_threshold`, `display_name`, `currency`, `custom_props`. The duplicate-config and duplicate-display-name checks exclude this goal itself, so re-saving a goal under its own current config/name is never rejected as a collision with itself.

Metrics recompute live from ClickHouse against the goal's current definition, so retargeting `match_type`/`match_value`/`match_operator` is safe — historical numbers simply reflect the new definition going forward. A goal's funnel usage is unaffected: a funnel step is its own inline match definition, not a foreign key to a saved goal.

**Response** — the updated goal (same fields as the list). Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X PATCH https://clickbase.so/api/sites/example.com/goals/{goal} \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"match_type": "scroll", "match_value": "/pricing", "scroll_threshold": 90}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch(`https://clickbase.so/api/sites/example.com/goals/${goalId}`, {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({ match_type: 'scroll', match_value: '/pricing', scroll_threshold: 90 }),
  })
  ```

  ```python title="Python" theme={null}
  requests.patch(
      f'https://clickbase.so/api/sites/example.com/goals/{goal_id}',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={'match_type': 'scroll', 'match_value': '/pricing', 'scroll_threshold': 90},
  )
  ```
</CodeGroup>

### Batch-create event goals

```http theme={null}
POST /api/sites/{siteKey}/goals/batch
```

Create one `event`-type goal per given event name in a single call — the "add all suggested events" action on the web goals settings page. Any name that already exists as a goal, or otherwise fails a goal's create validation (e.g. a reserved event name), is silently skipped rather than aborting the whole batch.

**Body parameters**

| Parameter     | Type      | Required | Description                                     |
| ------------- | --------- | -------- | ----------------------------------------------- |
| `event_names` | string\[] | Yes      | At least 1 event name. Each name max 255 chars. |

**Response** — the site's full, current goal list (all goals, not just the ones this call created), newest first. Flat JSON array, not paginated. Status: `200 OK`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X POST https://clickbase.so/api/sites/example.com/goals/batch \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"event_names": ["signup", "purchase"]}'
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch('https://clickbase.so/api/sites/example.com/goals/batch', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({ event_names: ['signup', 'purchase'] }),
  })
  ```

  ```python title="Python" theme={null}
  requests.post(
      'https://clickbase.so/api/sites/example.com/goals/batch',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
      json={'event_names': ['signup', 'purchase']},
  )
  ```
</CodeGroup>

### Delete a goal

```http theme={null}
DELETE /api/sites/{siteKey}/goals/{goal}
```

Delete a goal. Cannot be undone. This does not touch any funnel: a funnel step is its own inline match definition, not a foreign key to a saved goal, so there is nothing to cascade.

**Path parameters**

| Parameter | Type   | Required | Description                                  |
| --------- | ------ | -------- | -------------------------------------------- |
| `siteKey` | string | Yes      | Site UUID or domain.                         |
| `goal`    | string | Yes      | Goal id. A foreign/unknown id returns `404`. |

Status: `204 No Content`.

<CodeGroup>
  ```bash title="curl" theme={null}
  curl -X DELETE https://clickbase.so/api/sites/example.com/goals/{goal} \
    -H "Authorization: Bearer {token}" \
    -H "Accept: application/json"
  ```

  ```javascript title="JavaScript" theme={null}
  await fetch(`https://clickbase.so/api/sites/example.com/goals/${goalId}`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  })
  ```

  ```python title="Python" theme={null}
  requests.delete(
      f'https://clickbase.so/api/sites/example.com/goals/{goal_id}',
      headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
  )
  ```
</CodeGroup>
