REST API
Sites
Manage the current team's sites (properties) and read their topline analytics. Every endpoint is scoped to the token's team. A site is addressed by {siteKey} — either its UUID or its domain (a domain segment may contain dots).
All endpoints require Authorization: Bearer {token}.
List sites
GET /api/sites
List the current team's sites, each with a period-scoped overview (default last 24 hours). Pinned sites float to the top.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search |
string | No | Case-insensitive match against the site domain. Max 255 chars. |
sort |
string | No | One of most_visitors, fewest_visitors, domain_asc, domain_desc. Default most_visitors. |
period |
string | No | One of 24h, 7d, 30d. Default 24h. Invalid values fall back to 24h. |
Response — flat JSON array of:
| Field | Type | Description |
|---|---|---|
id |
string (uuid) | Site id. |
domain |
string | Site domain. |
favicon_url |
string (url) | The site's favicon, proxied through Clickbase. |
timezone |
string | IANA timezone for the site's reports. |
created_at |
string (ISO 8601) | null | When the site was created. |
has_data |
bool | Whether the site has received its first event. |
is_pinned |
bool | Whether the site is pinned to the top of the list. |
visitors |
int | Unique visitors in the selected period. |
change |
int | Percent change versus the previous equal window. |
sparkline |
int[] | Visitor counts per bucket (24 hourly, or 7/30 daily). |
revenue |
int | Period revenue in minor units (site reporting currency). |
revenue_sparkline |
int[] | Non-refunded revenue per bucket (same length as sparkline). |
currency |
string | Site reporting currency (ISO). |
has_revenue |
bool | Stripe connected or any API payment recorded for the site. |
revenue / revenue_sparkline are period payments (Stripe or API invoices in the window), not MRR. There is no top-level workspace summary on this endpoint — the response stays a flat array for compatibility. Use the MCP list-sites-tool (or the web dashboard) when you need aggregated totals.
Breaking change: visitors_24h / change_24h were renamed to visitors / change and are always scoped to the selected period (default 24h).
Status: 200 OK.
curl https://clickbase.so/api/sites \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.get('https://clickbase.so/api/sites', headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'})
Create a site
POST /api/sites
Create a new site in the current team.
This endpoint is gated by the team's site quota (SitePolicy::create, via Gate::authorize('create', [Site::class, $team])): a team at its plan's site ceiling, or with no active plan and no trial, is rejected with 403 Forbidden. The ceiling is a single global setting (config('clickbase.max_sites_per_team'), default 30) that applies to every team regardless of plan.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | Max 255 chars. Must be unique within the team (else 422, "This team already has a site for that domain."). |
timezone |
string | No | A valid IANA timezone. Max 255 chars. Defaults to UTC. |
Response — a single site (see Response fields). Status: 201 Created.
curl -X POST https://clickbase.so/api/sites \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"domain": "example.com", "timezone": "Europe/Lisbon"}'
await fetch('https://clickbase.so/api/sites', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ domain: 'example.com', timezone: 'Europe/Lisbon' }),
})
requests.post(
'https://clickbase.so/api/sites',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'domain': 'example.com', 'timezone': 'Europe/Lisbon'},
)
{
"id": "9b2c1f7e-...",
"domain": "example.com",
"favicon_url": "https://clickbase.so/favicon/example.com",
"timezone": "Europe/Lisbon",
"currency": "USD",
"tracking_mode": "cookieless",
"is_public": false,
"is_pinned": false,
"has_data": false,
"track_errors": false,
"track_web_vitals": false,
"track_session_replay": false,
"created_at": "2026-07-11T12:00:00.000000Z"
}
Site overview
GET /api/sites/{siteKey}/overview
Topline analytics for one site over a date range, with the equivalent previous period for a delta.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteKey |
string | Yes | Site UUID or domain. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
range |
string | No | Preset range: today, yesterday, last_7_days, last_30_days, this_month, last_month, last_12_months, all. Default today. Ignored when from and to are given. |
from |
string | No | Custom range start, Y-m-d. Requires to. |
to |
string | No | Custom range end, Y-m-d. Requires from. |
Response — a flat object:
| Field | Type | Description |
|---|---|---|
kpi |
object | The site's headline KPI descriptor — always { metric: "mrr", label: "MRR change", currency: string, static: false } for SaaS. Always present. |
pageviews |
int | Total pageviews. |
visitors |
int | Unique visitors. |
sessions |
int | Sessions. |
views_per_visit |
float | Pageviews per session. |
bounce_rate |
float | Percentage of single-pageview sessions. |
avg_session_seconds |
float | Average session duration in seconds. |
custom_events |
int | Custom event count. |
revenue |
int | Total payment revenue (minor units). Present only when the current or previous period has at least one payment. |
recurring_revenue |
int | The recurring subset of revenue. Present under the same condition as revenue. |
payments |
int | Payment event count. Present under the same condition as revenue. |
mrr |
int | Net list-price MRR change across the range in minor units (SumMrrMovements of signed start/end/upgrade/downgrade deltas — not Instant end−start). Present only when the site has a connected Stripe integration. Current stock MRR lives on the revenue MRR endpoint. |
new_customers |
int | Distinct customers whose first payment for the site falls in the range (CountNewCustomers). Same Stripe presence gate as mrr. |
conversion_rate |
float | new_customers ÷ visitors as a percentage (one decimal). Same Stripe gate. In goal-filter mode this key means unique conversions ÷ visitors instead. |
mrr_per_visitor |
int | Instant MRR at range end ÷ visitors in minor units (0 when visitors is 0). Same Stripe gate. |
days |
int | Inclusive day span of the current period. |
previous |
object | The same metrics for the equivalent previous period (same optional-by-presence rules). |
Status: 200 OK.
curl "https://clickbase.so/api/sites/example.com/overview?range=last_7_days" \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/overview?range=last_7_days', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.get(
'https://clickbase.so/api/sites/example.com/overview',
params={'range': 'last_7_days'},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Update domain
PATCH /api/sites/{siteKey}/domain
Change a site's domain. The domain keys the dashboard and public share URLs — update any bookmarks/integrations after this runs.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | Max 255 chars. Must be unique within the team, excluding this site (else 422, "This team already has a site for that domain."). |
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/domain \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"domain": "newdomain.com"}'
await fetch('https://clickbase.so/api/sites/example.com/domain', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ domain: 'newdomain.com' }),
})
requests.patch(
'https://clickbase.so/api/sites/example.com/domain',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'domain': 'newdomain.com'},
)
Update timezone
PATCH /api/sites/{siteKey}/timezone
Change the timezone that defines the site's reporting day.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
timezone |
string | Yes | A valid IANA timezone. Max 255 chars. |
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/timezone \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"timezone": "America/New_York"}'
await fetch('https://clickbase.so/api/sites/example.com/timezone', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ timezone: 'America/New_York' }),
})
requests.patch(
'https://clickbase.so/api/sites/example.com/timezone',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'timezone': 'America/New_York'},
)
Update currency
PATCH /api/sites/{siteKey}/currency
Change a site's reporting currency.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
currency |
string | Yes | ISO 4217 currency code, exactly 3 chars. |
Only a fixed subset of currencies is accepted: USD, EUR, GBP, BRL, JPY, CAD, AUD, CHF, CNY, INR, MXN, ZAR, SEK, NOK, DKK, PLN, CZK, HUF, RUB, TRY, KRW, SGD, HKD, NZD, THB, IDR, MYR, PHP, VND, AED. Anything else returns 422 ("This currency is not supported.").
The UpdateSiteCurrency Action also rejects the change (both 422) when:
- The site has any revenue goal (a goal with a non-null
currency) —"Delete your revenue goals before changing the site currency."A revenue goal's target amount is denominated in the currency active when it was created, so switching the site currency afterward would silently fork reporting into two currencies that can never be summed together again. - The site has payments recorded in a different reporting currency than the target (checked via
SiteHasPaymentsInAnotherCurrency, which scans ClickHouse for any payment event whoserevenue_reporting_currencydiffers) —"This team has payments recorded in another currency. Reconcile or remove them before changing the reporting currency."Every revenue reader sums payment amounts with no currency filter, so mixed-currency payments would add different minor units into one meaningless total.
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/currency \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"currency": "EUR"}'
await fetch('https://clickbase.so/api/sites/example.com/currency', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ currency: 'EUR' }),
})
requests.patch(
'https://clickbase.so/api/sites/example.com/currency',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'currency': 'EUR'},
)
Update tracking mode
PATCH /api/sites/{siteKey}/tracking-mode
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tracking_mode |
string | Yes | cookieless or cookie. |
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/tracking-mode \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"tracking_mode": "cookie"}'
await fetch('https://clickbase.so/api/sites/example.com/tracking-mode', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ tracking_mode: 'cookie' }),
})
requests.patch(
'https://clickbase.so/api/sites/example.com/tracking-mode',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'tracking_mode': 'cookie'},
)
Update sharing
PATCH /api/sites/{siteKey}/sharing
Toggle whether the site's dashboard is publicly shared.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
is_public |
bool | Yes | Whether the dashboard is publicly shared. |
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/sharing \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"is_public": true}'
await fetch('https://clickbase.so/api/sites/example.com/sharing', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ is_public: true }),
})
requests.patch(
'https://clickbase.so/api/sites/example.com/sharing',
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
json={'is_public': True},
)
Toggle pin
PATCH /api/sites/{siteKey}/pin
Pin the site to the top of the team's site list, or unpin it if it's already pinned. No body.
Response — a single site (see Response fields). Status: 200 OK.
curl -X PATCH https://clickbase.so/api/sites/example.com/pin \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/pin', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.patch('https://clickbase.so/api/sites/example.com/pin', headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'})
Delete a site
DELETE /api/sites/{siteKey}
Permanently delete a site. Queues the removal of its analytics events and session replays (both in ClickHouse and their R2 blobs). Cannot be undone.
Requires Admin or Owner. Authorized via SitePolicy::delete, which checks the site:delete team permission — only the Owner and Admin roles have it. A plain Member gets 403 Forbidden.
Status: 204 No Content.
curl -X DELETE https://clickbase.so/api/sites/example.com \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.delete('https://clickbase.so/api/sites/example.com', headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'})
Timezone & currency options
GET /api/timezones
GET /api/currencies
The accepted values for Update timezone and Update currency, as combobox options — the same lists the dashboard's site-settings selects use. Neither is scoped to a site. GET /api/timezones lists every IANA timezone identifier; GET /api/currencies lists every reporting currency UpdateSiteCurrency accepts, with a human-readable name from ICU.
Response — a flat JSON array of:
| Field | Type | Description |
|---|---|---|
value |
string | The identifier to send (e.g. America/New_York, EUR). |
label |
string | Human-readable label (e.g. EUR — Euro). |
Status: 200 OK.
curl https://clickbase.so/api/currencies \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/currencies', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
})
requests.get('https://clickbase.so/api/currencies', headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'})
Response fields
The single-site endpoints (store, updateDomain, updateTimezone, updateCurrency, updateTrackingMode, updateSharing, togglePin) return:
| Field | Type | Description |
|---|---|---|
id |
string (uuid) | Site id. |
domain |
string | Site domain. |
favicon_url |
string (url) | The site's favicon, proxied through Clickbase. |
timezone |
string | IANA timezone. |
currency |
string | Reporting currency (ISO 4217). Defaults to USD. |
tracking_mode |
string | cookieless or cookie. Defaults to cookieless. |
is_public |
bool | Whether the dashboard is publicly shared. |
is_pinned |
bool | Whether the site is pinned to the top of the list. |
has_data |
bool | Whether the site has received its first event. |
track_errors |
bool | Whether error tracking is enabled. |
track_web_vitals |
bool | Whether Web Vitals / performance tracking is enabled. |
track_session_replay |
bool | Whether session replay is enabled. |
created_at |
string (ISO 8601) | null | When the site was created. |
This is a different (richer) shape than the list sites endpoint, which returns a lighter per-site overview (favicon_url, visitors, change, sparkline, revenue, etc.) instead.