Reporting usage across companies

Plenty of questions come down to one number per company: how much of a metered feature did they use between these two dates. Who is pushing their limit and ready for an upgrade conversation, which accounts grew month over month, what does the usage distribution look like. GET /feature-usage-history answers that, one row per company, per feature, per period.

Prefer this over GET /feature-usage-timeseries. Its points are cumulative within the billing period, so summing them double counts. Feature usage history reports incremental usage, so its rows can be summed.

Getting a month

Ask for the window and read the rows.

1import { SchematicClient } from "@schematichq/schematic-typescript-node";
2
3const client = new SchematicClient({ apiKey: process.env.SCHEMATIC_API_KEY });
4
5const page = await client.entitlements.listFeatureUsageHistory({
6 startTime: new Date("2026-06-01T00:00:00Z"),
7 endTime: new Date("2026-07-01T00:00:00Z"),
8});
9
10for (const row of page.data) {
11 console.log(row.companyId, row.featureId, row.usage);
12}

Each row carries:

FieldNotes
companyIdThe company, by Schematic ID (comp_...).
featureIdThe feature the row reports.
eventSubtypeThe event key the usage was measured from.
periodStart / periodEndThe window the figure covers, clamped to what you asked for.
usageIncremental total for that period. Rows can be summed.

This endpoint scans every company in the environment, so it requires a secret API key (sch_ prefix). Publishable keys and the Stripe app are deliberately shut out. See Authentication.

The start is included, the end is not

June is 2026-06-01T00:00:00Z to 2026-07-01T00:00:00Z, which covers all of June and nothing in July. Usage recorded at exactly midnight on July 1 belongs to July. That is what lets you run twelve consecutive months and have them tile with no gaps and nothing counted twice.

Both bounds must land on an hour boundary in UTC. Usage is measured hourly, so an off-hour bound is rejected rather than rounded, and the error names whichever end is wrong. We would rather fail loudly than quietly shift the window you asked for.

$# 400: The start of the range must fall on an hour boundary in UTC.
$start_time=2026-06-01T00:30:00Z

Event features only

Usage over a date range is only meaningful for features that measure events. Trait and boolean feature IDs return a 400 telling you so.

A company with no usage is absent, not zero

If a company recorded no events in the window, it has no row in the response. It does not appear with usage: 0.

So the response tells you who used the feature, not who exists. If you need the full picture, including the accounts that used nothing, iterate your own list of companies and treat a missing company as zero.

Bucketing with granularity

Omit granularity and you get a single total per company and feature covering the whole window. That is what a ranking or a one-off total wants.

Pass hourly, daily, weekly, or monthly to break the window into buckets, with one row per company, per feature, per bucket.

A single request may span at most 366 buckets. That is a year of days, or roughly two weeks of hours. Longer requests are rejected; ask for a shorter range or a coarser granularity.

Paging

Default page size is 100 and the maximum is 250. Page by raising offset until a page comes back empty.

1const rows = [];
2const limit = 250;
3let offset = 0;
4
5while (true) {
6 const page = await client.entitlements.listFeatureUsageHistory({
7 startTime: new Date("2026-06-01T00:00:00Z"),
8 endTime: new Date("2026-07-01T00:00:00Z"),
9 limit,
10 offset,
11 });
12
13 rows.push(...page.data);
14 if (page.data.length === 0) break;
15 offset += limit;
16}

Advance offset by limit, not by the number of rows you received.

Where two features measure the same event key, both are reported, so a page can come back with more rows than the limit you asked for. Paging happens before that fan-out. Rows are never dropped or repeated, but a loop that advances by page.data.length will skip some.

Worked example: finding upgrade candidates

Pull last month for one feature, sum per company, and rank. The companies at the top are the ones leaning hardest on the feature, which is where an upgrade conversation starts.

1import { SchematicClient } from "@schematichq/schematic-typescript-node";
2
3const client = new SchematicClient({ apiKey: process.env.SCHEMATIC_API_KEY });
4
5const startTime = new Date("2026-06-01T00:00:00Z");
6const endTime = new Date("2026-07-01T00:00:00Z");
7
8// 1. Pull every row for the window, narrowed to the feature you care about.
9const rows = [];
10const limit = 250;
11let offset = 0;
12
13while (true) {
14 const page = await client.entitlements.listFeatureUsageHistory({
15 startTime,
16 endTime,
17 featureIds: ["feat_api_calls"],
18 limit,
19 offset,
20 });
21 rows.push(...page.data);
22 if (page.data.length === 0) break;
23 offset += limit;
24}
25
26// 2. Sum per company. usage is incremental, so this is just addition.
27const totals = new Map();
28for (const row of rows) {
29 totals.set(row.companyId, (totals.get(row.companyId) ?? 0) + row.usage);
30}
31
32// 3. Rank, and take the companies worth a conversation.
33const candidates = [...totals.entries()]
34 .sort((a, b) => b[1] - a[1])
35 .filter(([, used]) => used > UPGRADE_THRESHOLD);

listFeatureUsageHistory requires @schematichq/schematic-typescript-node 1.5.9 or newer. Earlier releases predate the endpoint and do not have the method. Other SDKs may need a release cut before it appears.