TrustAnalytics API
The server API takes events that only your backend knows for sure: an order your payment provider confirmed, a refund, a signup confirmed by e-mail, an error in a background job. They land in the same website as the browser events, and when you pass the browser’s session id, in the same visit.
With the same key you read the statistics of the project’s websites for reports, dashboards or a spreadsheet of your own.
Base path: https://api.trustcomponent.com/analytics/v1 Auth: Authorization: Bearer ak_… (a Project API key)
Authentication
Section titled “Authentication”Create an API key under Settings › API Keys & Webhooks of the project that holds the Analytics module, and keep it on your server — never in the browser. The key names the project; every batch and every statistics request names the website by its website id, shown in the website settings next to the site key and listed by GET /websites. A website of another project answers 404.
The browser SDK uses the public site key instead and needs no API key.
Send events
Section titled “Send events”curl -X POST "https://api.trustcomponent.com/analytics/v1/server-events/batches" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ak_your_key_here" \ -d '{ "websiteId": "cc2e2d5e-d1ef-4a7f-a7bd-dec5b37df47a", "sentAt": "2026-11-02T10:30:00.000Z", "hasConsent": true, "release": "2026.11.1", "events": [ { "id": "5f0e6a1c-2b8d-4c1e-9a7f-3d2b1c0e9f8a", "occurredAt": "2026-11-02T10:29:58.000Z", "name": "signup_confirmed", "category": "CUSTOM_EVENTS", "userId": "user-4711", "properties": { "plan": "pro" } } ] }'Batch:
| Field | Required | Description |
|---|---|---|
websiteId | Yes | Website the events belong to |
sentAt | Yes | When your server sent the batch; used to correct clock drift |
hasConsent | Yes | Whether the end users of this batch consented; applies to every event without its own hasConsent, see Consent |
release | No | Version of your backend, to compare errors between releases (up to 100 characters) |
events | Yes | 1 to 50 events, in the order they happened |
Event:
| Field | Required | Description |
|---|---|---|
id | Yes | A string of 1 to 100 characters, unique within the website and compared exactly: a UUID, or the id the event already has in your system (an order id, the id of a payment provider’s refund or webhook event). A retried batch carries the same ids, see Retries |
occurredAt | Yes | When it happened; events older than 48 hours are dropped as STALE |
name | Yes | Event name (up to 100 characters). purchase, refund, error and identify are reserved |
category | Yes | Category of the event, e.g. CUSTOM_EVENTS, COMMERCE, ERRORS, IDENTITY |
pageViewId | No | The page load in the browser the event belongs to, see Link to visits |
userId | If no pageViewId | Id of the user in your system — never an e-mail address (up to 200 characters). Not needed for a refund, which its order id identifies |
url | No | Page the event relates to; only hostname, path, the query parameters the website’s filter keeps and campaign parameters are kept |
pagePattern | No | Route of the page with its variables written as :name or *, e.g. /orders/:orderId/complete; groups the page in reports and is ignored when it does not match the path of url |
ipAddress | No | Address of the end user, for country and rules only, never stored |
userAgent | No | User agent of the end user, for browser and device only, never stored |
hasConsent | No | Whether the end user of this event consented; takes precedence over hasConsent of the batch, which applies when it is left out, see Consent |
properties | No | Up to 50 string values (up to 500 characters each); for identify these are the user properties |
commerce | For purchase | See Purchases and refunds |
refund | For refund | See Purchases and refunds |
error | For category ERRORS | type, message (up to 500), stack (up to 4000), source |
The address and user agent of the request itself belong to your server and are never used for geo, device or bot detection. Without ipAddress and userAgent, country and device stay unknown.
Browser-only data is refused with BROWSER_ONLY: pageview and pageleave, and the categories WEB_VITALS, NETWORK_DIAGNOSTICS, CONSOLE_DIAGNOSTICS and HEATMAPS.
Consent
Section titled “Consent”A server has no consent banner, so the batch says whether the end user agreed: hasConsent is true when your own consent records hold a consent for the users in this batch. An event can say it for its own user with hasConsent on the event, which takes precedence over the batch; an event without it follows the batch. The website’s collection modes decide what that means: an event of a group on Only with consent without consent is dropped as CONSENT, a group on Always is stored either way.
On top of that, the website’s switches, your plan and your rules apply as for browser events — a rule with the condition Source › Server applies only to server events. You vouch for the consent with the batch and with every event that states its own.
The userId of a server event, and a server identify with user properties, need Recognition and cookie to be collected for the event — on Always, or on Only with consent when the event or, without its own value, the batch says hasConsent: true. Otherwise the event is kept without the user id and gets a session of its own unless it names a pageViewId.
Events of users with different consent can share one batch: set hasConsent on the events whose user differs from the batch value.
Link to visits
Section titled “Link to visits”- With
pageViewIdthe event joins the visit of that page load — its source, campaign, landing page and device. Read the id in the browser withtrustanalytics.getPageViewId()and send it to your backend with the order or form, e.g. as a hidden field or request property. - Without
pageViewIdthe event joins the latest session of the sameuserIdwithin the last 30 minutes. If there is none, it opens a server session without a page (shown as(server)) and counts as Unattributed in source reports. - Either way, the event carries the source Server, which you can filter by in every report (Sent from).
Purchases and refunds
Section titled “Purchases and refunds”{ "id": "9d1c…", "occurredAt": "2026-11-02T10:29:58.000Z", "name": "purchase", "category": "COMMERCE", "pageViewId": "1b6f…", "commerce": { "orderId": "order-10045", "amountMinor": 5490, "taxMinor": 877, "shippingMinor": 490, "discountMinor": 490, "coupon": "AUTUMN10", "currency": "EUR", "products": [ { "productId": "sku-2041", "name": "Trail running shoe", "quantity": 2, "unitPriceMinor": 2495 }, { "productId": "sku-0007", "unitPriceMinor": 500 } ] }}{ "id": "4a7e…", "occurredAt": "2026-11-05T08:12:00.000Z", "name": "refund", "category": "COMMERCE", "userId": "user-4711", "refund": { "orderId": "order-10045", "amountMinor": 2495 }}- Both use the category
COMMERCE; amounts are in the minor unit of the order currency (cents for EUR). commerce:orderId(up to 100 characters, unique per website),amountMinor,currency(ISO 4217), optionalitemCountand up to 50productswithproductId,unitPriceMinorand optionalname,category,quantity.- Tax, shipping, discount, coupon: optional
taxMinorandshippingMinor(the parts ofamountMinorthat are tax and shipping),discountMinor(what was already taken offamountMinor, as a positive number) andcoupon(the code the customer redeemed, up to 100 characters) are stored with the order as your shop reports them.amountMinorstays the total paid and is never computed from them. The three amounts lie between 0 and 1000000000000 likeamountMinor; a purchase with one outside that range is dropped asMALFORMED. - Browser and server: a server purchase replaces a browser purchase with the same order id, a browser purchase never replaces a server one. Without
products, the order lines of the browser purchase are kept, and so are its tax, shipping, discount and coupon wherever the server purchase leaves one out. The server event itself is not stored again and is reported asORDER_CONFIRMED. A second server purchase with the same order id is dropped asDUPLICATE_ORDER. - Refunds need the order to be known to the website — otherwise they are dropped as
UNKNOWN_ORDER. A refund is counted once per event id, so send the id of the refund (or of the payment provider’s refund) as theidof the event. Several partial refunds add up, never beyond the order total. Revenue in the reports is net of refunds.
Responses
Section titled “Responses”| Code | Meaning |
|---|---|
202 | Batch processed — the body lists what was stored and what was dropped |
400 | Batch malformed, or more than 50 events |
401 | API key missing or unknown |
403 | Tracking suspended, because the project is locked or its trial has expired |
404 | No website with this id in the project of the API key |
413 | Request body larger than 1 MB |
429 | Too many batches for this API key — wait the seconds in Retry-After and retry |
{ "accepted": 48, "dropped": [ { "reason": "DUPLICATE", "count": 1 }, { "reason": "CONSENT", "count": 1 } ]}A batch is never refused because of single events: events that are not allowed are dropped one by one, and dropped lists each reason that occurred with its count — MALFORMED, STALE, PRIVACY_SIGNAL, CONSENT, PLAN, RULE, DUPLICATE, DUPLICATE_ORDER, ORDER_CONFIRMED, UNKNOWN_ORDER, BROWSER_ONLY, ERASED (HOSTNAME and BOT apply to browser events only). They also appear under Settings › Data collection & privacy › Last 24 hours of the website, see Dropped events.
Retries
Section titled “Retries”Generate each event’s id once and store it with the event (or derive it from your own id, e.g. a UUID v5 of the order id for a purchase; a refund uses its own id, never the order id, or the second partial refund of an order is dropped as DUPLICATE). On a timeout, 429 or 5xx, send the same batch again (after a 429 once the seconds in its Retry-After header have passed): events that were already stored are counted as DUPLICATE, never twice. Retry within 48 hours, after that the events are STALE.
Read statistics
Section titled “Read statistics”curl "https://api.trustcomponent.com/analytics/v1/websites/cc2e2d5e-d1ef-4a7f-a7bd-dec5b37df47a/stats/breakdowns?dimension=PATH&from=2026-10-01T00:00:00.000Z&to=2026-11-01T00:00:00.000Z&filter=COUNTRY:IS:DE&pageSize=10" \ -H "Authorization: Bearer ak_your_key_here"| Endpoint | Returns |
|---|---|
GET /websites | The websites of the project with id, name, hostnames and time zone, paged with page and pageSize (up to 1,000, 100 by default) |
GET /websites/{id}/stats/overview | Pageviews, sessions, visitors, events, bounces and engaged time of the range and of the period before it |
GET /websites/{id}/stats/timeseries | The same figures per hour or per day (granularity=HOUR or DAY), empty buckets as zero, at most 1,000 points |
GET /websites/{id}/stats/breakdowns | The figures split by one dimension — PATH, ENTRY_PATH, REFERRER_SOURCE, UTM_CAMPAIGN, COUNTRY, BROWSER, EVENT_NAME and more — paged with page and pageSize (up to 1,000) |
GET /websites/{id}/stats/conversions | Conversions, conversion rate and revenue of every goal |
GET /websites/{id}/realtime | Sessions active in the last five minutes, their pages and the pageviews per minute |
- Range:
from(inclusive) andto(exclusive) as UTC timestamps, both required. Days start at midnight in the time zone of the website. - Filters:
filter=dimension:operator:value, repeatable up to 20 times — for examplePATH:STARTS_WITH:/blog,UTM_SOURCE:IS:newsletterorBOUNCE:IS:false. Operators areIS,IS_NOT,CONTAINS,NOT_CONTAINS,STARTS_WITH,ENDS_WITHandMATCHES(a regular expression). Filters on different dimensions must all match, filters on the same dimension are alternatives. - Match mode: only
GET /websites/{id}/stats/conversionstakesfilterMatchMode:ANYcounts a session that matches any one of the filters instead of all of them,ALLis the default. The other endpoints always require all filters on different dimensions to match. - Retention: without a filter the figures come from the aggregates and reach back as far as those are kept; with a filter they are counted from the stored sessions and events, as in Explore. A range beyond that answers
400. Beyond the stored sessions,visitorsis estimated from the hourly aggregates (within about 1 %). - Limits: 60 requests per minute and API key; beyond that
429with aRetry-Afterheader that names the seconds until the next request is accepted. All plans include the statistics API. While the project is locked or its trial has expired, the statistics answer403;GET /websitesstill lists the websites.