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
| Limit | Value | On exceed |
|---|---|---|
| Batch size | 500 records per request | 400 malformed/too large |
| Request body | 1 MB | 413 payload too large |
| Rate limit (per IP) | 100 requests / 10 seconds | 429 rate limited |
| Rate limit (per project) | 1,000 requests / 10 seconds | 429 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
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.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
event | string | Required | - | Event name, for example Signed Up. |
properties | object | Required | - | Event properties; must include distinct_id. |
properties.distinct_id | string | Required | - | Who this event belongs to. |
properties.time | integer | Optional | - | Unix ms. Defaults to server receive time if omitted. |
properties.$insert_id | string | Optional | - | Client-generated idempotency key for deduplication. |
properties.token | string | Optional | - | Per-event project token; overrides the X-Cohorly-Token header for this event, then is stripped before storage. |
properties.* | any | Optional | - | Any other JSON-serializable property is stored as-is. |
bashcurl -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
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).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
distinct_id | string | Required | - | The profile to update. |
$set | object | Optional | - | Set properties, overwriting existing values. |
$set_once | object | Optional | - | Set properties only if not already present. |
$add | object of numbers | Optional | - | Increment numeric properties. |
$unset | string[] | Optional | - | Remove the named properties. Requires an owner or superadmin credential - see below. |
$delete | boolean | Optional | - | Delete the entire profile row. Requires an owner or superadmin credential - see below. |
token | string | Optional | - | 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" }]}
bashcurl -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
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.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
alias | string | Required | - | The (usually anonymous) id being linked in. |
distinct_id | string | Required | - | The id whose identity it joins. |
token | string | Optional | - | Per-item project token; overrides the X-Cohorly-Token header. |
bashcurl -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 }