REST API

Conventions

The cross-cutting contract every authenticated Clickbase endpoint shares — base URL, headers, the response envelope, money, date ranges, filters, and error codes. Read this once and the per-endpoint pages only need to describe what is unique to them.

Base URL & headers

All endpoints live on the root domain, under the /api prefix:

https://clickbase.so/api

Send these headers on every authenticated request:

Header Value
Authorization Bearer {token} — a Passport OAuth bearer token.
Accept application/json
Content-Type application/json — on any request that carries a JSON body.

See Authentication for how tokens are created and scoped to a team.

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

requests.get(
    'https://clickbase.so/api/sites',
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)

Response envelope

Responses are unwrapped. Clickbase calls JsonResource::withoutWrapping() globally in AppServiceProvider, so:

  • A single resource serializes flat — its fields are the top-level object, with no data envelope.
  • A non-paginated collection serializes as a flat JSON array.
  • Only a paginated list endpoint keeps the standard data / meta / links envelope.

The paginated envelope looks like this:

{
    "data": [ /* the page of resources */ ],
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "total": 240,
        "last_page": 10,
        "from": 1,
        "to": 25,
        "path": "https://clickbase.so/api/sites/example.com/goals"
    },
    "links": {
        "first": "https://clickbase.so/api/sites/example.com/goals?page=1",
        "last": "https://clickbase.so/api/sites/example.com/goals?page=10",
        "prev": null,
        "next": "https://clickbase.so/api/sites/example.com/goals?page=2"
    }
}

Paginated endpoints

These are the only endpoints that return the data / meta / links envelope. Every other data endpoint returns a flat object or flat array.

Endpoint page per_page
GET /api/sites/{siteKey}/goals Yes 1–100, default 25
GET /api/sites/{siteKey}/sessions Yes Fixed page size (internal)
GET /api/sites/{siteKey}/stats/events Yes 1–100, default 50
GET /api/sites/{siteKey}/stats/page-titles Yes 1–100, default 25
GET /api/sites/{siteKey}/stats/users Yes 1–100, default 25
GET /api/sites/{siteKey}/stats/errors Yes 1–100, default 20
GET /api/sites/{siteKey}/stats/errors/{error}/events Yes 1–100, default 20
GET /api/sites/{siteKey}/replays Yes Fixed page size (internal)

Advance through pages with ?page=. Where per_page is accepted it is validated as an integer 1–100; a value outside that range returns 422. The sessions and replays lists page at a fixed internal size, so they take ?page= but not ?per_page=.

Money

Money is always an integer in minor units (cents) — never a float and never a formatted string. A revenue of 4200 means $42.00. This holds everywhere revenue appears (goal revenue, the Revenue reports, session/user values). Format for display on your side by dividing by 100 for a two-decimal currency; the currency itself is reported separately as an ISO 4217 code.

Date ranges

Every reporting endpoint accepts the same date-range inputs. Pass either a preset range or an explicit from/to pair (which takes precedence). Boundaries are computed in the site's own timezone, then converted to UTC.

Preset range values (from App\Actions\Stats\ResolveDateRange::RANGES):

Value Window
today The current calendar day at the site (default).
yesterday The previous calendar day.
last_7_days The trailing 7 days, ending today.
last_30_days The trailing 30 days, ending today.
this_month Month-to-date.
last_month The whole previous calendar month.
last_12_months The trailing 12 months.
all Since the beginning of the site's data.

When range is missing or unrecognized it falls back to today.

Custom range — supply both from and to as Y-m-d dates (e.g. from=2026-01-01&to=2026-01-31). Both must be valid and from must be on or before to, otherwise the pair is ignored and the preset range applies.

Parameter Type Description
range string One of the preset values above.
from string Custom range start, Y-m-d. Requires to.
to string Custom range end, Y-m-d. Requires from.
curl "https://clickbase.so/api/sites/example.com/stats/events?range=last_7_days" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
const params = new URLSearchParams({ range: 'last_7_days' });
await fetch(`https://clickbase.so/api/sites/example.com/stats/events?${params}`, {
  headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
import requests

requests.get(
    'https://clickbase.so/api/sites/example.com/stats/events',
    params={'range': 'last_7_days'},
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)

Filters

Reporting endpoints accept the same dashboard filter keys as query parameters (allowlisted in App\Actions\Stats\ResolveFilters). Any key not in this list is ignored.

Group Keys
Page path, entry_path, title, hostname, event_name
Visitor user_id, visit_count
Acquisition referrer, channel, source, campaign
UTM utm_source, utm_medium, utm_campaign, utm_content, utm_term
Tech browser, os, size
Location country, region, city
Goal goal
Property prop_key + prop_value (both required together)

Each dimension takes a single value or a list. Repeat the key with [] to match any of several values (WHERE IN):

?country=BR
?path[]=/pricing&path[]=/signup

visit_count is single-value and uses an operator prefix — all-time tracked session count per visitor:

?visit_count=is:1
?visit_count=gte:2
?visit_count=lte:5

Operators: is, gte, lte. The prop_key / prop_value pair must be supplied together — either both are present (non-empty) or neither is applied.

curl "https://clickbase.so/api/sites/example.com/stats/events?country=BR&path[]=/pricing&path[]=/signup" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
const params = new URLSearchParams({ country: 'BR' });
params.append('path[]', '/pricing');
params.append('path[]', '/signup');
await fetch(`https://clickbase.so/api/sites/example.com/stats/events?${params}`, {
  headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
import requests

requests.get(
    'https://clickbase.so/api/sites/example.com/stats/events',
    params={'country': 'BR', 'path[]': ['/pricing', '/signup']},
    headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)

Errors & status codes

Status Meaning
401 Unauthorized Missing or invalid bearer token (the auth:api guard rejected it).
402 Payment Required The token's team has no active subscription. See Authentication.
403 Forbidden The token is bound to a team you no longer belong to, or your team role lacks the permission for a write (e.g. deleting a site requires Owner/Admin).
404 Not Found The site / goal / funnel / resource is unknown or not in your team.
422 Unprocessable Entity Validation failed, or your account has no active team.
429 Too Many Requests Rate limit exceeded (the authenticated group is 60 requests/minute).

A validation error (422) carries a message and an errors map keyed by field name:

{
    "message": "The range field must be a string.",
    "errors": {
        "range": ["The range field must be a string."]
    }
}

Other errors carry a plain message:

{
    "message": "An active subscription is required to access this team."
}