API Reference
The Clickport API lets you query your analytics data programmatically, send events from your server, and access realtime visitor counts. All endpoints require authentication via API key.
Common things people build with it, each with a complete example below:
- A weekly traffic summary posted to Slack or Teams
- A live "visitors right now" counter on your own site
- A "popular posts" widget fed by real traffic data
- Conversion and revenue tracking from your backend
- An alert when traffic drops or the tracker breaks
- A daily feed into a spreadsheet, BI tool, or warehouse
- AI agent monitoring from your server logs (own docs page)
Authentication
Every API request must include your site's API key. You can find and manage your API keys in Settings → API Keys in your dashboard.
Pass the key in the x-api-key HTTP header:
curl https://clickport.io/api/query \
-H "x-api-key: ck_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"metrics": ["visitors"], "period": "30d"}'
API keys start with ck_ and are scoped to a single site. Keys created in Settings → API Keys carry read and tracker permissions, allowing you to query data and send events. Connector keys created in Settings → Integrations → AI Agents carry the agent-visits scope instead and work only with the agent-visits endpoint below.
Base URL
All API endpoints use the base URL:
https://clickport.io/api
Rate limits
The event ingestion endpoint (/api/event) is rate-limited to 60 requests per minute per IP address. When the limit is exceeded, events are silently dropped (the response still returns 200).
Read endpoints (Stats, Realtime, Goals, convenience endpoints, and the MCP server) are limited to 300 requests per hour and 5 concurrent requests per API key. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers; past the limit you get 429 with Retry-After. If you hit the ceiling with a real use case, tell us.
MCP server
Clickport hosts an MCP server, so AI assistants can query your analytics directly. Connect it once and ask things like "what were my top pages last week?", "which countries convert best?", or "who is on the site right now?" from Claude Code, Cursor, Claude Desktop, or any MCP client that supports remote servers with custom headers.
The endpoint is https://clickport.io/api/mcp (Streamable HTTP), authenticated with your regular API key (scope read). It is read-only and scoped to the key's site: assistants can query numbers, never change anything.
Connect from Claude Code
claude mcp add --transport http clickport https://clickport.io/api/mcp \
--header "Authorization: Bearer ck_live_your_key"
Connect from Cursor or other clients
Add this to your client's MCP configuration (Cursor: .cursor/mcp.json):
{
"mcpServers": {
"clickport": {
"url": "https://clickport.io/api/mcp",
"headers": { "Authorization": "Bearer ck_live_your_key" }
}
}
}
Available tools
The server exposes seven read-only tools: describe_site (the site and its valid metrics, dimensions, and periods; assistants call this first), get_stats (aggregate numbers for a period), get_breakdown (top pages, sources, countries, and any other dimension), get_timeseries (a metric over time), get_realtime (who is on the site now), list_goals, and get_goal_conversions (conversions, rates, and revenue for one goal).
Example questions that work out of the box: "Compare this month's visitors to last month", "Which blog posts brought the most signups in the last 90 days?", "Show my newsletter goal's conversion rate per week."
Recipes
Complete, copy-paste examples for the most common uses. All of them run server-side (Node 18+ shown, any language with an HTTP client works) with your API key in an environment variable.
Post a weekly traffic summary to Slack
Run this on a weekly cron. One aggregate query with include_comparison gives you the numbers and the change versus the previous week; post the result to an incoming webhook.
// weekly-report.mjs, run by cron every Monday morning
const res = await fetch('https://clickport.io/api/query', {
method: 'POST',
headers: {
'x-api-key': process.env.CLICKPORT_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
metrics: ['visitors', 'pageviews', 'bounce_rate'],
period: '7d',
include_comparison: true,
}),
});
const { results, comparison } = await res.json();
const change = comparison.values.visitors.change;
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `Last 7 days: ${results.visitors} visitors (${change > 0 ? '+' : ''}${change}% vs previous week), ${results.pageviews} pageviews, ${results.bounce_rate}% bounce rate`,
}),
});
Show a live visitor counter on your site
Your API key must never appear in browser code, so add a tiny proxy route to your own backend and let the page poll that. Cache it for 30 seconds so a traffic spike on your site does not turn into an API hammer.
// your server (Express shown): the key stays server-side
app.get('/visitors-now', async (req, res) => {
const r = await fetch('https://clickport.io/api/realtime', {
headers: { 'x-api-key': process.env.CLICKPORT_API_KEY },
});
const { count } = await r.json();
res.set('Cache-Control', 'public, max-age=30');
res.json({ count });
});
<!-- on your page -->
<span id="live-count"></span> reading right now
<script>
const el = document.getElementById('live-count');
const refresh = () => fetch('/visitors-now')
.then(r => r.json()).then(d => { el.textContent = d.count; });
refresh(); setInterval(refresh, 30000);
</script>
Build a "popular posts" widget
Query the top blog pages by visitors over the last 30 days and render the result into your site. The starts_with operator (API only) keeps it to one section of your site. Cache the response and rebuild it hourly or at deploy time: popularity does not change by the minute.
const res = await fetch('https://clickport.io/api/query', {
method: 'POST',
headers: {
'x-api-key': process.env.CLICKPORT_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
metrics: ['visitors'],
dimensions: ['page'],
period: '30d',
filters: [{ dimension: 'page', operator: 'starts_with', value: '/blog/' }],
limit: 5,
}),
});
const { results } = await res.json();
// results: [{ page: '/blog/most-read-post', visitors: 312 }, ...]
Track conversions and revenue from your backend
When money moves outside the browser (an order confirmed by a job queue, an invoice paid, a renewal), send it to the Revenue API below - it dedupes retries, handles refunds, and can attribute the sale to the buyer's session. For non-monetary conversions, send a custom event from the code that knows about it:
// e.g. inside your billing worker after a successful renewal
await fetch('https://clickport.io/api/event', {
method: 'POST',
headers: {
'x-api-key': process.env.CLICKPORT_API_KEY,
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 ...',
},
body: JSON.stringify({
type: 'custom',
name: 'Renewal',
url: 'https://yoursite.com/billing/renewal',
revenue_amount: 49.00,
revenue_currency: 'EUR',
meta_keys: ['plan'],
meta_values: ['pro'],
}),
});
Server-sent events land as their own session; they are not stitched to the visitor's earlier browser session. Use them for counting and revenue, not for journey analysis.
Alert when traffic drops to zero
A silent tracker failure (a bad deploy removed the snippet, a CSP change blocked it) is invisible until someone opens the dashboard. A tiny cron check catches it within the hour:
// traffic-check.mjs, run hourly
const res = await fetch('https://clickport.io/api/query', {
method: 'POST',
headers: {
'x-api-key': process.env.CLICKPORT_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ metrics: ['visitors'], period: 'today', timezone: 'Europe/Berlin' }),
});
const { results } = await res.json();
const hour = new Date().getHours();
if (hour >= 10 && results.visitors === 0) {
// zero visitors by mid-morning almost always means the tracker is gone:
// post to your alerting channel here
}
Adapt the threshold to your traffic. A higher-traffic site can compare against a same-weekday baseline instead of checking for zero.
Feed a spreadsheet, BI tool, or warehouse
Pull a daily time series and hand it to whatever your team already uses: a scheduled import into Google Sheets, a Grafana or Metabase datasource, or a nightly load into your warehouse.
const res = await fetch('https://clickport.io/api/query', {
method: 'POST',
headers: {
'x-api-key': process.env.CLICKPORT_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
metrics: ['visitors', 'pageviews', 'bounce_rate'],
dimensions: ['date'],
period: '90d',
order_dir: 'asc',
}),
});
const { results } = await res.json();
const csv = [
'date,visitors,pageviews,bounce_rate',
...results.map(r => `${r.date},${r.visitors},${r.pageviews},${r.bounce_rate}`),
].join('\n');
For a one-off export of everything, the dashboard's CSV export (a ZIP with every panel as its own file) is faster than scripting: see Export data.
Stats API
Query your analytics data with flexible metrics, dimensions, filters, and date ranges.
Request body
metrics["visitors"]dimensionsperiod"today"date_range{"start": "2026-03-01", "end": "2026-03-31"}. End date is exclusive.filters[{"dimension": "country", "operator": "is", "value": "DE"}]. value can also be an array of strings: {"dimension": "country", "operator": "is", "value": ["DE", "AT", "CH"]} matches any of the values. Multiple values combine with OR for is / contains and AND-of-NOT for is_not / not_contains.order_byorder_dir"asc" or "desc". Default: "desc"limit100offset0timezone"UTC"include_comparisonfalseAvailable metrics
visitors- Unique visitorssessions- Total sessionspageviews- Total pageviewsviews_per_visit- Average pageviews per sessionbounce_rate- Percentage of single-page sessions (0-100)avg_duration- Average session duration in secondsavg_scroll- Average scroll depth (0-100)clickers- Sessions with at least one click event
Available dimensions
- URL:
page,entry_page,exit_page,outbound - Acquisition:
source,channel,campaign,utm_source,utm_medium,utm_campaign,utm_content,utm_term,referrer_url - Location:
continent,country,region,city - Device:
browser,browser_version,os,os_version,device,screen - Custom properties:
prop:<key>for any property your site sends, e.g.prop:author. Works indimensionsand infilters. See Custom properties below. - Time (grouping only):
hour,date,week,month
Periods
realtime- Last 30 minutestoday,yesterday7d,14d,30d,90d- Last N days12m- Last 12 monthsall- All time- Or use
date_rangefor custom ranges
Filter operators
is(alias"is") - Exact match, case-sensitive. Multi-value emitsIN (...).is_not(alias"is not") - Excludes exact matches, case-sensitive. Multi-value emitsNOT IN (...).contains- Substring match, case-insensitive (uses ClickHouseILIKE). Multi-value combines with OR.not_contains(alias"does not contain") - Excludes substring matches, case-insensitive. Multi-value combines with AND-of-NOT.starts_with- Starts with value, case-sensitive. API only, not exposed in the dashboard UI.ends_with- Ends with value, case-sensitive. API only, not exposed in the dashboard UI.
The dashboard UI exposes is, is_not, contains, and not_contains. The API additionally accepts starts_with and ends_with for direct integrations.
Example: Aggregate query
Get total visitors, pageviews, and bounce rate for the last 30 days:
curl -X POST https://clickport.io/api/query \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["visitors", "pageviews", "bounce_rate"],
"period": "30d"
}'
Response:
{
"results": {
"visitors": 4821,
"pageviews": 12493,
"bounce_rate": 42
}
}
Example: Breakdown query
Get top pages by visitors for a custom date range, filtered to Germany:
curl -X POST https://clickport.io/api/query \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["visitors", "pageviews", "avg_scroll"],
"dimensions": ["page"],
"date_range": {"start": "2026-03-01", "end": "2026-03-31"},
"filters": [{"dimension": "country", "operator": "is", "value": "DE"}],
"limit": 10,
"timezone": "Europe/Berlin"
}'
Response:
{
"results": [
{ "page": "/", "visitors": 312, "pageviews": 487, "avg_scroll": 65 },
{ "page": "/pricing", "visitors": 198, "pageviews": 214, "avg_scroll": 78 },
{ "page": "/blog/privacy", "visitors": 145, "pageviews": 152, "avg_scroll": 82 }
]
}
Custom properties
Any custom property your site sends is queryable as prop:<key>, both as a breakdown dimension and as a filter dimension. All six filter operators apply. As a breakdown it must be the only dimension in the request; rows return visitors, sessions, and events per value, and an empty-string value means the property was present without a value (the dashboard's (none) bucket).
Traffic by author over the last 30 days, restricted to one section:
curl -X POST https://clickport.io/api/query \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["visitors"],
"dimensions": ["prop:author"],
"filters": [{"dimension": "prop:section", "operator": "is", "value": "Climate"}],
"period": "30d"
}'
Response:
{
"results": [
{ "prop:author": "Jane Doe", "visitors": 1204, "sessions": 1290, "events": 1873 },
{ "prop:author": "Marcus Chen", "visitors": 710, "sessions": 744, "events": 1102 }
]
}
Property filters are visit-scoped: a clause matches visits containing at least one pageview or event where the property matches. Internal signal keys (click_url, form_id, and similar) are reserved and rejected as property dimensions.
Example: Time series
Get daily visitor counts for the last 7 days:
curl -X POST https://clickport.io/api/query \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["visitors", "pageviews"],
"dimensions": ["date"],
"period": "7d"
}'
Response:
{
"results": [
{ "date": "2026-03-25", "visitors": 183, "pageviews": 421 },
{ "date": "2026-03-26", "visitors": 201, "pageviews": 467 },
{ "date": "2026-03-27", "visitors": 195, "pageviews": 442 }
]
}
Events API
Send events from your server. Use this for server-side tracking, mobile apps, or any environment where the JavaScript tracker is not available.
Request body
type"pageview", "custom", "click", "form". Default: "pageview"url"https://yoursite.com/page"referrername"Signup"utm_sourceutm_mediumutm_campaignscreen_widthtimezone"UTC"revenue_amountrevenue_currency"USD", "EUR"meta_keysmeta_valuesmeta_keys. Max 2000 chars each.User-Agent header with your request. Clickport uses it for browser/OS detection and to filter bots. Requests with missing or bot-like User-Agents may be rejected.
Example: Track a pageview
curl -X POST https://clickport.io/api/event \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-H "User-Agent: Mozilla/5.0 ..." \
-d '{
"type": "pageview",
"url": "https://yoursite.com/pricing",
"referrer": "https://google.com/"
}'
Response:
{
"success": true,
"session_id": 48291,
"page_visit_id": "a1b2c3d4"
}
Example: Track a custom event with revenue
curl -X POST https://clickport.io/api/event \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-H "User-Agent: Mozilla/5.0 ..." \
-d '{
"type": "custom",
"name": "Purchase",
"url": "https://yoursite.com/checkout/complete",
"revenue_amount": 49.99,
"revenue_currency": "USD",
"meta_keys": ["plan", "interval"],
"meta_values": ["pro", "annual"]
}'
Privacy and processing
- Tracking parameters (
gclid,fbclid,msclkid, etc.) are automatically stripped from stored URLs - Query parameters are stripped from referrer URLs
- UTM parameters are extracted and stored separately from the page URL
- IP addresses are used for geolocation but never stored
- If your site has Do Not Track respect enabled, requests with
DNT: 1headers are excluded
Revenue API
Records a sale or refund server-side. This is the provider-neutral counterpart to the Stripe and Paddle integrations: call it from any system that knows an order happened - a store platform's automation, an order hook, a job queue, or your own backend. Amounts land in the dashboard's Revenue stat, the chart overlay, and per-channel revenue breakdowns.
curl -X POST https://clickport.io/api/revenue \
-H "x-api-key: ck_your_key" \
-H "Content-Type: application/json" \
-d '{
"order_id": "order-1042",
"amount": 49.99,
"currency": "EUR"
}'
Request body
order_id- required. Your unique id for the order (or refund). Sending the same id again is acknowledged but never counted twice, so retries are always safe.amount- required. The amount in major units (49.99, not cents). A negative amount records a refund.currency- required. Three-letter ISO code. Converted into your site's reporting currency on arrival.ref- optional. The visitor's checkout token fromclickport.checkoutRef(), captured in the browser at checkout time and passed through your order flow. With it, the revenue is attributed to the visit that produced the sale - channel, campaign, country, device. Without it, the amount still counts in totals, marked unattributed.refund_of- optional, for refunds. Theorder_idof the original purchase; the negative amount then lands on the same visit as the original sale, keeping per-channel revenue net-correct.occurred_at- optional. ISO 8601 timestamp or epoch seconds when the order happened. Defaults to arrival time; accepted up to 90 days in the past.source- optional label for where the call came from (for examplewix-automation), stored on the event.click_id- optional. A Google Ads click ID (gclid) you captured yourself, for example in a hidden signup-form field. Conversions sent with it are included in the Google Ads conversions feed even when they happen in a later session than the ad click.click_id_param(optional) names the parameter it came from; defaults togclid.
Behavior
- Never creates sessions, visits, or pageviews - money only. Unlike server-sent custom events, a revenue call can join the buyer's real browser session via
ref. - Responds
200with{"ok": true, "ingested": true, "attributed": true|false}; duplicates return"ingested": falsewith"reason": "duplicate". - Revenue appears as a
Purchaseevent (orRefundfor negative amounts), so a goal named Purchase picks it up automatically.
Agent Visits API
Reports server-side requests so Clickport can identify and verify AI agents reading your site. This is the endpoint behind the AI Agents connectors; use it directly from any backend, edge function, or log pipeline.
Send newline-delimited JSON, one object per request your server saw, authenticated with a connector key in the Authorization header:
curl -X POST https://clickport.io/api/agent-visits \
-H "Authorization: Bearer ck_your_connector_key" \
-H "Content-Type: application/x-ndjson" \
--data-binary $'{"ts":"2026-07-13T09:00:00Z","path":"/blog/post","method":"GET","status":200,"duration_ms":42,"headers":{"User-Agent":"Mozilla/5.0 (compatible; GPTBot/1.2)","Remote-Addr":"203.0.113.7"},"connector":"rest"}\n'
Line fields
ts- ISO 8601 timestamp of the request. Timestamps older than 35 days or in the future are clamped to arrival time.path- request path. Required. Query strings are stripped server-side.method- HTTP method.status- response status code.duration_ms- response time in milliseconds.headers- object with the original request's headers. Required. IncludeUser-Agentand the requester's address asRemote-Addr(orX-Forwarded-For); without an address, hits verify as unverifiable.connector-rest, or one of the built-in connector identifiers.
Behavior and limits
- Send every content request: human hits are discarded server-side, agents are classified and verified against each operator's published IP ranges. The requester's address is used for that check and then discarded; it is never stored.
- Up to 5,000 lines per request, 5 MB body,
Content-Encoding: gzipsupported. A malformed line is skipped without failing the batch. - Responds
204 No Contenton success, with the accepted row count in thex-agent-visits-acceptedresponse header. - Agent visits never count as pageviews and do not consume your plan's quota.
Realtime API
Get the number of currently active visitors on your site, the same data behind the dashboard's realtime view.
curl https://clickport.io/api/realtime \
-H "x-api-key: ck_your_key"
Response:
{
"count": 7,
"visitors": [
{
"session_id": 91823,
"entry_page": "/",
"exit_page": "/pricing",
"pageviews": 3,
"duration": 124,
"source": "google",
"country": "DE",
"city": "Berlin",
"device": "Desktop",
"browser": "Chrome",
"os": "macOS",
"scroll": 72
}
]
}
Returns up to 20 currently active visitors (active within the last 5 minutes). Each visitor includes their session details, current page, traffic source, location, and engagement data.
Goals API
Retrieve goal conversion data for your site.
start_dateend_datetimezone"UTC"curl "https://clickport.io/api/goals?start_date=2026-03-01&end_date=2026-03-31" \
-H "x-api-key: ck_your_key"
Response:
{
"goals": [
{
"id": 1,
"name": "Contact Form",
"type": "form",
"visitors": 84,
"submissions": 91
},
{
"id": 2,
"name": "Purchase",
"type": "custom",
"visitors": 37,
"events": 42,
"total_revenue": 1849.50
}
]
}
Convenience endpoints
These GET endpoints return pre-formatted breakdowns for common dimensions. All accept start_date and end_date as query parameters. /api/pages returns the top pages by pageviews and also accepts limit (default 100, max 1,000) and offset for paging. Its avg_duration is the engaged active time on each page, in seconds. Numeric fields are returned as JSON numbers.
GET /api/pages- Top pages with visitors, pageviews, scroll depth, durationGET /api/sources- Traffic sources with visitors, bounce rate, engagementGET /api/referrers- Full referrer URLs with visitors and bounce rateGET /api/countries- Country breakdown with visitor countsGET /api/languages- Visitor language breakdown (from the browser's preferred language)GET /api/locales- Locale variants (en-US, de-CH) with their base languageGET /api/campaigns- Campaign data (campaign, source, medium)GET /api/utm-sources- UTM source breakdownGET /api/utm-mediums- UTM medium breakdownGET /api/utm-content- UTM content breakdownGET /api/utm-term- UTM term breakdownGET /api/entry-pages- Entry pages with session countsGET /api/exit-pages- Exit pages with session countsGET /api/tech?type=device- Device type breakdownGET /api/tech?type=browser- Browser breakdownGET /api/tech?type=os- Operating system breakdown
Example
curl "https://clickport.io/api/pages?start_date=2026-03-01&end_date=2026-03-31" \
-H "x-api-key: ck_your_key"
Error responses
The API returns standard HTTP status codes:
200- Success401- Invalid or missing API key403- The API key is not permitted to use this endpoint. Keys work on the endpoints documented on this page: dashboard features require logging in to the dashboard.404- Endpoint not found500- Server error
Error responses include a JSON body:
{
"error": "Unauthorized",
"message": "Invalid API key"
}