Skip to content

[Admin][API] Usage & Spending — ledger, platform-paid vs BYOK, cost + AI kill switch #32

Description

@serge-ivo

[Admin][API] Usage & Spending — aggregate ledger, platform-paid vs BYOK split, cost + AI kill switch

Why

The core operator concern. The platform currently pays for internal AI (PLATFORM_AI_ENABLED="true": embeddings + summaries + translation fallback) and that spend is invisible in-app. We need cross-user usage + a clear platform-paid vs BYOK cost split, and an operator control to cap/kill platform spend.

Scope

  • GET /v1/admin/usage (behind requireAdmin): aggregate the ai_usage ledger across ALL users — group by provider, model, kind, user, agent/instance, and day. Return token totals and cost_micros totals with a time window (?days=).
  • Platform-paid vs BYOK split: classify each ai_usage row as platform-paid (internal kinds run on env.AI when PLATFORM_AI_ENABLED — embeddings/summary/translation) vs BYOK (user's Anthropic/CF key). Since CF platform calls currently ledger at cost_micros≈0, add estimated cost via a neuron/token price table so platform spend is a real number, not zero. Document the estimate.
  • GET /v1/admin/spending — top-line: total platform-paid $ (30d), total BYOK tokens (informational), top spenders, top models, trend series.
  • AI kill switch: GET/PUT /v1/admin/settings/platform-ai to read and toggle the effective PLATFORM_AI_ENABLED behavior at runtime (e.g. a platform_settings row overriding the env default), so the operator can turn platform-paid AI off without a redeploy. Off → embeddings/summaries/translation no-op (BYOK-only); note the known side effect that RAG goes dark until a BYOK-embedding path exists (track separately).

Acceptance criteria

  • /v1/admin/usage aggregates across all users with provider/model/kind/user/day breakdowns and correct totals.
  • Platform-paid vs BYOK split is correct and platform-paid rows carry a non-zero estimated cost.
  • /v1/admin/spending returns 30-day platform spend, top spenders/models, trend.
  • Toggling the AI kill switch changes runtime behavior (verified: with it off, an embed call no-ops) and writes an audit-log row.
  • Non-admin → 403; tests for aggregation + gate + toggle.

Relevant code

  • Ledger: workers/api/src/lib/usage.ts (recordUsageai_usage, has cost_micros), migration 0048_ai_usage.sql
  • Platform-AI gate: agent-do.ts:72, routes/instances-translation.ts, wrangler.toml PLATFORM_AI_ENABLED
  • Existing per-user view to reuse shapes: routes/usage.ts

References

  • PAS platform analytics endpoint pattern (/v1/analytics/admin/platform). PAS has no usage/cost admin — this closes that gap for PAGS.

Part of the PAGS Admin Portal epic. Depends on Foundation. Directly addresses the "platform should not pay for AI" concern.


Best-practice hardening (2026-08-01)

  • Authoritative spend = Cloudflare billing actuals, not only a token estimate. Platform-paid Workers AI is billed per neuron; our ai_usage.cost_micros is ~0 for CF and the token→price table is a derived guess. Pull the real platform AI cost from Cloudflare's GraphQL Analytics / billing API (Workers AI usage) as the authoritative number for the "platform spend" headline. Keep the token estimate only for BYOK (where we genuinely can't see the provider bill) and label estimates as estimates in the API + UI so the two are never conflated.
  • Cache the CF billing pull (it's rate-limited + slow); expose source: "cf-actuals" | "estimate" per figure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    admin-portalPAGS admin/operator portalenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions