Skip to content

Ingest API

The Swift SDK and the web tracker speak this API for you. Use it directly to write an SDK for another platform, or to send events from a server.

Base URL: https://e.millimetry.app.

For native apps: a batch of events from one device.

Header Required Notes
Content-Type: application/json yes
Content-Encoding: gzip recommended Uncompressed bodies are accepted too
X-App-Key yes The app’s public key, ak_…
X-Batch-Id yes A UUID you create when you form the batch, reused on every retry of it
{
"sdk": "kotlin@0.1.0",
"system": {
"os_name": "Android",
"os_version": "16",
"device_model": "Pixel 10",
"locale": "en-CA",
"app_version": "1.5.0",
"app_build": "42",
"is_debug": false
},
"events": [
{
"name": "screen_view",
"ts": "2026-10-05T17:03:12.345Z",
"session_id": "0199b3c2-5f1e-7a10-9c3d-2b8e4f6a1d22",
"props": { "screen": "home", "items": 3, "premium": true }
}
]
}
  • system describes the device once per batch. Start a new batch when any of it changes, for example after an app update.
  • session_id is a UUID (version 7 preferred) you create at the start of a session and keep in memory only. End a session after 30 minutes in the background or without events.
  • Never send an identifier for the device or the person. The server derives the daily visitor ID itself.
Response Meaning What your SDK does
200 {"accepted": n, "dropped": m} Stored, or this batch ID was already stored Delete the batch
200 {"accepted": 0, "dropped": n, "reason": "over_quota"} The organization’s monthly limit is used up Delete the batch
200 {"accepted": 0, "dropped": n, "reason": "paused"} The app is paused in the dashboard Delete the batch
400 Malformed body or invalid system Delete the batch; it will never succeed
401 Unknown key Delete the batch
413 Body too large Split the batch and retry
429 with Retry-After Rate limited Keep the batch; retry after the delay
503, possibly with Retry-After Temporarily unavailable, or this batch ID is still being stored Keep the batch; retry with exponential backoff

Because the batch ID is reused across retries, a batch is stored once however many times it arrives.

For websites: one event per request, sent as JSON with Content-Type: text/plain. That keeps it a simple CORS request, with no preflight, and lets navigator.sendBeacon send it.

{ "k": "wk_…", "n": "pageview", "u": "https://example.com/pricing?utm_source=newsletter", "r": "https://www.google.com/", "w": 1440, "p": { "plan": "pro" } }
Field Meaning
k The site’s key, wk_…
n Event name (pageview for page views)
u The page URL
r document.referrer (optional)
w screen.width (optional)
p Properties (optional)

The request’s Origin must be one of the site’s origins. The answer is 202 with an empty body, 400 for a malformed event, 403 for an unknown key or origin, and 429 when rate limited. Trackers do not retry.

Item Rule
Events per batch 1–100
Body at most 512 KB after decompression
Event name 1–64 characters, no control characters; names starting with $ are reserved
Event time ISO 8601 in UTC. Up to 10 minutes in the future is treated as now; anything later, or older than 7 days, is dropped
Properties at most 25; keys 1–64 characters; strings cut at 256 characters; finite numbers; Booleans; null, arrays and objects are dropped
Personal data properties named like personal data, and values that look like an email, a phone number or a UUID, are dropped
  • Per client address: 10 batch requests a second (bursts of 20), or 30 web events a second (bursts of 60).
  • Per key: in proportion to your plan, averaged over a minute, and at least 50 events a second.

GET /healthz answers 200 while the service is up.