diff --git a/src/ui/lib/billingApi.js b/src/ui/lib/billingApi.js
index f124b03..a7c5b4d 100644
--- a/src/ui/lib/billingApi.js
+++ b/src/ui/lib/billingApi.js
@@ -71,3 +71,47 @@ export const getUsageLimits = (orgId) =>
// Never 404s — a brand-new org gets honest zeros / null period.
export const getBillingSummary = (orgId) =>
get(`/billing/organizations/${orgId}/summary`);
+
+function qs(params) {
+ return Object.entries(params)
+ .filter(([, v]) => v != null)
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
+ .join("&");
+}
+
+// GET /billing/organizations/{orgId}/usage?start_date&end_date
+// -> { organization_id, period_start, period_end,
+// services: [{ service, action, resource, unit, quantity }] }
+// Raw usage quantity per (service, action, resource) dimension, summed
+// server-side from usage_daily_rollups. Never 404s — an org/period
+// with no recorded usage gets an honest empty `services` array.
+export const getUsageSummary = (orgId, startDate, endDate) =>
+ get(`/billing/organizations/${orgId}/usage?${qs({ start_date: startDate, end_date: endDate })}`);
+
+// GET /billing/organizations/{orgId}/cost-history?start_date&end_date
+// -> { organization_id, period_start, period_end, currency,
+// history: [{ date, cost }] }
+// One point per day that had rated usage — a quiet day has no point at
+// all, not an explicit zero-cost entry (see billing_reporting_service.
+// get_cost_history's docstring), so an empty `history` is the normal
+// shape for a sparsely-used org.
+export const getCostHistory = (orgId, startDate, endDate) =>
+ get(`/billing/organizations/${orgId}/cost-history?${qs({ start_date: startDate, end_date: endDate })}`);
+
+// GET /billing/organizations/{orgId}/cost-breakdown?start_date&end_date&group_by
+// -> { organization_id, period_start, period_end, group_by, currency,
+// breakdown: { [groupKey]: { quantity, cost } } }
+// group_by is one of service|action|resource|month server-side;
+// defaults to "service" here as the most legible grouping for a
+// per-org summary view.
+export const getCostBreakdown = (orgId, startDate, endDate, groupBy = "service") =>
+ get(`/billing/organizations/${orgId}/cost-breakdown?${qs({ start_date: startDate, end_date: endDate, group_by: groupBy })}`);
+
+// Deliberately NOT wrapped here: GET /billing/organizations/{orgId}/usage-events
+// is the per-user raw event log (added for HIPAA RAG-query-log read access —
+// see billing.py's router section comment), distinct from the aggregate
+// usage/cost endpoints above. Surfacing individual per-user activity data is
+// its own explicit product decision, not something that rides along by
+// default with an aggregate reporting client — same reasoning as the
+// cron-log exclusion. Add a dedicated wrapper (and its own UI surface) only
+// when that decision is made on purpose.
diff --git a/src/ui/pages/Billing.jsx b/src/ui/pages/Billing.jsx
index 9fd4a27..bf2d453 100644
--- a/src/ui/pages/Billing.jsx
+++ b/src/ui/pages/Billing.jsx
@@ -1,5 +1,5 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { Badge, Card, Button, ProgressBar, Spinner } from "@omnibioai/ui";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+import { Badge, Card, Button, ProgressBar, Spinner, Tabs, Table } from "@omnibioai/ui";
import Login from "../components/Login";
import * as billingApi from "../lib/billingApi";
@@ -9,7 +9,10 @@ import * as billingApi from "../lib/billingApi";
// write path exists in that service — no plan changes, no payment-method
// management, no invoice actions — so this page has none either.
//
-// Data shown maps 1:1 to what the API actually returns today:
+// The page is two tabs: "Overview" (plan/status/limits/period, unchanged
+// from before) and "Usage" (raw usage + cost reporting, added alongside it).
+//
+// Overview data maps 1:1 to what the API actually returns today:
// /billing/organizations/{orgId}/subscription -> plan + status + dates + feature flags
// /billing/organizations/{orgId}/subscription/usage-limits -> per-dimension included/used
// /billing/organizations/{orgId}/summary -> current period + cost + invoice/outstanding totals
@@ -20,6 +23,23 @@ import * as billingApi from "../lib/billingApi";
// test fixtures — see omnibioai-billing/app/core/feature_catalog.py)
// - billing account details (billing_email / provider / customer id — the
// BillingAccount model has these but no endpoint returns them)
+//
+// Usage tab data, over a trailing 30-day window:
+// /billing/organizations/{orgId}/usage -> raw usage by service/action/resource
+// /billing/organizations/{orgId}/cost-history -> daily $ over the window
+// /billing/organizations/{orgId}/cost-breakdown -> $ grouped by service (group_by default)
+//
+// Deliberately NOT shown: /billing/organizations/{orgId}/usage-events, the
+// per-user raw event log. Individual user activity data is its own explicit
+// product decision, not something that ships by default alongside aggregate
+// usage/cost reporting — same reasoning as the cron-log exclusion. See
+// billingApi.js's comment above that endpoint for the full rationale.
+//
+// Most orgs will show near-empty results here — real usage-service traffic
+// today is concentrated in one org's rag/model/workflow events, and cost
+// rollups are near-empty everywhere else — so every section below uses the
+// same honest-empty-state wording as the Overview tab's Plan card rather
+// than a blank chart or a false error.
const STATUS_VARIANT = {
active: "success",
@@ -159,6 +179,22 @@ function BillingSummary({ orgId }) {
read-only view of organization #{orgId}’s current billing state
+
+
+