---
title: "Project Cdn Timeseries"
method: GET
path: "/v1/projects/{project_id}/usage/cdn/timeseries"
tags: ["reports"]
---

# Project Cdn Timeseries

`GET /v1/projects/{project_id}/usage/cdn/timeseries`

## Request

- Base URL: `https://api.sonenta.dev`
- URL: `https://api.sonenta.dev/v1/projects/{project_id}/usage/cdn/timeseries`
- Auth: none declared

Per-day CDN egress + storage (GB) for THIS project, last `days` days
(billing-grade from cdn_usage_daily) + the project's last CDN publish.
Powers the project dashboard 'Diffusion & stockage (30j)' card.

POINT SEMANTICS — the two fields do NOT aggregate the same way:
- `bytes_egress` is the traffic OF THAT DAY, not a running total. SUM the
  points for a window total. This holds on BOTH egress paths below, but the
  two are established differently, and the distinction matters if you ever
  re-verify it:
    * shared-zone path — by CONTRACT: `cdn_metrics.sync_for_date(d)` writes
      `$set` (never `$inc`) of a value derived from that single day's log,
      keyed (org, project, date), so a re-sync overwrites instead of adding
      and accumulation is structurally impossible.
    * dedicated-zone path — by MEASUREMENT: the values come from Bunny's
      `BandwidthUsedChart` and are passed through untransformed, so our code
      cannot guarantee their semantics. Verified 2026-07-28 on four
      dedicated-zone projects with multi-day traffic: every one of them
      DECREASES between consecutive days, which a cumulative series cannot
      do. Re-check on a project with several non-zero days — one with a
      single day of traffic cannot tell the two readings apart.
- `storage_bytes` is a LEVEL (current state), snapshotted daily and
  overwritten each run by `sync_storage_snapshot()`. Take the LAST point;
  summing it would add N photographs of the same stock.

EGRESS HAS TWO SOURCES BEHIND ONE FIELD NAME — know this before comparing
two projects on this endpoint:
- shared-zone project: parsed from the Bunny pull-zone LOG (per request).
- dedicated-zone project (`projects.bunny_zone_id` set): Bunny's per-zone
  AGGREGATE (`BandwidthUsedChart`), because its traffic never appears in the
  shared log. That aggregate has no per-request dimension, so anything that
  filters requests (e.g. excluding our own integrity re-reads) can apply to
  the shared-log path only.
Storage has no such split: it always comes from the shared storage walk.

Egress therefore includes the platform's OWN post-publish integrity fetch
(`cdn_bunny._verify_edge_fresh` re-downloads each bundle through the edge to
recompute its sha256). A freshly published project with zero consumers still
reports egress; it is real traffic, but it is ours, not the customer's.

## Path parameters

- `project_id` string, required

## Query parameters

- `days` integer

## Response `200`

Successful Response

- ProjectCdnTimeseriesOut
  - `project_uuid` string, required
  - `days` integer, required
  - `last_published_at` string, date-time, nullable
  - `points` ProjectCdnDailyPoint[], required
    - `date` string, required
    - `bytes_egress` integer, required
    - `egress_gb` number, required
    - `storage_bytes` integer, required
    - `storage_gb` number, required
  - `consumed` boolean, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/sonenta/apis/sonenta-api.md) · [All operations](https://skmtc.dev/sonenta/apis/sonenta-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sonenta/apis/sonenta-api/revisions/f853ea49fb05?raw)
