Analytics
Emit your own product events, and read back aggregated usage, API volume and revenue for this application.
Your own product events, and the aggregates built from them.
POST /analytics/events emits events; GET /analytics reads back usage, API volume and revenue for your application.
await connect.analytics.emit([
{ name: "campaign_sent", occurredAt: new Date().toISOString(), value: 1 },
]);
const { series } = await connect.analytics.series({
metrics: ["usage.events", "api.requests"],
from,
to,
});Send events in batches - one request per user action is a request per user action.
Events are bucketed by UTC day. An occurredAt far in the past or the future is clamped rather than rejected, so a client with a wrong clock cannot rewrite history or poison tomorrow. Backfill has a bounded window; anything older is clamped to it.
Distinct from Logs: an audit entry is one attributable action a person can be asked about, an analytics event is a number in an aggregate.
Endpoints
POST /api/v1/application/events
Emit your own product events
Scope analytics:write · Rate limit 120 requests / min per key
Pushes your platform's own product events into Connect's analytics pipeline, so your usage shows up alongside the numbers Connect already collects. The sibling of logs:write: that one is a searchable log line, this one is a countable event.
Implementation
1. Prerequisite - an API key scoped to analytics:write.
2. Send up to 50 events per request:
curl -X POST "https://connect.cosmoops.com/api/v1/application/events" \
-H "Authorization: Bearer YOUR_API_KEY_PLAINTEXT" \
-H "Content-Type: application/json" \
-d '{"events":[{"name":"checkout.completed","value":4999,"unit":"paise","sessionToken":"..."}]}'3. Name your events namespace.action, lowercase. Prefixes Connect emits itself (api., billing., addon., subscription., connect.) are rejected, so your events can never be mistaken for - or inflate - Connect's own metered numbers.
4. Pass sessionToken to attribute an event to the signed-in user and their workspace. Without it the event still lands, just unattributed: attributed in the response tells you how many were tied to a user. An expired or wrong-application token is not an error either, for the same reason - losing the event would be worse than losing its attribution.
value is a whole number in the smallest unit of unit (paise, bytes, milliseconds). Fractional measures belong in props.
occurredAt is optional and clamped server-side to within 48 hours - enough to replay a queue after an outage, not enough to rewrite a period that has already been reported.
Errors
forbidden (403 - key missing analytics:write), validation_error (422 - reserved prefix, malformed name, or oversized props), rate_limited (429).
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
events | array | yes |
Responses
| Status | Meaning |
|---|---|
200 | Events accepted |
403 | forbidden - key missing analytics:write |
422 | validation_error |
429 | rate_limited |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
accepted | integer | yes | |
attributed | integer | yes |
GET /api/v1/application/analytics
Read this application's aggregated analytics
Scope analytics:read · Rate limit 60 requests / min per key
Daily numbers for your application - usage volume, API call counts, active users, and subscription/revenue totals - over a date range of up to 90 days.
Implementation
1. Prerequisite - an API key scoped to analytics:read.
2. Send the request:
curl "https://connect.cosmoops.com/api/v1/application/analytics?metrics=usage.events,api.requests&from=2026-08-01&to=2026-08-25" \
-H "Authorization: Bearer YOUR_API_KEY_PLAINTEXT"3. Read series - one entry per requested metric, each with a points array of { day, value }.
A value of null is not zero. It means that day is not measurable for that metric: usage.active_users is a distinct count that only exists after the nightly rollup, so today's is always null, and future days are null for the obvious reason. total sums only the measurable days and partial is true whenever any day was skipped - render that caveat rather than a confident total.
The scope is always your application, taken from the presenting key. There is no parameter for it, by design.
Errors
forbidden (403 - key missing analytics:read), validation_error (422 - unknown metric, inverted range, or a range of 90 days or more), rate_limited (429).
Responses
| Status | Meaning |
|---|---|
200 | Daily metric series |
403 | forbidden - key missing analytics:read |
422 | validation_error |
429 | rate_limited |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
from | string | yes | |
to | string | yes | |
series | array | yes |