Skip to content

Admin Run Rollup

POST
/api/billing/admin/run-rollup
curl --request POST \
--url https://example.com/api/billing/admin/run-rollup \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "device_basis": "mtd_max", "dry_run": true, "organization_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "period_date": "2026-04-15" }'

Superuser-only, env-gated DRY-RUN of the §13.7 tier tariff (TODO-119).

Returns what the stack computes for organization_id through period_date from its real counters and stored usage_daily evidence, so a live suite can assert the billed amount (the nightly rollup has no on-demand trigger).

The result is a preview, not the invoice: for a closed month it can differ from run_tier_period_billing when that job would skip the month (final day never settled into usage_daily) or withhold it (BUG-293 mid-period downgrade guard), and for today it also folds in the live active-device count the nightly settle pass has not stored yet. See AdminRollupResponse.

Strictly read-only: no usage_daily upsert, no evidence-chain event, no freeze-gate key, no gateway call, no audit row. dry_run=false is refused with 422 because a real settle/bill is deliberately not exposed here. Hidden (404) unless BILLING_ADMIN_ROLLUP_ENABLED is true.

Media type application/json
AdminRollupRequest

POST /api/billing/admin/run-rollup (TODO-119) — a dry-run tariff preview.

dry_run exists so the wire contract already names the thing that would one day be a real settle/bill; today only true is accepted and false is refused with 422 by the route.

object
device_basis
Device Basis
string
default: mtd_max
Allowed values: mtd_max p95
dry_run
Dry Run
boolean
default: true
organization_id
required
Organization Id
string format: uuid
period_date
Any of:
string format: date

Successful Response

Media type application/json
AdminRollupResponse

The §13.7 tier tariff for one org/period, itemized. Never charged.

This is a PREVIEW, not the invoice. For a closed month it can legitimately differ from what run_tier_period_billing (the period-boundary job) would do: that job skips a month whose final day was never settled into usage_daily, withholds an overage charge under the BUG-293 mid-period downgrade guard, and uses only stored day rows, whereas this preview, when period_date is today (UTC), also folds in today’s live active-device count (the row tonight’s settle pass would write). For a settled, closed month with no tier change the two agree field for field.

object
datapoint_overage_blocks
required
Datapoint Overage Blocks
integer
datapoint_overage_cents
required
Datapoint Overage Cents
integer
datapoint_overage_qty
required
Datapoint Overage Qty
integer
datapoint_overage_rate_cents
required
Datapoint Overage Rate Cents
integer
device_basis
required
Device Basis
integer
device_basis_kind
required
Device Basis Kind
string
device_basis_sample_days
required
Device Basis Sample Days
integer
device_overage_cents
required
Device Overage Cents
integer
device_overage_qty
required
Device Overage Qty
integer
device_overage_rate_cents
required
Device Overage Rate Cents
integer
dry_run
required
Dry Run
boolean
flat_cents
required
Flat Cents
integer
included_datapoints_month
required
Included Datapoints Month
integer
included_devices
required
Included Devices
integer
month_start
required
Month Start
string format: date
mtd_metric_points
required
Mtd Metric Points
integer
organization_id
required
Organization Id
string format: uuid
overage_allowed
required
Overage Allowed
boolean
overage_enabled
required
Overage Enabled
boolean
overage_on
required
Overage On
boolean
overage_suppressed
required
Overage Suppressed
boolean
overage_suppressed_reason
Any of:
string
period_date
required
Period Date
string format: date
plan_code
required
Plan Code
string
total_cents
required
Total Cents
integer
unbilled_datapoint_overage_cents
required
Unbilled Datapoint Overage Cents
integer
unbilled_device_overage_cents
required
Unbilled Device Overage Cents
integer
Example generated
{
"datapoint_overage_blocks": 1,
"datapoint_overage_cents": 1,
"datapoint_overage_qty": 1,
"datapoint_overage_rate_cents": 1,
"device_basis": 1,
"device_basis_kind": "example",
"device_basis_sample_days": 1,
"device_overage_cents": 1,
"device_overage_qty": 1,
"device_overage_rate_cents": 1,
"dry_run": true,
"flat_cents": 1,
"included_datapoints_month": 1,
"included_devices": 1,
"month_start": "2026-04-15",
"mtd_metric_points": 1,
"organization_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"overage_allowed": true,
"overage_enabled": true,
"overage_on": true,
"overage_suppressed": true,
"overage_suppressed_reason": "example",
"period_date": "2026-04-15",
"plan_code": "example",
"total_cents": 1,
"unbilled_datapoint_overage_cents": 1,
"unbilled_device_overage_cents": 1
}

Validation Error

Media type application/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
ctx
Context
object
input
Input
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
Example generated
{
"detail": [
{
"ctx": {},
"input": "example",
"loc": [
"example"
],
"msg": "example",
"type": "example"
}
]
}