List closed billing period statements
Changed onGET /billing/usage/periods/ and GET /billing/usage/periods/{id}/ -- the durable statements CycleCloseService writes at cycle close (see BillingPeriodSummary's docstring). List and detail are bundled deliberately: they share a queryset, a permission, and a serializer tree, so shipping them together avoided a second PR that would only add fifty lines.
Scoped to the caller's resolved pool exactly like BillingUsageViewSet: resolve_billing_root then get_pooled_scope_ids, both resolved once in get_queryset(). A pk outside that pool is filtered out of the queryset before get_object() ever runs, so it 404s -- never 403 -- and this endpoint never confirms the existence of another tenant's statement.
IsAuthenticated only, matching GET /billing/usage/'s read-never-blocks rule: a closed statement is exactly the kind of read an scope needs in order to resolve billing, including while RESTRICTED.
History is forward-only: an scope with no closed periods yet gets 200 with an empty list, never a 404 -- there is nothing wrong with that scope, cycle close simply has not run for it yet. A caller with no active scope (request.scope is None) is a different state -- there is no pool to resolve a billing root against at all -- and gets 403, matching GET /billing/usage/'s _require_scope rule rather than the empty-list state above.
Request
- The document declares no server URL.
- Auth: one of:
- HTTP bearer
- API key in cookie sessionid
Path parameters
Query parameters
Only periods starting on or after this instant (inclusive).
Only periods starting on or before this instant (inclusive).
Filter by whether the period's overage was charged
Number of results to return per page.
The initial index from which to return the results.
Headers
Selects the active organization for this request. Optional for callers that belong to exactly one active organization — the single membership is resolved implicitly. Required when the caller has two or more active memberships; omitting it in that case returns 400. If the header names an organization the caller is not an active member of, the server returns 403.
Response
Example response
{
"count": 123,
"next": "http://api.example.org/accounts/?offset=400&limit=100",
"previous": "http://api.example.org/accounts/?offset=200&limit=100"
}Changes
- ○
endpoint added
- ○