Usage History

Track storage consumption over time, per project or per bucket. Snapshots capture per-bucket bytes used, file counts, and quotas daily — building a historical record for usage charts and billing audit.

Why this exists

The live /storage/usage endpoint tells you what your storage looks like right now. That's perfect for the dashboard's overview card or a one-shot health check, but it doesn't tell you whether you've grown 10% this week, doubled this month, or stayed flat.

Usage history fills that gap. A daily snapshot per bucket gives you trend lines instead of point-in-time numbers, and gives Koolbase an audit trail of usage that future billing can reconcile against.

What gets captured

One row per (project, bucket, day) in the storage_usage_snapshots table. Each row stores:

  • current_size_bytes — total bytes the bucket held that day
  • file_count — number of objects in the bucket that day
  • max_size_bytes — the bucket's quota at snapshot time (nullable; some buckets are uncapped). Captured per-day because quotas change — historical usage_percent has to use the quota that was set on that day, not today's.
  • captured_at — the timestamp the snapshot was written

Bucket names aren't captured — they're always resolved via JOIN to storage_buckets by ID. That way, if you rename a bucket, its historical chart stays correctly attributed.

When snapshots happen

A background goroutine on the API runs at 00:30 UTC daily, and also fires once on every API boot. The 30-minute offset past midnight gives upload-confirm and reaper flows a moment to settle before counters are read.

The capture is one atomic SQL statement that touches every bucket. The primary key on (bucket_id, snapshot_date) means re-runs the same day are idempotent — they update the existing row rather than failing. Restart the API four times in a day and you still get exactly one row per bucket for that day.

Snapshots aren't retroactive

If the API was down on a given day — or if a bucket was created mid-day — there's no row for that day. Charts will simply skip the missing date. Backfilling historical data isn't supported (it would be making up numbers).

API endpoint

GET /v1/projects/{project_id}/storage/usage/history

Query parameters:

ParamTypeDefault
fromYYYY-MM-DD30 days ago
toYYYY-MM-DDtoday
bucket_idUUIDall buckets (aggregated)

Project-wide response

When bucket_id is omitted, each point in the series is a daily aggregate across every bucket in the project.

{
  "from": "2026-05-07",
  "to": "2026-06-05",
  "series": [
    {
      "date": "2026-06-05",
      "current_size_bytes": 1597222,
      "file_count": 9,
      "bucket_count": 3
    }
  ]
}

Per-bucket response

When bucket_id is set, the series is daily snapshots for that one bucket, with max_size_bytes and usage_percent populated when a quota was set.

{
  "from": "2026-05-07",
  "to": "2026-06-05",
  "bucket_id": "60c9dfb8-4283-4c03-bfbf-86767394b45e",
  "series": [
    {
      "date": "2026-06-05",
      "current_size_bytes": 1597222,
      "file_count": 9,
      "max_size_bytes": 10485760,
      "usage_percent": 15.2
    }
  ]
}

Curl examples

# Default window (last 30 days, project-wide)
curl -H "Authorization: Bearer $TOKEN"   "https://api.koolbase.com/v1/projects/$PROJECT_ID/storage/usage/history"

# Explicit range
curl -H "Authorization: Bearer $TOKEN"   "https://api.koolbase.com/v1/projects/$PROJECT_ID/storage/usage/history?from=2026-05-01&to=2026-06-05"

# Per-bucket
curl -H "Authorization: Bearer $TOKEN"   "https://api.koolbase.com/v1/projects/$PROJECT_ID/storage/usage/history?bucket_id=$BUCKET_ID"

Dashboard

On the Storage page, the Usage button (next to Trusted Origins and Presets in the stats row) opens a modal showing storage over time. Inside the modal you can:

  • Toggle the time window between 30 days, 90 days, and 1 year
  • Switch between project-wide totals and a single bucket via the dropdown
  • Hover the chart for daily details (size, file count, and either bucket count or quota usage)

When there's only one snapshot (day one of a project), the modal shows a clean 3-card summary instead of a sparse chart, with a caption explaining the chart will fill in over coming days.

Retention

Snapshots are kept indefinitely. Each row is around 80 bytes, so a project with 10 buckets running for a year is roughly 300KB — well below any meaningful storage concern in Postgres.

If a project ever crosses a threshold where indefinite retention becomes an issue, we'll add a monthly rollup table and start aging out individual daily rows older than ~13 months. Not built yet because there's no reason to build it speculatively.

Troubleshooting

“No snapshots yet in this window”

The writer captures on API boot and at 00:30 UTC daily. If you just deployed and don't see today's row, the boot capture might have failed — check the API logs for storage usage snapshot captured. If it's missing, restart the API and watch the logs.

Gaps in the chart

Days without snapshots aren't reconstructed. The most common cause is the API being down across midnight UTC. If your uptime is solid, you should see a contiguous series.

Per-bucket query returns the same totals as project-wide

That just means the other buckets in your project are empty — the selected bucket holds all the data. Empty buckets still get snapshot rows (with current_size_bytes: 0), they just sum to nothing.