by, the breakdown by org (teams and classrooms, kind tells them apart) or by| Location | Name | Required | Meaning |
|---|---|---|---|
| query | by | Conditional / optional | {"type":"string","enum":["org","account"]} |
| query | from | Conditional / optional | {"type":"string","format":"date"} |
| query | to | Conditional / optional | {"type":"string","format":"date"} |
by, the breakdown by org (teams and classrooms, kind tells them apart) or by{error:{code,message}} for CloudError failures, attaches Request-Id, and disables caching. Handle 401 by signing in; show role refusal for 403 forbidden, reauthenticate only for step_up_required, and collect a reason for 400 reason_required. A CSRF failure needs a current page/session token, not blind retries. Operation-specific 404/409/413/422/502/503 errors retain their status. Honor Retry-After on 429./**
* GET /api/admin/usage/rollup?from&to&by=org|account: daily platform series for the range (default: 30 days to
* yesterday, at most 400) and, with `by`, the breakdown by org (teams and classrooms, `kind` tells them apart) or by
* account (pseudonymous principals from each day's top 50). Rollup rows only; never a scan.
*/
export const GET = adminRoute("usage.read", async (request) => {
const params = new URL(request.url).searchParams;
const by = params.get("by");
if (by !== null && by !== "org" && by !== "account") throw invalid("by must be org or account.");
const range = parseRange(params.get("from"), params.get("to"));
const rows = await listRollups("usage", range.from, range.to);
return adminJson({
range,
series: usageSeries(rows, range.from, range.to),
...(by === "org" && { orgs: orgBreakdown(rows) }),
...(by === "account" && { accounts: accountBreakdown(rows) }),
});
});