Server-side ingest API
Send pageviews and custom events from your backend with the server-side ingest API: the endpoint, ingest key authentication, event fields, limits and responses.
Send events from your server when the browser can't or shouldn't: payment webhooks, signups confirmed by email, API products, backend jobs, or pages served to clients without JavaScript.
Endpoint#
POST https://privatusanalytics.com/api/events
Authorization: Bearer pik_…
Content-Type: application/json
{
"events": [
{
"type": "event",
"name": "Purchase",
"url": "https://example.com/checkout",
"revenue": 49.99,
"currency": "USD",
"props": { "plan": "pro" },
"user_agent": "Mozilla/5.0 (Macintosh; …) Safari/605.1.15",
"ip": "203.0.113.7"
}
]
}
Authenticate with the site's secret ingest key (pik_…), either as a
Bearer token or in an X-Ingest-Key header. Each key belongs to one site.
See Ingest keys.
Event fields#
| Field | Required | Description |
|---|---|---|
type |
Yes | pageview or event (custom is an alias). engagement, vital and click are also accepted but need an existing visit |
name |
For events | Event name. Longer names are cut to 120 characters. For vital, one of LCP, INP, CLS, FCP or TTFB |
url |
Yes | Full http(s) URL of the page. Its hostname must be one of the site's domains (or a subdomain of one) |
referrer |
No | Where the visitor came from, which drives sources and channels |
props |
No | Up to 30 properties. More are ignored |
revenue |
No | Amount in major units, e.g. 49.99 (Revenue) |
currency |
No | ISO 4217 code. Defaults to the site currency |
timestamp |
No | ISO 8601. Must be within the last 72 hours. Otherwise (or if missing or in the future) the time of receipt is used |
user_agent |
In practice, yes | The visitor's browser User-Agent. Missing or library User-Agents are dropped as bots (see below) |
ip |
No | The visitor's IP. Used in memory for the daily visit key, the data-center check and IP-range traffic rules, then dropped. It is not used for location |
session_hint |
No | An opaque string that groups your server events into one visit. See Sessions |
language |
No | e.g. en-US. Cut to 16 characters |
screen_width |
No | Pixels, stored as a size bucket (xs, sm, md, lg or xl) |
dnt, gpc |
No | true when the visitor sent Do Not Track or Global Privacy Control. The event is dropped if the site honors that signal (DNT and GPC) |
hash_mode |
No | true to keep the URL's # fragment as part of the page path |
engaged_ms |
No | For engagement: engaged time in milliseconds, capped at 4 hours |
scroll_depth |
No | For engagement: scroll depth from 0 to 100, rounded down to a multiple of 10 |
value |
No | For vital: the measured value |
selector |
No | For click: the clicked element's selector, cut to 512 characters |
Country. The country is read from the request that delivers the batch,
not from the ip field. Server-side events therefore carry the country of
the server that sent them, not the visitor's.
The short keys the browser tracker uses (t, n, u, r, p…) are
accepted too. See What the tracker sends.
Limits#
- Up to 100 events per request. More returns
422. - The request body must be at most 32 KB. A larger body, or one that
isn't a JSON object, is read as empty: you get
202with an emptydatalist and nothing is recorded. Split big batches. - Up to 600 requests per minute per ingest key. More returns
429with aRetry-Afterheader. - Everything else (property limits, name length, PII scrubbing, path masking, traffic rules, bot filtering) works exactly as for browser hits.
Response#
202 Accepted with one result per event, in order:
{
"data": [
{ "index": 0, "status": "accepted", "reason": null },
{ "index": 1, "status": "dropped", "reason": "bot_automation" }
]
}
accepted means the event was queued. It is written in a batch and
normally appears in the dashboard within seconds. dropped events aren't stored and don't count toward
usage. Reasons:
| Reason | Meaning |
|---|---|
invalid |
A field has the wrong type: a text field (url, name, referrer, ip, user_agent, session_hint and so on) is an object, a list or a boolean, or a number field (screen_width) isn't a finite number |
bad_type |
type isn't one of the accepted values |
bad_url |
url is missing or not an http(s) URL |
foreign_host |
The URL's hostname isn't one of the site's domains |
privacy_signal |
The event carried DNT/GPC (dnt/gpc fields) and the site honors it |
bot_empty_user_agent |
No user_agent |
bot_known_bot |
A known crawler User-Agent |
bot_automation |
An automation or HTTP-library User-Agent (see Bot rules) |
bot_datacenter |
The ip belongs to a cloud or hosting network |
bot_referrer_spam |
The referrer is on the referrer-spam list |
rule_<list> |
Blocked by a traffic rule. <list> is hostnames, paths, referrers, countries, ip_ranges, user_agents or events |
vital_sampled |
A Web Vital outside the site's sample rate, or with an unknown name |
no_visit |
An engagement, vital or click hit with no visit to attach to |
paused |
Collection is paused for the workspace by Privatus Analytics support |
limit_reached |
The workspace reached its monthly event limit, so pageviews and custom events are not recorded until the reset or an upgrade (see Monthly limit) |
Errors:
| Status | Body error.code |
When |
|---|---|---|
401 |
unauthorized |
Missing or unknown ingest key |
422 |
validation_failed |
More than 100 events, or events isn't a list of objects |
429 |
rate_limited |
More than 600 requests in a minute with one key |
Next#
- Examples in curl, Node.js, Python, PHP, Go, Ruby and Next.js.
- The browser's no-JavaScript pixel for pages without JavaScript.
Create, rotate and revoke the secret ingest key that authenticates server-side events, keep it out of browser code, and see how it differs from an API token.
How server-side events are grouped into visits with the daily salted visit key and how session_hint splits or joins visits.
Bot filtering rules for User-Agents, crawlers and data centers, and how to send server-side events with the visitor's User-Agent and IP so they count.
Server-side ingest examples that send an event with curl, Node.js, Python, PHP, Go, Rails and Next.js, forwarding the visitor's User-Agent and IP.