Every integration sends data to UMAP360 through a single endpoint: POST /v1-batch. You send a JSON batch of events, authenticated with a
write key, and the platform
resolves identity, computes attribution, and evaluates engagement rules for you.
The endpoint
POST https://YOUR_PROJECT_REF.supabase.co/functions/v1/v1-batch
Authorization: Bearer uk_live_YOUR_WRITE_KEY
Content-Type: application/jsonThe request body is a single object with a batch array of events:
{ "batch": [ /* 1–100 event objects */ ] }Content-Type must be application/json — any other value is rejected with
400. Ingestion requires a write key (uk_live_); an admin key also works,
but a read key returns 403.
A complete request
curl -X POST https://YOUR_PROJECT_REF.supabase.co/functions/v1/v1-batch \
-H "Authorization: Bearer uk_live_YOUR_WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"batch": [
{
"type": "track",
"event": "Purchase Completed",
"messageId": "msg_abc123",
"timestamp": "2026-02-28T10:00:00.000Z",
"anonymousId": "anon_xyz789",
"userId": "user_456",
"properties": {
"order_id": "ORD-123",
"total": 4999,
"currency": "INR"
},
"context": {
"page": { "url": "https://example.com/checkout", "path": "/checkout" },
"campaign": { "source": "google", "medium": "cpc", "gclid": "abc123" }
}
}
]
}'Event schema
Every event is a JSON object. Four fields are required on every event, regardless of type — if any is missing, that event is rejected (the rest of the batch still processes):
| Field | Type | Notes |
|---|---|---|
type | string | One of track, identify, page, screen, alias, group. |
messageId | string | Unique per event. Used for de-duplication on the server. |
anonymousId | string | Stable per-device/browser anonymous identifier. |
timestamp | string | ISO 8601, e.g. 2026-02-28T10:00:00.000Z. |
The remaining fields are conditional or optional:
| Field | Type | Notes |
|---|---|---|
event | string | Required for track. The event name, e.g. Purchase Completed. |
userId | string | Required for alias. A known user identifier; send it once you've identified the visitor. |
groupId | string | Required for group. The group/account identifier. |
previousId | string | Conventional on alias — the prior identity to link from. |
properties | object | Event properties (for track). |
traits | object | User traits such as email, phone, name (for identify). |
context | object | Device, page, and campaign context — see The context object. |
What the server actually validates
The server enforces the four required fields above plus the per-type rule shown
in the table below (track needs event, alias needs userId, group needs
groupId). It does not reject an identify, page, or screen event for
missing optional fields — but an identify with no userId and no traits, or
a screen with no name in properties, carries no useful signal. Send the
recommended fields even though they aren't strictly enforced.
It also screens identifiers: anonymousId (always) and userId /
previousId (when present) are rejected if they exceed 200 characters or match a
denylist of junk/sentinel values (null, undefined, anonymous, guest, the
all-zero UUID, [object Object], …, matched case-insensitively after Unicode
normalization) — the per-event reasons are Invalid anonymousId /
Invalid userId / Invalid previousId. A track event whose event name
exceeds 200 characters is rejected with event name too long. Each bad event is
dropped on its own; the rest of the batch still ingests. This guards the identity
graph from poisoning — the SDK mints anon_<uuid> ids that always pass.
Event types
| Type | Purpose | Additionally required |
|---|---|---|
track | A custom behavioural event. | event |
identify | Attach a userId and traits to the current visitor. | — (send userId and/or traits) |
page | A page view (web). | — |
screen | A screen view (app). | — |
alias | Link a previous identity to a userId. | userId |
group | Associate the visitor with a group/account. | groupId |
How event names are stored
track events are stored under the name you send in event. The other types
are stored under a system name, which is what you'll see in the dashboard and
the Profiles API:
| Sent type | Stored event_name |
|---|---|
page | $pageview |
screen | $screen |
identify | $identify |
alias | $alias |
group | $group |
alias now merges profiles; group does not yet
alias is acted on server-side: an alias event whose previousId is
distinct from both userId and anonymousId folds the prior identity's profile
into the userId profile (a real identity merge). A self-alias — or one where
previousId equals the current anonymousId — is handled by ordinary identity
resolution rather than a merge. Because previousId now drives a merge, an
illegal/junk previousId is rejected per-event (Invalid previousId).
group is not yet acted on — group events are accepted, validated, and
stored as $group, but groupId is not used to associate profiles today.
For the common anonymous→known case, identify (the same anonymousId plus a
userId/traits) is still the simplest path; reach for alias when you need to
link two distinct identities. See Identity
stitching.
The context object
context is free-form JSON and is stored largely as-sent alongside the
event — the server never validates or rejects an event for its context contents.
It makes two changes on the way in: it adds a server-derived ip_address, and it
strips context.fingerprint when consent is absent (see below). UMAP360
recognises the following fields for attribution and identity:
interface Context {
page?: { url?: string; path?: string; title?: string; referrer?: string;
search?: string; hash?: string; language?: string };
campaign?: { source?: string; medium?: string; name?: string; term?: string; content?: string;
// click IDs fed to the offline-conversion (Google / Meta CAPI) pipeline:
gclid?: string; fbclid?: string; ttclid?: string; msclkid?: string; li_fat_id?: string;
gbraid?: string; wbraid?: string; twclid?: string; igshid?: string;
epik?: string; dclid?: string; rdt_cid?: string; gad_source?: string;
referrer_chain?: { url: string; timestamp: string }[] };
device?: { id?: string; type?: string; manufacturer?: string; model?: string; uaClientHints?: object };
browser?: { name?: string; version?: string };
os?: { name?: string; version?: string };
screen?: { width?: number; height?: number; density?: number;
viewport?: { innerWidth?: number; innerHeight?: number } };
network?: { online?: boolean; wifi?: boolean; cellular?: boolean; saveData?: boolean };
session?: { id?: string; started_at?: string; page_views?: number;
events_count?: number; is_new_session?: boolean };
fingerprint?: { hash?: string; components?: object };
consent?: boolean | { fingerprinting?: boolean; identity?: boolean; analytics?: boolean };
gpc?: boolean; // Global Privacy Control signal, when asserted
locale?: string;
timezone?: string;
timezone_offset_mins?: number;
app?: { name?: string; version?: string; build?: string; namespace?: string };
library?: { name?: string; version?: string };
user_agent?: string;
}The Web SDK populates all of these for you; direct-REST integrators send whatever subset they have.
Click-ID fields under campaign (gclid, fbclid, …) are associated with the
visitor's profile and used by the offline-conversion pipeline (Google / Meta
CAPI), which reads each profile's stored click-IDs.
Fingerprint requires explicit consent
If you send context.fingerprint, the server strips it unless the same
context carries an explicit grant — consent: true, consent.fingerprinting: true, or consent.identity: true. Without that grant the fingerprint is dropped
before identity resolution and never stored. See Consent — DPDP &
GDPR.
The response
A request that passes authentication, batch-shape validation, rate limits, and
your plan's event quota returns 200 OK. The body reports how many events were
accepted:
{ "success": true, "processed": 1, "failed": 0, "duration_ms": 45 }If some events in the batch fail validation or processing, the request is
still 200 — success flips to false and a per-event errors array
lists the first ten failures, each as "<messageId>: <reason>":
{
"success": false,
"processed": 8,
"failed": 2,
"errors": [
"msg_abc: messageId is required",
"msg_def: event name is required for track events"
],
"duration_ms": 120
}Check the body, not just the status code
Because partial failures return 200, a 200 does not guarantee every event
landed. Inspect failed and errors on every response, not just the HTTP
status. The full list of per-event reasons is on the Errors & rate
limits page.
An empty batch ({ "batch": [] }) is accepted and returns
{ "success": true, "processed": 0, "failed": 0, "duration_ms": ... }.
Response headers
Every response carries X-Request-ID (your value echoed back, or a server-minted
one — useful for support correlation), API-Version: 1, X-Content-Type-Options: nosniff, and Referrer-Policy: no-referrer — see Response
headers.
While your org is in a billing grace period, a successful 200 also carries
X-UMAP360-Quota-Status: grace (exposed via Access-Control-Expose-Headers, so
cross-origin JS can read it); an active org sends no such header.
Two warn-only transforms (not rejections)
Two server-side adjustments are applied but never reject an event: (1) a
timestamp dated more than ~5 minutes in the future is clamped (which also
keeps retry de-duplication stable); (2) oversized properties / traits (more
than ~300 keys, a value over ~32 KiB, or nesting deeper than 8 levels) emit a
soft-cap warning but are still stored. The only hard request ceilings are the
1 MiB body and 100-event batch caps below.
Batch limits
| Limit | Value | On breach |
|---|---|---|
| Events per request | 100 | 400 — Batch too large: maximum 100 events per request |
| Request body size | 1 MiB (1,048,576 bytes) | 413 — structured body with limit_bytes and a hint |
The 1 MiB cap is enforced twice, so a request with a missing or understated
Content-Length can't slip past it: first as a fast pre-auth check on the
Content-Length header, then again while the body is read — the parser streams
and aborts at the ceiling on the actual bytes received. Either way you get
the structured 413 shown above. Split large volumes into multiple requests of
≤100 events / ≤1 MiB each — see Event
volume.
Next
- Errors & rate limits — every status code, the error envelope, rate limits, and the retry policy.
- Profiles API — read the profiles your events resolve into.
- Consent API — record / read per-purpose consent decisions.
- Authentication — key types and how to keep them safe.
Last updated 2026-06-10