VisitorPing Analytics API v1
Read aggregate analytics for a website already connected to VisitorPing. No additional tracker or duplicate event pipeline is required. This API is read-only and server-to-server.
Pro or Agency required. Creating and using API keys requires an active eligible plan. Starter, including the Starter trial, does not include API access. Every request rechecks eligibility; existing keys stop working after a downgrade or loss of subscription access. Owners and admins can still list and revoke keys. View plans
Create a key
Workspace owners/admins open Settings → Manage analytics API keys (/dashboard/settings/api-keys). Choose a website, name, and expiry (30, 90, or 365 days). Copy the secret when it appears; only a SHA-256 hash and non-secret prefix are stored. Revocation takes effect on the next authenticated request. To rotate, create a replacement, update your application, then revoke the old key. Each key reads one website; create separate keys for separate websites.
Keep secrets in server environment variables. Never embed them in frontend JavaScript, URLs, screenshots, or public repositories. API authentication does not accept a browser session or the public tracker site key. There is no browser CORS grant. Your backend can expose selected aggregate results to your own frontend.
Requests
Base URL: https://visitorping.com/api/v1
Send Authorization: Bearer YOUR_API_KEY. All endpoints are GET-only.
| Endpoint | Result |
|---|---|
/sites | The website accessible to this key: id, name, domain |
/sites/{id}/analytics | Totals and UTC time series |
/sites/{id}/breakdowns | Aggregate rows by country, source, page, or device |
/sites/{id}/events | Aggregate rows by event name |
Parameters for analytics, breakdowns, and events
from,to: ISO UTC timestamps, e.g.2026-09-11T12:00:00Z. Range is from inclusive, to exclusive. Defaults: now minus 7 days through now, bounded by available history. No future end dates. Requests outside the workspace's available history return 403 instead of silently shortening the range.traffic:exclude_bots(default) orall. Default excludes known_bot and likely_bot sessions, but includes unknown classification. It does not certify real humans.event: optional exact event name, e.g.wall_impression.propertyandvalue: optional exact event-data property filter. Requireseventand both parameters. Property is a single top-level field, 1–64 letters/digits/underscores beginning with a letter. Values are 1–200 characters, compared as text (numbers and booleans use their textual form). No arbitrary SQL, nested expressions, or regex filters.- Analytics only:
interval=day(default) orhour. Hourly windows may span at most 7 days. Days/hours use UTC boundaries. - Breakdowns only:
dimension=country(default),source,page, ordevice. - Breakdowns/events:
limit=20(default), 1–100. Rows are ordered by event count, then value.truncated=truemeans additional groups exist; there is no cursor pagination in v1. - Unknown/repeated parameters return 400.
Metric definitions
Every metric is based on event occurrence time, not the session's start date. Sessions that began before the requested window count if they contain a matching event in the window.
events: number of matching ingested event records. This is not an impression count unless you filter to your impression event.uniqueVisitors: distinct stored visitor identifiers among matching events. Browser/storage changes can create another identifier; this is not a verified-person count.sessions: distinct sessions containing matching events.pageviews: matchingpage_viewevents. Filtering to a different custom event naturally returns zero pageviews.- A reload is another pageview; an SPA ad swap needs an explicit impression event if you want to measure it.
- Series omits empty buckets; clients can fill missing buckets with zero. Do not sum unique counts across buckets to calculate total unique visitors.
- Breakdown metrics have the same definitions. Visitors/sessions can appear in multiple groups. Country/device come from session metadata. Source prefers UTM source, then referrer host, then Direct. Page uses the event path with query/fragment removed. Avoid sending sensitive information in paths or UTM labels.
- No visitor IDs, IP addresses, contact records, raw event properties, or payment details are exported.
- There is no all-time guarantee beyond retained, plan-accessible data. Eligible plans retain their existing analytics history limits; API keys do not bypass them.
Response envelopes
GET /sites returns { apiVersion: "1", data: [{ id, name, domain }], generatedAt }.
The other endpoints return:
{
"apiVersion": "1",
"site": { "id": "SITE_ID", "name": "My site", "domain": "example.com" },
"range": { "from": "2026-09-11T00:00:00.000Z", "to": "2026-09-11T12:00:00.000Z", "timezone": "UTC", "interval": "day" },
"traffic": "exclude_bots",
"generatedAt": "2026-09-11T12:00:01.000Z",
"lastEventAt": "2026-09-11T11:59:58.000Z",
"historyDays": 30,
"freshness": "Eventually consistent. lastEventAt is site activity, not a completeness watermark.",
"data": {
"totals": { "events": 12, "uniqueVisitors": 3, "sessions": 4, "pageviews": 6 },
"series": [{ "bucket": "2026-09-11T00:00:00Z", "events": 12, "uniqueVisitors": 3, "sessions": 4, "pageviews": 6 }]
}
}Illustrative numbers only. filter is also present when an event filter is used. Breakdowns/events replace data with { dimension, rows: [{ value, events, uniqueVisitors, sessions, pageviews }], truncated }. range.interval appears only for analytics. lastEventAt may be null.
Results are eventually consistent as queued tracking events arrive. generatedAt records request generation time, not ingestion completeness. lastEventAt is the site's last recorded activity, not a promise that all earlier events have arrived. Retry reads to incorporate late events. No live stream or aggregate-push subscription is included in this v1; the existing alert webhooks are a separate feature.
Server example
const response = await fetch(
`https://visitorping.com/api/v1/sites/${encodeURIComponent(process.env.VISITORPING_SITE_ID)}/analytics`,
{ headers: { Authorization: `Bearer ${process.env.VISITORPING_API_KEY}` }, cache: "no-store" }
);
if (!response.ok) throw new Error(`VisitorPing API returned ${response.status}`);
const analytics = await response.json();
// Return only the aggregates your public UI needs, never the API key.Custom-event example (Take The Wall or any other application)
The existing browser tracker supports:
window.VisitorPing?.track("wall_impression", { takeoverId: "YOUR_TAKEOVER_ID" });
window.VisitorPing?.track("wall_owner_link_click", { takeoverId: "YOUR_TAKEOVER_ID" });Emit impressions only when the ad becomes visible, not on every render. Attach application context explicitly; automatic tracking cannot infer which owner is currently displayed. Record clicks without blocking navigation. No buyer email or sensitive fields belong in analytics.
Query analytics?event=wall_impression&property=takeoverId&value=YOUR_TAKEOVER_ID for impressions/uniques and breakdowns?dimension=country&event=wall_impression&property=takeoverId&value=YOUR_TAKEOVER_ID for countries. Query the click event separately. Set from and to to an available time window covering the reign; the property filter keeps simultaneous/adjacent reigns separate. The API never hardcodes takeover semantics.
Limits and failures
60 requests/minute/key and 300/minute/workspace across API endpoints. Creating keys is limited to 10/hour/workspace. Cache aggregate results in your backend for at least 15 minutes for background reports, share them with your clients, and keep the last successful result if VisitorPing is temporarily unavailable. Do not launch a separate API poll for every public browser. Local immediate counters, if used, remain separate; never add them to VisitorPing counts.
Errors: { "error": { "code": "...", "message": "..." } }.
- 400: invalid parameters/time window.
- 401: missing, invalid, revoked, or expired key.
- 403: plan_required when your plan is ineligible, or history_limit when requested history is outside the allowed window.
- 404: website is not accessible to this key.
- 429: rate limited; obey
Retry-Afterseconds with jitter. - 503: unavailable; retry with backoff and retain your last successful snapshot.
All API responses use Cache-Control: private, no-store and Vary: Authorization. Public embedding is a deliberate decision by the site owner through their own backend; the API itself never makes a site's analytics publicly accessible.
Track a conversion
After the VisitorPing tracker loads, record a conversion with a stable lead or order ID. Use whole cents and an uppercase three-letter currency. A missing conversionId is generated automatically; supplying the same ID on retries prevents duplicate conversions for that website. This is browser tracking, not a server-side payment confirmation API. Do not include buyer email or payment credentials.
window.VisitorPing?.conversion({
conversionId: "order-123", // Reuse the same ID when retrying this conversion.
name: "Purchase",
valueCents: 1299,
currency: "USD"
});Outgoing alert webhooks
The existing notification webhooks remain available under Dashboard → Integrations. They send selected visitor alerts to an HTTPS destination. They are separate from this read-only aggregate API and are not a complete analytics event stream.
Configured custom alert webhooks send JSON with an event of visitor.arrival or visitor.hot_lead and the alert payload in data. Delivery follows the configured notification rules; it is not an event for every page view. The current sender does not include an HMAC signature or an X-VisitorPing-Signature header. Do not use it as proof of payment or authorization for privileged actions.