Billing and usage metering
Per-capability cost attribution, CO₂ estimates, and chargeback reports — available to organisation admins via client.billing.*
Every AI capability call — LLM completion, speech-to-text transcription, embedding, TTS synthesis, OCR, image generation, video analysis — produces a metered usage event. The billing.* API lets organisation admins query cost, token, run, and CO₂ estimates across any combination of workspace, team, user, capability, vendor, model, or region.
Billing metering is observe-only. A failed billing write never blocks the capability call. Figures labelled CO₂ are modelled estimates, not energy-meter readings — see Carbon methodology below.
API overview
All billing.* methods are available on the Better Auth admin client (getAdminClient()) from @scrydon/multi-tenancy/auth/client.
| Method | Auth required | What it returns |
|---|---|---|
billing.log(...) | M2M internal (billing:admin) | Records one usage event. Called by the capability runtime — not by product code directly. |
billing.summary(...) | Org admin session | Totals + optional grouped breakdown |
billing.timeseries(...) | Org admin session | Day-by-day series |
billing.topN(...) | Org admin session | Top N keys by a metric |
billing.chargeback(...) | Org admin session | Per-key cost + share of org total |
billing.carbonFactors(...) | Org admin session | Full carbon-factor table (for auditors) |
Usage event fields
Each metered event captures:
| Field | Type | Description |
|---|---|---|
organizationId | string | Organisation that owns the event |
occurredAt | string (ISO) | When the capability call completed |
workspaceId | string | null | Workspace that drove the call, if known |
teamId | string | null | Team that drove the call, if known |
userId | string | null | User who triggered the call, if known |
source | "workflow" | "copilot" | "chat" | "wand" | "kb_ingest" | "api" | Product surface |
capability | "llm" | "stt" | "embedding" | "tts" | "ocr" | "video" | "image" | Capability category |
vendor | string | Vendor slug (e.g. openai, anthropic) |
model | string | Model identifier (e.g. gpt-4o, whisper-1) |
region | string | null | Hosting region used for carbon estimation |
tokensIn | number | Input tokens consumed |
tokensOut | number | Output tokens produced |
units | number | null | Non-token usage (audio-seconds, pages, images, video-seconds) |
durationMs | number | Wall-clock time of the capability call |
success | boolean | Whether the call succeeded |
costUsd | number | Frozen cost at write time (USD) |
co2Grams | number | Frozen CO₂ estimate at write time (grams CO₂e) |
pricingVersion | string | Pricing-catalog version used to compute costUsd |
carbonVersion | string | Carbon-factor-table version used to compute co2Grams |
executionId | string | null | Workflow execution that triggered the call, if any |
workflowId | string | null | Workflow definition that triggered the call, if any |
Summary
Returns aggregate totals for the organisation over a date range, with an optional per-dimension breakdown.
const { data, error } = await getAdminClient().billing.summary({
organizationId: "org_abc123",
range: { from: "2026-06-01T00:00:00Z", to: "2026-07-01T00:00:00Z" },
groupBy: "workspace", // optional
filters: { capability: "llm" }, // optional dimension filter
})Request
| Field | Type | Required | Description |
|---|---|---|---|
organizationId | string | Yes | Organisation to query |
range.from | string (ISO) | Yes | Start of the range (inclusive) |
range.to | string (ISO) | Yes | End of the range (exclusive) |
groupBy | UsageDimension | No | Group the breakdown by this dimension |
filters | Partial<Record<UsageDimension, string>> | No | Narrow results to a single dimension value |
UsageDimension is one of: workspace, team, user, source, capability, vendor, model, region.
Response (UsageSummary)
{
costUsd: number, // total cost over the range
co2Grams: number, // total CO₂ estimate (grams CO₂e)
tokensIn: number,
tokensOut: number,
runs: number, // total capability calls
successes: number,
groups: Array<{
key: string, // dimension value (e.g. workspace id)
costUsd: number,
co2Grams: number,
tokens: number,
runs: number,
}>,
}Timeseries
Returns a day-by-day series for charting consumption over time.
const { data, error } = await getAdminClient().billing.timeseries({
organizationId: "org_abc123",
range: { from: "2026-06-01T00:00:00Z", to: "2026-07-01T00:00:00Z" },
granularity: "day",
groupBy: "capability", // optional
})Request
| Field | Type | Required | Description |
|---|---|---|---|
organizationId | string | Yes | Organisation to query |
range.from | string (ISO) | Yes | Start (inclusive) |
range.to | string (ISO) | Yes | End (exclusive) |
granularity | "day" | Yes | Only daily granularity is supported in v1 |
groupBy | UsageDimension | No | Additional grouping key per bucket |
Response
{
points: Array<{
bucket: string, // ISO day (YYYY-MM-DD)
costUsd: number,
co2Grams: number,
tokens: number,
runs: number,
}>,
}Top-N
Returns the top N keys by a chosen metric over a date range — useful for identifying the highest-cost workspaces, models, or vendors.
const { data, error } = await getAdminClient().billing.topN({
organizationId: "org_abc123",
range: { from: "2026-06-01T00:00:00Z", to: "2026-07-01T00:00:00Z" },
dimension: "model",
metric: "cost_usd",
limit: 10,
})Request
| Field | Type | Required | Description |
|---|---|---|---|
organizationId | string | Yes | Organisation to query |
range | UsageRange | Yes | Date range |
dimension | UsageDimension | Yes | What to rank |
metric | UsageMetric | Yes | What to rank by: cost_usd, co2_grams, tokens, runs |
limit | number (1–100) | Yes | Number of rows to return |
Response
{
rows: Array<{
key: string, // dimension value
value: number, // metric total
}>,
}Chargeback
Returns cost, CO₂, token, and run totals per key, plus each key's share of the organisation total. Use this for internal cost allocation.
const { data, error } = await getAdminClient().billing.chargeback({
organizationId: "org_abc123",
range: { from: "2026-06-01T00:00:00Z", to: "2026-07-01T00:00:00Z" },
dimension: "workspace",
})Response (ChargebackRow[])
Array<{
key: string, // dimension value (e.g. workspace id)
costUsd: number,
co2Grams: number,
tokens: number,
runs: number,
share: number, // 0..1 — fraction of org-total cost
}>share values across all rows sum to approximately 1. Events with no value for the chosen dimension are grouped under "(unattributed)".
Carbon factors
Returns the full carbon-factor table used to compute co2Grams on every event. Intended for auditors and ESG reporting.
const { data, error } = await getAdminClient().billing.carbonFactors({
organizationId: "org_abc123",
})Response
{
version: string, // e.g. "2026.06.0" — matches usage_event.carbon_version
regionFallback: string, // region used when vendor region is unknown
gridGco2PerKwh: Record<string, number>, // gCO₂e per kWh by region
energyWhPer1k: Record<string, number>, // Wh per 1k tokens/units by model class
pue: number, // power-usage effectiveness multiplier
citation: string, // provenance string for audit
}Carbon methodology
CO₂ figures are modelled estimates, not energy-meter readings. Present them as estimates in any external report.
Scrydon estimates AI-capability carbon footprint using a three-factor model:
CO₂ (g) = energy_per_1k_units_Wh × (total_tokens_or_units / 1000) × PUE × (grid_gco2_per_kwh / 1000)- Energy per 1k tokens (Wh): Binned by model class (
llm_large,llm_small,embedding,stt, etc.) using published per-token energy estimates. Non-token classes (STT, OCR, image, video) use per-audio-second, per-page, or per-image denominators. - PUE (Power-Usage Effectiveness): A fixed multiplier of 1.12 — a mid-range value within the 1.1–1.2 range published by major hyperscalers.
- Grid carbon intensity (gCO₂e/kWh): Region-specific values from public electricity-grid carbon intensity databases. Requests without a known hosting region fall back to the global average (436 gCO₂e/kWh).
The factor table is versioned (carbon_version). Every usage_event row records the version in use at write time, so historical totals remain reproducible after factor updates. Retrieve the current table and its provenance string via billing.carbonFactors(...).
Required role
All billing.summary, billing.timeseries, billing.topN, billing.chargeback, and billing.carbonFactors calls require an org-admin session for the queried organizationId. A non-admin member receives a 403 Forbidden response.
billing.log is an internal M2M endpoint gated on the billing:admin capability. It is called by the capability runtime — not by product code or SDK consumers.
Related
- Platform admins and impersonation — how to confirm your org-admin role.
- Integrations — connecting vendors whose usage is metered here.
- Compliance — how usage metering maps to EU AI-Act Article 13 and ESG attestation requirements.