Ingest analytics events

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_typeproperties
session.startsource (string), is_first_session (boolean)
session.endduration_ms (integer)
screen.viewscreen (string), duration_ms (integer)
interaction.tapelement (string), screen (string)
interaction.searchquery (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.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…

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

FieldRequiredNotes
sdk_versionYes
app_versionYes
platformYesios or android
device_modelNo
os_versionNo
localeNoe.g. nl-NL
network_typeNowifi, 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

FieldRequiredNotes
event_typeYesDot-namespaced, e.g. screen.view
occurred_atYesDevice timestamp, ISO 8601, when the event started
ended_atNoWhen it ended. Omitted → the next event's occurred_at is used
propertiesNoEvent-specific key/values
correlation_idsNoCorrelation ids from API responses during this event
performanceNoCost of the event itself
exceptionNoError captured during the event

Recognised event types

event_typeproperties
session.startsource (string), is_first_session (boolean)
session.endduration_ms (integer)
screen.viewscreen (string), duration_ms (integer)
interaction.tapelement (string), screen (string)
interaction.searchquery (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

Every 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

Send 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

StatustypeMeaning
400analytics-empty-batchevents was empty
400Malformed body, missing context field, or invalid platform
502analytics-ingest-failedThe batch could not be queued. Retryable
📘

Treat any non-202 as "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 in properties.

No e-mail addresses, names, addresses, tokens or ids that identify an individual. interaction.search carries 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.

Body Params
context
object
required
events
array of objects
required
length ≥ 1
events*
Headers
uuid
required

Campaign scope for this request. Filters content to the specified campaign.

uuid

Correlation ID. Send a stable value per session so a session's batches group into one trace; one is generated if omitted.

Responses
202

Batch accepted for processing.

Language
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/problem+json