Ingestion API

The ingestion endpoints are what the SDKs call to send data. They are authenticated by a project token, not the admin key. Send the token in the X-Cohorly-Token header (applies to the whole batch) or per record (which wins over the header).

Authentication

Pass your project token via the X-Cohorly-Token header. An unknown or missing token returns 401 with { "status": 0, "error": "invalid token" }. Per-record tokens (properties.token on /track, the token field on /engage and /alias) override the header and are stripped before storage.

Limits

LimitValueOn exceed
Batch size500 records per request400 malformed/too large
Request body1 MB413 payload too large
Rate limit (per IP)100 requests / 10 seconds429 rate limited
Rate limit (per project)1,000 requests / 10 seconds429 rate limited

The rate limit applies to /track and /engage. See Rate limits for the full 429 contract, including the per-project limit, the monthly event quota, and recommended retry behavior.

Track events

POST/track

Record one event, or a batch of up to 500.

The body is a single event object or an array of them (max 500). Each event needs an event name and a properties object containing at least distinct_id. The server enriches every stored event with receive time, processing time, payload size, and geo from the client IP - client-sent values are never overridden.

An event carrying both $device_id and $user_id, with the two different and distinct_id equal to $user_id, implicitly links $device_id's history into $user_id's identity - no separate /alias call needed. See Identity and Link an identity below.

NameTypeRequiredDefaultDescription
eventstringRequired-

Event name, for example Signed Up.

propertiesobjectRequired-

Event properties; must include distinct_id.

properties.distinct_idstringRequired-

Who this event belongs to.

properties.timeintegerOptional-

Unix ms. Defaults to server receive time if omitted.

properties.$insert_idstringOptional-

Client-generated idempotency key for deduplication.

properties.tokenstringOptional-

Per-event project token; overrides the X-Cohorly-Token header for this event, then is stripped before storage.

properties.*anyOptional-

Any other JSON-serializable property is stored as-is.

bash
curl -X POST https://cohorly-service.velloalabs.com/track \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '[
{
"event": "Signed Up",
"properties": { "distinct_id": "user_123", "plan": "pro" }
},
{
"event": "Page Viewed",
"properties": {
"distinct_id": "user_123",
"time": 1753900800000,
"$insert_id": "b1f2c3d4-...",
"path": "/pricing"
}
}
]'
json
{ "status": 1, "inserted": 2 }

Update user profiles

POST/engage

Set, increment, or delete profile properties for a distinct_id.

The body is a single operation or an array of them (max 500). Each operation targets a distinct_id and carries one or more of the profile update verbs. $set/$set_once ops receive Mixpanel-parity geo and $last_seen defaults merged in server-side (client-sent keys always win).

NameTypeRequiredDefaultDescription
distinct_idstringRequired-

The profile to update.

$setobjectOptional-

Set properties, overwriting existing values.

$set_onceobjectOptional-

Set properties only if not already present.

$addobject of numbersOptional-

Increment numeric properties.

$unsetstring[]Optional-

Remove the named properties. Requires an owner or superadmin credential - see below.

$deletebooleanOptional-

Delete the entire profile row. Requires an owner or superadmin credential - see below.

tokenstringOptional-

Per-op project token; overrides the X-Cohorly-Token header.

Destructive verbs need more than the project token

The project token ships in your public client bundle, so $unset and $delete are not honored on the token alone. Send an Authorization: Bearer header with a credential valid for the admin API - your superadmin API key, or a Firebase ID token for an owner of the org that owns the target project. Ops without it are refused individually: the rest of the batch still applies, and the response stays HTTP 200 so SDK retry queues are never wedged (a 4xx here would make retry-on-failure queues replay non-idempotent $add ops). $set, $set_once and $add continue to work with the project token alone.

json
{
"status": 0,
"error": "$delete/$unset require an owner or superadmin credential",
"applied": 1,
"refused": [{ "index": 1, "op": "$delete" }]
}
bash
curl -X POST https://cohorly-service.velloalabs.com/engage \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '{
"distinct_id": "user_123",
"$set": { "name": "Ada Lovelace", "plan": "pro" },
"$add": { "logins": 1 }
}'
json
{ "status": 1, "applied": 1 }

Link an identity

POST/alias

Link an id into a known id's identity.

Both alias and distinct_id are required. This links alias (typically an anonymous id) into distinct_id's identity cluster, so both resolve to one person. Whether it rewrites history or only routes future events depends on alias's own stored history: if everything ever stored under alias looks like an anonymous, device-minted id - or nothing is stored under it at all - the link is retroactive, and events already stored under alias are rewritten onto distinct_id. Otherwise (alias already has events of its own, including from a previously identified user) the link only takes effect going forward: new events under alias route to distinct_id, but nothing already stored moves. Either way it cannot be undone. See Identity for the full mechanics, including the other cases where a link is silently refused (for example, alias already belongs to a different cluster) - the response below does not change in any of these cases, since a governance decision must never wedge an SDK retry queue.

NameTypeRequiredDefaultDescription
aliasstringRequired-

The (usually anonymous) id being linked in.

distinct_idstringRequired-

The id whose identity it joins.

tokenstringOptional-

Per-item project token; overrides the X-Cohorly-Token header.

bash
curl -X POST https://cohorly-service.velloalabs.com/alias \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '{ "alias": "anon_abc", "distinct_id": "user_123" }'
json
{ "status": 1 }
PreviousGo
NextRate limits