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) — amatch_valuepage path. - Scroll goal (
scroll) — a page path plus ascroll_threshold. - Event goal (
event) — a custom event name. - Autocapture goal (
outbound,download,button,form,copy) — an optional selector; omitmatch_valueto 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'},
)