PUBLIC
Accepts a batch of analytics events from the mobile SDK.
The batch is enqueued for async processing — the caller receives 202 immediately.
Every event carries event_type (dot-namespaced), occurred_at (ISO 8601 device timestamp),
and an optional properties map with event-specific fields:
| event_type | properties |
|---|---|
session.start | source (string), is_first_session (boolean) |
session.end | duration_ms (integer) |
screen.view | screen (string), duration_ms (integer) |
interaction.tap | element (string), screen (string) |
interaction.search | query (string), screen (string), result_count (integer) |
Any event may carry an optional exception subfield (see AnalyticsException) and/or
an optional performance subfield (see AnalyticsPerformance) describing the cost of
the event itself.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
Why this endpoint exists
Storefront behaviour — which screens customers open, what they search for, where they drop out, which requests were slow, what crashed — is the data that explains conversion. Collecting it centrally means it can be correlated with the orders and catalog data the platform already holds, which your own analytics cannot see.
The endpoint is batch and asynchronous: you post a set of events and get 202 immediately. The batch is queued and processed out of band, so ingestion never sits on your customer's critical path.
Built for the mobile SDK, but usable by any client that can post JSON.
Request
curl -X POST https://api.acc.funtrips.io/v2/analytics/events \
-H "x-campaign-id: $CAMPAIGN_ID" \
-H "x-correlation-id: 0f4c8e1a-9b2d-4c7e-8a1f-2d3b4c5e6f70" \
-H "Content-Type: application/json" \
-d '{
"context": {
"sdk_version": "2.4.1",
"app_version": "8.12.0",
"platform": "ios",
"device_model": "iPhone15,2",
"os_version": "18.4",
"locale": "nl-NL",
"network_type": "wifi"
},
"events": [
{
"event_type": "session.start",
"occurred_at": "2026-09-03T14:02:11Z",
"properties": { "source": "push", "is_first_session": false }
},
{
"event_type": "screen.view",
"occurred_at": "2026-09-03T14:02:12Z",
"ended_at": "2026-09-03T14:02:31Z",
"properties": { "screen": "merchant_detail", "duration_ms": 19000 },
"correlation_ids": ["9a1f…", "b47c…"]
},
{
"event_type": "interaction.search",
"occurred_at": "2026-09-03T14:02:40Z",
"properties": { "query": "zoo", "screen": "search", "result_count": 12 }
}
]
}'context — required, once per batch
context — required, once per batch| Field | Required | Notes |
|---|---|---|
sdk_version | Yes | |
app_version | Yes | |
platform | Yes | ios or android |
device_model | No | |
os_version | No | |
locale | No | e.g. nl-NL |
network_type | No | wifi, cellular, ethernet, offline |
Sent once per batch rather than per event, because it describes the client, not the event. platform is a closed enum — a web client has no valid value today, which is worth knowing before you plan a browser integration on this endpoint.
events — at least one
events — at least one| Field | Required | Notes |
|---|---|---|
event_type | Yes | Dot-namespaced, e.g. screen.view |
occurred_at | Yes | Device timestamp, ISO 8601, when the event started |
ended_at | No | When it ended. Omitted → the next event's occurred_at is used |
properties | No | Event-specific key/values |
correlation_ids | No | Correlation ids from API responses during this event |
performance | No | Cost of the event itself |
exception | No | Error captured during the event |
Recognised event types
event_type | properties |
|---|---|
session.start | source (string), is_first_session (boolean) |
session.end | duration_ms (integer) |
screen.view | screen (string), duration_ms (integer) |
interaction.tap | element (string), screen (string) |
interaction.search | query (string), screen (string), result_count (integer) |
Any event may also carry exception and performance subfields. Use the documented types where they fit — they are what downstream reporting understands. A custom event_type is accepted but will not appear in standard reports.
correlation_ids is the reason to bother
correlation_ids is the reason to botherEvery API response carries a correlation id. Attach the ones you collected during an event and a slow or failed screen becomes traceable to the exact platform requests behind it.
That turns "the merchant page feels slow" into a specific set of server-side traces. Without it, a client-side timing is a number nobody can act on. Collect the X-Correlation-Id response header on each call and attach it to the event that made the call.
x-correlation-id: one per session
x-correlation-id: one per sessionSend a stable value for the whole session so all of that session's batches group into one trace. Omit it and one is generated per request, which scatters a single session across unrelated traces.
This is the opposite of the per-request advice everywhere else in the API, and it is intentional: here the unit of interest is the session, not the call.
Response
202 Accepted — queued. No body.
The 202 means accepted for processing, not processed. There is no per-event validation feedback, and no way to query what happened to a batch. Malformed individual events may be dropped downstream without notifying you, so validate client-side before sending.
Errors
| Status | type | Meaning |
|---|---|---|
400 | analytics-empty-batch | events was empty |
400 | — | Malformed body, missing context field, or invalid platform |
502 | analytics-ingest-failed | The batch could not be queued. Retryable |
Treat any non-202as "retry later, then drop". Analytics must never block the customer.
Client guidance
Batch, do not stream. Buffer events and flush on a timer, on a size threshold, or on backgrounding. One event per request is a waste of the customer's battery and network.
Never block the UI. Fire and forget. Analytics failing must never be visible to a customer.
Drop rather than grow. Cap your local buffer and discard the oldest events when it fills. Analytics is not worth unbounded memory or unbounded disk, and a client that retries a poisoned batch forever will eventually be the reason your app is slow.
Retry with backoff, then give up. A 502 deserves a couple of retries; after that, drop the batch.
Do not put personal data inproperties.No e-mail addresses, names, addresses, tokens or ids that identify an individual.
interaction.searchcarries the customer's query text — that is expected — but it is also the field where personal data most easily leaks in by accident, because customers type all sorts of things into search boxes. Consider whether you need the raw query at all, or whether length and result count would answer your question.
202Batch accepted for processing.
