import { Autumn } from 'autumn-js'
const autumn = new Autumn()
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "api_calls",
range: "30d",
});from autumn_sdk import Autumn
autumn = Autumn(secret_key="am_sk_test...")
res = autumn.events.aggregate(
feature_id="api_calls",
customer_id="cus_123",
range="30d",
bin_size="day",
)curl --request POST \
--url https://api.useautumn.com/v1/events.aggregate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'x-api-version: <x-api-version>' \
--data '
{
"customer_id": "cus_123",
"feature_id": "api_calls",
"range": "30d",
"bin_size": "day"
}
'{
"list": [
{
"period": 1762905600000,
"values": {
"messages": 10,
"sessions": 3
}
},
{
"period": 1762992000000,
"values": {
"messages": 3,
"sessions": 12
}
}
],
"total": {
"messages": {
"count": 2,
"sum": 13
},
"sessions": {
"count": 2,
"sum": 15
}
}
}
Aggregate Events
Aggregate usage events by time period. Returns usage totals grouped by feature and optionally by a custom property.
import { Autumn } from 'autumn-js'
const autumn = new Autumn()
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "api_calls",
range: "30d",
});from autumn_sdk import Autumn
autumn = Autumn(secret_key="am_sk_test...")
res = autumn.events.aggregate(
feature_id="api_calls",
customer_id="cus_123",
range="30d",
bin_size="day",
)curl --request POST \
--url https://api.useautumn.com/v1/events.aggregate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'x-api-version: <x-api-version>' \
--data '
{
"customer_id": "cus_123",
"feature_id": "api_calls",
"range": "30d",
"bin_size": "day"
}
'{
"list": [
{
"period": 1762905600000,
"values": {
"messages": 10,
"sessions": 3
}
},
{
"period": 1762992000000,
"values": {
"messages": 3,
"sessions": 12
}
}
],
"total": {
"messages": {
"count": 2,
"sum": 13
},
"sessions": {
"count": 2,
"sum": 15
}
}
}
Working with Properties
When tracking events, you can attach custom properties that can later be used for grouping aggregations:// Track an event with properties
await autumn.track({
customerId: "cus_123",
featureId: "api_calls",
value: 1,
properties: {
model: "gpt-4",
source: "api",
region: "us-east"
}
});
group_by parameter:
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "api_calls",
range: "7d",
groupBy: "properties.model" // Group by the "model" property
});
Special Group By Operators
In addition to custom properties, you can group by built-in columns using$-prefixed operators:
$customer_id— Group results by customer ID. Useful when aggregating across all customers (i.e. nocustomer_idspecified).$entity_id— Group results by entity ID. Useful for seeing usage broken down per entity.
// Aggregate across all customers, grouped by customer
const result = await autumn.events.aggregate({
featureId: "api_calls",
range: "7d",
groupBy: "$customer_id"
});
// Aggregate for a customer, grouped by entity
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "api_calls",
range: "7d",
groupBy: "$entity_id"
});
Response Format
The response structure changes based on whethergroup_by is provided:
Without group_by (Flat Response)
When no grouping is specified, values contains the aggregated sum for each feature:
{
"list": [
{
"period": 1762905600000,
"values": {
"api_calls": 150,
"messages": 45
}
}
],
"total": {
"api_calls": { "count": 10, "sum": 150 },
"messages": { "count": 5, "sum": 45 }
}
}
With group_by (Grouped Response)
When grouping is specified, values contains the total sum while grouped_values breaks down values by group:
{
"list": [
{
"period": 1762905600000,
"values": {
"api_calls": 150
},
"grouped_values": {
"api_calls": {
"gpt-4": 100,
"gpt-3.5": 50
}
}
}
],
"total": {
"api_calls": { "count": 10, "sum": 150 }
}
}
grouped_values field is only present when group_by is provided in the request.Deduction Breakdowns
By default, aggregations answer “how much usage was tracked?”. Passingaggregate_on: "deducted" additionally answers “which balances did that usage actually come out of?” — the response gains a deductions array, keyed by the balance-owning feature rather than the tracked event. The list and total fields are unchanged.
A customer_id is required in this mode, since deductions are resolved against a specific customer’s balances.
There are two situations where this matters:
Case 1: Usage spilling into a credit system
A feature can have its own included allowance and feed a credit system — usage drains the allowance first, then overflows into credits at the feature’s credit cost. The standard aggregation only shows tracked totals, so the overflow is invisible. Withaggregate_on: "deducted", the two sides show up separately:
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "observability_events",
range: "30d",
aggregateOn: "deducted"
});
{
"deductions": [
{
"period": 1762905600000,
"values": {
"observability_events": {
"feature_type": "metered",
"deducted": 1000000,
"events": 12,
"balances": [
{
"balance_id": "cus_ent_abc",
"entity_id": null,
"plan_id": "team",
"reset": { "interval": "month", "resets_at": 1789218895573 },
"credit_cost": null,
"deducted": 1000000,
"events": 12
}
]
},
"usage_credits": {
"feature_type": "credit_system",
"deducted": 0.8,
"events": 1,
"balances": [
{
"balance_id": "cus_ent_def",
"entity_id": null,
"plan_id": "team",
"reset": { "interval": "month", "resets_at": 1789218895573 },
"credit_cost": 0.00008,
"deducted": 0.8,
"events": 1
}
]
}
}
}
]
}
usage_credits pool, burning 0.8 credits at the feature’s credit cost of 0.00008. Each entry is in that balance’s own unit — events for the metered feature, credits for the credit system.
credit_cost is only populated when the request pins a single non-credit feature via feature_id, since several features feeding one pool each convert at a different rate. It reflects the credit system’s current schema.Case 2: Per-entity balances falling through to a shared pool
When entities (e.g. seats) each carry their own balance and overflow lands on a customer-level shared balance, group by$entity_id to see who spent from where:
const result = await autumn.events.aggregate({
customerId: "cus_123",
featureId: "ai_credits",
range: "30d",
aggregateOn: "deducted",
groupBy: "$entity_id"
});
{
"deductions": [
{
"period": 1762905600000,
"values": {
"ai_credits": {
"feature_type": "credit_system",
"deducted": 1650,
"events": 5,
"balances": [
{ "balance_id": "cus_ent_seat_3", "entity_id": "seat_3", "plan_id": "team_seat", "deducted": 600, "events": 2, ... },
{ "balance_id": "cus_ent_seat_4", "entity_id": "seat_4", "plan_id": "team_seat", "deducted": 600, "events": 1, ... },
{ "balance_id": "cus_ent_shared", "entity_id": null, "plan_id": "team_yearly", "deducted": 50, "events": 3, ... }
]
}
},
"grouped_values": {
"cus_ent_seat_3": { "seat_3": { "deducted": 600 } },
"cus_ent_seat_4": { "seat_4": { "deducted": 600 } },
"cus_ent_shared": {
"seat_3": { "deducted": 25 },
"seat_4": { "deducted": 25 }
}
}
}
]
}
- In
balances, find the entry whoseentity_idisnull. That’s the customer-level shared balance — the pool that entities fall through to when their own balance runs out. (Entries with anentity_idare balances owned by that entity.) - Take that entry’s
balance_idand look it up ingrouped_values. The keys of that object are the entities that spent from the shared pool, and eachdeductedis exactly how much they overdrew.
cus_ent_shared is the shared balance, and grouped_values["cus_ent_shared"] shows seats 3 and 4 each pulled 25 credits from it beyond their own 600-credit seat balances — the “used 25 extra credits” number to show next to each seat.
The seat-owned balances appear in grouped_values too, but for them the owner and the spender are the same entity, so the split just restates the balance total.
group_by: "$plan_id" is rejected with aggregate_on — every balance belongs to exactly one plan, so balances[].plan_id already carries the plan split.Body Parameters
Response
{
"list": [
{
"period": 1762905600000,
"values": {
"messages": 10,
"sessions": 3
}
},
{
"period": 1762992000000,
"values": {
"messages": 3,
"sessions": 12
}
}
],
"total": {
"messages": {
"count": 2,
"sum": 13
},
"sessions": {
"count": 2,
"sum": 15
}
}
}
Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Body
Feature ID(s) to aggregate events for
1Customer ID to aggregate events for
1Entity ID to filter aggregated events for (e.g., per-seat or per-resource limits)
1Property to group events by (e.g. "properties.region"), or "$customer_id" / "$entity_id" / "$plan_id" to group by those columns. When aggregate_on is "deducted", "$feature_id" groups deductions by the tracked feature that consumed each balance.
Time range to aggregate events for. Either range or custom_range must be provided
24h, 7d, 30d, 90d, last_cycle, 1bc, 3bc Size of the time bins to aggregate events for. Defaults to hour if range is 24h, otherwise day
day, hour, week, month Custom time range to aggregate events for. If provided, range must not be provided
Show child attributes
Show child attributes
Filter events by property values, e.g. {"model": "gpt-4", "region": "us"}. Maximum 5 filters.
Show child attributes
Show child attributes
Maximum number of distinct group values to return per time bin when using group_by. Remaining values are bundled into an 'Other' bucket. Defaults to 9
1 <= x <= 250Set to "deducted" to additionally return a per-balance breakdown of what each event consumed, under deductions. Purely additive: list and total are unchanged. Requires customer_id.
deducted Response
OK
Array of time periods with aggregated values
Show child attributes
Show child attributes
Total aggregations per feature. Keys are feature IDs, values contain count and sum.
Show child attributes
Show child attributes
Per-balance breakdown of what was consumed. Present only when aggregate_on is "deducted".
Show child attributes
Show child attributes