API Reference
Events
The Event resource is the core unit of the Cobalt analytics graph. Every interaction — page views, clicks,
backend mutations, server-side jobs — is captured as a typed event tied to a Session and an Identity.
This page documents the three endpoints that make up the event surface.
Overview
Events are immutable once accepted. Each event carries a type string (using reverse-DNS notation
like checkout.completed or auth.session.refreshed), a Timestamp, the
session_id it was emitted within, and an arbitrary properties object validated against
the project’s schema registry.
Cobalt accepts events in two shapes: single-event POST /v1/events and batched POST /v1/events:batch
(recommended for backend ingest, where you’ll commonly send 100–500 events per request). Both endpoints return
the canonical Event object with the server-assigned id and any schema_warnings
the validator surfaced.
Authentication
All requests require a project API key supplied as a bearer token in the Authorization header.
Keys are scoped per project and per environment — a cblt_test_… key cannot read or write to a
live project. Rotate keys from the project settings; rotated keys remain valid for a 24-hour grace window.
curl https://api.cobalt.dev/v1/events \
-H "Authorization: Bearer cblt_live_4f8a2e1c9d7b3f6a" \
-H "Content-Type: application/json" \
-d '{
"type": "checkout.completed",
"session_id": "ses_01HZQ3X2K8M9N4P7R6T5V0W3Y1",
"properties": {
"order_id": "ord_92ab4f",
"amount_cents": 4900,
"currency": "USD"
}
}'
Endpoints
POST
/v1/events
Create a single event. Events are accepted asynchronously — the response indicates the event was queued
for ingest, not that it has been written to long-term storage. Use the id returned to look it
up later via GET /v1/events/:id.
| Name | Type | Required | Description |
|---|---|---|---|
type
|
string
|
Yes | Reverse-DNS event type, max 128 chars. |
session_id
|
string
|
Yes |
The
Session
this event belongs to. Must already exist or be auto-created via the SDK.
|
occurred_at
|
Timestamp
|
No | RFC3339 timestamp. Defaults to server receive time. |
properties
|
object
|
No | Arbitrary JSON, validated against the project schema registry. Max 64KB. |
idempotency_key
|
string
|
No | Client-supplied dedupe key, stable for 24h. |
Example request
import { Cobalt } from "@cobalt/sdk";
const cobalt = new Cobalt({ apiKey: process.env.COBALT_KEY! });
const event = await cobalt.events.create({
type: "checkout.completed",
session_id: "ses_01HZQ3X2K8M9N4P7R6T5V0W3Y1",
occurred_at: new Date().toISOString(),
properties: {
order_id: "ord_92ab4f",
amount_cents: 4900,
currency: "USD",
},
idempotency_key: "ord_92ab4f-completed",
});
Example response
{
"id": "evt_01HZQ3XAC6F4N9R2T8V5W7Y0K3",
"type": "checkout.completed",
"session_id": "ses_01HZQ3X2K8M9N4P7R6T5V0W3Y1",
"identity_id": "idy_74kp2m",
"occurred_at": "2026-04-30T08:14:22.117Z",
"received_at": "2026-04-30T08:14:22.418Z",
"properties": {
"order_id": "ord_92ab4f",
"amount_cents": 4900,
"currency": "USD"
},
"schema_warnings": []
}
GET
/v1/sessions/:id
Retrieve a single Session by id. The response includes the session’s full event timeline by
default — pass ?include=summary to receive a summary projection instead, which is dramatically
cheaper for sessions with thousands of events.
| Name | Type | Required | Description |
|---|---|---|---|
id
|
string
|
Yes |
Path parameter — the session id, e.g.
ses_01HZQ3…
.
|
include
|
"events" | "summary"
|
No |
Defaults to
events
.
|
limit
|
integer
|
No | Cap on events returned. Max 1000, default 200. |
cursor
|
string
|
No | Pagination cursor returned by the previous page. |
Example response
{
"id": "ses_01HZQ3X2K8M9N4P7R6T5V0W3Y1",
"status": "active",
"started_at": "2026-04-30T07:51:09.220Z",
"last_seen_at": "2026-04-30T08:14:22.418Z",
"identity_id": "idy_74kp2m",
"event_count": 47,
"events": [
{ "id": "evt_01HZQ3X2…", "type": "page.viewed", "occurred_at": "2026-04-30T07:51:09.220Z" },
{ "id": "evt_01HZQ3X9…", "type": "search.submitted", "occurred_at": "2026-04-30T07:52:34.001Z" }
],
"next_cursor": "eyJ0IjoiMjAyNi0wNC0zMFQwOC..."
}
GET
/v1/insights
List computed Insight objects for the project. Insights are projections of the event stream —
funnels, retention curves, anomaly detections — refreshed on a schedule defined per insight. The response
is paginated and ordered by computed_at descending.
| Name | Type | Required | Description |
|---|---|---|---|
kind
|
"funnel" | "retention" | "anomaly"
|
No | Filter by insight kind. |
since
|
Timestamp
|
No | Only return insights computed at or after this time. |
status
|
Session.Status
|
No | Restrict to insights derived from sessions in a given status. |
limit
|
integer
|
No | Page size. Max 100, default 25. |
Example request
const insights = await cobalt.insights.list({
kind: "funnel",
since: "2026-04-01T00:00:00Z",
limit: 50,
});
for (const insight of insights.data) {
console.log(insight.id, insight.computed_at, insight.summary);
}
Errors
The Cobalt API uses conventional HTTP status codes and returns a structured error body for every non-2xx
response. The body contains a stable code, a human-readable message, and — when
applicable — a request_id you should include in support tickets.
400 Bad Request- The request body did not parse, or a required field was missing. The error
codewill be one ofinvalid_payload,missing_field, orschema_violation. 401 Unauthorized- The bearer token was missing, malformed, or has been revoked. Rotate the key from project settings if you suspect compromise.
404 Not Found- The resource id does not exist within the authenticated project. Cross-project lookups are never permitted — use the project's own key.
409 Conflict- An
idempotency_keywas reused with a different payload. Cobalt will not silently overwrite a stored event; resolve the conflict client-side. 429 Too Many Requests- The project’s per-second ingest budget has been exhausted. The response includes
Retry-AfterandX-Cobalt-RateLimit-Resetheaders — back off and retry.
Webhooks
Cobalt can deliver event-derived notifications to your own HTTPS endpoint. Configure subscriptions from the
project settings, choose the event types you care about, and verify the Cobalt-Signature
header on every incoming request using the shared signing secret.
Verifying a delivery
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyCobaltSignature(
rawBody: string,
header: string,
secret: string,
): boolean {
const [tsPart, sigPart] = header.split(",");
const timestamp = tsPart?.split("=")[1] ?? "";
const signature = sigPart?.split("=")[1] ?? "";
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && timingSafeEqual(a, b);
}