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 whose revenue_reporting_currency differs) — "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.