Docs

Getting started

Spreelytics has two HTTP APIs. The Tracking API records what happens on your site: pageviews, custom events, sign-ins and the end of each visit. It is the API the tracking script talks to, and you can call it yourself. The reporting API reads your data back, the same numbers the app shows, and makes the changes an endpoint allows, such as saving a dashboard or adding a goal. A third route, Record a payment, takes payments from your server.

This page covers what the endpoint pages have in common: where each API lives, which key it takes, the audience id, dates, filters, errors and limits.

The two APIs

Tracking APIReporting API
Base URLhttps://sdk.spreelytics.com/1.0https://app.spreelytics.com, followed by a path prefix (see Reporting API basics)
What it doesRecords pageviews, custom events, identify calls and session ends. Reads one visitor's visit counts.Reads reports, visitors, sessions, people and notifications. Creates and changes dashboards, reports, segments, references and notification rules, and creates goals and funnels.
CredentialThe audience's client key, which is publicA secret key: an audience key or an account key
Where the key goesclientKey in the query string or in the form bodyThe header Authorization: Bearer <key>
Reference pagesTrack a pageview, Track an event, Identify a visitor, End a session, Get visitor statsThe Endpoints pages, starting with Get the headline numbers

Record a payment has an address of its own, POST https://api.spreeflo.com/1.0/revenue, and takes an account key.

Tracking API basics

Every tracking route sits under https://sdk.spreelytics.com/1.0, so /track/page means https://sdk.spreelytics.com/1.0/track/page. If you send the script's calls through your own domain (see Proxy guide), your proxy forwards them to this address.

The client key

Each tracking call carries the audience's client key. You find it in Settings → API keys, under Site tracking key, in the Client key field. It is also inside the snippet on Settings → Tracking script.

The client key is public by design. It sits in your page source where anyone can read it, and all it allows is sending data to that one audience. It cannot read your reports, and it does not work on the reporting API.

Two ways to send the body

Every tracking route that takes a body accepts it in two encodings:

EncodingContent-TypeBodyWhere the client key goes
Form, as the script sends itapplication/x-www-form-urlencodedTwo fields: clientKey, and data, which holds the JSON payload as a stringThe clientKey form field
JSONapplication/jsonThe JSON payload itselfThe query string: ?clientKey=YOUR_CLIENT_KEY

The form encoding exists for browsers. Before a browser sends a JSON request to another site, it makes an extra permission check called a CORS preflight: an OPTIONS request that asks the server whether the real request is allowed. A form-encoded request needs no preflight, so it takes one round trip instead of two. The collector accepts both encodings, and lets any site send them; which sites it records is up to Allowed domains, described below.

On Track a pageview, Track an event and Identify a visitor, the JSON payload must not contain clientKey. Those routes reject any field they do not know with 400, and clientKey is not one of their fields. End a session is the exception: its payload requires clientKey.

What a tracking call answers

Tracking calls are recorded in the background. A call answers before its data is stored, and the data shows up in the app shortly after.

AnswerWhat it means
200 with an empty bodyAccepted.
200 with the body OKDropped on purpose and not stored. See What the Tracking API drops.
400A required field is missing, a field has the wrong type, or the payload has a field the route does not accept. The JSON answer has "message": "Request validation failed" and names each problem under fields.
401 with the body Event API: UnauthorisedNo client key, a client key that does not exist, or a call from a site that is not in Allowed domains. The body is the same in all three cases.
413The body is larger than 250,000 bytes.
500In the form encoding, data is not valid JSON.

Allowed domains lives in Settings → Details, and appears when Allow all domains is off. When the list has entries, the collector reads the hostname from the request's Origin header, or from Referer when there is no Origin, and refuses the call with 401 unless that hostname is on the list exactly. www.example.com and example.com are separate entries, and there are no wildcards. A browser sends Origin by itself; a call from a server has to add it.

What the Tracking API drops

Three filters run before a tracking call is stored. A dropped call still answers 200, with the body OK, so that a bot running your page's code has no reason to retry. Nothing about the call is kept, and there is no setting to turn these filters off.

  1. Bots and tools. Every request names the program that sent it in its User-Agent header. When that name belongs to a crawler, a fetcher, a command line tool or an HTTP library, the call is dropped. That covers curl, Wget, HTTPie, PowerShell, Python's requests, httpx and urllib, axios, node-fetch, undici, the Go and Java HTTP clients, OkHttp, Guzzle, Postman and many more. A User-Agent that contains headless, lighthouse, phantomjs, puppeteer or playwright is dropped as well.
  2. Cloud addresses. A call from an IPv4 address in the published ranges of Amazon Web Services, Google Cloud, Oracle Cloud, DigitalOcean or Linode is dropped, apart from ranges that VPN services use, so people browsing through a VPN still count. The address checked is the one that connects to Spreelytics. An X-Forwarded-For header is ignored. Many servers and serverless functions run on these clouds (Vercel, for example, runs on AWS), and every tracking call they make is dropped.
  3. The hard limit. Once your workspace has recorded three times its monthly event allowance (one and a half times, on allowances above 1 million events), new pageviews and custom events are dropped. Identify calls and session ends still go through. See Billing.

More on how filtered traffic affects your reports in Filter traffic.

Calling the Tracking API from a server

The tracking routes are built for browsers. From a browser, the visitor's own address, User-Agent and Origin travel with each call, and everything works as it does for the script. From a server, check each of these first:

  • Where the server runs. If it is on Amazon Web Services, Google Cloud, Oracle Cloud, DigitalOcean or Linode, every call is dropped. Nothing in the request can change that.
  • The User-Agent. Send the User-Agent of the visitor you are reporting on, copied from their request to your server. Your HTTP library's own User-Agent is dropped as a bot, and the visitor's is also where the browser, operating system and device type in your reports come from.
  • The Origin header. If the audience has Allowed domains, send an Origin whose hostname is on the list, such as https://www.example.com.
  • Location. Country, region and city come from the address the call arrives from, which is your server's. Every visitor you report from the server is placed where the server is.
  • Cookieless audiences. With cookieless tracking on, the collector ignores the visitor and session ids you send and works them out from the request's address and User-Agent. Every call from your server shares one address, so visitors whose browsers send the same User-Agent would be counted as one. Send server-side calls only to audiences that use the cookie.

Reporting API basics

The reporting API lives on https://app.spreelytics.com, under three path prefixes. Each endpoint page shows the full address in its samples.

PrefixWhat it serves
/api/v1/eventWeb analytics: headline numbers and breakdowns, pages, events, sessions, visitors, retention and journeys, and the results of goals and funnels
/api/v1/spreelyticsPeople, dashboards, reports and charts, segments (called cohorts in the API), insights, references, notification rules and notifications, the event catalog and plan usage
/api/v1/spreeGoals and funnels, and the list of your audiences

Send the key in the Authorization header, never in the address:

curl 'https://app.spreelytics.com/api/v1/event/analytics/web-analytics/summary?contactListId=8f14e45f-ceea-4e7a-9b2c-1d5a3f6b7c8d&startDate=2026-09-01&endDate=2026-09-30' \
  -H 'Authorization: Bearer YOUR_ACCOUNT_KEY' \
  -H 'x-spreeflo-app: spreelytics'

The x-spreeflo-app header

Send x-spreeflo-app: spreelytics with every reporting call that uses an account key, as the app itself does. The name is historical, and the value tells the server the call is for Spreelytics. Without it, a workspace you joined through an invitation can refuse your account key, with 401 under /api/v1/event and /api/v1/spreelytics and 410 under /api/v1/spree, and the audience list can leave that workspace's audiences out. Audience keys do not need the header.

Keys

The reporting API takes two kinds of secret key. Both go in the same header, Authorization: Bearer <key>.

Audience settings, API keys tab, with the Site tracking key section above the Audience API keys list
The Account page's API keys tab, listing account API keys with their values masked
Audience keyAccount key
Where to create itSettings → API keys, under Audience API keys, with Create keyAccount → API keys, under Account API keys, with Create key
Who can create itAdmins of the workspaceAnyone signed in, for themselves
What it reachesThe one audience it was created inEvery workspace you belong to
What it can doRead, nothing elseWhat your role allows in each workspace: a Viewer reads, an Editor or Admin can also make the changes an endpoint allows
What it looks likeStarts with sp_A short code of capital letters and digits
Seeing it againShown once, when you create it. Only a hash of it is stored, so a lost key has to be replaced.Shown any time with Show key, and copied with Copy key in its row menu
Typical useA reporting script or a server that reads one siteYour own scripts and servers, MCP, and Record a payment

Revoke in a key's row menu turns it off for good. Give each tool its own key, so you can revoke one without breaking the others. Treat both kinds as passwords: keep them on your server, out of browser code, out of addresses and out of source control.

Which endpoint takes which key

Every endpoint on the Endpoints pages has a Keys line that says which kinds it accepts.

  • Endpoints under /api/v1/event never take audience keys.
  • Audience keys open a set of read endpoints under /api/v1/spreelytics: people, saved dashboards, reports, segments and references, charts, the event catalog, insights, plan usage, and notification rules and notifications. An audience key used for any other audience, or on an endpoint that does not list it, answers 401.
  • An endpoint whose Keys line says none works only from inside the app, signed in.

Which workspace a call acts on

An endpoint that takes an audience id acts on the workspace that owns that audience, and your role in that workspace decides what you may do. An endpoint that takes no audience id, such as plan usage, acts on one workspace that the call has to pick: name it with the header X-Team-Id: <workspace id>. Without the header, the workspace you get depends on the endpoint, so send it whenever you use an account key on one of these. Record a payment works a little differently, and its page explains how. The next section shows where workspace ids come from.

Your audience id

Endpoints that read or change one audience take its id as contactListId. Record a payment calls the same value audienceId. It is a UUID, a 36-character id such as 8f14e45f-ceea-4e7a-9b2c-1d5a3f6b7c8d.

The app does not show audience ids. There are two ways to get them:

  • From an AI app. Once an AI app is connected over MCP, ask it to list your audiences. Its sply_list_audiences tool answers with each audience's name, domain and id.
  • From the API. GET https://app.spreelytics.com/api/v1/spree/contact/list with an account key lists the audiences in every workspace you belong to. page (starting at 0) and pageSize are required. search narrows the list to names that contain it, ignoring case. Add X-Team-Id to list one workspace only.

In the answer, id is the audience id, teamId is the id of the workspace that owns it (the value X-Team-Id takes), and timezone is the audience's Reporting timezone.

Request

curl 'https://app.spreelytics.com/api/v1/spree/contact/list?page=0&pageSize=50' \
  -H 'Authorization: Bearer YOUR_ACCOUNT_KEY' \
  -H 'x-spreeflo-app: spreelytics'
// On your server: an account key must never reach a browser.
const res = await fetch('https://app.spreelytics.com/api/v1/spree/contact/list?page=0&pageSize=50', {
  headers: {
    Authorization: 'Bearer YOUR_ACCOUNT_KEY',
    'x-spreeflo-app': 'spreelytics',
  },
});
const { content: audiences } = await res.json();
import requests

res = requests.get(
    "https://app.spreelytics.com/api/v1/spree/contact/list",
    params={"page": 0, "pageSize": 50},
    headers={
        "Authorization": "Bearer YOUR_ACCOUNT_KEY",
        "x-spreeflo-app": "spreelytics",
    },
)
audiences = res.json()["content"]

Response

{
  "totalPages": 1,
  "totalItems": 1,
  "page": 0,
  "size": 50,
  "content": [
    {
      "id": "8f14e45f-ceea-4e7a-9b2c-1d5a3f6b7c8d",
      "name": "Marketing site",
      "domain": "www.example.com",
      "timezone": "Europe/London",
      "teamId": "c9f0f895-fb98-4b91-9f2d-6a1e0b2c3d4e",
      "teamName": "Acme"
    }
  ]
}

Audience keys cannot call this endpoint, and do not need to: an audience key belongs to one audience, so use that audience's id with it.

Dates

Endpoints that report on a period take startDate and endDate, in either of two forms:

FormExampleHow it is read
A date2026-09-01The whole day, in UTC. The end date is included.
A date and time2026-09-01T04:00:00ZThat exact moment, in UTC. It must end in Z, and may carry fractions of a second. The end moment is not included.

So startDate=2026-09-01&endDate=2026-09-30 covers all of September, from midnight UTC on the 1st to midnight UTC on 1 October. UTC (Coordinated Universal Time) is the time zone with no offset, and these parameters are always read in it, whatever the audience's Reporting timezone. To report on days in another time zone, send exact moments: September in New York is startDate=2026-09-01T04:00:00Z&endDate=2026-10-01T04:00:00Z.

Endpoints that return a series also take granularity, the size of each step: minute, hour, day, week or month.

When your workspace has gone past its monthly event allowance, endpoints under /api/v1/event cut the period off at the moment it did, the same way the app does. See Billing.

Filters

Endpoints that take filter narrow their answer the way the Filter button does in the app. The value is a JSON array of conditions, sent URL-encoded in the query string. Each condition is an array of a field, a list of values and an optional operator:

[
  ["country", ["US", "GB"]],
  ["channel", ["Organic Search"], "is"],
  ["page", ["/blog"], "startsWith"]
]

That filter keeps visits from the United States or the United Kingdom that came from organic search, on pages whose path starts with /blog. With curl, --data-urlencode does the encoding for you:

curl -G 'https://app.spreelytics.com/api/v1/event/analytics/web-analytics/summary' \
  -H 'Authorization: Bearer YOUR_ACCOUNT_KEY' \
  -H 'x-spreeflo-app: spreelytics' \
  --data-urlencode 'contactListId=8f14e45f-ceea-4e7a-9b2c-1d5a3f6b7c8d' \
  --data-urlencode 'startDate=2026-09-01' \
  --data-urlencode 'endDate=2026-09-30' \
  --data-urlencode 'filter=[["country",["US","GB"]],["page",["/blog"],"startsWith"]]'

Fields

FieldValues
page, entry_page, exit_pageA page path, such as /pricing
hostnameA hostname, such as www.example.com. Compared in lower case.
event_nameA custom event name
goal_idA goal's id. Keeps the people who completed the goal.
cohort_idA segment's id. Keeps the segment's members.
channelA channel as the app names it, such as Organic Search, Paid Social or Direct
referrer_sourceA source, such as Google or ChatGPT
referrerA referring address without https://, such as news.ycombinator.com/item
utm_source, utm_medium, utm_campaign, utm_content, utm_termThe value of that UTM tag
countryA two-letter country code, such as US
region, cityA region or city name
browser, browser_version, os, os_versionA browser or operating system, such as Chrome or Windows, and a version such as 129.0
screen_sizeDesktop, Mobile or Tablet
identified["true"] keeps identified people only, ["false"] anonymous visitors only. Takes no operator.

Operators

OperatorKeeps values that
isEqual one of the values. Used when you leave the operator out.
isNotEqual none of the values
contains, notContainsContain one of the values, or none of them. Ignores case.
startsWith, endsWithStart or end with one of the values. Ignores case.
regex, notRegexMatch one of the patterns, or none of them
isNull, isNotNullAre empty, or are not. Pass an empty list of values: ["utm_source", [], "isNull"].

Patterns for regex use RE2 syntax, a common regular expression dialect with no lookahead, lookbehind or backreferences. A pattern that uses them, or that is not a valid expression, is refused with 400. A condition with a pattern longer than 500 characters is ignored.

How conditions combine

  • Every condition has to match. Each field can appear once; if you repeat one, the last condition for it wins.
  • Inside one condition, any of the values can match, except for isNot, notContains and notRegex, where none of them may.
  • A filter that is not valid JSON is ignored as a whole, and so is a condition with an unknown field or no usable values. The answer is then wider than you meant, with no error to warn you, so check the JSON before you trust the numbers.

Pages of results

Lists that come in pages take page, which starts at 0, and pageSize. They answer with the rows in content, alongside page, size, totalItems and totalPages. To read everything, ask for page 0, then keep going until page reaches totalPages - 1.

Errors

Errors from /api/v1/event and /api/v1/spreelytics look like { "error": "Event API: FORBIDDEN", "message": "..." }, except 401, which is the plain text Event API: Unauthorised. Errors from /api/v1/spree and from Record a payment look like { "status": 403, "title": "Forbidden", "detail": "..." }.

StatusWhen
400A parameter is missing or malformed: an audience id that is not a UUID, an unusable regex pattern, a body that does not match the endpoint. Under /api/v1/spree, also a call that names no workspace when none can be worked out.
401No key, a key that does not exist or was revoked, an audience key used outside its audience or on an endpoint that does not take it, or an account key for an audience in a workspace you do not belong to.
403Your role in that workspace is too low for the change, for example This action needs the editor role.
404Under /api/v1/event and /api/v1/spreelytics: an item you asked for by id, such as a dashboard or a report, does not exist.
410Under /api/v1/spree and for payments, the same case: the goal, funnel or audience does not exist, or is in a workspace the call does not act on.
413The body is too large.
429Too many requests in flight at once. See Limits.

With an account key, an audience id that is a valid UUID but matches no audience is not an error under /api/v1/event: the answer is empty, as if the audience had no data.

Limits

  • Requests in flight. Under /api/v1/spree, and for Record a payment, each person can have 10 requests running at once across all of their account keys. The eleventh answers 429 with Too many concurrent requests. Please try again later. This limits how many run at the same time, not how many you make: requests sent one after another never reach it.
  • Endpoints under /api/v1/event and /api/v1/spreelytics do not limit how many requests a key makes.
  • Body size. Request bodies under /api/v1/event and /api/v1/spreelytics, and on the Tracking API, can be up to 250,000 bytes.
  • Plan allowance. Past three times your monthly event allowance, the Tracking API drops new pageviews and events. Past the allowance itself, reports stop at the moment it ran out. See Billing.

See your own numbers today.

Free up to 100k events a month · no credit card

Contact

Tell us what you need.

A real person reads these. We usually reply within one working day.

Or email us directly at contact@spreelytics.com.