Data model
Cohorly's data model has three core concepts - events, users, and projects - all scoped by project. Event properties are stored as schemaless JSON, so you never define a schema up front.
Projects
A project represents one app you are tracking. All data is scoped to a project; create projects in the dashboard under Settings.
interface Project {
id: number;
name: string;
token: string; // UUID, authenticates ingestion
created_at: number; // unix ms
}The token is what SDKs send with each request; Cohorly resolves it to a project_id and stores data under it. Ingestion is authed by this token, while the admin and query API is authed by your account API key.
Events
An event is an immutable record of something a user did. Events belong to a project and a distinct_id, carry a time, and store their properties as jsonb.
| Column | Type | Notes |
|---|---|---|
project_id | integer | Owning project. |
event | text | Event name. |
distinct_id | text | User/device this event belongs to. |
time | bigint | Unix milliseconds. |
$insert_id | text | Idempotency key for deduplication. |
properties | jsonb | Arbitrary event properties (GIN indexed). |
Properties are stored as jsonb with a GIN index, so filters and breakdowns on property keys are queryable without a fixed schema.
Users
A user (profile) aggregates identity and profile properties for a distinct_id within a project. Profiles are mutated via the /engage endpoint (the People API), while the derived stats come from the underlying events.
interface UserProfile {
distinct_id: string;
properties: Record<string, unknown>; // from $set / $set_once / $add / $unset
first_seen: number | null; // unix ms
last_seen: number | null; // unix ms
event_count: number;
}Aliases
Aliasing links an anonymous distinct_id to a known one so that pre-login and post-login activity resolve to a single user. The web SDK issues an /alias call automatically on the first identify() after being anonymous.
Multi-project isolation
Every table is keyed by project_id, and every query resolves a target project (from the token on ingestion, or the projectId parameter on the admin/query API). This keeps data for separate apps fully isolated within one Cohorly deployment.