Events API v2
v2 supports custom events and batch ingestion.
Event naming
Events use a two-part naming scheme:
event_type— entity noun (e.g.project,invite,billing)action— verb describing what happened (e.g.created,sent,invoice_paid)
Both fields use snake_case: pattern ^[a-z][a-z0-9_]*$
Preferred format (recommended)
Send event_type and action as separate fields:
| event_type | action | canonical_name |
|---|---|---|
project |
created |
project_created |
invite |
sent |
invite_sent |
checkout |
abandoned |
checkout_abandoned |
billing |
invoice_paid |
billing_invoice_paid |
Unified event format (new)
Send a single event string. The server auto-splits it into event_type and action using the first separator found (dot, colon, or underscore):
| event | event_type | action |
|---|---|---|
user.signed_up |
user |
signed_up |
billing:invoice_paid |
billing |
invoice_paid |
project_created |
project |
created |
No deprecation headers are returned for this format.
Legacy format (deprecated)
A flat event_type string without action is still accepted for backward compatibility.
The server auto-splits it (e.g. project_created → event_type: "project", action: "created").
Legacy requests receive deprecation headers:
Deprecation: trueSunset: 2026-09-01
Single event endpoint
POST /api/v2/events
Accepts:
event— unified event name with separator (one ofevent/event_typerequired)event_type— entity noun (one ofevent/event_typerequired)action— verb (omit for unified or legacy flat format)subject_type(user,account,subscription)subject_idoccurred_atproperties(object)metadata(alias ofproperties)external_id(idempotency key)
Example (preferred format)
curl -X POST "/api/v2/events" \
-H "Authorization: Bearer YOUR_EVENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_type": "project",
"action": "created",
"subject_type": "user",
"subject_id": "user_123",
"occurred_at": "2026-02-10T12:00:00Z",
"properties": {
"workspace_id": "ws_1",
"template": "kanban"
}
}'
Response:
{
"data": {
"uuid": "evt_abc123",
"event_type": "project",
"action": "created",
"canonical_name": "project_created",
"source": "api",
"subject_type": "user",
"subject_id": "user_123",
"occurred_at": "2026-02-10T12:00:00Z",
"external_id": null
}
}
Example (unified event format)
curl -X POST "/api/v2/events" \
-H "Authorization: Bearer YOUR_EVENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "user.signed_up",
"subject_type": "user",
"subject_id": "user_123",
"occurred_at": "2026-02-10T12:00:00Z",
"properties": {
"source": "organic",
"plan": "free"
}
}'
Batch endpoint
POST /api/v2/events/batch
Use this for higher throughput and fewer network round-trips.
Example
curl -X POST "/api/v2/events/batch" \
-H "Authorization: Bearer YOUR_EVENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"event_type": "invite",
"action": "sent",
"subject_type": "user",
"subject_id": "user_1",
"occurred_at": "2026-02-10T12:00:00Z",
"external_id": "evt_1",
"properties": { "channel": "email" }
},
{
"event_type": "invite",
"action": "accepted",
"subject_type": "user",
"subject_id": "user_2",
"occurred_at": "2026-02-10T12:00:10Z",
"external_id": "evt_2",
"properties": { "channel": "email" }
}
]
}'
Example (batch with unified event format)
curl -X POST "/api/v2/events/batch" \
-H "Authorization: Bearer YOUR_EVENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"event": "feature.enabled",
"subject_type": "user",
"subject_id": "user_1",
"occurred_at": "2026-02-10T12:00:00Z"
},
{
"event": "feature.disabled",
"subject_type": "user",
"subject_id": "user_2",
"occurred_at": "2026-02-10T12:00:10Z"
}
]
}'
Idempotency
Use external_id to safely retry requests.
Deduplication is scoped by:
- product
- source
- external_id