Skip to content

Usage & Cost

Per-app usage records and billing rollups. Usage is metered per app; billing is aggregated per group. Costs are derived from the pricing table (the rate in effect at record creation is snapshotted onto each row).

Environment

These routes take no environment query parameter — the API process is bound to a single environment and resolves it server-side.

Scope

All routes are caller-scoped. A tenant sees only their own group's apps; a platform-admin caller (platform_wide) spans every group. Cross-tenant lookups return 404 (existence-hiding), never 403.


Endpoints

Method Path Scope Description
GET /api/usage/{app_name} read Raw usage records for an app
GET /api/usage/{app_name}/summary read Billing summary for one app
GET /api/usage/report/all read Usage across every app the caller can see
GET /api/usage/group/{group_slug}/summary read Per-group invoice-shaped rollup

Usage records

GET /api/usage/{app_name}?record_type=&since=&status=&limit=100
Query Type Default Notes
record_type string — Filter by record type (e.g. build, container_runtime)
since ISO-8601 — Only records started at/after this timestamp (422 if unparseable)
status string — Filter by record status
limit int 100 Max 500

Response: 200 OK

{
  "records": [
    {
      "id": "550e8400-...",
      "app_name": "gallery",
      "branch": "main",
      "service_name": "web",
      "record_type": "container_runtime",
      "status": "completed",
      "started_at": "2026-07-20T10:00:00Z",
      "completed_at": "2026-07-20T11:00:00Z",
      "duration_seconds": 3600,
      "memory_mb": 256,
      "rate_applied": 0.018,
      "cost": 0.018
    }
  ],
  "count": 1
}

App summary

GET /api/usage/{app_name}/summary?since=2026-07-01T00:00:00Z

Aggregates the app's records into totals per record type. since is optional (422 if unparseable).

Response: 200 OK

{
  "app_name": "gallery",
  "environment": "dev",
  "builds": {
    "count": 5,
    "total_seconds": 900,
    "total_minutes": 15.0,
    "cost": 0.05
  },
  "container_runtime": {
    "web": {
      "count": 24,
      "total_hours": 24.0,
      "memory_mb": 256,
      "gb_hours": 6.0,
      "cost": 0.432
    }
  },
  "active_containers": [
    {"service_name": "web", "instance_id": "…", "memory_mb": 256, "started_at": "2026-07-20T10:00:00Z"}
  ],
  "backups": {
    "count": 3,
    "total_seconds": 42,
    "total_size_mb": 128,
    "cost": 0.0012
  }
}

container_runtime is keyed by service name, so a multi-service app shows a per-service breakdown. active_containers lists what is running right now, regardless of since.


Report — all visible apps

GET /api/usage/report/all?since=

Returns usage grouped by (app_name, record_type) across every app the caller can see. Platform admins span all groups; tenants are filtered to their own group's apps.

{
  "environment": "dev",
  "records": [
    {
      "app_name": "gallery",
      "record_type": "container_runtime",
      "count": 24,
      "total_seconds": 86400,
      "max_memory_mb": 256,
      "total_cost": 0.4320
    }
  ]
}

Group summary (invoice rollup)

GET /api/usage/group/{group_slug}/summary?since=

The per-group billing unit. Aggregates every usage record owned by the group — keyed on the stamped group_id, so the roll-up survives app deletion or rehoming — into a per-app / per-record-type breakdown plus a grand total. This is the invoice-shaped view the dashboard and mecca usage group consume.

A caller may only read their own group's bill: an unknown slug, or a slug belonging to another group (for a non-platform_wide caller), returns 404.

Query Type Notes
since ISO-8601 Optional lower bound on started_at (422 if unparseable)

Response: 200 OK

{
  "group_slug": "acme",
  "records": [
    {
      "app_name": "gallery",
      "record_type": "container_runtime",
      "count": 24,
      "total_seconds": 86400,
      "total_cost": 0.4320
    },
    {
      "app_name": "gallery",
      "record_type": "build",
      "count": 5,
      "total_seconds": 900,
      "total_cost": 0.0500
    }
  ],
  "total_cost": 0.4820
}