REST API

Goals

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

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

curl "https://clickbase.so/api/sites/example.com/goals?per_page=50" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
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' },
})
requests.get(
    'https://clickbase.so/api/sites/example.com/goals',
    params={'per_page': 50},
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
{
    "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

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

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}'
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 }),
})
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},
)

Update a goal

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

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}'
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 }),
})
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},
)

Batch-create event goals

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.

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"]}'
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'] }),
})
requests.post(
    'https://clickbase.so/api/sites/example.com/goals/batch',
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
    json={'event_names': ['signup', 'purchase']},
)

Delete a goal

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.

curl -X DELETE https://clickbase.so/api/sites/example.com/goals/{goal} \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
await fetch(`https://clickbase.so/api/sites/example.com/goals/${goalId}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.delete(
    f'https://clickbase.so/api/sites/example.com/goals/{goal_id}',
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)