---
title: "API Reference"
canonical: "https://pharos.watch/docs/api-reference/"
description: "Public Pharos API contracts for stablecoin data: endpoints, authentication, response schemas, pagination, freshness headers, and dataset exports."
---

# Pharos API Reference

The Pharos API is a REST API served by a Cloudflare Worker backed by a D1 database. It powers the [pharos.watch](https://pharos.watch) stablecoin analytics dashboard through a split website-data lane plus an external integration API. On `https://api.pharos.watch`, all public routes are API-key protected unless this reference explicitly marks them as exempt.

**Base URL:** `https://api.pharos.watch`

Unless noted otherwise, responses are `Content-Type: application/json`. Exceptions: `GET /api/og/*` / `HEAD /api/og/*` return `image/png` for known image routes, and `POST /api/telegram-webhook` returns a plain-text `ok` body. CORS headers are added to every response, but `Access-Control-Allow-Origin` is restricted by the Worker `CORS_ORIGIN` allowlist (production repo config: `https://pharos.watch,https://ops.pharos.watch`). When the request `Origin` matches an allowlisted entry, the Worker echoes that origin and sets `Vary: Origin`; when a request includes a foreign `Origin`, the worker omits `Access-Control-Allow-Origin`, and `OPTIONS` preflights from foreign origins receive `403`. Requests without an `Origin` header keep the existing first-allowlisted-origin fallback. Non-exempt `/api/*` requests on `api.pharos.watch` require a valid `X-API-Key`; missing or invalid keys return `401 Unauthorized`. Per-key rate-limit overages return `429`, and cold auth/limiter dependency failures can still return `503`.

## Surface Split

The runtime now uses three HTTP lanes:

- `https://api.pharos.watch` is the external integration API. Protected public routes require `X-API-Key`.
- `https://site-api.pharos.watch` is the website-internal Worker host. It accepts only allowlisted `GET` reads plus `X-Pharos-Site-Proxy-Secret`.
- `/_site-data/*` is the same-origin Pages Functions proxy used by browsers on `pharos.watch`, `ops.pharos.watch`, `stablecoin-dashboard.pages.dev`, and subdomains of `stablecoin-dashboard.pages.dev`.

Static dataset exports are served from the public website, not from the Worker API, and do not require `X-API-Key`. The Stablecoin Cemetery export is available as JSON at `https://pharos.watch/datasets/stablecoin-cemetery.json` and CSV at `https://pharos.watch/datasets/stablecoin-cemetery.csv`.

Machine-readable integration artifacts are also served from the public website for onboarding. The OpenAPI endpoint catalogue is available at `https://pharos.watch/openapi.json`, and Postman artifacts are available at `https://pharos.watch/postman/pharos-api.postman_collection.json` plus `https://pharos.watch/postman/pharos-api.postman_environment.json`. Import both Postman files, then replace the environment `apiKey` placeholder with a real `X-API-Key`. The generated OpenAPI artifact includes named schemas for the richer Yield Intelligence ranking and history payloads, and the Postman collection includes both best-source and source-key yield-history examples. These are public integration/read onboarding artifacts, not a complete dump of every no-key route; they intentionally exclude Cloudflare-Access-gated admin routes, self-serve key issuance POST endpoints, feedback submission, Telegram webhook ingestion, Telegram Mini App endpoints, and dynamic OG image routes. Request keys through `https://pharos.watch/api/`.

Browser consumers should use same-origin `/_site-data/*` via the frontend helpers in `src/lib/api.ts`. In production, that Pages proxy targets `https://site-api.pharos.watch` through `SITE_API_ORIGIN`. Direct integrations, CI smoke, and build-time sync scripts should target `https://api.pharos.watch` and send `X-API-Key` for protected public reads, including `/api/telegram-pulse`.

Production Pages does not proxy public self-serve `/api/*` POST requests. The public form at `https://pharos.watch/api/` calls `https://api.pharos.watch/api/api-key-requests` and `https://api.pharos.watch/api/api-key-requests/verify` with normal CORS preflights for JSON `POST` requests.

Self-serve API-key request honeypot submissions are intentionally no-op accepted: `POST /api/api-key-requests` returns `200 { "ok": true }` when the optional `website` field is non-empty, without creating an API-key request or sending email. Normal non-honeypot submissions return `202 Accepted` with `pending_verification`.

The direct Worker cache profiles below describe responses from `api.pharos.watch` / `site-api.pharos.watch`. The Pages `/_site-data/*` proxy adds a separate same-origin Cache API layer for successful responses without `Set-Cookie`, without `Cache-Control: no-store`, and without freshness `Warning: 110`; it does not cache no-store routes such as `/api/health`.

## Public API Auth

Unless a route is explicitly called out below as exempt, requests to `https://api.pharos.watch` must send:

- header: `X-API-Key: ph_live_<16 hex prefix>_<32 char base64url secret>`
- example shape: `ph_live_0123456789abcdef_abcdefghijklmnopqrstuvwxyzABCDEF`

Public, non-admin routes on `https://api.pharos.watch` that do not require `X-API-Key` are limited to:

- `GET /api/health`
- `GET /api/og/*`
- `HEAD /api/og/*`
- `POST /api/feedback`
- `POST /api/api-key-requests`
- `POST /api/api-key-requests/verify`
- `POST /api/telegram-webhook`
- `POST /api/telegram-mini-app/session`
- `POST /api/telegram-mini-app/mutate`

`POST /api/telegram-webhook` is externally reachable but not anonymous: it requires `X-Telegram-Bot-Api-Secret-Token` instead of `X-API-Key`.

`POST /api/telegram-mini-app/session` and `POST /api/telegram-mini-app/mutate` are also externally reachable but not anonymous. They require Telegram Mini App `initData` signed for `@PharosWatchBot`; the worker validates the HMAC, `auth_date`, and user payload before any D1-backed state write. These endpoints are denied on the website-internal site-data lane and are intended only for the Mini App at `https://pharos.watch/pharoswatchbot/app/`.

Admin/operator routes are also outside the public API-key gate, but they remain Cloudflare-Access-gated and are supported only through `ops-api.pharos.watch` or the `ops.pharos.watch/api/admin/*` Pages proxy. The public API host rejects registered admin paths and configured admin-like root families before API-key auth, so a public API key cannot be used to reach registered admin routes or malformed children of configured roots such as `/api/api-keys*`, `/api/api-key-requests-admin*`, and `/api/discovery-candidates*` on `api.pharos.watch`.

The public self-serve request form lives at `https://pharos.watch/api/`. It sends an email verification link, then exchanges that one-time token for a default key after verification. Default self-serve keys are `tier="self-serve"`, `trafficClass="external"`, limited to `30` requests per minute, expire after `60` days, and allow one active/pending self-serve claim per normalized email. Request details are available only in the private `ops.pharos.watch/admin-api/` UI.

The worker stores only the key prefix plus a peppered HMAC of the secret portion. Admin callers create, rotate, and deactivate keys through the operator lane (`ops.pharos.watch` / `ops-api.pharos.watch`); plaintext tokens are returned only once at creation/rotation time. Self-serve issuance uses the same storage model and returns the plaintext token only once after verification.

For protected cacheable `GET` routes, the worker keeps a bounded isolate-local verified-key cache and a bounded isolate-local limiter. A recently verified non-self-serve key can use that local path for hot edge-cache hits, and can continue to read cached routes during a brief D1 auth/limiter outage. Self-serve, unknown, stale-cache, or not-yet-verified keys still fail closed.

---

## Stablecoin IDs

Most endpoints use the Pharos stablecoin ID in `ticker-issuer` format (e.g. `usdt-tether`). IDs are checked through the shared stablecoin-ID registry (`shared/lib/stablecoin-id-registry.ts`). Unknown or non-canonical IDs return `404`.

Canonical IDs use `ticker-issuer` format — lowercase ticker symbol hyphenated with the issuer/protocol name:

| Example             | Asset           |
| ------------------- | --------------- |
| `"usdt-tether"`     | Tether (USDT)   |
| `"usdc-circle"`     | USD Coin (USDC) |
| `"paxg-paxos"`      | PAX Gold (PAXG) |
| `"ustb-superstate"` | Superstate USTB |
| `"gyen-gyen"`       | GYEN            |

The full list is exported from `shared/lib/stablecoins/registry.ts`, with editable per-coin metadata stored in `shared/data/stablecoins/coins/*.json`, the checked-in generated aggregate at `shared/data/stablecoins/coins.generated.json`, and validation in `shared/lib/stablecoins/schema.ts`. The API accepts canonical IDs only. Non-canonical stablecoin detail URLs and legacy frontend route aliases are retired and unsupported.

---

## Response Headers

Endpoints backed by the cron cache include these additional headers:

| Header       | Description                                                                                                                                                             |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Data-Age` | Seconds elapsed since the cron last wrote this data to D1                                                                                                               |
| `Warning`    | Freshness warning (`110`) when cached data is older than the generic freshness runway, plus endpoint-specific advisory warnings (`199`) on a few compute-on-read routes |

Generic freshness status is `fresh` through `8x maxAge`, `degraded` through `12x maxAge`, then `stale`. Generic freshness headers emit `Warning` and downgrade `Cache-Control` to `no-store` after `age > 8x maxAge` so edge/browser caches do not keep serving an old payload after the underlying cron data recovers. Some routes also use `Warning` for dependency or quality advisories even when the age is still inside that runway; clients should treat body `_meta.status` as authoritative when it exists.

---

## Response Body Freshness (`_meta`)

Endpoints that emit `_meta` into plain-object (non-array) response bodies do so through `createCacheHandler()` or route-specific manual injection, alongside the HTTP freshness headers above. This provides inline freshness metadata for consumers that prefer not to parse response headers.

**Shape:**

```json
{
  "_meta": {
    "updatedAt": 1710500000,
    "ageSeconds": 42,
    "status": "fresh"
  }
}
```

| Field        | Type     | Description                                                                                 |
| ------------ | -------- | ------------------------------------------------------------------------------------------- |
| `updatedAt`  | `number` | Unix epoch seconds when the cron last wrote this data to D1                                 |
| `ageSeconds` | `number` | `floor(now / 1000) - updatedAt`                                                             |
| `status`     | `string` | `"fresh"` (age/max <= 8.0), `"degraded"` (8.0 < ratio <= 12.0), or `"stale"` (ratio > 12.0) |

Route-specific manual `_meta` injectors can be stricter. `GET /api/chains` uses its 1800-second budget directly (`fresh <= 1x`, `degraded <= 2x`, then `stale`) and switches its response to `no-store` whenever the chain snapshot is not fresh.

**Endpoints with `_meta`:**

| Endpoint                    | Max Age (sec) | Source                                       |
| --------------------------- | ------------- | -------------------------------------------- |
| `GET /api/stablecoins`      | 600           | `createCacheHandler`                         |
| `GET /api/chains`           | 1800          | `worker/src/api/chains.ts`                   |
| `GET /api/bluechip-ratings` | 43200         | `createCacheHandler`                         |
| `GET /api/usds-status`      | 86400         | `createCacheHandler`                         |
| `GET /api/yield-rankings`   | 3600          | Manual injection after live safety hydration |
| `GET /api/depeg-resolver`   | 900           | `worker/src/api/depeg-resolver.ts`           |
| `GET /api/depeg-resolver-review` | 900      | `worker/src/api/depeg-resolver-review.ts`    |

Array-typed responses (e.g., endpoints returning a JSON array at the top level) do not include `_meta`. They receive `X-Data-Age` / `Warning` only when their handler wires freshness metadata explicitly. Supply history, safety score history, and non-USD share are explicit history-endpoint exceptions that emit freshness headers; DEX liquidity history currently exposes cache headers but no freshness headers.

The frontend `apiFetchWithMeta()` helper (in `src/lib/api.ts`) reads `_meta` from the response body when present, falling back to the `X-Data-Age` header for endpoints that do not include it.

---

## Cache-Control Profiles

These profiles apply while the dataset is within its generic freshness runway. Once a cache-backed response exceeds `8x` its endpoint max age, the worker overrides that response to `Cache-Control: no-store` until a fresh response is generated.

All rows below are members of the centralized `API_CACHE_PROFILES` map (`shared/lib/api-cache-profiles.ts`) except `immutable-snapshot`, which is a route-local constant (`IMMUTABLE_CACHE_CONTROL` in `worker/src/api/snapshot.ts`) reused for the immutable public-snapshot routes.

| Profile            | `Cache-Control`                        | Used by                                                                                                                                                                                                                                                                                                                                                     |
| ------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| realtime           | `public, s-maxage=60, max-age=10`      | stablecoins, stablecoin-summary, blacklist, blacklist-summary, depeg-events, peg-summary, mint-burn-events, chains                                                                                                                                                                                                                                          |
| standard           | `public, s-maxage=300, max-age=60`     | stablecoin-charts, depeg-resolver, depeg-resolver-review, redemption-backstops, usds-status, daily-digest, digest-archive, report-cards, stability-index, yield-rankings, mint-burn-flows, stress-signals                                                                                                                                                    |
| custom             | `public, s-maxage=300, max-age=300`    | dex-liquidity (browser-side max-age extended to match CDN TTL)                                                                                                                                                                                                                                                                                              |
| per-coin           | `public, s-maxage=300, max-age=10`     | stablecoin/:id (cache-aside with 5-min per-coin TTL in D1)                                                                                                                                                                                                                                                                                                  |
| slow               | `public, s-maxage=3600, max-age=300`   | supply-history, dex-liquidity-history, bluechip-ratings, yield-history, safety-score-history, non-usd-share                                                                                                                                                                                                                                                 |
| archive            | `public, s-maxage=86400, max-age=3600` | digest-snapshot, snapshots-index                                                                                                                                                                                                                                                                                                                            |
| immutable-snapshot | `public, s-maxage=31536000, max-age=31536000, immutable` | snapshots/:date.json, snapshot/:date/stablecoin/:id                                                                                                                                                                                                                                                                                                          |
| public-status      | `public, max-age=60`                   | public-status-history                                                                                                                                                                                                                                                                                                                                       |
| og-image           | `public, max-age=900, s-maxage=900`    | dynamic Open Graph images                                                                                                                                                                                                                                                                                                                                   |
| reserve-live       | `public, s-maxage=3600, max-age=300`   | stablecoin-reserves live mode                                                                                                                                                                                                                                                                                                                               |
| reserve-live-stale | `public, s-maxage=1800, max-age=120`   | stablecoin-reserves live-stale mode                                                                                                                                                                                                                                                                                                                         |
| reserve-fallback   | `public, s-maxage=300, max-age=60`     | stablecoin-reserves curated/template/unavailable fallback modes                                                                                                                                                                                                                                                                                             |
| no-store           | `no-store`                             | health plus all admin GET routes via the router override or admin route wrapper (`status`, `status-history`, `request-source-stats`, `yield-source-decisions`, API key inventory/audit routes, `admin-action-log`, `debug-sync-state`, `backfill-dews`, `backfill-dews?repair=...&dry-run=true`, `audit-depeg-history?dry-run=true`, `discovery-candidates`, `admin-telegram-chat/:chatId`, `status-probe-history`) |

`POST /api/feedback`, `POST /api/api-key-requests`, `POST /api/api-key-requests/verify`, `POST /api/telegram-webhook`, `POST /api/telegram-mini-app/session`, `POST /api/telegram-mini-app/mutate`, and admin POST endpoints bypass edge caching because they are non-GET request paths. The self-serve API-key endpoints and Telegram Mini App endpoints explicitly return no-store responses so verification tokens, plaintext API keys, and per-chat alert state are never cacheable.

---

## Polling Guidance

Recommended minimum polling cadence for external integrations:

| Cache profile | Minimum poll interval | Notes                                                           |
| ------------- | --------------------- | --------------------------------------------------------------- |
| realtime      | 60 seconds            | Polling faster usually re-fetches the same edge-cached payload  |
| standard      | 300 seconds           | Preferred baseline for most dashboards                          |
| per-coin      | 300 seconds           | `GET /api/stablecoin/:id` is history-heavy; avoid short loops   |
| slow          | 3600 seconds          | Historical/timeline endpoints should generally be polled hourly |
| archive       | 86400 seconds         | Historical digest snapshots and public snapshot index listings   |
| immutable-snapshot | On-demand only        | Dated public dataset snapshots are content-addressed and immutable |
| no-store      | On-demand only        | Health/admin diagnostics; avoid high-frequency polling          |

Client best practices:

- Add interval jitter (`±10%`) to avoid synchronized bursts.
- Read `X-Data-Age` + `Warning` for freshness/stale decisions when those optional headers are present.
- Back off exponentially on `429` and `5xx` responses.

---

## Rate Limits

Public API traffic enforces per-key rate limiting to ensure fair usage. Non-exempt `/api/*` requests require a valid `X-API-Key`; the no-key public exceptions are `GET /api/health`, `GET /api/og/*`, `HEAD /api/og/*`, `POST /api/feedback`, `POST /api/api-key-requests`, `POST /api/api-key-requests/verify`, `POST /api/telegram-webhook`, `POST /api/telegram-mini-app/session`, and `POST /api/telegram-mini-app/mutate`. The Telegram webhook is authenticated separately with `X-Telegram-Bot-Api-Secret-Token`; Telegram Mini App endpoints are authenticated with signed Telegram `initData`.

### Per-key limit

| Scope       | Limit                | Window     |
| ----------- | -------------------- | ---------- |
| Per API key | Varies (default 120) | 60 seconds |

Per-key overrides are stored in `api_keys.rate_limit_per_minute`.

Self-serve keys are issued with a fixed default of `30` requests per minute and a `60` day expiry. The request workflow has separate abuse limits: initial submissions are throttled by salted IP hash (`5/hour`) and private email hash (`3/day`), verification attempts are throttled by salted IP hash (`20/10 minutes`) and token hash (`5/10 minutes`), and successful issuance allows one self-serve key creation per salted IP hash per 24 hours.

When the per-key limiter is exceeded, the API returns `429 Too Many Requests`:

```json
{
  "error": "Rate limit exceeded"
}
```

Rate-limited responses include the retry delay in the HTTP `Retry-After` header when the worker can compute one.

`POST /api/feedback` also has a form-specific limiter. Its `429` body is `{ "error": "Too many submissions. Please wait a few minutes." }`, and it should be handled as a local submission throttle rather than as a public API quota response. If the feedback limiter's D1 dependency is unavailable, the endpoint returns `503 Service Unavailable` with `{ "error": "Feedback service temporarily unavailable. Please try again." }` and `Retry-After: 60`.

API-key authentication and per-key limiter storage normally rely on D1. For protected cacheable `GET` edge-cache hits, the worker can serve a recently verified non-self-serve key through a bounded isolate-local auth/limiter path. It can also continue serving a recently verified non-self-serve key during a brief D1 outage by reusing its bounded verified-key cache and isolate-local limiter. Self-serve keys are refused when their D1 lookup is unavailable because revocation and claim state cannot be rechecked from stale isolate cache. Unknown or not-yet-verified keys still fail closed with `503 Service Unavailable`, `{ "error": "Public API temporarily unavailable" }`, and `Retry-After: 60`. Best-effort API-key usage timestamp updates do not fail otherwise successful reads.

### Retry Guidance

- Respect the `Retry-After` header when present
- Add random jitter (0–2 seconds) to avoid thundering-herd retries
- Use exponential backoff for sustained 429 responses
- Combine with the polling cadences in the section above to stay well under limits

## Error Response Conventions

JSON API handlers use `{ "error": "message" }` JSON format. `GET /api/og/*` and `HEAD /api/og/*` return `image/png` on success for known image routes; unknown OG route patterns return the normal JSON error body, while OG data/render failures inside known image routes can return `text/plain`.

| Status | Meaning               | When                                                                                                                                                                                                                                                                                                                                           |
| ------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Bad Request           | Missing required parameters, invalid enum values, malformed numeric input, or out-of-range numeric/filter values on handlers that opt into rejection (`rangePolicy: "reject"`). Some endpoints intentionally clamp or default selected numeric params; endpoint sections call this out where it is part of the contract.                       |
| 401    | Unauthorized          | Public `/api/*` endpoint called without a valid `X-API-Key`, or admin endpoint called without a valid `ops-api` Access JWT (typically obtained through Cloudflare Access user login or service-token auth)                                                                                                                                     |
| 403    | Forbidden             | Disallowed CORS preflight from a foreign `Origin`, Pages ops proxy mutating request without a matching same-origin `Origin`, or mutating admin request missing `X-Pharos-Admin: 1`                                                                                                                                                             |
| 404    | Not Found             | Unknown stablecoin ID or missing resource                                                                                                                                                                                                                                                                                                      |
| 429    | Too Many Requests     | Rate limit exceeded (per-key public API limiter or feedback-specific limiter; feedback uses its own message body)                                                                                                                                                                                                                              |
| 500    | Internal Server Error | Unhandled exception (caught by `withErrorHandler`)                                                                                                                                                                                                                                                                                             |
| 502    | Bad Gateway           | Upstream fetch failed (external data provider or Pages proxy upstream), or the ops proxy received a Cloudflare Access login redirect from `ops-api`                                                                                                                                                                                            |
| 503    | Service Unavailable   | Cache-passthrough endpoint where cache has never been populated, cached payload is corrupt / rejected by validation, a protected public API request cannot be authenticated from D1 or the recent verified-key cache, the feedback limiter/storage dependency fails, or `MAINTENANCE_MODE=true` (global kill switch via `wrangler secret put`) |
| 504    | Gateway Timeout       | Pages `/_site-data/*` or `/api/admin/*` proxy timed out waiting for its Worker upstream (10 s default; 20 s for ops `/api/status` and `/api/status-history`; 45 s for ops `/api/audit-depeg-history`)                                                                                                                                          |

**Rule:** Cache-passthrough handlers return **503** when data hasn't been populated yet or when the stored cache payload is malformed and rejected at read time. Query handlers that find no matching rows return **200** with empty results (e.g., `{ events: [], total: 0 }`). When `MAINTENANCE_MODE` is set to `"true"`, all non-`OPTIONS` requests immediately return `503` with `{ "error": "maintenance", "message": "..." }` — used during DB migrations. `OPTIONS` CORS preflights are handled before the maintenance gate.

---

## Method Gating Policy

HTTP method allowance is defined centrally in `shared/lib/api-endpoints/` and enforced by `worker/src/router.ts` (`validateEndpointMethod`).

- `GET` is accepted for read endpoints (plus admin debug/status endpoints, `GET /api/backfill-dews`, and dry-run repair previews for `GET /api/backfill-dews?repair=...&dry-run=true`).
- `POST` is accepted for mutating admin endpoints, `POST /api/feedback`, `POST /api/api-key-requests`, `POST /api/api-key-requests/verify`, `POST /api/telegram-webhook`, `POST /api/telegram-mini-app/session`, and `POST /api/telegram-mini-app/mutate`.
- `GET, POST` is accepted on `/api/api-keys` so operators can list keys and create a new key through the same route.
- `POST` is accepted on `/api/api-keys/:id/update`, `/api/api-keys/:id/deactivate`, and `/api/api-keys/:id/rotate`.
- `/api/audit-depeg-history` allows `GET` only with `?dry-run=true`; otherwise it is `POST`-only.
- `/api/backfill-dews` allows `GET` for the historical backtest and for `repair=...&dry-run=true` previews; mutating repair runs are `POST`-only.
- Unknown public `/api/*` requests can return `401` first when the API key is missing or invalid. After lane auth succeeds, unregistered paths return `404` because no route dependencies can be hydrated. Once a static or dynamic route family is registered, known paths with disallowed methods return `405` with `Allow`; unsupported verbs on known endpoint families return `405` with `Allow: GET, POST`.

The same shared endpoint descriptors now also carry static worker dependency-hydration hints consumed by `worker/src/routes/registry.ts`, where the worker binds shared endpoint keys directly to handlers through a single static route-definition list. That keeps endpoint metadata, router behavior, method guards, admin status-page actions, and worker-side static route wiring aligned from one source of truth plus one worker binding table.


## Public Endpoints

Unless an endpoint section explicitly says `Authentication: exempt`, routes in this section require `X-API-Key` when called on `https://api.pharos.watch`.

<!-- GENERATED-START: public-endpoints-quick-reference -->
<!-- This block is generated by scripts/maintenance/generate-api-reference.mjs from public/openapi.json. -->
<!-- Do not edit by hand. Run `node scripts/maintenance/generate-api-reference.mjs` to refresh. -->

### Public Endpoints Quick Reference

Generated from `public/openapi.json` (`Pharos API` v1.0.0). The OpenAPI artifact intentionally excludes Cloudflare-Access-gated admin routes, self-serve key issuance POST endpoints, feedback submission, Telegram webhook ingestion, Telegram Mini App endpoints, and dynamic OG image routes. Those endpoints are documented in the hand-written sections below.

Total documented public operations: **38**.

| Method | Path | Summary | Tags | Auth | Parameters | Status codes |
| ------ | ---- | ------- | ---- | ---- | ---------- | ------------ |
| GET | `/api/blacklist` | Blacklist events | Blacklist | X-API-Key | `stablecoin?`, `chain?`, `chainId?`, `eventType?`, `q?`, `sortBy?`, `sortDirection?`, `limit?`, `offset?`, `includeTotal?` | 200, 400, 401, 429, 503 |
| GET | `/api/blacklist-summary` | Blacklist summary | Blacklist | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/bluechip-ratings` | Bluechip ratings | Risk | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/chains` | Chains | Chains | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/daily-digest` | Daily digest | Digest | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/depeg-events` | Depeg events | Peg Monitoring | X-API-Key | `stablecoin?`, `limit?`, `offset?`, `cursor?`, `active?`, `includeTotal?`, `includePending?` | 200, 400, 401, 429, 503 |
| GET | `/api/depeg-resolver` | Depeg Duration Resolver | Risk, Peg Monitoring | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/depeg-resolver-review` | Depeg Duration Resolver Reviewer | Risk, Peg Monitoring | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/dex-liquidity` | DEX liquidity | Liquidity | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/dex-liquidity-history` | DEX liquidity history | Liquidity, History | X-API-Key | `stablecoin`, `days?` | 200, 400, 401, 429, 503 |
| GET | `/api/digest-archive` | Digest archive | Digest | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/digest-snapshot` | Digest snapshot | Digest | X-API-Key | `date` | 200, 400, 401, 429, 503 |
| GET | `/api/events` | Tape events | Risk | X-API-Key | `type?`, `class?`, `coin?`, `pegCurrency?`, `chain?`, `q?`, `severityFloor?`, `since?`, `until?`, `cursor?`, `limit?`, `includeTotal?` | 200, 400, 401, 429, 503 |
| GET | `/api/health` | Health check | Health | none | — | 200, 400, 503 |
| GET | `/api/mint-burn-events` | Mint and burn events | Flows | X-API-Key | `stablecoin`, `direction?`, `chain?`, `burnType?`, `scope?`, `minAmount?`, `limit?`, `offset?`, `cursor?`, `includeTotal?` | 200, 400, 401, 429, 503 |
| GET | `/api/mint-burn-flows` | Mint and burn flows | Flows | X-API-Key | `stablecoin?`, `hours?` | 200, 400, 401, 429, 503 |
| GET | `/api/non-usd-share` | Non-USD share | Market Structure, History | X-API-Key | `days?` | 200, 400, 401, 429, 503 |
| GET | `/api/peg-summary` | Peg summary | Peg Monitoring | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/public-status-history` | Public status history | Status | X-API-Key | `limit?`, `window?` | 200, 400, 401, 429, 503 |
| GET | `/api/redemption-backstops` | Redemption backstops | Risk, Reserves | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/report-cards` | Report cards | Risk | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/safety-score-history` | Safety score history | Risk, History | X-API-Key | `stablecoin`, `days?` | 200, 400, 401, 429, 503 |
| GET | `/api/snapshot/{date}/stablecoin/{stablecoinId}` | Public snapshot projection for a single coin | Digest, Stablecoins, History | X-API-Key | `date`, `stablecoinId` | 200, 400, 401, 429, 503 |
| GET | `/api/snapshots/{date}.json` | Public snapshot for a single day | Digest, History | X-API-Key | `date` | 200, 400, 401, 429, 503 |
| GET | `/api/snapshots/index` | Public snapshot index | Digest | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/stability-index` | Pharos Stability Index | Risk | X-API-Key | `detail?` | 200, 400, 401, 429, 503 |
| GET | `/api/stablecoin-charts` | Stablecoin charts | Stablecoins, History | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/stablecoin-reserves/{stablecoinId}` | Stablecoin reserves | Stablecoins, Reserves | X-API-Key | `stablecoinId` | 200, 400, 401, 429, 503 |
| GET | `/api/stablecoin-summary/{stablecoinId}` | Stablecoin summary | Stablecoins | X-API-Key | `stablecoinId` | 200, 400, 401, 429, 503 |
| GET | `/api/stablecoin/{stablecoinId}` | Stablecoin detail | Stablecoins | X-API-Key | `stablecoinId` | 200, 400, 401, 429, 503 |
| GET | `/api/stablecoins` | List stablecoins | Stablecoins | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/stress-signals` | Stress signals | Risk, Peg Monitoring | X-API-Key | `stablecoin?`, `days?` | 200, 400, 401, 429, 503 |
| GET | `/api/supply-history` | Supply history | History | X-API-Key | `stablecoin`, `days?` | 200, 400, 401, 429, 503 |
| GET | `/api/telegram-pulse` | Telegram pulse | Status | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/usds-status` | USDS freeze status | Risk | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/yield-adapter-manifest` | Yield adapter manifest | Yield | X-API-Key | — | 200, 400, 401, 429, 503 |
| GET | `/api/yield-history` | Yield history | Yield, History | X-API-Key | `stablecoin`, `days?`, `mode?`, `sourceKey?` | 200, 400, 401, 429, 503 |
| GET | `/api/yield-rankings` | Yield rankings | Yield | X-API-Key | — | 200, 400, 401, 429, 503 |

<!-- GENERATED-END: public-endpoints-quick-reference -->

### `GET /api/events`

Tape events surface, backed by `worker/src/api/events.ts`. The handler already accepts `type`, `class`, `coin` (multi-value), `pegCurrency`, `chain`, `q` (case-insensitive free-text search), `severityFloor`, `since` / `until` (epoch ms), `cursor`, `limit`, and `includeTotal` per the quick-reference table above. The response envelope is `{ events[], nextCursor, total, totalExact, _meta }`: `nextCursor` is a keyset cursor string (null when there are no more rows), `total` is the exact count only when `includeTotal=true` (otherwise null), and `totalExact` mirrors that boolean.

As of the May 2026 detail-page pass, the frontend hook `useChartAnnotations` (`src/hooks/use-chart-annotations.ts`) consumes this endpoint to drive per-coin chart annotations on the stablecoin detail route. The hook is gated by `NEXT_PUBLIC_PHAROS_CHART_ANNOTATIONS` — see process/feature-flags.md.

**Phase 1 (shipped May 2026):** the hook is wired through the consumer surface (`<ChartAnnotationDots>` + screen-reader-only legend) but returns an empty array. Charts render byte-identically to the pre-flag baseline; the flag-off path never fetches.

**Phase 2 (planned):** align the hook's URL params to the handler's existing shape — `coin=<id>` + `since` / `until` in epoch ms — or extend the worker to accept chart-friendly aliases (`stablecoin`, `from`, `to`). Phase 2 will also wire `useApiQueryWithMeta`, map tape-event rows into `ChartAnnotation`, and clamp results to the rendered chart's `[fromMs, toMs]` window inside the memo so out-of-range markers cannot push the data domain.

### `GET /api/stablecoins`

Full stablecoin list with current supply, price, chain breakdown, and FX rates. Data is refreshed by cron every 15 minutes; the cache entry has a 10-minute max-age.

**Cache:** realtime — `X-Data-Age` and `Warning` headers included.

The canonical `stablecoins` cache is written only after `StablecoinListResponseSchema` validation. Worker consumers that require the published public contract can opt into the same schema on cache read and return `503` for schema-invalid cached objects. Compatibility readers that only need critical fields may still salvage valid entries from older or partially malformed payloads, but they surface that state as degraded with a filtered-entry count instead of treating the filtered payload as fully healthy.

**Response**

```text
{
  "peggedAssets": [StablecoinData, ...],
  "fxFallbackRates": { "peggedEUR": 1.082, "peggedGBP": 1.26 },
  "_meta": { "updatedAt": 1710500000, "ageSeconds": 42, "status": "fresh" }
}
```

`fxFallbackRates` is present when the FX-rate state loaded by `sync-stablecoins` has usable fresh or static references; inputs can come from Frankfurter/ECB, secondary or tertiary FX mirrors, commodity references, or cached/static fallback rates. Keys are `pegType` strings (e.g. `"peggedEUR"`), values are rates in USD.

**`StablecoinData` fields**

| Field                          | Type                                               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ------------------------------ | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                           | `string`                                           | Pharos stablecoin ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `name`                         | `string`                                           | Full name (e.g. `"Tether"`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `symbol`                       | `string`                                           | Ticker (e.g. `"USDT"`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `geckoId`                      | `string \| null`                                   | CoinGecko ID (normalized output key; upstream DefiLlama uses `gecko_id`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `pegType`                      | `string`                                           | DefiLlama peg type (e.g. `"peggedUSD"`, `"peggedEUR"`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `pegMechanism`                 | `string`                                           | `"fiat-backed"`, `"crypto-backed-algorithmic"`, etc.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `priceSource`                  | `string`                                           | Source label for the current price (`"defillama-list"`, `"coingecko"`, composite agreement labels such as `"binance+coingecko+kraken"`, `"geckoterminal"`, `"protocol-redeem"`, `"dexscreener"`, etc.). For high-confidence consensus this label can describe the full agreeing cluster even when the published price is the cluster median rather than one member's raw mark. When no usable current price survives validation, the cache keeps `price = null` and serializes `priceSource = "missing"` for contract stability.                                                                                                                                                                   |
| `priceConfidence`              | `string \| null`                                   | Price confidence level: `"high"` (cross-validated agreement), `"single-source"`, `"low"` (sources diverge), `"fallback"` (enrichment pipeline)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `priceUpdatedAt`               | `number \| null`                                   | Compatibility timestamp for the current price; mirrors the effective observation time when available                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `priceObservedAt`              | `number \| null`                                   | Unix seconds for the effective observation time attached to the selected source price; interpret alongside `priceObservedAtMode`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `priceObservedAtMode`          | `"upstream" \| "local_fetch" \| "unknown" \| null` | Whether `priceObservedAt` came from source-native freshness metadata, local fetch time, or legacy/unknown provenance                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `priceSyncedAt`                | `number \| null`                                   | Unix seconds when Pharos selected and wrote the current price during the sync                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `supplySource`                 | `string \| undefined`                              | Supply data source: `"defillama"`, `"defillama-history-gap-fill"` (used when a tracked DefiLlama live row collapses to zero supply but recent DefiLlama chart history still has a fresh non-zero total), `"coingecko-gap-fill"` (used when tracked deployments are missing from DefiLlama chain coverage and CoinGecko repairs the total/history buckets), `"coingecko-fallback"`, `"onchain-total-supply"` (used when a supplemental asset is normalized from on-chain total supply instead of an upstream market-cap field), or `"onchain-circulating-supply"` (used when the same live on-chain fallback subtracts configured non-circulating protocol inventory balances before normalization) |
| `price`                        | `number \| null`                                   | Current price in USD. For high-confidence consensus this is the median of the winning agreeing cluster; for single-source, low-confidence, or fallback outcomes it is the selected source price.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `circulating`                  | `Record<string, number>`                           | Current supply in USD, keyed by pegType (e.g. `{ "peggedUSD": 138000000 }`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `circulatingPrevDay`           | `Record<string, number>`                           | Supply 24 h ago                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `circulatingPrevWeek`          | `Record<string, number>`                           | Supply 7 days ago                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `circulatingPrevMonth`         | `Record<string, number>`                           | Supply ~30 days ago                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `chainCirculating`             | `Record<string, ChainCirculating>`                 | Per-chain breakdown. For `"coingecko-gap-fill"` and `"defillama-history-gap-fill"` assets this remains DefiLlama-led unless the missing total can be allocated safely to one tracked chain, so the per-chain sum may be a lower bound on total supply.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `chains`                       | `string[]`                                         | List of chain names where the token is deployed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `contracts`                    | `ContractDeployment[] \| undefined`                | Curated on-chain deployments for tracked stablecoins (active and frozen). Omitted when curated metadata has no contracts on file. Use this to map a Pharos `id` to its on-chain token contracts when joining with `/api/report-cards` or other endpoints keyed by `id`.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `consensusSources`             | `string[]`                                         | Source names that returned a valid price for this coin during the sync cycle. Defaults to `[]` when absent.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `priceSourceConfidenceProfile` | `PriceSourceConfidenceProfile \| undefined`        | Present for DEX-inclusive primary prices. Summarizes active protocol DEX lanes, the freshest DEX lane age, and whether the price relies only on the aggregate `dex-promoted` lane.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `agreeSources`                 | `string[] \| undefined`                            | Compatibility alias for agreeing/current price sources when present                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

**`ContractDeployment`**

| Field      | Type     | Description                                                              |
| ---------- | -------- | ------------------------------------------------------------------------ |
| `chain`    | `string` | Pharos chain identifier (e.g. `"ethereum"`, `"arbitrum"`, `"solana"`).   |
| `address`  | `string` | Token contract address as published by the issuer.                       |
| `decimals` | `number` | Token decimals.                                                          |

**`PriceSourceConfidenceProfile`**

| Field                   | Type             | Description                                                                                    |
| ----------------------- | ---------------- | ---------------------------------------------------------------------------------------------- |
| `activeDexLanes`        | `number`         | Count of accepted protocol-specific DEX lanes such as `balancer-dex` or `raydium-dex`.         |
| `freshestDexLaneAgeSec` | `number \| null` | Age in seconds of the freshest accepted DEX lane when the source carried observation metadata. |
| `aggregateLaneOnly`     | `boolean`        | `true` when the only DEX contribution is the legacy aggregate `dex-promoted` source.           |

**`ChainCirculating`**

```json
{
  "current": 50000000,
  "circulatingPrevDay": 49000000,
  "circulatingPrevWeek": 47000000,
  "circulatingPrevMonth": 44000000
}
```

All `circulating` values are already in USD (the list endpoint does not return native-currency values for non-USD pegs). Do not multiply by price.

---

### `GET /api/stablecoin/:id`

Historical price and supply chart data for a single stablecoin. Proxies DefiLlama (or CoinGecko for commodity/CG-only tokens) with a 5-minute server-side cache.
All upstream calls use `fetchWithRetry` with explicit per-request timeouts; on upstream/parse failures, or when CoinGecko-derived history is empty/stale, logs include source tags and stablecoin ID before stale-cache fallback or `supply_history` reconstruction. CoinGecko history is treated as stale when its newest point is more than 72 hours old.

When a D1 detail cache row exists but is older than the 5-minute TTL and younger than 24 hours, the Worker serves that stale row immediately with `Warning: 110`, `X-Data-Age`, and `Cache-Control: no-store`, then refreshes the coin in the background. Refresh work is best-effort single-flight per coin within a Worker isolate, so bursts of stale reads do not all fan out to upstream providers. Rows older than 24 hours are not served as stale fallback; they force the same synchronous refresh path used by cold misses, sharing an in-flight refresh where one already exists in the same isolate.

**Path parameter:** `:id` — Pharos stablecoin ID.

**Cache:** per-coin — custom `Cache-Control` with a 5-minute server-side D1 TTL (`public, s-maxage<=300, max-age=10`)

**Response**

```text
{
  "tokens": [TokenPoint, ...]
}
```

**`TokenPoint`**

| Field                 | Type                     | Description                            |
| --------------------- | ------------------------ | -------------------------------------- |
| `date`                | `number`                 | Unix timestamp (seconds)               |
| `totalCirculatingUSD` | `Record<string, number>` | Supply in USD per pegType key          |
| `totalCirculating`    | `Record<string, number>` | Supply in native units per pegType key |

For regular stablecoins the response still includes the raw DefiLlama detail fields, but the worker now also materializes `totalCirculatingUSD` and `totalCirculating` on each token row for contract consistency. Commodity and CG-only tokens are returned directly in the normalized shape above.

For non-USD pegs, `totalCirculating` remains in native units while `totalCirculatingUSD` is converted to USD using the current token price before caching, so the USD field always reflects market cap regardless of peg type.

---

### `GET /api/stablecoin-summary/:id`

Lightweight per-coin snapshot sourced from cached `stablecoins` data. Designed for integrators that need current price/supply context without transferring full `/api/stablecoin/:id` history payloads.
Browser surfaces on `pharos.watch` and `ops.pharos.watch` should reach this route through same-origin `/_site-data/stablecoin-summary/:id`, which proxies onto the internal `site-api` lane instead of the external API-key lane.

**Path parameter:** `:id` — Pharos stablecoin ID.

**Cache:** realtime — `X-Data-Age` and `Warning` headers included.

**Error responses:** `503` when the shared `stablecoins` cache is missing or structurally corrupt; `404` when the requested coin ID is absent from an otherwise valid cache snapshot.

**Response**

```json
{
  "id": "usdt-tether",
  "name": "Tether",
  "symbol": "USDT",
  "pegType": "peggedUSD",
  "pegMechanism": "fiat-backed",
  "priceUsd": 1.0001,
  "priceSource": "coingecko+defillama-list",
  "priceConfidence": "high",
  "supplySource": "defillama",
  "supplyByPegUsd": { "peggedUSD": 183883564940.52 },
  "supplyUsd": {
    "current": 183883564940.52,
    "prevDay": 183697699496.48,
    "prevWeek": 183673067145.19,
    "prevMonth": 185316486043.16,
    "change1d": 185865444.03,
    "change7d": 210497795.33,
    "change30d": -1432921102.64
  },
  "chainCount": 17,
  "updatedAt": 1772718367
}
```

| Field             | Type                     | Description                                                                                                                               |
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | `string`                 | Pharos stablecoin ID                                                                                                                      |
| `name`            | `string`                 | Asset name                                                                                                                                |
| `symbol`          | `string`                 | Ticker symbol                                                                                                                             |
| `pegType`         | `string`                 | Peg type key (`peggedUSD`, `peggedEUR`, etc.)                                                                                             |
| `pegMechanism`    | `string`                 | Backing/mechanism classification                                                                                                          |
| `priceUsd`        | `number \| null`         | Current price in USD                                                                                                                      |
| `priceSource`     | `string`                 | Price source identifier. When `priceUsd` is `null`, this may be `"missing"` to indicate that no usable current price survived validation. |
| `priceConfidence` | `string \| null`         | Price confidence label                                                                                                                    |
| `supplySource`    | `string \| null`         | Supply source identifier                                                                                                                  |
| `supplyByPegUsd`  | `Record<string, number>` | Current supply by peg bucket (USD)                                                                                                        |
| `supplyUsd`       | `object`                 | Aggregate USD supply values and deltas (`current`, `prevDay`, `prevWeek`, `prevMonth`, `change1d`, `change7d`, `change30d`)               |
| `chainCount`      | `number`                 | Number of chains where the asset is deployed                                                                                              |
| `updatedAt`       | `number`                 | Unix seconds of the stablecoins snapshot used for this response                                                                           |

---

### `GET /api/non-usd-share`

Returns historical non-USD stablecoin market share data from `supply_history`, split into commodity-pegged (gold/silver) and non-commodity non-USD buckets. The response keeps the legacy `fiatNonUsd*` field names for wire compatibility, but those fields include currency-linked plus other non-commodity non-USD pegs. Data is downsampled: daily for the last 90 days, weekly for the last 2 years, monthly beyond that.

**Cache:** slow — `public, s-maxage=3600, max-age=300`

Freshness headers are emitted from the latest completed `snapshot-supply` run when available. Stale responses include `X-Data-Age` and can downgrade to `Cache-Control: no-store` with `Warning: 110` once the daily history runway is exceeded. Rows newer than the completed daily snapshot marker are hidden so a failed chunked write cannot expose a partial latest day.

| Param  | Type     | Default | Constraints      | Description             |
| ------ | -------- | ------- | ---------------- | ----------------------- |
| `days` | `number` | `5000`  | min 30, max 5000 | Lookback window in days |

Unlike most numeric-query handlers, this endpoint defaults missing or malformed `days` values to `5000` and clamps most out-of-range values into `30..5000` instead of returning `400`. Current parser quirk: `days=0` is treated like a missing value and returns the default `5000` rather than the minimum `30`.

**Response:** `Array<{ date, commodityShare, fiatNonUsdShare, commodity, fiatNonUsd, total }>`

| Field             | Type     | Description                                                                   |
| ----------------- | -------- | ----------------------------------------------------------------------------- |
| `date`            | `number` | Unix seconds (snapshot date)                                                  |
| `commodityShare`  | `number` | Commodity-pegged share as % of total supply                                   |
| `fiatNonUsdShare` | `number` | Non-commodity non-USD share as % of total supply, using the legacy field name |
| `commodity`       | `number` | Commodity-pegged circulating USD                                              |
| `fiatNonUsd`      | `number` | Non-commodity non-USD circulating USD, using the legacy field name            |
| `total`           | `number` | Total circulating USD across all tracked coins                                |

---

### `GET /api/chains`

Returns chain-level stablecoin aggregates with Chain Health Scores. Computed on-the-fly from the stablecoins cache and report-card cache (two D1 reads) — no dedicated chain table is required for the live leaderboard. The response body also carries `_meta`, so the frontend can distinguish fresh, degraded, and missing-dependency states without inferring freshness from fetch timing alone.

**Cache:** realtime — `public, s-maxage=60, max-age=10`

**Freshness threshold:** 1800 seconds. Returns `503` when the stablecoins cache is unavailable or structurally corrupt. When dependent snapshots lag, the endpoint stays readable but the body `_meta.status` degrades and the frontend surfaces stale-data warnings.

**Status codes:**

| Status | Meaning                                                                            |
| ------ | ---------------------------------------------------------------------------------- |
| 200    | Chain aggregates computed successfully; freshness may still be degraded in `_meta` |
| 503    | Stablecoins cache unavailable (missing or structurally corrupt)                    |

**Response (`ChainsResponse`):**

```text
{
  "_meta": {
    "updatedAt": 1710500000,
    "ageSeconds": 42,
    "status": "fresh",
    "dependencies": {
      "reportCards": {
        "updatedAt": 1710499800,
        "ageSeconds": 242,
        "status": "fresh"
      }
    }
  },
  "chains": [ChainSummary, ...],
  "globalTotalUsd": 230000000000,
  "chainAttributedTotalUsd": 218000000000,
  "unattributedTotalUsd": 12000000000,
  "globalChange24hPct": 0.0012,
  "globalChange7dPct": 0.0045,
  "globalChange30dPct": 0.018,
  "updatedAt": 1710500000,
  "healthMethodologyVersion": "1.2"
}
```

| Field                      | Type             | Description                                                                              |
| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------- |
| `chains`                   | `ChainSummary[]` | Chains sorted by `totalUsd` descending                                                   |
| `globalTotalUsd`           | `number`         | Total tracked stablecoin supply in USD, matching `GET /api/stablecoins` aggregate supply |
| `chainAttributedTotalUsd`  | `number`         | Supply that the source data attributes to concrete chains in USD                         |
| `unattributedTotalUsd`     | `number`         | Positive residual between tracked supply and chain-attributed supply in USD              |
| `globalChange24hPct`       | `number`         | 24h change for total tracked stablecoin supply as a decimal share                        |
| `globalChange7dPct`        | `number`         | 7d change for total tracked stablecoin supply as a decimal share                         |
| `globalChange30dPct`       | `number`         | 30d change for total tracked stablecoin supply as a decimal share                        |
| `updatedAt`                | `number`         | Unix epoch seconds of the underlying stablecoins snapshot                                |
| `healthMethodologyVersion` | `string`         | Chain Health Score methodology version (currently `"1.2"`)                               |

`_meta.dependencies.reportCards` is present when the endpoint can determine report-card freshness. When that dependency is stale or unavailable, `healthScore` degrades to `null` and the route UI surfaces the dependency reason instead of pretending the chain is fully fresh.

**`ChainSummary` fields:**

| Field                | Type                                 | Description                                                                                                                  |
| -------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | `string`                             | Canonical chain identifier (DefiLlama chain name)                                                                            |
| `name`               | `string`                             | Human-readable chain name                                                                                                    |
| `logoPath`           | `string \| null`                     | Path to chain logo asset                                                                                                     |
| `type`               | `"evm" \| "tron" \| "other"`         | Chain runtime family from `CHAIN_META`                                                                                       |
| `totalUsd`           | `number`                             | Total stablecoin supply on this chain in USD                                                                                 |
| `change24h`          | `number`                             | Absolute 24h supply change in USD                                                                                            |
| `change24hPct`       | `number`                             | 24h supply change as a percentage                                                                                            |
| `change7d`           | `number`                             | Absolute 7d supply change in USD                                                                                             |
| `change7dPct`        | `number`                             | 7d supply change as a percentage                                                                                             |
| `change30d`          | `number`                             | Absolute 30d supply change in USD                                                                                            |
| `change30dPct`       | `number`                             | 30d supply change as a percentage                                                                                            |
| `stablecoinCount`    | `number`                             | Number of distinct stablecoins on this chain                                                                                 |
| `dominantStablecoin` | `{ id, symbol, share }`              | Largest stablecoin by supply on the chain                                                                                    |
| `topStablecoins`     | `{ id, symbol, share, supplyUsd }[]` | Up to five largest stablecoins by supply on the chain; `share` is chain-local (0–1) and `supplyUsd` is USD-denominated       |
| `dominanceShare`     | `number`                             | Chain share of `globalTotalUsd` (0–1); chain rows may sum below 1 when source data has unattributed supply                   |
| `healthScore`        | `number \| null`                     | Chain Health Score 0–100, or `null` if insufficient data                                                                     |
| `healthBand`         | `string \| null`                     | Health band label: `"robust"` (80–100), `"healthy"` (60–79), `"mixed"` (40–59), `"fragile"` (20–39), `"concentrated"` (0–19) |
| `healthFactors`      | `ChainHealthFactors`                 | Raw sub-factor scores (0–100 each; `quality` may still be `null`)                                                            |

**`ChainHealthFactors` fields:**

| Field              | Type             | Description                                                                                                                 |
| ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `concentration`    | `number`         | HHI-based supply concentration score (higher = more diverse)                                                                |
| `quality`          | `number \| null` | Supply-weighted average stablecoin quality from report-card grades; `null` when rated supply coverage is below 50% by value |
| `chainEnvironment` | `number`         | Resilience-tier score for the chain itself (`100` tier 1, `60` tier 2, `20` tier 3)                                         |
| `pegStability`     | `number`         | Supply-weighted average peg deviation score                                                                                 |
| `backingDiversity` | `number`         | Shannon entropy of the active backing split across the chain (`rwa-backed` vs `crypto-backed`)                              |

---

### `GET /api/stablecoin-reserves/:id`

Returns the resolved reserve presentation for a stablecoin with `liveReservesConfig`.

- Unknown IDs or coins without live reserve support return `404`.
- Live-enabled coins return `200` even before the first successful sync; the payload includes fallback mode + sync state.
- This endpoint powers the stablecoin detail-page reserve card. The same underlying live-reserve dataset also feeds report-card collateral quality, reserve-drift monitoring, and `/status`, but those surfaces read D1-backed reserve snapshots directly rather than calling this endpoint.
- A response is treated as `live` only when the stored reserve snapshot matches the latest successful sync state and passes strict integrity validation; orphaned partial writes or corrupt stored snapshots fall back to the curated/template presentation instead of presenting malformed live data as authoritative.
- Successful responses are covered by the shared `StablecoinReservesResponseSchema`; frontend API clients validate `200` payloads strictly while preserving `404` as the not-live-enabled/null path.

**Cache:** dynamic

- Live snapshots: slow (`public, s-maxage=3600, max-age=300`)
- `live-stale` snapshots: `public, s-maxage=1800, max-age=120`
- Bootstrap / fallback / unavailable presentations: shorter (`public, s-maxage=300, max-age=60`) so pre-sync fallback responses do not stay pinned at the edge after the first successful live sync

**Response (200):**

| Field          | Type             | Description                                                                                                                                                                                                          |
| -------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `stablecoinId` | `string`         | Pharos coin ID                                                                                                                                                                                                       |
| `mode`         | `string`         | One of `live`, `live-stale`, `curated-fallback`, `template-fallback`, `unavailable`. This is snapshot transport/freshness state, not the user-facing reserve badge semantics.                                        |
| `reserves`     | `ReserveSlice[]` | Reserve slices currently being shown to the user                                                                                                                                                                     |
| `estimated`    | `boolean`        | `true` only when using the classification template fallback                                                                                                                                                          |
| `liveAt`       | `number?`        | Unix seconds of the last successful live snapshot. Present only when live data exists                                                                                                                                |
| `source`       | `string?`        | Adapter key (for example `"infinifi"`, `"m0"`, `"openeden-usdo"`, or `"accountable"`). Present only when live data exists                                                                                            |
| `displayUrl`   | `string?`        | Curated click-through page shown as `Source` in the UI. Present only when configured                                                                                                                                 |
| `evidenceUrls` | `string[]?`      | Adapter-emitted evidence URLs for the authoritative live snapshot, shown separately as `Evidence` links when available                                                                                               |
| `displayBadge` | `object?`        | User-facing reserve badge semantics for authoritative live snapshots (`live`, `curated-validated`, or `proof`)                                                                                                       |
| `metadata`     | `object?`        | Adapter snapshot metadata for authoritative live snapshots. This can include feed-specific context such as `yieldBasisCollateralPct` for `crvusd`; adapter-specific metadata and nested `details` remain passthrough |
| `provenance`   | `object?`        | Evidence-quality envelope for authoritative live snapshots (`evidenceClass`, `sourceModel`, optional `freshnessMode`, `scoringEligible`)                                                                             |
| `sync`         | `object?`        | Live sync state (`status`, `bootstrap`, `stale`, `lastAttemptedAt`, `lastSuccessAt`, `warnings`, `lastError`, optional `failureCategory`, optional `uncertainWrite`). Present only when live-enabled                 |

`sync.warnings` can include both adapter-emitted warnings from the latest attempt and storage-integrity warnings when a stored live snapshot is rejected and the endpoint fails closed to a fallback presentation.
`sync.failureCategory` is copied from `reserve_sync_state.metadata.failureCategory` when available. `sync.uncertainWrite=true` means the latest attempt hit the D1 write-timeout / finalize-rejection path, so the endpoint may be serving the last consistent snapshot or fallback while the attempted write remains ambiguous until the next clean run.

`displayUrl` and `evidenceUrls` are intentionally different:

- `displayUrl` is the curated reserve-card destination
- `evidenceUrls` are adapter-emitted URLs tied to the authoritative live snapshot metadata
- some live feeds expose only `displayUrl`, while others expose both

When present, `displayBadge` has:

| Field   | Type                                       | Description                                                                    |
| ------- | ------------------------------------------ | ------------------------------------------------------------------------------ |
| `kind`  | `"live" \| "curated-validated" \| "proof"` | User-facing reserve badge classification                                       |
| `label` | `string`                                   | Badge label rendered by the frontend (`Live`, `Curated-Validated`, or `Proof`) |

When present, `provenance` has:

| Field             | Type                                                          | Description                                                                                                       |
| ----------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `evidenceClass`   | `"independent" \| "static-validated" \| "weak-live-probe"`    | Evidence class used for scoring and provenance. This is related to, but not identical to, the UI badge semantics. |
| `sourceModel`     | `"dynamic-mix" \| "validated-static" \| "single-bucket"`      | Structural shape of the reserve feed                                                                              |
| `freshnessMode`   | `"verified" \| "unverified" \| "not-applicable" \| undefined` | Explicit freshness policy when the adapter emits one                                                              |
| `scoringEligible` | `boolean`                                                     | Whether this exact snapshot is currently eligible for collateral-quality passthrough                              |

**Response (404):** unknown or non-canonical IDs, known active coins without live reserve support, and live-enabled coins with no resolved reserve result return `{ "error": "Not found" }`.

---

### `GET /api/stablecoin-charts`

Aggregate historical supply chart data across the live homepage market-cap universe, broken down by peg type. The hourly `stablecoin-charts` cache still starts from DefiLlama's aggregate chart history, but the worker now reconciles structurally supplemental tracked assets (for example wrapper NAV tokens and commodity tokens that are not present in DefiLlama's aggregate chart feed) from D1 `supply_history` before publishing the cache. At read time the handler also appends or replaces the latest point with a live snapshot derived from the current `stablecoins` cache so the endpoint's trailing point matches the homepage KPI card. `sync-stablecoin-charts` is triggered every 30 minutes, but a `stablecoin-charts:last-write` cooldown caps successful refreshes at once per hour; `/api/health` treats the cache as healthy for up to 1 hour.

**Cache:** standard — `X-Data-Age` and `Warning` headers included. This array response gets freshness headers only; it does not receive a response-body `_meta` envelope.

**Response:** A top-level array.

```json
[
  {
    "date": 1511913600,
    "totalCirculatingUSD": {
      "peggedUSD": 110105,
      "peggedEUR": 14967600
    }
  }
]
```

| Field                 | Type                     | Description                                                                                                                                          |
| --------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `date`                | `number`                 | Unix timestamp (seconds). Historical points are downsampled cache entries; the trailing point may be a fresher live `stablecoins` snapshot timestamp |
| `totalCirculatingUSD` | `Record<string, number>` | Aggregate supply in USD per peg type                                                                                                                 |

---

### `GET /api/blacklist`

Freeze, blacklist, block/unblock, account-pause, and token-destruction events for symbols in the shared `BLACKLIST_STABLECOINS` set. EURC mirror-zero rows are preserved with suppression metadata and excluded from public aggregates. Data is sourced from on-chain logs via Etherscan, Tron, and EVM RPCs.

**Cache:** realtime

**Freshness note:** `X-Data-Age` / `Warning` track the latest successful 6-hourly `sync-blacklist` writer timestamp. Public freshness stays `fresh` through that 6-hour budget and only degrades once the scheduled blacklist sync is actually late.

**Query parameters**

| Param           | Type      | Default | Description                                                                                                                                                                    |
| --------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `stablecoin`    | `string`  | —       | Filter by uppercase blacklist-tracker symbol from the full `BLACKLIST_STABLECOINS` set in `shared/types/market.ts` (for example `USDT`, not `usdt-tether`)                     |
| `chain`         | `string`  | —       | Filter by exact chain display name (e.g. `Ethereum`, `Tron`)                                                                                                                   |
| `chainId`       | `string`  | —       | Filter by canonical chain-registry ID (e.g. `ethereum`, `tron`). When both `chain` and `chainId` are supplied, they must identify the same chain or the endpoint returns `400` |
| `eventType`     | `string`  | —       | Filter by type: `blacklist`, `unblacklist`, `destroy`                                                                                                                          |
| `q`             | `string`  | —       | Case-insensitive address substring search                                                                                                                                      |
| `sortBy`        | `string`  | `date`  | Sort field: `date`, `stablecoin`, `chain`, `event`                                                                                                                             |
| `sortDirection` | `string`  | `desc`  | Sort direction: `asc`, `desc`                                                                                                                                                  |
| `limit`         | `integer` | `1000`  | Max results (0–1000; `0` maps to default `1000`)                                                                                                                               |
| `offset`        | `integer` | `0`     | Pagination offset                                                                                                                                                              |
| `includeTotal`  | `boolean` | `true`  | When `false`, skips the exact `COUNT(*)`; `total` becomes a page lower bound and `totalExact` is `false`                                                                       |

**Response**

```text
{
  "events": [BlacklistEvent, ...],
  "total": 13422,
  "methodology": {
    "version": "3.99",
    "versionLabel": "v3.99",
    "currentVersion": "3.993",
    "currentVersionLabel": "v3.993",
    "changelogPath": "/methodology/blacklist-tracker-changelog/",
    "asOf": 1776729600,
    "isCurrent": false
  }
}
```

**`BlacklistEvent`**

| Field                | Type             | Description                                                                                                                                                                                               |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | `string`         | Composite ID: `{chainId}-{txHash}-{logIndex}`                                                                                                                                                             |
| `stablecoin`         | `string`         | Token symbol (`USDC`, `USDT`, etc.)                                                                                                                                                                       |
| `chainId`            | `string`         | Stable chain identifier from the shared chain registry (e.g. `"ethereum"`, `"tron"`). This is the preferred join key for integrations                                                                     |
| `chainName`          | `string`         | Human-readable chain name (e.g. `"Ethereum"`)                                                                                                                                                             |
| `eventType`          | `string`         | `"blacklist"`, `"unblacklist"`, or `"destroy"`                                                                                                                                                            |
| `address`            | `string`         | Affected address (EVM `0x…` or Tron `T…`)                                                                                                                                                                 |
| `amountNative`       | `number \| null` | Canonical token-native amount recovered from event data or historical balance lookup                                                                                                                      |
| `amountUsdAtEvent`   | `number \| null` | Event-time USD value when Pharos can justify one                                                                                                                                                          |
| `amountSource`       | `string`         | `event`, `historical_balance`, `current_balance_snapshot`, `derived`, `legacy_migration`, or `unavailable`                                                                                                |
| `amountStatus`       | `string`         | `resolved`, `recoverable_pending`, `permanently_unavailable`, `provider_failed`, `ambiguous`                                                                                                              |
| `txHash`             | `string`         | Transaction hash                                                                                                                                                                                          |
| `blockNumber`        | `number`         | Block number                                                                                                                                                                                              |
| `timestamp`          | `number`         | Unix seconds                                                                                                                                                                                              |
| `methodologyVersion` | `string`         | Methodology version attributed to this event row                                                                                                                                                          |
| `contractAddress`    | `string \| null` | Emitting token contract when known                                                                                                                                                                        |
| `configKey`          | `string \| null` | Internal tracker config identity (`{chainId}-{contract}`)                                                                                                                                                 |
| `eventSignature`     | `string \| null` | Human-readable event signature/name when known                                                                                                                                                            |
| `eventTopic0`        | `string \| null` | Raw EVM topic0 when applicable                                                                                                                                                                            |
| `suppressionReason`  | `string \| null` | Always `null` or absent on public rows because `/api/blacklist` filters `suppression_reason IS NULL`; non-null reasons are retained only on internal/audit DB rows excluded from public aggregates/events |
| `explorerTxUrl`      | `string`         | Block explorer URL for the transaction                                                                                                                                                                    |
| `explorerAddressUrl` | `string`         | Block explorer URL for the address                                                                                                                                                                        |

**`methodology`**

| Field                 | Type      | Description                                                       |
| --------------------- | --------- | ----------------------------------------------------------------- |
| `version`             | `string`  | Methodology version of the latest returned event in this response |
| `versionLabel`        | `string`  | Display label (e.g. `"v3.2"`)                                     |
| `currentVersion`      | `string`  | Latest methodology version                                        |
| `currentVersionLabel` | `string`  | Display label for latest methodology version                      |
| `changelogPath`       | `string`  | Relative URL to the methodology changelog page                    |
| `asOf`                | `number`  | Unix timestamp of latest event used for freshness                 |
| `isCurrent`           | `boolean` | Whether `version` matches `currentVersion`                        |

---

### `GET /api/blacklist-summary`

Server-side aggregates for the Blacklist Tracker overview cards, chart, and filter options. This lets the frontend render summary state without hydrating the full blacklist history first.

`stats.destroyedTotal` remains an event-history total. `stats.activeAddressCount`, `stats.activeFrozenTotal`, and `stats.activeAmountGapCount` are legacy wire-compatible fields for Pharos' local net-active blacklist state machine. `stats.trackedFrozenTotal` is the persistent freeze-ledger total sourced from `blacklist_current_balances`, including reconciled historical bootstrap rows where later seizures or unblacklists would otherwise hide the frozen amount. New consumers should prefer `trackedAddressCount`, `trackedFrozenTotal`, and `trackedAmountGapCount` for public freeze-ledger exposure, and use the active fields only when they specifically need the current local net-frozen state. These current-balance totals are last-known successful snapshots, not a live guarantee; provider refresh failures preserve the last successful amount and should be interpreted through freshness, status, and provenance metadata when present. New snapshot rows are contract/config-scoped; older rows can still fall back to the legacy symbol/chain/address identity until remediated. `stats.recentCount` covers the last 30 days, while `stats.recentCount24h` is the last-24-hours subset used by chrome-level monitoring surfaces. The `chart` now uses that same freeze ledger and attributes each tracked balance back to its latest recorded blacklist quarter, so the quarterly buckets explain the `trackedFrozenTotal` headline rather than raw event-time intake.

The four `perCoin*` maps power the per-coin "Blacklist Activity" block on stablecoin detail pages. `perCoinFrozenAddressCount` counts addresses whose latest event is `blacklist` (net-frozen). `perCoinFrozenTotal` sums last-known successful `blacklist_current_balances.balance_usd` snapshots per coin. `perCoinDestroyedTotal` sums `amount_usd_at_event` over `destroy` events per coin. `perCoinQuarterlyEventTypes` contains each coin's quarterly breakdown of event-type counts, zero-filled between the coin's first and last event quarters so bars render contiguously. All per-coin aggregations exclude rows where `suppression_reason` is set (e.g. EURC mirror zero-balance entries).

**Cache:** realtime

**Freshness note:** Shares the same 6-hourly freshness headers as `GET /api/blacklist`, keyed to the latest successful `sync-blacklist` write rather than the request time of the summary endpoint itself.

**Response**

```json
{
  "stats": {
    "usdcBlacklisted": 1204,
    "usdtBlacklisted": 3881,
    "goldBlacklisted": 19,
    "frozenAddresses": 5071,
    "destroyedTotal": 158938221.19,
    "activeAddressCount": 5071,
    "activeFrozenTotal": 2120456789.42,
    "activeAmountGapCount": 17,
    "trackedAddressCount": 9466,
    "trackedFrozenTotal": 3235360796.7,
    "trackedAmountGapCount": 0,
    "recentCount": 42,
    "recentCount24h": 3,
    "recoverableGapCount": 17,
    "perCoinBlacklistCounts": { "USDC": 1204, "USDT": 3881 },
    "perCoinTotalEvents": { "USDC": 1210, "USDT": 3945 },
    "perCoinFrozenAddressCount": { "USDC": 1151, "USDT": 3794 },
    "perCoinFrozenTotal": { "USDC": 143000000, "USDT": 1800000000 },
    "perCoinDestroyedTotal": { "USDC": 0, "USDT": 158900000 },
    "perCoinQuarterlyEventTypes": {
      "USDC": [{ "quarter": "Q1 '26", "blacklist": 42, "unblacklist": 0, "destroy": 1 }]
    }
  },
  "chart": [{ "quarter": "Q1 '24", "USDT": 1200000, "USDC": 850000, "PAXG": 0, "XAUT": 0, "total": 2050000 }],
  "chains": [
    { "id": "ethereum", "name": "Ethereum" },
    { "id": "tron", "name": "Tron" }
  ],
  "coverage": {
    "supported": [
      {
        "symbol": "USDT",
        "stablecoinId": "usdt-tether",
        "chainId": "ethereum",
        "chainName": "Ethereum",
        "contractAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",
        "configKey": "ethereum-0xdac17f958d2ee523a2206206994597c13d831ec7",
        "providerSource": "evm-logs",
        "eventFamilies": ["USDT legacy"],
        "eventTypes": ["blacklist", "unblacklist", "destroy"]
      }
    ],
    "unsupportedDeferred": [
      { "symbol": "TUSD", "chainId": "bsc", "reason": "deferred_contract_creation_verification" }
    ],
    "counts": {
      "supportedConfigs": 71,
      "unsupportedDeferredConfigs": 10,
      "bySymbol": { "USDT": 8 },
      "byChain": { "ethereum": 35 },
      "byProviderSource": { "evm-logs": 70, "trongrid": 1 }
    }
  },
  "freezeLedgerMeta": {
    "totalRows": 9466,
    "scopedRows": 240,
    "legacyRows": 9226,
    "oldestObservedAt": 1710000000,
    "newestObservedAt": 1776729600,
    "oldestAgeSec": 66600000,
    "newestAgeSec": 1200,
    "statusDistribution": { "resolved": 9466 },
    "sourceDistribution": { "current_balance": 240, "bootstrap_kyc_rip": 9226 },
    "freshnessDistribution": { "fresh": 9450, "degraded": 10, "stale": 6 },
    "currentFreshnessDistribution": { "fresh": 240, "degraded": 0, "stale": 0 },
    "providerFailedCount": 0,
    "lastErrorClassDistribution": {},
    "sourceCategoryCounts": { "bootstrap": 9226, "current": 240, "destroy": 0, "other": 0 },
    "gaps": {
      "tracked": 0,
      "recoverable": 17,
      "unrecoverable": 0,
      "recentRecoverable": 0,
      "neverAttempted": 0,
      "repeatedFailures": 0,
      "oldestRecoverableAgeSec": null,
      "amountStatusDistribution": { "resolved": 13405, "recoverable_pending": 17 },
      "amountSourceDistribution": { "historical_balance": 9000, "event": 4405, "unavailable": 17 }
    }
  },
  "dataQuality": {
    "status": "ok",
    "warnings": [],
    "amountGaps": {
      "totalEvents": 13422,
      "recoverable": 17,
      "unrecoverable": 0,
      "recentRecoverable": 0,
      "missingRatio": 0.0013,
      "recentWindowSec": 86400
    },
    "freezeLedger": {
      "providerFailedCount": 0,
      "staleSnapshotCount": 0,
      "trackedGapCount": 0,
      "scopedRows": 240,
      "legacyRows": 9226
    },
    "coverage": { "supportedConfigs": 71, "unsupportedDeferredConfigs": 10 }
  },
  "totalEvents": 13422,
  "methodology": {
    "version": "3.993",
    "versionLabel": "v3.993",
    "currentVersion": "3.993",
    "currentVersionLabel": "v3.993",
    "changelogPath": "/methodology/blacklist-tracker-changelog/",
    "asOf": 1776729600,
    "isCurrent": true
  }
}
```

`coverage` is the machine-readable tracker coverage inventory. `supported` entries are contract/config-level rows; each row includes the required tracked fields `symbol`, `stablecoinId`, `chainId`, `chainName`, `contractAddress`, `configKey`, `providerSource`, `eventFamilies`, and `eventTypes`. `unsupportedDeferred` identifies known deferred or explicitly de-scoped deployments from the runtime manifest and the reason they are not live; current examples use `chainId` values from the same shared chain registry as event rows. `freezeLedgerMeta` describes the last-known snapshot ledger used by `trackedFrozenTotal`, including scoped-vs-legacy row counts, observed-age bounds, source/status distributions, provider failures, and amount-gap distributions. `freshnessDistribution` covers every historical ledger row; `currentFreshnessDistribution` isolates rows produced by the current-balance provider when present and is what current API versions use for `dataQuality.freezeLedger.staleSnapshotCount`. `dataQuality.status` summarizes those coverage, gap, and current-provider snapshot signals into `ok`, `degraded`, or `stale`; clients should display or alert on `warnings` rather than inferring quality from null amount fields alone.

---

### `GET /api/depeg-events`

Peg deviation events (≥ 100 bps for USD-pegged, ≥ 150 bps for non-USD pegs). Events are detected every 15 minutes by the cron.

**Cache:** realtime

**Query parameters**

| Param            | Type      | Default | Description                                                                                              |
| ---------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------- |
| `stablecoin`     | `string`  | —       | Filter by Pharos stablecoin ID                                                                           |
| `active`         | `"true"`  | —       | When `"true"`, return only ongoing (unresolved) depeg events                                             |
| `limit`          | `integer` | `100`   | Max results (1–1000)                                                                                     |
| `offset`         | `integer` | `0`     | Pagination offset (0–50,000); cannot be combined with `cursor`                                           |
| `cursor`         | `string`  | —       | Opaque keyset cursor from `nextCursor`                                                                   |
| `includeTotal`   | `boolean` | `true`  | When `false`, skips the exact `COUNT(*)`; `total` becomes a page lower bound and `totalExact` is `false` |
| `includePending` | `boolean` | `false` | When `true`, includes pending incidents awaiting confirmation in `pending`                               |

**Response**

```text
{
  "events": [DepegEvent, ...],
  "pending": [DepegPendingIncident, ...],
  "total": 4080,
  "totalExact": true,
  "nextCursor": "eyJ2IjoxLCJ2YWx1ZXMiOlsxNzcyNjA2NDAwLDQwODBdfQ",
  "methodology": {
    "version": "6.0",
    "versionLabel": "v6.0",
    "currentVersion": "6.0",
    "currentVersionLabel": "v6.0",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}
```

Results are ordered by `startedAt DESC, id DESC`. Prefer `cursor`/`nextCursor` for deep pagination; offset pagination is retained for shallow compatibility only.

Results are ordered by `startedAt` descending (most recent first).

**`DepegEvent`**

| Field                 | Type                   | Description                                                                                                                                                                                                                                                                                                                                                                    |
| --------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                  | `number`               | Auto-increment DB ID                                                                                                                                                                                                                                                                                                                                                           |
| `stablecoinId`        | `string`               | Pharos stablecoin ID                                                                                                                                                                                                                                                                                                                                                           |
| `symbol`              | `string`               | Token symbol                                                                                                                                                                                                                                                                                                                                                                   |
| `pegType`             | `string`               | DefiLlama peg type (e.g. `"peggedUSD"`)                                                                                                                                                                                                                                                                                                                                        |
| `direction`           | `"above" \| "below"`   | Whether the price was above or below the peg                                                                                                                                                                                                                                                                                                                                   |
| `peakDeviationBps`    | `number`               | Largest deviation observed (basis points, signed; negative = below peg, positive = above peg)                                                                                                                                                                                                                                                                                  |
| `startedAt`           | `number`               | Unix seconds when depeg was first detected                                                                                                                                                                                                                                                                                                                                     |
| `endedAt`             | `number \| null`       | Unix seconds when price returned to peg; `null` if still active                                                                                                                                                                                                                                                                                                                |
| `startPrice`          | `number`               | Price at event start (USD)                                                                                                                                                                                                                                                                                                                                                     |
| `peakPrice`           | `number \| null`       | Price at worst deviation                                                                                                                                                                                                                                                                                                                                                       |
| `recoveryPrice`       | `number \| null`       | Price at recovery                                                                                                                                                                                                                                                                                                                                                              |
| `pegReference`        | `number`               | Reference peg value used (USD)                                                                                                                                                                                                                                                                                                                                                 |
| `source`              | `"live" \| "backfill"` | Detection method                                                                                                                                                                                                                                                                                                                                                               |
| `confirmationSources` | `string \| null`       | Composite provenance tag recorded when a pending depeg was promoted. Components (joined with `+`): the off-chain source label (`CoinGecko`, `DefiLlama`, or `NativePeg(<currency>)`), `DEX`, `CEX`, `Pool`. Example: `"DEX+CEX"` or `"CoinGecko+Pool"`. `null` for events that bypassed the pending lane (small-cap authoritative direct-insert and historical backfill rows). |
| `pendingReason`       | `string \| null`       | Composite reason the incident entered the pending lane, e.g. `"large-cap"`, `"low-confidence"`, `"large-cap+low-confidence"`, `"extreme-move"`. `null` when the event did not enter pending.                                                                                                                                                                                   |
| `provenance`          | `object \| null`       | Public replay/audit metadata when available: `sourceKind`, `replayRunId`, `replayVersion`, `sourcePriceProviders`, `quoteMode`, `pegReferenceSource`, `supplySource`, `confirmationPolicy`, `confirmationPointCount`, `confidenceTier`, `auditVerdict`, `pegScoreEligible`, and `updatedAt`. Legacy rows return `null`.                                                                            |

**`DepegPendingIncident`** — returned only when `includePending=true`

| Field                             | Type                 | Description                                                                                  |
| --------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `stablecoinId`                    | `string`             | Pharos stablecoin ID                                                                         |
| `symbol`                          | `string`             | Token symbol                                                                                 |
| `direction`                       | `"above" \| "below"` | Pending deviation direction                                                                  |
| `firstSeenAt`                     | `number`             | Unix seconds when the pending incident was first observed                                    |
| `lastSeenAt`                      | `number`             | Unix seconds when the pending incident was last refreshed                                    |
| `firstSeenBps`                    | `number`             | First observed deviation in basis points                                                     |
| `lastSeenBps`                     | `number`             | Most recent observed deviation in basis points                                               |
| `peakSeenBps`                     | `number`             | Worst observed pending deviation in basis points                                             |
| `reason`                          | `string`             | Pending lane reason, e.g. `"large-cap"` or `"large-cap+low-confidence"`                      |
| `ageSec`                          | `number`             | Seconds elapsed since `firstSeenAt`                                                          |
| `expiresAt`                       | `number`             | Unix seconds when the pending row expires if it is not confirmed                             |
| `availableConfirmationCategories` | `string[]`           | Confirmation categories currently derivable from public metadata / D1 snapshots              |
| `missingConfirmationCategories`   | `string[]`           | Expected confirmation categories not currently derivable from public metadata / D1 snapshots |

**`methodology`**

| Field                 | Type      | Description                                                             |
| --------------------- | --------- | ----------------------------------------------------------------------- |
| `version`             | `string`  | Methodology version attributed from the latest returned event timestamp |
| `versionLabel`        | `string`  | Display label (e.g. `"v5.94"`)                                          |
| `currentVersion`      | `string`  | Latest methodology version                                              |
| `currentVersionLabel` | `string`  | Display label for latest methodology version                            |
| `changelogPath`       | `string`  | Relative URL to the methodology changelog page                          |
| `asOf`                | `number`  | Unix timestamp used for methodology attribution                         |
| `isCurrent`           | `boolean` | Whether `version` matches `currentVersion`                              |

---

### `GET /api/depeg-resolver`

Cache-backed Depeg Duration Resolver readouts for active/current confirmed depeg incidents. DDRv2 emits one row per canonical incident projection, keyed by `incidentKey`, and separates live facts from the official public lock outcome.

**Cache:** standard — `X-Data-Age` and `Warning` headers included. Freshness threshold: 900 s. Missing or invalid snapshots return `200` with `_meta.degraded=true` and `rows: []`; stale snapshots mark `_meta.degraded=true`, include the read overlay when available, and keep pre-publication rows free of verdict/duration details.

**Row states**

| State | Meaning |
| ----- | ------- |
| `pending_lock` | Incident is active and younger than the 24h public lock point. Live facts and lock metadata may render; no verdict or duration is exposed. |
| `lock_deferred` | The lock point arrived, but a deterministic system-health predicate failed. Shows deferral/retry status only; no no-call, verdict, or duration is created. |
| `publication_retry_pending` | A lock outcome sealed, but first-publication manifest finalization has not succeeded. The sealed outcome stays hidden until publication. |
| `frozen` | First-published official prediction. Shows frozen verdict/duration, lock timestamp, lock timing, anchored duration, and live overlay facts separately. |
| `no_call` | Healthy lock run had insufficient row-level signal. Shows missing inputs and lock metadata, not a recovery/terminal verdict. |
| `invalidated` | Append-only erratum invalidated the original first-published prediction or no-call; original exposure remains visible with correction history. |

**Response**

```text
{
  "_meta": {
    "schemaVersion": 2,
    "dataAsOf": 1779700000,
    "modelAsOf": 1779700000,
    "computedAt": 1779700000,
    "expiresAt": 1779701800,
    "snapshotToken": "ddrpub_...",
    "snapshotGeneration": 2,
    "publicPredictionIds": [101, 102],
    "publicPredictionRowHashes": { "101": "..." },
    "basePayloadHash": "...",
    "readOverlay": {
      "degradedLockDeferralIncidentKeys": [],
      "closedPendingReviewIncidentKeys": [],
      "suppressedIncidentKeys": []
    },
    "degraded": false,
    "degradedReason": null,
    "publicWarning": "Probabilistic estimate from Pharos historical data. Not investment advice or a credit rating.",
    "resolutionRubricVersion": "resolution-rubric-v1",
    "durationModelVersion": "duration-landmark-v1",
    "incidentGroupingVersion": "incident-group-v1",
    "supportRulesVersion": "support-rules-v1",
    "lineage": { "eventCount": 34129, "incidentCount": 1820, "coinCount": 142, "quarantinedCoins": 7 }
  },
  "rows": [DdrV2ResponseRow, ...],
  "methodology": { "version": "2.0", "versionLabel": "v2.0", "changelogPath": "/methodology/depeg-resolver-changelog/" }
}
```

`DdrV2ResponseRow.kind` is one of `pending`, `prediction`, `no_call`, or `invalidated_prediction`. `prediction.state` carries the public state above. Prediction rows include `frozen.resolution` and `frozen.duration`; no-call rows include `noCall.missingReasons`; invalidated rows include `originalOutcome`, `latestErratum`, and errata history. All rows include a `live` overlay with current event age, peak/current deviation, event state, freshness, and degraded reason.

---

### `GET /api/depeg-resolver-review`

Cache-backed Depeg Duration Resolver Reviewer snapshot. DDRR v2 reviews frozen public predictions and no-calls that reached first publication, then reports the full incident-scoped policy universe so missing predictions are visible coverage debt rather than silently excluded.

**Cache:** standard — `X-Data-Age` and `Warning` headers included. Freshness threshold: 900 s. Missing or invalid snapshots return `200` with `_meta.degraded=true`, an empty summary, and `rows: []`; stale snapshots keep review rows but set `_meta.degraded=true` and `degradedReason="stale-cache"`.

**Headline fields**

| Field | Type | Meaning |
| ----- | ---- | ------- |
| `summary.headline.recoveryLikelihoodAccuracyPct` | `number \| null` | Strict scored DDR recovery-verdict accuracy over first-published frozen predictions. |
| `summary.headline.meanSignedDurationErrorSec` | `number \| null` | Mean observed-minus-DDR duration error for recovered rows with a DDR median remaining-time estimate. Positive means observed recovery was slower than DDR predicted; negative means faster. |
| `summary.headline.meanAbsoluteDurationErrorSec` | `number \| null` | Mean absolute duration miss for the same scored recovered rows. |
| `summary.headline.durationScoredCount` | `number` | Number of recovered, duration-scored rows included in duration-error averages. |
| `summary.headline.predictionRatePct` | `number \| null` | Share of eligible finalized incidents that received a published prediction/no-call decision. |
| `summary.headline.finalizedCoveragePct` | `number \| null` | Share of the policy universe assigned to a finalized/public coverage state. |
| `summary.headline.noCallRatePct` | `number \| null` | Share of finalized lock outcomes that became no-calls. |
| `summary.headline.invalidatedPct` | `number \| null` | Share of first-published predictions invalidated by errata. |

**Response**

```text
{
  "_meta": {
    "computedAt": 1779700000,
    "expiresAt": 1779701800,
    "degraded": false,
    "degradedReason": null,
    "reviewerVersion": "ddr-reviewer-v2",
    "assessedEventCount": 12,
    "reviewedEventCount": 12,
    "pendingEventCount": 8,
    "durationScoredCount": 6,
    "verdictScoredCount": 10,
    "methodologyVersions": ["2.0"]
  },
  "summary": {
    "headlineScope": "current_policy",
    "headlineLabel": "DDR v2 public predictions",
    "headline": {
      "policyUniverseIncidentCount": 20,
      "predictionRatePct": 0.65,
      "finalizedCoveragePct": 0.9,
      "noCallRatePct": 0.1,
      "invalidatedPct": 0.05,
      "recoveryLikelihoodAccuracyPct": 0.7,
      "meanSignedDurationErrorSec": 3600,
      "meanAbsoluteDurationErrorSec": 7200,
      "horizonHitRates": [{ "horizon": "6h", "scored": 5, "hits": 3, "misses": 2, "hitRate": 0.6 }]
    },
    "byPredictionPolicy": []
  },
  "rows": [DdrrRow, ...],
  "methodology": { "version": "2.0", "versionLabel": "v2.0", "changelogPath": "/methodology/depeg-resolver-changelog/" }
}
```

`DdrrRow.kind` is one of `prediction_review`, `no_call_review`, `coverage`, or `invalidated_prediction`. Only `prediction_review` rows enter recovery-likelihood and duration accuracy. `no_call_review` rows are deliberate lock outcomes but unscored. `coverage` rows include states such as `resolved_before_prediction`, `terminal_before_prediction`, `missed_lock_recovered`, `missed_lock_terminal`, `publication_retry_pending`, and `publication_failed`. `invalidated_prediction` rows retain original exposure and attach errata history.

---

### `GET /api/peg-summary`

Composite peg scores and aggregate statistics for tracked stablecoins. Scores are computed over a 4-year window from live depeg events, DEX prices, and current prices. The `coins` array can still include NAV / other non-peg rows with `currentDeviationBps = null`, while the summary counters only cover rows with a live peg-status deviation.

**Cache:** realtime

**Response**

```text
{
  "coins": [PegSummaryCoin, ...],
  "summary": PegSummaryStats,
  "methodology": {
    "version": "6.0",
    "versionLabel": "v6.0",
    "currentVersion": "6.0",
    "currentVersionLabel": "v6.0",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}
```

**`PegSummaryCoin`**

| Field                       | Type                                                       | Description                                                                                                                                                                                                                                                                                                                                                         |
| --------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                        | `string`                                                   | Pharos stablecoin ID                                                                                                                                                                                                                                                                                                                                                |
| `symbol`                    | `string`                                                   | Token symbol                                                                                                                                                                                                                                                                                                                                                        |
| `name`                      | `string`                                                   | Full name                                                                                                                                                                                                                                                                                                                                                           |
| `pegType`                   | `string`                                                   | DefiLlama peg type                                                                                                                                                                                                                                                                                                                                                  |
| `pegCurrency`               | `string`                                                   | Peg currency code (`USD`, `EUR`, `GOLD`, etc.)                                                                                                                                                                                                                                                                                                                      |
| `governance`                | `string`                                                   | `"centralized"`, `"centralized-dependent"`, `"decentralized"`                                                                                                                                                                                                                                                                                                       |
| `currentDeviationBps`       | `number \| null`                                           | Live price deviation from peg (basis points, signed). `null` for NAV / non-fixed-peg rows, for coins with current supply below the live depeg-event floor, or when price / peg-reference inputs are missing.                                                                                                                                                        |
| `depegEventCoverageLimited` | `boolean`                                                  | Present when the coin's current supply is below the live depeg-event floor (`$1M`). Use this to distinguish "below coverage floor" from generic missing-price cases when `currentDeviationBps` is `null`.                                                                                                                                                           |
| `priceSource`               | `string`                                                   | Primary price source label used for current deviation (`defillama-list`, `coingecko`, composite agreement labels such as `binance+coingecko+kraken`, `protocol-redeem`, `defillama-contract`, `coinmarketcap`, `dexscreener`, `cached`, etc.). High-confidence consensus can expose the agreeing cluster label even when the published price is the cluster median. |
| `priceConfidence`           | `"high" \| "single-source" \| "low" \| "fallback" \| null` | Confidence tier attached to the primary price input                                                                                                                                                                                                                                                                                                                 |
| `priceUpdatedAt`            | `number \| null`                                           | Compatibility timestamp for the primary price; now mirrors the effective observation time rather than the cache-write time                                                                                                                                                                                                                                          |
| `priceObservedAt`           | `number \| null`                                           | Unix seconds for the effective observation time attached to the selected primary price; interpret alongside `priceObservedAtMode`                                                                                                                                                                                                                                   |
| `priceObservedAtMode`       | `"upstream" \| "local_fetch" \| "unknown" \| null`         | Whether `priceObservedAt` came from source-native freshness metadata, local fetch time, or legacy/unknown provenance                                                                                                                                                                                                                                                |
| `priceSyncedAt`             | `number \| null`                                           | Unix seconds when Pharos selected and wrote the primary price during the sync                                                                                                                                                                                                                                                                                       |
| `primaryTrust`              | `"authoritative" \| "confirm_required" \| "unusable"`      | Whether the current primary price is trusted to mutate live depeg state directly                                                                                                                                                                                                                                                                                    |
| `pegScore`                  | `number \| null`                                           | Composite peg score 0–100 (higher = more stable)                                                                                                                                                                                                                                                                                                                    |
| `pegPct`                    | `number`                                                   | % of tracked time within ±100 bps                                                                                                                                                                                                                                                                                                                                   |
| `severityScore`             | `number`                                                   | Severity sub-score (0–100)                                                                                                                                                                                                                                                                                                                                          |
| `spreadPenalty`             | `number`                                                   | Spread/liquidity penalty applied to score                                                                                                                                                                                                                                                                                                                           |
| `eventCount`                | `number`                                                   | Number of depeg events in the 4-year window                                                                                                                                                                                                                                                                                                                         |
| `worstDeviationBps`         | `number \| null`                                           | Worst single deviation seen (basis points)                                                                                                                                                                                                                                                                                                                          |
| `activeDepeg`               | `boolean`                                                  | Whether a depeg event is currently open                                                                                                                                                                                                                                                                                                                             |
| `lastEventAt`               | `number \| null`                                           | Unix seconds of most recent depeg event                                                                                                                                                                                                                                                                                                                             |
| `trackingSpanDays`          | `number`                                                   | Days of history used for score computation                                                                                                                                                                                                                                                                                                                          |
| `methodologyVersion`        | `string`                                                   | Methodology version attributed to this coin snapshot                                                                                                                                                                                                                                                                                                                |
| `dexPriceCheck`             | `DexPriceCheck \| null`                                    | Optional cross-validation against DEX price (shown when coin supply is at or above the live depeg-event floor, DEX data is ≤ 60 minutes old, and aggregate source TVL is ≥ $250K)                                                                                                                                                                                   |
| `consensusSources`          | `string[]`                                                 | Source names that returned a valid price for this coin. Defaults to `[]` when absent.                                                                                                                                                                                                                                                                               |
| `agreeSources`              | `string[] \| undefined`                                    | Compatibility alias for agreeing/current price sources when present                                                                                                                                                                                                                                                                                                 |

**`DexPriceCheck`**

| Field             | Type      | Description                                         |
| ----------------- | --------- | --------------------------------------------------- |
| `dexPrice`        | `number`  | DEX-derived price (USD)                             |
| `dexDeviationBps` | `number`  | DEX price deviation from peg (basis points, signed) |
| `agrees`          | `boolean` | Whether primary and DEX prices are within 50 bps    |
| `sourcePools`     | `number`  | Number of DEX pools contributing to the price       |
| `sourceTvl`       | `number`  | Combined TVL of those pools (USD)                   |

**`PegSummaryStats`**

| Field                  | Type                          | Description                                                                                                                   |
| ---------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `activeDepegCount`     | `number`                      | Coins with an open depeg event                                                                                                |
| `medianDeviationBps`   | `number`                      | Median absolute deviation across rows with a live current deviation                                                           |
| `worstCurrent`         | `{ id, symbol, bps } \| null` | Coin with the largest current deviation among rows with a live current deviation                                              |
| `coinsAtPeg`           | `number`                      | Rows with a live current deviation that are below their live depeg threshold (100 bps for USD pegs, 150 bps for non-USD pegs) |
| `totalTracked`         | `number`                      | Rows included in the live peg-status aggregate (`currentDeviationBps !== null`)                                               |
| `depegEventsToday`     | `number`                      | Number of depeg events whose `startedAt` is in the current UTC day                                                            |
| `depegEventsYesterday` | `number`                      | Number of depeg events whose `startedAt` is in the previous UTC day                                                           |
| `fallbackPegRates`     | `string[]`                    | _(optional)_ pegType keys using stale FX fallback rates                                                                       |

**`methodology`** — same fields and semantics as `/api/depeg-events`

---

### `GET /api/usds-status`

Sky/USDS protocol status — whether the freeze module is currently active.

**Cache:** standard — `X-Data-Age` and `Warning` headers included.

**Response**

```json
{
  "freezeActive": false,
  "implementationAddress": "0x1923dfee706a8e78157416c29cbccfde7cdf4102",
  "lastChecked": 1771809338,
  "_meta": { "updatedAt": 1710500000, "ageSeconds": 42, "status": "fresh" }
}
```

| Field                   | Type      | Description                                         |
| ----------------------- | --------- | --------------------------------------------------- |
| `freezeActive`          | `boolean` | Whether the USDS freeze module is currently enabled |
| `implementationAddress` | `string`  | Address of the current USDS implementation contract |
| `lastChecked`           | `number`  | Unix seconds when this was last fetched on-chain    |

---

### `GET /api/bluechip-ratings`

Safety ratings from [bluechip.org](https://bluechip.org) for covered stablecoins. Updated daily at 08:05 UTC.

**Cache:** slow — `X-Data-Age` and `Warning` headers included.

**Response:** Object keyed by Pharos stablecoin ID, plus top-level `_meta` freshness metadata.

```text
{
  "usdt-tether": BluechipRating,
  "usdc-circle": BluechipRating,
  "_meta": { "updatedAt": 1710500000, "ageSeconds": 42, "status": "fresh" }
}
```

**`BluechipRating`**

| Field                | Type             | Description                                         |
| -------------------- | ---------------- | --------------------------------------------------- |
| `grade`              | `string`         | Letter grade: `"A+"`, `"A"`, `"A-"`, `"B+"` … `"F"` |
| `slug`               | `string`         | Bluechip report slug (e.g. `"usdt"`)                |
| `collateralization`  | `number`         | Collateralization percentage                        |
| `smartContractAudit` | `boolean`        | Whether an audit exists                             |
| `dateOfRating`       | `string`         | ISO 8601 date of rating                             |
| `dateLastChange`     | `string \| null` | ISO 8601 date of last grade change                  |
| `smidge`             | `BluechipSmidge` | Plain-text evaluation summaries (HTML stripped)     |

**`BluechipSmidge`** — each field is `string | null`:

| Field              | Description                                      |
| ------------------ | ------------------------------------------------ |
| `stability`        | Reserves management and stabilization mechanisms |
| `management`       | Personnel restrictions and track records         |
| `implementation`   | Smart contract implementation assessment         |
| `decentralization` | Decentralization posture                         |
| `governance`       | Governance and redemption terms                  |
| `externals`        | External risk factors                            |

---

### `GET /api/dex-liquidity`

DEX liquidity scores, pool breakdowns, source-confidence metadata, and on-chain DEX price data for all tracked stablecoins. Updated every 30 minutes. Trend data is only returned when a trusted historical baseline exists.

**Cache:** custom — `public, s-maxage=300, max-age=300`

**Freshness note:** In addition to stale-data warnings, this endpoint can also emit a `Warning` header when the latest `sync-dex-liquidity` run finished in `degraded` or `error` state and the API is serving the last successful dataset.

**Response:** Object keyed by Pharos stablecoin ID plus a `__global__` aggregate sentinel row.

```text
{
  "usdt-tether": DexLiquidityData,
  "usdc-circle": DexLiquidityData,
  "__global__": DexLiquidityData
}
```

**`DexLiquidityData`**

| Field                          | Type                                                                        | Description                                                                                                                                                             |
| ------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `totalTvlUsd`                  | `number`                                                                    | Total DEX TVL (USD)                                                                                                                                                     |
| `totalVolume24hUsd`            | `number`                                                                    | 24 h trading volume (USD)                                                                                                                                               |
| `totalVolume7dUsd`             | `number`                                                                    | 7-day trading volume (USD)                                                                                                                                              |
| `poolCount`                    | `number`                                                                    | Number of liquidity pools                                                                                                                                               |
| `pairCount`                    | `number`                                                                    | Number of unique trading pairs                                                                                                                                          |
| `chainCount`                   | `number`                                                                    | Number of chains with active pools                                                                                                                                      |
| `protocolTvl`                  | `Record<string, number>`                                                    | TVL per DEX protocol (e.g. `{ "uniswap-v3": 100000 }`)                                                                                                                  |
| `chainTvl`                     | `Record<string, number>`                                                    | TVL per chain (e.g. `{ "Ethereum": 500000 }`)                                                                                                                           |
| `topPools`                     | `DexLiquidityPool[]`                                                        | Top 10 retained pools sorted by 24h volume, then TVL                                                                                                                    |
| `liquidityScore`               | `number \| null`                                                            | Composite liquidity score 0–100                                                                                                                                         |
| `concentrationHhi`             | `number \| null`                                                            | Herfindahl–Hirschman Index for pool concentration (0–1; lower = more distributed), computed from the full retained pool set before top-10 truncation                    |
| `depthStability`               | `number \| null`                                                            | Pool depth stability metric                                                                                                                                             |
| `tvlChange24h`                 | `number \| null`                                                            | % TVL change vs. 24 h ago                                                                                                                                               |
| `tvlChange7d`                  | `number \| null`                                                            | % TVL change vs. 7 days ago                                                                                                                                             |
| `updatedAt`                    | `number`                                                                    | Unix seconds of last cron update                                                                                                                                        |
| `dexPriceUsd`                  | `number \| null`                                                            | DEX-derived price (USD)                                                                                                                                                 |
| `dexDeviationBps`              | `number \| null`                                                            | DEX price deviation from peg (basis points, signed)                                                                                                                     |
| `priceSourceCount`             | `number \| null`                                                            | Number of pools used for DEX price (all must meet the shared $50K observation floor)                                                                                    |
| `priceSourceTvl`               | `number \| null`                                                            | Combined TVL of price-source pools (USD)                                                                                                                                |
| `priceSources`                 | `DexPriceSource[] \| null`                                                  | Aggregated price sources by protocol (for example one `balancer` or `raydium` entry per asset)                                                                          |
| `effectiveTvlUsd`              | `number`                                                                    | TVL after applying quality multipliers                                                                                                                                  |
| `avgPoolStress`                | `number \| null`                                                            | Average pool stress index on a 0–100 scale (0 = balanced, 100 = maximally stressed / imbalanced)                                                                        |
| `weightedBalanceRatio`         | `number \| null`                                                            | TVL-weighted balance ratio across pools                                                                                                                                 |
| `organicFraction`              | `number \| null`                                                            | Fraction of TVL from organic (non-incentivized) pools                                                                                                                   |
| `durabilityScore`              | `number \| null`                                                            | Score for pool maturity and reliability                                                                                                                                 |
| `coverageClass`                | `"primary" \| "mixed" \| "fallback" \| "legacy" \| "unobserved" \| null`    | Coverage-confidence classification for the retained pool set; `primary` includes pure `dl` and pure `direct_api` rows. The `__global__` aggregate sentinel uses `null`. |
| `coverageConfidence`           | `number`                                                                    | Evidence-weighted confidence (`0-1`) derived from retained-pool breadth, measured TVL share, and synthetic/decayed dependence                                           |
| `liquidityEvidenceClass`       | `"unobserved" \| "measured" \| "partial_measured" \| "observed_unmeasured"` | Explicit classification of whether liquidity is balance-measured versus only observed from TVL / price evidence                                                         |
| `hasMeasuredLiquidityEvidence` | `boolean`                                                                   | Whether any retained liquidity evidence for the row includes measured pool balances                                                                                     |
| `trendworthy`                  | `boolean`                                                                   | Whether this row is suitable for trend baselines (`coverageConfidence >= 0.75`, positive TVL, and `primary`/`mixed` coverage)                                           |
| `sourceMix`                    | `Record<string, { poolCount: number; tvlUsd: number }>`                     | TVL/pool-count mix across source families (`dl`, `direct_api`, `cg_onchain`, `gecko_terminal`, `dexscreener`, `cg_tickers`)                                             |
| `balanceMeasuredTvlUsd`        | `number`                                                                    | TVL denominator actually used for `weightedBalanceRatio`                                                                                                                |
| `organicMeasuredTvlUsd`        | `number`                                                                    | TVL denominator actually used for `organicFraction`                                                                                                                     |
| `scoreComponents`              | `ScoreComponents \| null`                                                   | Breakdown of the composite liquidity score                                                                                                                              |
| `lockedLiquidityPct`           | `number \| null`                                                            | TVL-weighted fraction of liquidity reported as locked by source pools                                                                                                   |
| `methodologyVersion`           | `string`                                                                    | Methodology version attributed to this row                                                                                                                              |

**`ScoreComponents`**

| Field            | Type     | Description               |
| ---------------- | -------- | ------------------------- |
| `tvlDepth`       | `number` | TVL depth sub-score       |
| `volumeActivity` | `number` | Volume activity sub-score |
| `poolQuality`    | `number` | Pool quality sub-score    |
| `durability`     | `number` | Durability sub-score      |
| `pairDiversity`  | `number` | Pair diversity sub-score  |

**`DexLiquidityPool`**

| Field         | Type                  | Description                                                                                                             |
| ------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `project`     | `string`              | Protocol slug (e.g. `"curve-dex"`, `"uniswap-v3"`)                                                                      |
| `chain`       | `string`              | Chain name                                                                                                              |
| `tvlUsd`      | `number`              | Pool TVL (USD)                                                                                                          |
| `symbol`      | `string`              | Pool pair name (e.g. `"USDC-USDT"`), normalized to tracked tickers when direct-API sources only provide token addresses |
| `volumeUsd1d` | `number`              | 24 h volume (USD)                                                                                                       |
| `poolType`    | `string`              | Pool type (e.g. `"curve-stableswap"`, `"uniswap-v3-5bp"`)                                                               |
| `source`      | `string \| undefined` | Canonical source family for this retained pool                                                                          |
| `extra`       | `object \| undefined` | Optional detailed pool metrics (A-factor, balance ratio, measurement flags, etc.)                                       |

`extra` may include:

| Field                      | Type                                                                             | Description                                                                                                                                                     |
| -------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amplificationCoefficient` | `number \| undefined`                                                            | Curve amplification coefficient (`A`)                                                                                                                           |
| `balanceRatio`             | `number \| undefined`                                                            | Measured pool balance ratio from 0 to 1; Balancer weighted pools normalize against weights and Fluid uses official DexReservesResolver balances where deployed  |
| `feeTier`                  | `number \| undefined`                                                            | Normalized fee tier in basis points                                                                                                                             |
| `balanceDetails`           | `Array<{ symbol: string; balancePct: number; isTracked: boolean }> \| undefined` | Per-token USD composition shares used for balance tooltips/detail                                                                                               |
| `measurement`              | `object \| undefined`                                                            | Per-pool provenance flags such as `tvlMeasured`, `volumeMeasured`, `balanceMeasured`, `maturityMeasured`, `priceMeasured`, `synthetic`, `decayed`, and `capped` |

**`DexPriceSource`**

| Field      | Type     | Description            |
| ---------- | -------- | ---------------------- |
| `protocol` | `string` | DEX protocol name      |
| `chain`    | `string` | Chain name             |
| `price`    | `number` | Price from this source |
| `tvl`      | `number` | TVL of this pool (USD) |

---

### `GET /api/dex-liquidity-history`

Per-coin historical DEX liquidity snapshots. Snapshots are recorded daily (UTC midnight, first sync after day rollover). Baseline consumers should use `coverageClass` / `coverageConfidence` before treating a history point as trend-worthy.

**Cache:** slow — `public, s-maxage=3600, max-age=300`

**Required query parameter**

| Param        | Type     | Description                     |
| ------------ | -------- | ------------------------------- |
| `stablecoin` | `string` | Pharos stablecoin ID (required) |

**Optional query parameters**

| Param  | Type      | Default | Bounds | Description             |
| ------ | --------- | ------- | ------ | ----------------------- |
| `days` | `integer` | `90`    | 1–365  | Lookback window in days |

**Response:** Array sorted by `date` ascending.

```json
[
  {
    "tvl": 1658000000,
    "volume24h": 1700000000,
    "score": 93,
    "date": 1771500000,
    "coverageClass": "mixed",
    "coverageConfidence": 0.85,
    "liquidityEvidenceClass": "partial_measured",
    "hasMeasuredLiquidityEvidence": true,
    "trendworthy": true,
    "methodologyVersion": "3.1"
  }
]
```

| Field                          | Type             | Description                                                                                   |
| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
| `tvl`                          | `number`         | Total DEX TVL snapshot (USD)                                                                  |
| `volume24h`                    | `number`         | 24 h volume at time of snapshot (USD)                                                         |
| `score`                        | `number \| null` | Liquidity score at time of snapshot                                                           |
| `date`                         | `number`         | Unix seconds                                                                                  |
| `coverageClass`                | `string`         | Snapshot confidence class (`primary`, `mixed`, `fallback`, `legacy`, `unobserved`)            |
| `coverageConfidence`           | `number`         | Snapshot confidence score                                                                     |
| `liquidityEvidenceClass`       | `string`         | Snapshot evidence class (`measured`, `partial_measured`, `observed_unmeasured`, `unobserved`) |
| `hasMeasuredLiquidityEvidence` | `boolean`        | Whether the snapshot qualifies as balance-measured liquidity evidence                         |
| `trendworthy`                  | `boolean`        | Whether the snapshot is suitable for trend baselines rather than informational use            |
| `methodologyVersion`           | `string`         | Methodology version attributed to this snapshot                                               |

---

### `GET /api/supply-history`

Per-coin circulating supply and price history. The `snapshot-supply` cron writes one snapshot per UTC day; it runs on the quarter-hourly trigger once the previous day's snapshot is at least 20 hours old, with an 08:00 UTC daily trigger as a safety-net fallback.

**Cache:** slow — `public, s-maxage=3600, max-age=300`

Freshness headers are emitted from the latest completed `snapshot-supply` run when available. Rows newer than the completed daily snapshot marker are hidden so a failed chunked write cannot expose a partial latest day.

**Required query parameter**

| Param        | Type     | Description                     |
| ------------ | -------- | ------------------------------- |
| `stablecoin` | `string` | Pharos stablecoin ID (required) |

**Optional query parameters**

| Param  | Type      | Default | Bounds | Description             |
| ------ | --------- | ------- | ------ | ----------------------- |
| `days` | `integer` | `365`   | 1–5000 | Lookback window in days |

**Response:** Array sorted by `date` ascending.

```json
[
  {
    "date": 1771500000,
    "circulatingUsd": 138000000000,
    "price": 1.0001
  }
]
```

| Field            | Type             | Description                                                   |
| ---------------- | ---------------- | ------------------------------------------------------------- |
| `date`           | `number`         | Unix seconds                                                  |
| `circulatingUsd` | `number`         | Circulating supply in USD                                     |
| `price`          | `number \| null` | Price at snapshot time (USD); may be `null` for older records |

---

### `GET /api/daily-digest`

Latest AI-generated market summary, produced daily at 08:05 UTC via the Claude API.

**Cache:** standard. When a digest exists, the response includes `X-Data-Age` and `Warning` freshness headers keyed to a 24 h endpoint budget. The bootstrap `{ "digest": null }` response carries only `Cache-Control`.

**Response**

```json
{
  "digest": "USDC absorbed $812M of the market's $1.36B weekly inflow…",
  "editionNumber": 214,
  "riskSignal": {
    "kind": "depeg",
    "symbol": "PMUSD",
    "bps": -5284,
    "mcapUsd": 65610000,
    "severity": "critical",
    "activeCount": 7,
    "date": null
  },
  "riskTape": [{ "id": "risk-tape:depegs", "label": "Depegs", "value": "PMUSD 5284bps", "tone": "critical" }],
  "nextTriggers": [
    {
      "id": "trigger:depeg:pmusd",
      "label": "PMUSD depeg widening",
      "metric": "depeg-bps",
      "comparator": "abs-gte",
      "thresholdLabel": "5500 bps off peg",
      "thresholdValue": 5500,
      "symbol": "PMUSD",
      "rationale": "A wider deviation raises severity.",
      "detail": "If PMUSD reaches 5500 bps off peg, severity rises."
    }
  ]
}
```

If no digest exists yet, the endpoint returns only `{ "digest": null }`.

| Field                 | Type                                 | Description                                                                               |
| --------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
| `digest`              | `string \| null`                     | Tweet-ready summary (≤ 240 characters). `null` if no digest has been generated yet.       |
| `digestTitle`         | `string \| null`                     | Short headline for the digest                                                             |
| `digestExtended`      | `string \| null`                     | Extended commentary for the website view                                                  |
| `generatedAt`         | `number`                             | Unix seconds when this digest was generated (present only when `digest` is non-null)      |
| `editionNumber`       | `number \| null`                     | Sequential daily digest number (present only when `digest` is non-null)                   |
| `riskSignal`          | `DigestRiskSignal \| null`           | Compact active-depeg risk summary parsed from stored digest input data                    |
| `changeSummary`       | `DigestChangeSummary \| null`        | Deterministic "what changed since yesterday" summary parsed from stored digest input data |
| `nextTriggers`        | `DigestNextTrigger[] \| null`        | Structured forward-looking threshold checks for tomorrow's digest                         |
| `forwardLookOutcomes` | `DigestForwardLookOutcome[] \| null` | Evaluation of the previous digest's next triggers against the latest input                |
| `riskTape`            | `DigestRiskTapeItem[] \| null`       | Compact reader-facing risk state chips parsed from stored digest input data               |

---

### `GET /api/digest-archive`

Newest-first archive of up to 365 daily and weekly digests.

**Cache:** standard

**Response**

```json
{
  "digests": [
    {
      "digestText": "USDC absorbed $812M…",
      "digestTitle": "USDC Eats the Week",
      "digestExtended": "Longer editorial…",
      "generatedAt": 1771839719,
      "psiScore": 81.1,
      "psiBand": "STEADY",
      "totalMcapUsd": 234500000000,
      "riskSignal": {
        "kind": "depeg",
        "symbol": "PMUSD",
        "bps": -5284,
        "mcapUsd": 65610000,
        "severity": "critical",
        "activeCount": 7,
        "date": "2026-05-05"
      },
      "riskTape": [{ "id": "risk-tape:depegs", "label": "Depegs", "value": "PMUSD 5284bps", "tone": "critical" }],
      "nextTriggers": [
        {
          "id": "trigger:depeg:pmusd",
          "label": "PMUSD depeg widening",
          "metric": "depeg-bps",
          "comparator": "abs-gte",
          "thresholdLabel": "5500 bps off peg",
          "thresholdValue": 5500,
          "symbol": "PMUSD",
          "rationale": "A wider deviation raises severity.",
          "detail": "If PMUSD reaches 5500 bps off peg, severity rises."
        }
      ],
      "digestType": "daily",
      "editionNumber": 214
    }
  ]
}
```

Each element uses `digestText` (note: differs from the singular `/api/daily-digest` which uses `digest`).

| Field                 | Type                                 | Description                                                                              |
| --------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------- |
| `digestText`          | `string`                             | Tweet-ready summary                                                                      |
| `digestTitle`         | `string \| null`                     | Short headline                                                                           |
| `digestExtended`      | `string \| null`                     | Extended commentary                                                                      |
| `generatedAt`         | `number`                             | Unix seconds of generation time                                                          |
| `psiScore`            | `number \| null`                     | PSI score parsed from archived digest input data                                         |
| `psiBand`             | `string \| null`                     | PSI condition band parsed from archived digest input data                                |
| `totalMcapUsd`        | `number \| null`                     | Ecosystem market cap parsed from archived digest input data                              |
| `riskSignal`          | `DigestRiskSignal \| null`           | Compact active-depeg risk summary parsed from archived digest input data                 |
| `nextTriggers`        | `DigestNextTrigger[] \| null`        | Structured forward-looking threshold checks parsed from archived digest input data       |
| `forwardLookOutcomes` | `DigestForwardLookOutcome[] \| null` | Evaluation of the previous digest's next triggers parsed from archived digest input data |
| `riskTape`            | `DigestRiskTapeItem[] \| null`       | Compact risk-state chips parsed from archived digest input data                          |
| `digestType`          | `"daily" \| "weekly"`                | Digest cadence for this archived entry                                                   |
| `editionNumber`       | `number`                             | Sequential edition number within that digest cadence                                     |

**`DigestRiskSignal`**

| Field         | Type                    | Description                                                                         |
| ------------- | ----------------------- | ----------------------------------------------------------------------------------- |
| `kind`        | `"depeg"`               | Risk signal family; currently active-depeg context                                  |
| `symbol`      | `string`                | Stablecoin symbol                                                                   |
| `bps`         | `number`                | Signed basis-point deviation where available; archive badges display absolute value |
| `mcapUsd`     | `number \| null`        | Market cap associated with the stored digest signal                                 |
| `severity`    | `"critical" \| "watch"` | `"critical"` at ≥2,500 bps and ≥$50M mcap, or ≥5,000 bps and ≥$10M mcap             |
| `activeCount` | `number`                | Active depeg count from the stored digest input, when available                     |
| `date`        | `string \| null`        | Daily input date for weekly archive entries; `null` for latest daily responses      |

**Digest intelligence fields**

| Field                              | Type                                                                                                  | Description                                                                         |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `DigestRiskTapeItem.id`            | `string`                                                                                              | Stable identifier for the displayed tape item                                       |
| `DigestRiskTapeItem.label`         | `string`                                                                                              | Short label such as `PSI`, `Depegs`, `Gauge`, `DEWS`, or `Supply`                   |
| `DigestRiskTapeItem.value`         | `string`                                                                                              | Already formatted compact value for display                                         |
| `DigestRiskTapeItem.tone`          | `"critical" \| "warning" \| "neutral" \| "positive"`                                                  | Presentation severity                                                               |
| `DigestRiskTapeItem.detail`        | `string`                                                                                              | Optional supporting detail                                                          |
| `DigestNextTrigger.metric`         | `"depeg-bps" \| "supply-1d-usd" \| "supply-7d-usd" \| "bank-run-gauge" \| "dews-band" \| "psi-score"` | Metric that the next daily input can evaluate                                       |
| `DigestNextTrigger.comparator`     | `"abs-gte" \| "gte" \| "lte" \| "band-gte"`                                                           | Comparison operator for `thresholdValue`                                            |
| `DigestNextTrigger.thresholdLabel` | `string`                                                                                              | Display string for the threshold                                                    |
| `DigestForwardLookOutcome.status`  | `"hit" \| "missed" \| "pending"`                                                                      | Whether the prior trigger fired, failed, or still needs more data                   |
| `DigestChangeSummary.*Signals`     | `array`                                                                                               | Buckets of signal changes, each with `id`, `label`, `kind`, `symbols`, and `detail` |

---

### `GET /api/digest-snapshot`

Contextual data snapshot for a specific digest date — includes the digest's input data, active depeg events, and blacklist events for that day. Used by SSG builds for individual digest pages.

**Cache:** archive

**Required query parameter**

| Param  | Type     | Description                                                                           |
| ------ | -------- | ------------------------------------------------------------------------------------- |
| `date` | `string` | Date in `YYYY-MM-DD` format, or `YYYY-MM-DD-weekly` for weekly recap pages (required) |

**Response**

```text
{
  "date": "2026-02-27",
  "inputData": { "totalMcapUsd": 230000000000, "mcap7dDelta": 0.012, ... },
  "prevInputData": { ... },
  "depegEvents": [{ "stablecoinId": "usdt-tether", "symbol": "USDT", "direction": "below", "peakDeviationBps": -150, ... }],
  "blacklistEvents": [{ "stablecoin": "USDT", "chainName": "Ethereum", "eventType": "blacklist", ... }]
}
```

| Field             | Type             | Description                                                                                     |
| ----------------- | ---------------- | ----------------------------------------------------------------------------------------------- |
| `date`            | `string`         | The requested date                                                                              |
| `inputData`       | `object \| null` | Digest input data (mcap, depegs, supply changes, PSI, digest intelligence fields) for this date |
| `prevInputData`   | `object \| null` | Previous day's input data for delta computation                                                 |
| `depegEvents`     | `array`          | Up to 20 depeg events active on that date, ordered by severity                                  |
| `blacklistEvents` | `array`          | Up to 50 blacklist events on that date                                                          |

**Error responses:** `400` for missing/invalid date, `404` if no digest exists for that date.

---

### `GET /api/snapshots/index`

Lists immutable public daily dataset snapshots written by the `snapshot-public-dataset` cron. Each row points to a dated payload that can be fetched through `GET /api/snapshots/:date.json`.

**Cache:** archive

**Response**

```json
{
  "snapshots": [
    {
      "snapshotDate": "2026-05-17",
      "methodologyVersions": { "safetyScore": "5.9", "psi": "3.0" },
      "contentHash": "sha256-hex",
      "byteSize": 1234567,
      "createdAt": 1778976000
    }
  ]
}
```

| Field                               | Type                       | Description                                      |
| ----------------------------------- | -------------------------- | ------------------------------------------------ |
| `snapshots`                         | `array`                    | Snapshot index sorted newest first               |
| `snapshots[].snapshotDate`          | `string`                   | UTC snapshot date in `YYYY-MM-DD` format         |
| `snapshots[].methodologyVersions`   | `Record<string, string>?`  | Methodology versions embedded in the snapshot    |
| `snapshots[].contentHash`           | `string`                   | Snapshot payload hash used by dated `ETag`s      |
| `snapshots[].byteSize`              | `number`                   | Uncompressed JSON payload size in bytes          |
| `snapshots[].createdAt`             | `number`                   | Snapshot creation timestamp in Unix seconds      |

---

### `GET /api/snapshots/:date.json`

Returns the full immutable public dataset snapshot for a UTC date. The worker reads the gzipped payload from D1, decompresses it, and returns the original JSON envelope.

**Cache:** immutable-snapshot

**Path parameters**

| Param  | Type     | Description                             |
| ------ | -------- | --------------------------------------- |
| `date` | `string` | UTC snapshot date in `YYYY-MM-DD` form  |

**Response**

```text
PublicSnapshotEnvelope {
  snapshotDate,
  generatedAt,
  methodologyVersions,
  stablecoinRows,
  fxFallbackRates,
  reportCards,
  psi,
  dewsRows,
  liquidityRows
}
```

**Headers:** `ETag: "<contentHash>"`.

**Error responses:** `400` for invalid date format, `404` if no snapshot exists for that date, `500` if the stored snapshot payload is unreadable or corrupted.

---

### `GET /api/snapshot/:date/stablecoin/:id`

Returns a per-stablecoin projection from a dated public dataset snapshot. The projection includes the stablecoin row plus the matching report-card, DEWS, and liquidity rows when those datasets were present in the snapshot.

**Cache:** immutable-snapshot

**Path parameters**

| Param  | Type     | Description                            |
| ------ | -------- | -------------------------------------- |
| `date` | `string` | UTC snapshot date in `YYYY-MM-DD` form |
| `id`   | `string` | Canonical Pharos stablecoin ID         |

**Response**

```text
{
  snapshotDate: "2026-05-17",
  stablecoinId: "usdt-tether",
  generatedAt: 1778976000,
  methodologyVersions: { safetyScore: "5.9", psi: "3.0" },
  stablecoin: { id: "usdt-tether", symbol: "USDT" },
  scores: {
    reportCard,
    psi,
    dews,
    liquidity
  }
}
```

| Field                  | Type      | Description                                                    |
| ---------------------- | --------- | -------------------------------------------------------------- |
| `snapshotDate`          | `string`  | Served snapshot date                                           |
| `stablecoinId`          | `string`  | Requested stablecoin ID                                        |
| `generatedAt`           | `number`  | Snapshot generation timestamp                                  |
| `methodologyVersions`   | `object?` | Methodology versions embedded in the snapshot                  |
| `stablecoin`            | `object`  | Stablecoin row from the dated public dataset                   |
| `scores.reportCard`     | `object?` | Matching report-card score, or `null`                          |
| `scores.psi`            | `object?` | Snapshot-level PSI object, or `null`                           |
| `scores.dews`           | `object?` | Matching DEWS stress-signal row, or `null`                     |
| `scores.liquidity`      | `object?` | Matching DEX-liquidity row, or `null`                          |

**Headers:** `ETag: "<contentHash>-<stablecoinId>"`.

**Error responses:** `400` for invalid date format, `404` if no snapshot exists or the stablecoin is absent from that snapshot, `500` if the stored snapshot payload is unreadable or corrupted.

---

### `GET /api/health`

Worker health check. Reports cache freshness, blacklist integrity, mint/burn freshness, and circuit-breaker states. Not served from Cloudflare edge cache (`no-store`).

Cache freshness in `/api/health` separates producer cadence, endpoint freshness, and availability impact. `caches[*].maxAge` is the availability budget used by `/api/health`, `/api/status`, and the public/admin status pages. `endpointMaxAge` is the endpoint freshness basis used for `_meta`, `X-Data-Age`, and the generic freshness warning runway when it differs. `producerIntervalSec` is the expected writer cadence.

**Authentication:** exempt

**Response**

```json
{
  "status": "healthy",
  "timestamp": 1771856453,
  "warnings": [],
  "caches": {
    "stablecoins": {
      "ageSeconds": 323,
      "maxAge": 600,
      "healthy": true,
      "producerJob": "sync-stablecoins",
      "producerIntervalSec": 900,
      "endpointMaxAge": 600,
      "availabilityMaxAge": 600
    },
    "stablecoin-charts": {
      "ageSeconds": 323,
      "maxAge": 3600,
      "healthy": true,
      "producerJob": "sync-stablecoin-charts",
      "producerIntervalSec": 3600,
      "endpointMaxAge": 3600,
      "availabilityMaxAge": 3600
    },
    "usds-status": {
      "ageSeconds": 47118,
      "maxAge": 86400,
      "healthy": true,
      "producerJob": "sync-usds-status",
      "producerIntervalSec": 86400,
      "endpointMaxAge": 86400,
      "availabilityMaxAge": 86400
    },
    "fx-rates": {
      "ageSeconds": 1223,
      "maxAge": 1800,
      "healthy": true,
      "producerJob": "sync-fx-rates",
      "producerIntervalSec": 1800,
      "endpointMaxAge": 1800,
      "availabilityMaxAge": 1800,
      "mode": "live",
      "sourceUpdatedAt": 1771855200,
      "sourceAgeSeconds": 323,
      "sourceStatus": "fresh",
      "warning": null,
      "consecutiveFallbackRuns": 0
    },
    "bluechip-ratings": {
      "ageSeconds": 22815,
      "maxAge": 86400,
      "healthy": true,
      "producerJob": "sync-bluechip",
      "producerIntervalSec": 86400,
      "endpointMaxAge": 43200,
      "availabilityMaxAge": 86400
    },
    "dex-liquidity": {
      "ageSeconds": 290,
      "maxAge": 43200,
      "healthy": true,
      "producerJob": "sync-dex-liquidity",
      "producerIntervalSec": 1800,
      "endpointMaxAge": 3600,
      "availabilityMaxAge": 43200
    },
    "yield-data": {
      "ageSeconds": 820,
      "maxAge": 3600,
      "healthy": true,
      "producerJob": "sync-yield-data",
      "producerIntervalSec": 3600,
      "endpointMaxAge": 3600,
      "availabilityMaxAge": 3600
    },
    "dews": {
      "ageSeconds": 240,
      "maxAge": 1800,
      "healthy": true,
      "producerJob": "compute-dews",
      "producerIntervalSec": 1800,
      "endpointMaxAge": 1800,
      "availabilityMaxAge": 1800
    }
  },
  "blacklist": {
    "totalEvents": 13422,
    "missingAmounts": 0,
    "recentMissingAmounts": 0,
    "recentWindowSec": 86400,
    "missingRatio": 0
  },
  "mintBurn": {
    "totalEvents": null,
    "latestEventTs": null,
    "latestHourlyTs": null,
    "freshnessAgeSec": null,
    "majorStaleCount": 0,
    "staleMajorSymbols": [],
    "sync": {
      "lastSuccessfulSyncAt": 1771856400,
      "freshnessStatus": "fresh",
      "warning": null,
      "criticalLaneHealthy": true
    }
  },
  "circuits": {
    "defillama-stablecoins": { "state": "closed", "consecutiveFailures": 0, "lastSuccessAt": 1772190029 },
    "coingecko-prices": { "state": "closed", "consecutiveFailures": 0, "lastSuccessAt": 1772190030 }
  },
  "telegramSummary": {
    "totalChats": 142,
    "pendingDeliveries": 0,
    "lastDispatchAt": 1771856400,
    "lastDispatchStatus": "ok",
    "safetyAlertSourceState": "ok",
    "safetyAlertSourceAgeSeconds": 120,
    "safetyAlertsSuppressed": false,
    "safetyAlertSourceGeneration": "safety-7.12-alert-source-v1"
  }
}
```

| Field                                         | Type                                                                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`                                      | `string`                                                                  | `"healthy"` / `"degraded"` / `"stale"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `timestamp`                                   | `number`                                                                  | Unix seconds at time of response                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `warnings`                                    | `string[]`                                                                | Best-effort machine-readable warnings when health subqueries fail but the endpoint can still return a non-500 payload. Messages are sanitized for public output and do not include raw exception text, SQL fragments, or table names.                                                                                                                                                                                                                                                                                                      |
| `caches`                                      | `Record<string, CacheStatus>`                                             | Per-cache freshness status                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `caches["fx-rates"]`                          | `CacheStatus`                                                             | FX cache freshness plus source-cadence diagnostics (`mode`, `sourceUpdatedAt`, `sourceAgeSeconds`, `sourceStatus`, `warning`, `consecutiveFallbackRuns`)                                                                                                                                                                                                                                                                                                                                                                                   |
| `blacklist.totalEvents`                       | `number`                                                                  | Total events in blacklist table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `blacklist.missingAmounts`                    | `number`                                                                  | Events where `amount` is null (should be 0)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `blacklist.recentMissingAmounts`              | `number`                                                                  | Missing-amount events inside the recent monitoring window used by status logic                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `blacklist.recentWindowSec`                   | `number`                                                                  | Size of the recent monitoring window in seconds                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `blacklist.missingRatio`                      | `number`                                                                  | `missingAmounts / totalEvents` (0 when no blacklist rows exist yet)                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `telegramSummary`                             | `TelegramHealthSummary \| null`                                           | Lightweight Telegram delivery health summary. `null` when the Telegram tables are unavailable or not yet migrated                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `telegramSummary.totalChats`                  | `number`                                                                  | Total subscribed Telegram chats currently stored                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `telegramSummary.pendingDeliveries`           | `number`                                                                  | Pending overflow alert deliveries waiting in `telegram_pending_alerts`                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `telegramSummary.lastDispatchAt`              | `number \| null`                                                          | Unix seconds of the most recent `dispatch-telegram-alerts` cron run, if available                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `telegramSummary.lastDispatchStatus`          | `string \| null`                                                          | Status of the most recent `dispatch-telegram-alerts` cron run, if available                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `telegramSummary.safetyAlertSourceState`      | `"ok" \| "missing" \| "corrupt" \| "stale" \| "wrong-generation" \| null` | Live safety-alert source-cache state from the most recent Telegram dispatch run                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `telegramSummary.safetyAlertSourceAgeSeconds` | `number \| null`                                                          | Age of the current live safety-alert source snapshot when available                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `telegramSummary.safetyAlertsSuppressed`      | `boolean`                                                                 | `true` when safety alerts are paused because the live source snapshot is missing, corrupt, stale, or from the wrong generation                                                                                                                                                                                                                                                                                                                                                                                                             |
| `telegramSummary.safetyAlertSourceGeneration` | `string \| null`                                                          | Generation marker of the current live safety-alert source snapshot                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `mintBurn.totalEvents`                        | `number \| null`                                                          | Legacy advisory total. `null` on the budget-capped health path because `/api/health` no longer scans `mint_burn_hourly`; use `/api/mint-burn-flows` or `/api/mint-burn-events` for mint/burn data views.                                                                                                                                                                                                                                                                                                                                   |
| `mintBurn.latestEventTs`                      | `number \| null`                                                          | Legacy advisory timestamp. `null` on the budget-capped health path because `/api/health` no longer scans `mint_burn_events`; freshness is represented by `mintBurn.sync.lastSuccessfulSyncAt`.                                                                                                                                                                                                                                                                                                                                             |
| `mintBurn.latestHourlyTs`                     | `number \| null`                                                          | Legacy advisory timestamp. `null` on the budget-capped health path because `/api/health` no longer scans `mint_burn_hourly`.                                                                                                                                                                                                                                                                                                                                                                                                               |
| `mintBurn.freshnessAgeSec`                    | `number \| null`                                                          | Legacy advisory age. `null` on the budget-capped health path; derive critical-lane age from `mintBurn.sync.lastSuccessfulSyncAt`.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `mintBurn.majorStaleCount`                    | `number`                                                                  | Legacy advisory count. Always `0` on the budget-capped health path because per-symbol stale checks are intentionally not scanned from D1.                                                                                                                                                                                                                                                                                                                                                                                                  |
| `mintBurn.staleMajorSymbols`                  | `string[]`                                                                | Legacy advisory list. Always empty on the budget-capped health path because per-symbol stale checks are intentionally not scanned from D1.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `mintBurn.sync`                               | `object`                                                                  | Critical-lane sync freshness summary used for public health evaluation                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `mintBurn.sync.lastSuccessfulSyncAt`          | `number \| null`                                                          | Unix seconds of the latest successful `sync-mint-burn` run                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `mintBurn.sync.freshnessStatus`               | `"fresh" \| "degraded" \| "stale"`                                        | Public freshness state keyed to the 30-minute critical-lane cadence (`fresh <= 60m`, `degraded <= 90m`, `stale > 90m`)                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `mintBurn.sync.warning`                       | `string \| null`                                                          | Human-readable warning when the critical lane is stale, degraded, or errored                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `mintBurn.sync.criticalLaneHealthy`           | `boolean`                                                                 | `true` when the latest critical-lane run is `ok`, `degraded`, or `skipped_locked`                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `circuits`                                    | `Record<string, CircuitRecord>`                                           | Per-source circuit breaker states. Keys include `defillama-stablecoins`, `defillama-stablecoin-detail`, `defillama-coins`, `defillama-yields`, `defillama-protocols`, `coingecko-prices`, `coingecko-detail-platforms`, `coingecko-mcap`, `coingecko-discovery`, `coinmarketcap-prices`, `dexscreener-prices`, `dexscreener-search`, `treasury-rates`, `etherscan`, `alchemy`, `twitter-api`, `telegram-api`, `pyth-prices`, `binance-prices`, `coinbase-prices`, `redstone-prices`, `curve-onchain`, `curve-liquidity-api`, `fx-realtime` |

**`CacheStatus`**

| Field                      | Type                                                                       | Description                                                                                                                                                                 |
| -------------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ageSeconds`               | `number \| null`                                                           | Seconds since last cron update; `null` if never populated                                                                                                                   |
| `maxAge`                   | `number`                                                                   | Availability budget in seconds for this cache key; same value as `availabilityMaxAge` for current workers                                                                   |
| `healthy`                  | `boolean`                                                                  | `true` when `ageSeconds / maxAge ≤ 12.0`; status-page bands use `>8.0x` for degraded and `>12.0x` for stale                                                                 |
| `producerJob`              | `string \| null \| undefined`                                              | Cron job that produces the cache freshness signal                                                                                                                           |
| `producerIntervalSec`      | `number \| null \| undefined`                                              | Expected producer cadence in seconds                                                                                                                                        |
| `endpointMaxAge`           | `number \| null \| undefined`                                              | Endpoint freshness basis used by `_meta`, `X-Data-Age`, and generic freshness warning runway when available                                                                 |
| `availabilityMaxAge`       | `number \| null \| undefined`                                              | Availability budget used by `/api/health`, `/api/status`, and status-page cache ratios                                                                                      |
| `endpointBudgetReason`     | `string \| null \| undefined`                                              | Short explanation when endpoint freshness differs from producer cadence or availability budget                                                                              |
| `availabilityBudgetReason` | `string \| null \| undefined`                                              | Short explanation for the availability budget                                                                                                                               |
| `freshnessSource`          | `"freshness-sentinel" \| "table-fallback" \| "cron-fallback" \| undefined` | Source used to derive freshness for sentinel-backed cache lanes                                                                                                             |
| `sentinelValidationReason` | `string \| null \| undefined`                                              | Present when a malformed, stale, wrong-source, future-dated, or non-`ok` freshness sentinel was ignored                                                                     |
| `mode`                     | `"live" \| "cached-fallback" \| undefined`                                 | FX cache only: whether the latest usable sync came from a live fetch or cached fallback                                                                                     |
| `sourceUpdatedAt`          | `number \| null \| undefined`                                              | FX cache only: Unix seconds for the source currently driving `sourceStatus`                                                                                                 |
| `sourceAgeSeconds`         | `number \| null \| undefined`                                              | FX cache only: age of the source currently driving `sourceStatus`                                                                                                           |
| `sourceStatus`             | `"fresh" \| "degraded" \| "stale" \| "none"`                               | FX cache only: cadence-aware source freshness status                                                                                                                        |
| `warning`                  | `string \| null \| undefined`                                              | Human-readable warning when a lane is running on degraded freshness evidence (FX fallback/source cadence, or cache freshness fallback from sentinel to table/cron evidence) |
| `consecutiveFallbackRuns`  | `number \| undefined`                                                      | FX cache only: number of back-to-back cached-fallback runs                                                                                                                  |

The `/status/` page consumes the richer blacklist fields directly so it can distinguish long-tail historical cleanup from fresh incoming amount gaps.
Blacklist amount-gap severity is intentionally tolerant of isolated parser/provider misses: data-quality degrades when the missing-amount ratio reaches 1% or when at least 5 recent events are missing amounts, and becomes stale at 2% or at 25 recent missing events.

`dex-liquidity`, `yield-data`, and `dews` compute freshness from producer-owned cache sentinels first (`freshness:dex-liquidity`, `freshness:yield-data`, `freshness:dews`). A sentinel is trusted only when its JSON payload has `updatedAt`, the expected producer `source`, and `publishStatus: "ok"`; optional `rowsWritten` and `coverageRatio` fields may also be present. If the sentinel is missing or fails validation, the worker falls back to the legacy table query. If that freshness diagnostic also fails, it can fall back again to the latest successful producer cron timestamp. Invalid sentinels surface `freshnessSource`, `sentinelValidationReason`, and a cache `warning` instead of making the sentinel row authoritative.

**Overall status logic:**

- `healthy` — every cache impact is healthy, the public mint/burn lane is healthy, fewer than 3 public-impact circuit groups are open, and the health subqueries all resolved cleanly
- `degraded` — any cache impact is degraded (including FX cached-fallback or source-cadence lag), any of the blacklist/mint-burn/circuit health subqueries failed, the public mint/burn lane is warning-only, or 3+ public-impact circuit groups are open
- `stale` — any cache impact is stale, or the public mint/burn lane is stale versus its critical-lane cadence

`/api/health` still emits every circuit record under `circuits`, including dynamic per-coin `live-reserves:*` scopes, but those reserve-specific breakers do not change the top-level public status on their own; reserve sync health is evaluated on the dedicated reserve/data-quality lanes instead.

Blacklist ratio fields are still emitted here for the public surface, but threshold-based blacklist severity lives under `/api/status` data-quality; `/api/health` only escalates its top-level status when the blacklist health loader itself fails.

---

### `GET /api/public-status-history`

Public transition history for the read-only `/status/` page. Returns the current public status plus recent state transitions within a requested time window. Not edge-cached beyond the standard 60-second response cache.

**Query parameters**

| Param    | Type                     | Default | Description                                                                                                                               |
| -------- | ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `window` | `"24h" \| "7d" \| "30d"` | `"30d"` | Transition time window applied server-side before rows are returned                                                                       |
| `limit`  | `integer`                | `50`    | Max raw status transitions loaded within the time window before public-impact filtering (1–200); returned public transitions may be fewer |

**Response**

```json
{
  "timestamp": 1771856453,
  "currentStatus": "healthy",
  "lastChangedAt": 1771770000,
  "transitions": [
    {
      "id": 418,
      "from": "degraded",
      "to": "healthy",
      "transitionType": "recover",
      "reason": "raw-healthy-recovery-threshold",
      "at": 1771770000
    }
  ]
}
```

| Field           | Type                                 | Description                                                                                                  |
| --------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `timestamp`     | `number`                             | Unix seconds at time of response                                                                             |
| `currentStatus` | `"healthy" \| "degraded" \| "stale"` | Current public status, sourced from `assessPublicHealth` (matches the `status` field from `GET /api/health`) |
| `lastChangedAt` | `number \| null`                     | Unix seconds for the latest admin status-machine change, if known                                            |
| `transitions`   | `PublicStatusTransition[]`           | Recent public-impact incident transitions, inside the requested window, newest first                         |

This endpoint powers two separate public `/status/` views: the hero `Status runway` always uses `window=30d`, while the transition table owns its own user-selected `24h` / `7d` / `30d` filter.

Browser consumers on `pharos.watch` and `ops.pharos.watch` should use same-origin `/_site-data/public-status-history`, which proxies onto the internal website lane instead of calling the external API host directly.

**Public-impact filtering (2026-04-13):** The endpoint filters the admin state-machine transitions down to incidents opened by at least one public-facing impact code (`cache_ratio_*`, `cache_freshness_query_failed`, `cache_warning`, `fx_source_*`, `fx_cached_fallback`, `mint_burn_public_*`, `mint_burn_health_query_failed`, `open_circuit_groups`, `circuit_query_failed`, `cron_error_runs`, `multiple_unhealthy_crons`, `unhealthy_crons_present`, `db_unhealthy`). Admin-only data-quality causes (`missing_prices_*`, `blacklist_gaps_*`, `reserve_sync_*`, `onchain_*`, `watch_*`) are excluded, and `info`-severity causes cannot open a public incident. Once a public-impact incident is retained, the endpoint also retains the recovery path needed to return that incident to `healthy`, even when those recovery rows only carry info-level causes. This ensures the public `/status/` hero (driven by `/api/health`) and the uptime bar / transition timeline (driven by this endpoint) always agree. The unfiltered admin view is still available via the admin `/api/status` endpoint.

---

### `GET /api/telegram-pulse`

Lightweight Telegram adoption metrics for the public PharosWatchBot page. The canonical page route is `/pharoswatchbot/`; the legacy `/telegram` alias redirects there. Returns aggregate watcher/subscription counts, explicit vs preset-implied alert follows, the most subscribed coin symbols, and snapshot-backed watcher history when available. When active chats predate the first daily snapshot row, the history is prefixed with live active-chat cohort points so the public lifecycle chart shows the full available bot lifecycle. The common path serves the five-minute `telegram:pulse:snapshot` cache written by the Telegram cron sidecar; live aggregation is a stale/missing snapshot fallback.

Direct `https://api.pharos.watch/api/telegram-pulse` access is protected and requires `X-API-Key`. Public browser access on `pharos.watch` and `ops.pharos.watch` uses same-origin `/_site-data/telegram-pulse`, which proxies to the internal `site-api` lane with `X-Pharos-Site-Proxy-Secret`.

**Cache:** `public, max-age=300, s-maxage=300`

**Response**

```json
{
  "activeWatchers": 1842,
  "coinSubscriptions": 5621,
  "explicitCoinSubscriptions": 5000,
  "presetImpliedCoinSubscriptions": 621,
  "activePresetFollowers": 81,
  "newWatchersToday": 12,
  "churnedWatchersToday": 3,
  "reactivatedWatchersToday": 5,
  "historySource": "snapshot",
  "pendingDeliveries": 3,
  "quietHoursEnabledChats": 42,
  "miniAppSessionsToday": 88,
  "miniAppMutationsToday": 31,
  "miniAppDeniedToday": 2,
  "miniAppReplayClaimsToday": 1,
  "miniAppOpenToFirstMutationP50Sec": null,
  "alertTypeChats": {
    "dews": 1701,
    "depeg": 1644,
    "safety": 1512,
    "launch": 1208,
    "allTypes": 1191
  },
  "currentSnapshotAt": 1771856400,
  "lifecycleHistoryUpdatedAt": 1775088900,
  "lifecycleHistoryEverySeconds": 900,
  "quality": {
    "status": "complete",
    "unavailableFields": []
  },
  "privacy": {
    "exactActiveWatchers": true,
    "lowCardinalityThreshold": 5,
    "suppressedFields": []
  },
  "updatedAt": 1771856400,
  "updatedEverySeconds": 300,
  "topCoins": ["USDT", "USDC", "USDe"],
  "watcherHistory": [
    {
      "date": "2026-04-01",
      "timestamp": 1775001600000,
      "snapshotAt": 1775002500,
      "newWatchers": 12,
      "activeWatchers": 12
    },
    {
      "date": "2026-04-02",
      "timestamp": 1775088000000,
      "newWatchers": 9,
      "activeWatchers": 21,
      "churnedWatchers": 1,
      "reactivatedWatchers": 2
    }
  ]
}
```

| Field                            | Type                            | Description                                                                                                                                                                                                                                                                                                                                                                 |
| -------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `activeWatchers`                 | `number`                        | Subscribers with at least one active global, explicit coin, or preset alert follow                                                                                                                                                                                                                                                                                          |
| `coinSubscriptions`              | `number`                        | Total active alert follows, including explicit coin follows plus preset-implied follows                                                                                                                                                                                                                                                                                     |
| `explicitCoinSubscriptions`      | `number`                        | Active explicit per-coin subscription rows                                                                                                                                                                                                                                                                                                                                  |
| `presetImpliedCoinSubscriptions` | `number`                        | Dynamic preset follower count multiplied by each preset's currently resolved coin set                                                                                                                                                                                                                                                                                       |
| `activePresetFollowers`          | `number`                        | Chats with at least one active preset follow                                                                                                                                                                                                                                                                                                                                |
| `newWatchersToday`               | `number \| null`                | Active watchers created in the current UTC day snapshot; `null` when suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                        |
| `churnedWatchersToday`           | `number \| null`                | Snapshot-estimated active watcher churn for the current UTC day; `null` when suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                |
| `reactivatedWatchersToday`       | `number \| null`                | Snapshot-estimated active watcher reactivation for the current UTC day; `null` when suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                         |
| `historySource`                  | `"snapshot" \| "live-fallback"` | `snapshot` when the response is only fixed `telegram_watcher_lifecycle_daily` rows, or when no live fallback rows are available; `live-fallback` when subscriber-created-at aggregation supplies older active-chat cohort points ahead of the fixed snapshot rows                                                                                                           |
| `pendingDeliveries`              | `number \| null`                | Count of queued Telegram alert deliveries; `null` when unavailable or suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                       |
| `quietHoursEnabledChats`         | `number \| null`                | Aggregate count of chats with quiet hours enabled; `null` when suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                              |
| `miniAppSessionsToday`           | `number \| null`                | Valid Mini App session launches today; `null` when unavailable or suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                           |
| `miniAppMutationsToday`          | `number \| null`                | Successful Mini App mutations today; `null` when unavailable or suppressed by low-cardinality privacy filtering                                                                                                                                                                                                                                                             |
| `miniAppDeniedToday`             | `number \| null`                | Mini App denial count for abuse/health monitoring. This field is not suppressed by the low-cardinality privacy rule because it is an operational counter, not an adoption signal.                                                                                                                                                                                           |
| `miniAppReplayClaimsToday`       | `number \| null`                | Mini App replay-protection claim count for abuse/health monitoring. This field is not suppressed by the low-cardinality privacy rule because it is an operational counter, not an adoption signal.                                                                                                                                                                          |
| `miniAppOpenToFirstMutationP50Sec` | `number \| null`              | Reserved Mini App session-to-first-mutation latency metric; currently `null` until bucketed latency is wired through `telegram_usage_daily`                                                                                                                                                                                                                                 |
| `alertTypeChats`                 | `object`                        | Aggregate chat counts with DEWS, depeg, safety, launch, and all-four alert coverage                                                                                                                                                                                                                                                                                         |
| `currentSnapshotAt`              | `number`                        | Unix seconds when the current aggregate pulse snapshot was measured                                                                                                                                                                                                                                                                                                         |
| `lifecycleHistoryUpdatedAt`      | `number \| null`                | Unix seconds of the latest daily lifecycle snapshot when any snapshot row exists; can be non-null while `historySource="live-fallback"` when subscriber-created-at aggregation yields older chart points; `null` when no snapshot history exists                                                                                                                            |
| `lifecycleHistoryEverySeconds`   | `number`                        | Expected lifecycle-history snapshot cadence, currently 900 seconds                                                                                                                                                                                                                                                                                                          |
| `quality`                        | `object`                        | Public telemetry quality marker. `partial` means one or more non-critical fields were unavailable; raw errors are omitted from public pulse responses.                                                                                                                                                                                                                      |
| `privacy`                        | `object`                        | Public privacy stance and suppressed field list. Exact active watcher totals are public; nonzero supporting metrics below `lowCardinalityThreshold` are suppressed.                                                                                                                                                                                                         |
| `updatedAt`                      | `number`                        | Unix seconds when the pulse payload was produced                                                                                                                                                                                                                                                                                                                            |
| `updatedEverySeconds`            | `number`                        | Cache cadence for consumers that display freshness                                                                                                                                                                                                                                                                                                                          |
| `topCoins`                       | `string[]`                      | Up to five most subscribed coin tickers, ordered by subscription count                                                                                                                                                                                                                                                                                                      |
| `watcherHistory`                 | `array`                         | UTC day buckets. Snapshot-backed points preserve historical active counts and include `snapshotAt`; daily delta fields can be `null` when suppressed. During bootstrap, fallback prefix points use current active watcher created-at aggregation and cumulative active watchers so the public chart keeps all available historical points before fixed snapshots take over. |

Low-cardinality privacy rule: nonzero values below `privacy.lowCardinalityThreshold` are hidden for public daily deltas, pending deliveries, quiet-hours chats, Mini App session/mutation adoption counts, and lifecycle-history delta fields. Consumers should treat `null` as "not publicly shown", not as zero. Mini App denied/replay counters are an explicit exception because they are abuse/health counters; they remain visible when available and are not listed in `privacy.suppressedFields`.

---

### `GET /api/stability-index`

Latest Pharos Stability Index (PSI) sample plus daily history. The PSI is a composite ecosystem health score (0–100) computed from active depeg severity, affected-market breadth, DEWS stress breadth, and 7-day ecosystem trend across the PSI-eligible universe (tracked coins plus shadow assets used for historical continuity). If a dependency failure prevents a safe fresh sample, the endpoint continues serving the last healthy stored PSI sample instead of publishing a degraded substitute.

**Cache:** standard — `X-Data-Age` and `Warning` headers included after at least one PSI sample/history row exists. Before bootstrap, the empty response carries `Cache-Control` only and contains `{ current: null, history: [], methodology: ... }` without `malformedRows`.

**Error responses:** `503` when the canonical current PSI `components` or `input_snapshot` payload is missing or malformed.

**Optional query parameters**

| Param    | Type     | Default | Description                                                                                             |
| -------- | -------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `detail` | `"true"` | —       | When `"true"`, returns full history with persisted per-day component breakdowns instead of last 91 days |

**Response**

```json
{
  "current": {
    "score": 81.1,
    "band": "STEADY",
    "components": { "severity": 4.59, "breadth": 15, "stressBreadth": 1.8, "trend": 0.65 },
    "computedAt": 1771977600,
    "methodologyVersion": "3.2"
  },
  "history": [{ "date": 1771891200, "score": 81.0, "band": "STEADY", "methodologyVersion": "2.1" }],
  "methodology": {
    "version": "3.2",
    "versionLabel": "v3.2",
    "currentVersion": "3.2",
    "currentVersionLabel": "v3.2",
    "changelogPath": "/methodology/stability-index-changelog/",
    "asOf": 1771977600,
    "isCurrent": true
  }
}
```

| Field                          | Type                  | Description                                                                                                                                               |
| ------------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `current`                      | `object \| null`      | Latest PSI score and components. `null` if cron has not yet run                                                                                           |
| `current.score`                | `number`              | PSI score 0–100                                                                                                                                           |
| `current.band`                 | `string`              | Condition band: `"BEDROCK"`, `"STEADY"`, `"TREMOR"`, `"FRACTURE"`, `"CRISIS"`, `"MELTDOWN"`                                                               |
| `current.avg24h`               | `number \| undefined` | Rolling 24 h average PSI score                                                                                                                            |
| `current.avg24hBand`           | `string \| undefined` | Condition band for `avg24h`                                                                                                                               |
| `current.components`           | `object`              | Component breakdown: `severity`, `breadth`, `stressBreadth`, `trend`                                                                                      |
| `current.contributors`         | `array`               | Top per-coin contributors from `input_snapshot.contributors` (empty when unavailable)                                                                     |
| `current.inputDegradation`     | `object \| undefined` | Dependency-loss metadata carried by the served sample when the stored input snapshot recorded degraded upstream inputs                                    |
| `current.totalMcapUsd`         | `number`              | Total ecosystem market cap from the latest input snapshot (`0` when unavailable)                                                                          |
| `current.computedAt`           | `number`              | Unix seconds of computation                                                                                                                               |
| `current.methodologyVersion`   | `string`              | Methodology version used to compute the current score                                                                                                     |
| `history`                      | `array`               | Historical scores, newest first. With `detail=true`, persisted rows include `components`; the synthesized current-day running-average point may omit them |
| `malformedRows`                | `number`              | Count of historical rows dropped from `detail=true` because persisted `components` JSON was malformed                                                     |
| `history[].methodologyVersion` | `string`              | Methodology version used for that history point                                                                                                           |
| `methodology`                  | `object`              | Version metadata for current PSI methodology context                                                                                                      |
| `methodology.version`          | `string`              | Methodology version used by current score                                                                                                                 |
| `methodology.changelogPath`    | `string`              | Relative path to full methodology changelog                                                                                                               |

---

### `GET /api/og/*` / `HEAD /api/og/*`

Dynamic Open Graph PNG images used by share buttons and page metadata.

**Authentication:** exempt

**Supported routes**

- `/api/og/stablecoin/:id`
- `/api/og/safety-scores`
- `/api/og/depeg`
- `/api/og/stability-index`

**Content-Type:** `image/png`

**Cache:** `public, max-age=900, s-maxage=900`

**Error cases**

- `404` with `text/plain` for unknown coin IDs inside `/api/og/stablecoin/:id`; unknown OG route patterns return the standard JSON `{ "error": "Unknown OG route" }`
- `503` when required cached data is not yet available
- `400` for malformed URI encoding in `/api/og/stablecoin/:id`
- `500` with `text/plain` body when OG image rendering fails

`/api/og/stablecoin/:id` accepts tracked public stablecoin IDs only. The renderer assembles each card from cached stablecoin, DEWS, PSI, report-card, depeg, liquidity, and mint/burn data on the worker.

---

### `GET /api/report-cards`

Stablecoin risk grade cards with dimension-level scores. Output includes 5 dimensions; overall score is the weighted base (exit-liquidity/resilience/decentralization/dependency) plus peg-multiplier adjustment.

**Cache:** standard

**Response**

```text
{
  "cards": [ReportCard, ...],
  "dependencyGraph": {
    "edges": [{ "from": "usdc-circle", "to": "usde-ethena", "weight": 0.9, "type": "collateral" }, ...]
  },
  "methodology": {
    "version": "7.13",
    "weights": { "pegStability": 0, "liquidity": 0.30, "resilience": 0.20, "decentralization": 0.15, "dependencyRisk": 0.25 },
    "pegMultiplierExponent": 0.4,
    "activeDepegSeveritySource": "open-event-peak",
    "activeDepegCaps": {
      "d": { "thresholdBps": 1000, "score": 49 },
      "f": { "thresholdBps": 2500, "score": 39 }
    },
    "thresholds": [{ "grade": "A+", "min": 87 }, { "grade": "A", "min": 83 }, ...]
  },
  "liquidityStale": false,
  "redemptionStale": false,
  "inputFreshness": {
    "dexLiquidity": { "updatedAt": 1771977600, "ageSeconds": 120, "stale": false },
    "redemptionBackstops": { "updatedAt": 1771977600, "ageSeconds": 300, "stale": false }
  },
  "collateralDriftCoins": [{ "id": "jupusd-jupiter", "liveScore": 80, "curatedScore": 65, "delta": 15 }],
  "liveToFallbackCoins": ["usdaf-asymmetry"],
  "updatedAt": 1771977600
}
```

The Liquidity dimension now represents `effectiveExitScore`: the public DEX liquidity score remains the floor, while redeemable assets can receive uplift from `redemptionBackstopScore` when a meaningful direct exit path exists. Documented offchain issuer exits with eventual-only capacity can add only a DEX-gated primary-market bonus; they do not replace missing DEX liquidity. Last-known DEX liquidity remains usable even after its freshness runway, with staleness surfaced through `liquidityStale` and `inputFreshness.dexLiquidity.stale`. Low-confidence redemption routes stay visible but do not uplift the score, and materially stale redemption inputs are not blended. Report-card redemption inputs are treated as materially stale after more than twice the 4-hourly redemption sync cadence, so normal cron lag does not globally remove medium- or high-confidence redemption uplift.

When present, `collateralDriftCoins` lists live-reserve scoring deltas that exceed the reserve-drift threshold, and `liveToFallbackCoins` lists live-reserve-enabled assets whose report card used curated fallback reserves because no scoring-eligible live snapshot was available.

For peg handling, `rawInputs.pegScore` is the effective peg input used by report-card scoring. Most coins use their direct peg-summary value. Configured NAV wrappers can inherit peg stability from a referenced base stablecoin when the wrapper share price is not the right peg-tracking surface; pure NAV tokens without a configured reference remain `null` and keep neutral handling. `rawInputs.activeDepegBps` is the open active depeg event's absolute peak deviation used for final Safety Score caps; it is not the latest spot deviation.

`GET /api/report-cards` normally serves the full report-card payload from the private `report-cards:snapshot` cache envelope published by `publish-report-card-cache`. That envelope pins the expected cache generation and Safety Score methodology version; compute-on-read is used when the published snapshot is missing, malformed, generation-mismatched, or methodology-mismatched. The published envelope is also the preferred Safety Score source for yield hydration, while the smaller `report_card_cache` score map remains available for lightweight Chain Health/OG consumers.

Report-card generation treats the stablecoins cache and readable redemption-backstop table as hard dependencies. The stablecoins cache is read in published-contract mode, so malformed cached objects that fail `StablecoinListResponseSchema` validation fail closed instead of being partially filtered for scoring. DEX liquidity, bluechip ratings, live-reserve inputs, and materially stale redemption rows are soft dependencies: if one of those loaders is temporarily unavailable or stale beyond its scoring freshness runway, generation continues with a degraded snapshot instead of failing closed, with stale inputs suppressed from scoring.

**`dependencyGraph.edges`**: Pre-computed forward edges. `from` = upstream stablecoin ID, `to` = dependent stablecoin ID. `weight` and `type` carry the worker's canonical dependency metadata, so frontend graph consumers can use the snapshot directly instead of re-deriving edge semantics from static stablecoin metadata.

**`ReportCard`**

| Field                  | Type                                   | Description                                                                          |
| ---------------------- | -------------------------------------- | ------------------------------------------------------------------------------------ |
| `id`                   | `string`                               | Canonical Pharos stablecoin ID for live cards; stable cemetery ID for defunct cards  |
| `name`                 | `string`                               | Full name                                                                            |
| `symbol`               | `string`                               | Ticker                                                                               |
| `overallGrade`         | `string`                               | Letter grade: `"A+"` through `"F"`, or `"NR"`                                        |
| `overallScore`         | `number \| null`                       | Weighted score 0–100. `null` for unrated coins                                       |
| `baseScore`            | `number \| null`                       | Pre-peg-multiplier/no-liquidity/active-depeg-cap score after base dimension blending |
| `overallCapped`        | `boolean`                              | `true` when the card is capped at a tracked parent stablecoin's overall score        |
| `uncappedOverallScore` | `number \| null`                       | Post-dimension/post-peg score before the tracked-parent overall cap, when applicable |
| `dimensions`           | `Record<DimensionKey, DimensionScore>` | Per-dimension grade, score, and detail text                                          |
| `ratedDimensions`      | `number`                               | Number of dimensions with data (max 5)                                               |
| `rawInputs`            | `RawDimensionInputs`                   | Raw scoring inputs for client-side grade recomputation (stress testing)              |
| `isDefunct`            | `boolean`                              | `true` for cemetery coins (permanent F grade)                                        |

**`DependencyWeight`**: `{ id: string, weight: number, type?: DependencyType }` — upstream stablecoin ID + fraction of collateral from that source (0–1), with optional dependency category. When total dependency weight is ≤ 1.0, the remainder represents non-stablecoin collateral; when declared dependency weight exceeds 1.0, dependency scoring normalizes by raw total and uses no self-backed remainder.

**`RawDimensionInputs`**

| Field                              | Type                                                                                        |
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `pegScore`                         | `number \| null`                                                                            |
| `activeDepeg`                      | `boolean`                                                                                   |
| `activeDepegBps`                   | `number \| null`                                                                            |
| `depegEventCount`                  | `number`                                                                                    |
| `lastEventAt`                      | `number \| null`                                                                            |
| `liquidityScore`                   | `number \| null`                                                                            |
| `effectiveExitScore`               | `number \| null`                                                                            |
| `redemptionBackstopScore`          | `number \| null`                                                                            |
| `redemptionRouteFamily`            | `RedemptionRouteFamily \| null`                                                             |
| `redemptionModelConfidence`        | `"high" \| "medium" \| "low" \| null`                                                       |
| `redemptionUsedForLiquidity`       | `boolean`                                                                                   |
| `redemptionImmediateCapacityUsd`   | `number \| null`                                                                            |
| `redemptionImmediateCapacityRatio` | `number \| null`                                                                            |
| `concentrationHhi`                 | `number \| null`                                                                            |
| `bluechipGrade`                    | `BluechipGrade \| null`                                                                     |
| `canBeBlacklisted`                 | `boolean \| "possible" \| "inherited"`                                                       |
| `chainTier`                        | `ChainTier`                                                                                 |
| `deploymentModel`                  | `DeploymentModel`                                                                           |
| `collateralQuality`                | `CollateralQuality`                                                                         |
| `custodyModel`                     | `CustodyModel`                                                                              |
| `governanceTier`                   | `GovernanceType`                                                                            |
| `governanceQuality`                | `GovernanceQuality`                                                                         |
| `dependencies`                     | `DependencyWeight[]`                                                                        |
| `variantParentId`                  | `string \| null`                                                                            |
| `variantKind`                      | `"savings-passthrough" \| "strategy-vault" \| "risk-absorption" \| "bond-maturity" \| null` |
| `navToken`                         | `boolean`                                                                                   |
| `collateralFromLive`               | `boolean`                                                                                   |

`rawInputs.canBeBlacklisted` is the canonical resolved blacklist status used by report-card-backed product surfaces. It can therefore differ from the raw `StablecoinMeta.canBeBlacklisted` override field, which only carries manual metadata and never stores computed `"inherited"` values. Product labels map the wire values to the four-status model: `true` -> `Yes`, `"inherited"` -> `Upstream`, `"possible"` -> `Possible`, and `false` -> `No`. Admin mint authority is reviewed separately in Mint Authority and is not a FreezeWatch status. `"inherited"` / Upstream can be produced by any reserve, backing, custody, parent-asset, or CEX/custody-rail exposure; it does not require a majority reserve weight.

`rawInputs.collateralFromLive` is true when score-grade live reserve data drove collateral scoring for the card.

**Dimensions:** `pegStability`, `liquidity`, `resilience`, `decentralization`, `dependencyRisk`

---

### `GET /api/redemption-backstops`

Current redemption-backstop dataset for redeemable assets.

**Cache:** standard

**Error responses:** `503` when `redemption_backstop` has no rows yet, or when the current snapshot cannot be read cleanly.

Rows written by the current worker are grouped by a completed snapshot run manifest. The API serves the latest valid completed run when one exists, which prevents a partially written hourly sync from being treated as a fresh complete dataset. If the newest completed manifest is incomplete or its rows are unreadable, the reader tries recent earlier completed runs before returning `503`. If no completed manifest exists but the manifest table has run records and the current table contains rows with a non-null `snapshot_run_id`, the reader returns `503` instead of treating those partial manifested rows as legacy data. Legacy rows without a completed run remain readable during bootstrap and migration fallback only when the current table has no manifested rows.

**Response**

```json
{
  "coins": {
    "cusd-cap": {
      "stablecoinId": "cusd-cap",
      "score": 88,
      "effectiveExitScore": 56,
      "dexLiquidityScore": 29,
      "accessScore": 100,
      "settlementScore": 100,
      "executionCertaintyScore": 80,
      "capacityScore": 100,
      "outputAssetQualityScore": 80,
      "costScore": 40,
      "routeFamily": "basket-redeem",
      "accessModel": "permissionless-onchain",
      "settlementModel": "atomic",
      "executionModel": "deterministic-basket",
      "outputAssetType": "stable-basket",
      "provider": "supply-full-model",
      "immediateCapacityUsd": null,
      "immediateCapacityRatio": null,
      "sourceMode": "estimated",
      "resolutionState": "resolved",
      "routeStatus": "open",
      "routeStatusSource": "static-config",
      "holderEligibility": "any-holder",
      "capacityConfidence": "heuristic",
      "capacitySemantics": "eventual-only",
      "capacityKind": "documented-eventual",
      "freshnessKind": "reviewed-static",
      "sourceTimestamp": 1773350300,
      "sourceUrls": ["https://example.com/redemption-source"],
      "settlementDelaySec": 86400,
      "queueDepthUsd": 12000000,
      "dailyLimitUsd": 5000000,
      "minRedeemUsd": 100000,
      "liveHolderEligibility": "any-holder",
      "feeConfidence": "undisclosed-reviewed",
      "feeModelKind": "undisclosed-reviewed",
      "modelConfidence": "low",
      "feeBps": null,
      "queueEnabled": false,
      "updatedAt": 1773350400,
      "methodologyVersion": "4.06"
    }
  },
  "methodology": {
    "version": "4.06",
    "versionLabel": "v4.06",
    "currentVersion": "4.06",
    "currentVersionLabel": "v4.06",
    "changelogPath": "/methodology/#safety-scores-methodology",
    "asOf": 1773350400,
    "isCurrent": true,
    "componentWeights": {
      "access": 0.2,
      "settlement": 0.15,
      "executionCertainty": 0.15,
      "capacity": 0.25,
      "outputAssetQuality": 0.15,
      "cost": 0.1
    },
    "effectiveExitModel": {
      "model": "best-path",
      "diversificationFactor": 0.1,
      "modeledExitSize": {
        "supplyRatio": 0.05,
        "floorUsd": 100000,
        "capUsd": 25000000
      },
      "capacityFactor": {
        "formula": "min(1, currentExecutableCapacityUsd / modeledExitSizeUsd)",
        "missingCapacityBehavior": "unbounded"
      },
      "confidenceFactors": {
        "high": 1,
        "medium": 0.75,
        "low": 0.35
      },
      "diversificationPolicy": "Only independent issuer rails receive the secondary-path diversification bonus in v4 snapshots."
    },
    "routeFamilyCaps": {
      "queueRedeem": 70,
      "offchainIssuer": 65
    }
  },
  "updatedAt": 1773350400
}
```

`score` is the direct redemption-quality score.

`effectiveExitScore` is the raw best-path exit score written into the redemption snapshot when the route resolved cleanly and is not currently impaired. It reuses last-known DEX liquidity even when the DEX input is stale; the cron records that operational state through `metadata.liquidityStale`. In v4, redemption contribution is adjusted by `capacityFactor = min(1, currentExecutableCapacityUsd / modeledExitSizeUsd)` and by route confidence (`high = 1`, `medium = 0.75`, `low = 0.35`) before the best-path blend. `modeledExitSizeUsd` is `min(max(supplyUsd × 0.05, 100000), 25000000)`. When both DEX liquidity and adjusted redemption exist, only independent issuer rails receive the 10% secondary-path diversification bonus. Report cards may still recompute liquidity from the same underlying redemption score with additional confidence, eligibility, and active-depeg gating, so this raw endpoint value can differ numerically from `dimensions.liquidity.score`.

`methodology.version` is attributed from the latest completed redemption snapshot run, falling back to the latest stored row for legacy snapshots. `methodology.currentVersion` remains the live code version when the API is serving an older snapshot that has not yet been recomputed.

`sourceMode`:

- `dynamic` = live reserve/protocol telemetry
- `estimated` = modelled from current supply and conservative route assumptions
- `static` = route remains configured, but current runtime inputs did not resolve a usable score

`resolutionState`:

- `resolved` = the route produced a usable score
- `missing-cache` = the stablecoins snapshot did not include the asset or its current supply
- `missing-capacity` = the route is configured, but the snapshot could not resolve enough capacity to score it
- `failed` = a route-specific resolver failed
- `impaired` = the route shape is known but current market or route-availability evidence contradicts broad par redemption

`routeStatus` / `routeStatusSource` describe current route availability separately from the static route shape. Normal rows use `routeStatus: "open"` and `routeStatusSource: "static-config"`. A severe active depeg (`>=2500 bps`) can publish `routeStatus: "degraded"` and `routeStatusSource: "market-implied"` for static or non-live-direct routes; those impaired rows have `score = null`, `effectiveExitScore = null`, and `modelConfidence = "low"`. `holderEligibility` describes the modeled holder cohort, such as `any-holder`, `verified-customer`, `whitelisted-primary`, `pre-incident-holder`, `issuer-discretionary`, or `unknown`.

For v4-compatible snapshots, route-status and capacity telemetry remain part of the four-hour `sync-redemption-backstops` snapshot. The worker does not fetch a separate real-time route-status feed during this sync; route status comes from live-reserve adapter metadata, static reviewed policy, and market-implied severe-depeg overlays.

Top-level fields:

| Field         | Type                                      | Description                                                                                                |
| ------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `coins`       | `Record<string, RedemptionBackstopEntry>` | Current snapshot keyed by Pharos stablecoin ID                                                             |
| `methodology` | `object`                                  | Version metadata plus component weights, effective-exit blend weights, and route-family caps               |
| `updatedAt`   | `number`                                  | Freshest `updated_at` timestamp for the served completed run, or freshest current row for legacy snapshots |

`RedemptionBackstopEntry` highlights:

| Field                        | Type                                                                                                                                                       | Description                                                                                                                                                                                                                   |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `score`                      | `number \| null`                                                                                                                                           | Direct redemption-quality score after route-family/config caps                                                                                                                                                                |
| `effectiveExitScore`         | `number \| null`                                                                                                                                           | Blended exit score used by report cards                                                                                                                                                                                       |
| `dexLiquidityScore`          | `number \| null`                                                                                                                                           | DEX liquidity input used in the blend                                                                                                                                                                                         |
| `routeFamily`                | `string`                                                                                                                                                   | `stablecoin-redeem`, `basket-redeem`, `collateral-redeem`, `psm-swap`, `queue-redeem`, or `offchain-issuer`                                                                                                                   |
| `accessModel`                | `string`                                                                                                                                                   | `permissionless-onchain`, `whitelisted-onchain`, `issuer-api`, or `manual`                                                                                                                                                    |
| `settlementModel`            | `string`                                                                                                                                                   | `atomic`, `immediate`, `same-day`, `days`, or `queued`                                                                                                                                                                        |
| `outputAssetType`            | `string`                                                                                                                                                   | `stable-single`, `stable-basket`, `bluechip-collateral`, `mixed-collateral`, or `nav`                                                                                                                                         |
| `sourceMode`                 | `string`                                                                                                                                                   | `dynamic`, `estimated`, or `static` capacity provenance                                                                                                                                                                       |
| `resolutionState`            | `string`                                                                                                                                                   | `resolved`, `missing-cache`, `missing-capacity`, `failed`, or `impaired`                                                                                                                                                      |
| `routeStatus`                | `string`                                                                                                                                                   | Current route availability: `open`, `degraded`, `paused`, `cohort-limited`, or `unknown`                                                                                                                                      |
| `routeStatusSource`          | `string`                                                                                                                                                   | Source for current route availability: `static-config`, `market-implied`, `operator-notice`, `protocol-api`, or `onchain`                                                                                                     |
| `routeStatusReason`          | `string \| undefined`                                                                                                                                      | Human-readable explanation when current availability impairs scoring                                                                                                                                                          |
| `routeStatusReviewedAt`      | `string \| undefined`                                                                                                                                      | UTC date (`YYYY-MM-DD`) for the current route-status assessment                                                                                                                                                               |
| `holderEligibility`          | `string`                                                                                                                                                   | Modeled holder cohort: `any-holder`, `verified-customer`, `whitelisted-primary`, `pre-incident-holder`, `issuer-discretionary`, or `unknown`                                                                                  |
| `capacityConfidence`         | `string`                                                                                                                                                   | `live-direct`, `live-proxy`, `documented-bound`, `heuristic`, or legacy `dynamic` fidelity tag for the capacity model                                                                                                         |
| `capacityBasis`              | `string \| undefined`                                                                                                                                      | Typed basis for the modeled capacity, such as `issuer-term-redemption`, `full-system-eventual`, `psm-balance-share`, `strategy-buffer`, `hot-buffer`, `daily-limit`, `live-direct-telemetry`, or `live-proxy-buffer`          |
| `capacitySemantics`          | `string`                                                                                                                                                   | `immediate-bounded` or `eventual-only`, distinguishing current redeemable buffer from eventual redeemability                                                                                                                  |
| `capacityProfile`            | `object \| undefined`                                                                                                                                      | Optional v4 capacity profile separating immediate, daily, queued, eventual, and scoring capacity with a `scoringHorizon` and `capacityProfileConfidence`                                                                      |
| `capacityKind`               | `string \| undefined`                                                                                                                                      | Optional adapter-declared live evidence shape, such as `live-direct-bounded`, `live-queue`, `live-proxy-validated`, `documented-bound`, `documented-eventual`, or `heuristic`. Context only; not Safety eligibility by itself |
| `freshnessKind`              | `string \| undefined`                                                                                                                                      | Optional adapter-declared redemption freshness evidence, such as `verified-source-timestamp`, `same-run-onchain`, `same-run-api`, `reviewed-static`, or `unverified`                                                          |
| `confidenceDetails`          | `object \| undefined`                                                                                                                                      | Optional v4 confidence rollup dimensions for capacity evidence, fee evidence, route-status freshness, holder-cohort breadth, and source quality                                                                               |
| `sourceTimestamp`            | `number \| undefined`                                                                                                                                      | Optional source timestamp emitted by a live reserve adapter for the redemption telemetry                                                                                                                                      |
| `sourceUrls`                 | `string[] \| undefined`                                                                                                                                    | Optional source URLs emitted by a live reserve adapter for the redemption telemetry                                                                                                                                           |
| `settlementDelaySec`         | `number \| undefined`                                                                                                                                      | Optional adapter-emitted settlement delay constraint in seconds                                                                                                                                                               |
| `queueDepthUsd`              | `number \| undefined`                                                                                                                                      | Optional adapter-emitted queued redemption depth in USD                                                                                                                                                                       |
| `dailyLimitUsd`              | `number \| undefined`                                                                                                                                      | Optional adapter-emitted daily redemption limit in USD                                                                                                                                                                        |
| `minRedeemUsd`               | `number \| undefined`                                                                                                                                      | Optional adapter-emitted minimum redemption size in USD                                                                                                                                                                       |
| `liveHolderEligibility`      | `string \| undefined`                                                                                                                                      | Optional adapter-emitted holder eligibility context when it differs from or sharpens the static model                                                                                                                         |
| `eventualRedeemabilityScore` | `number \| null \| undefined`                                                                                                                              | Optional v4 long-tail legal/protocol redeemability score, separate from current executable exit capacity                                                                                                                      |
| `feeConfidence`              | `string`                                                                                                                                                   | `fixed`, `formula`, or `undisclosed-reviewed` fidelity tag for the fee model                                                                                                                                                  |
| `feeModelKind`               | `string`                                                                                                                                                   | `fixed-bps`, `formula`, `documented-variable`, or `undisclosed-reviewed`                                                                                                                                                      |
| `modelConfidence`            | `string`                                                                                                                                                   | Overall route-fidelity rollup: `high`, `medium`, or `low`                                                                                                                                                                     |
| `immediateCapacityUsd`       | `number \| null`                                                                                                                                           | Immediate redeemable capacity in USD. `null` when the model is eventual-only or currently unrated                                                                                                                             |
| `immediateCapacityRatio`     | `number \| null`                                                                                                                                           | Immediate redeemable capacity as a share of supply. `null` when not separately quantified                                                                                                                                     |
| `feeBps`                     | `number \| null`                                                                                                                                           | Explicit bounded fee when configured                                                                                                                                                                                          |
| `feeDescription`             | `string \| undefined`                                                                                                                                      | Docs-backed fee description for variable, conditional, flat-minimum, or undisclosed redemption schedules                                                                                                                      |
| `costScenarioScores`         | `object \| undefined`                                                                                                                                      | Optional v4 cost scores for retail, active-user, and institutional route-size scenarios                                                                                                                                       |
| `routeExitCorrelation`       | `string \| undefined`                                                                                                                                      | Optional v4 correlation tag for DEX-vs-redemption independence, such as `independent-issuer-rail`, `same-stablecoin-pool-backing`, `same-protocol-liquidity`, `wrapper-to-parent-dependency`, or `unknown`                    |
| `queueEnabled`               | `boolean`                                                                                                                                                  | Whether the modeled route is explicitly queued/serial                                                                                                                                                                         |
| `docs`                       | `{ label?: string, url?: string, reviewedAt?: string, provenance?: string, sources?: { label: string, url: string, supports?: string[] }[] } \| undefined` | Optional documentation / transparency metadata. `reviewedAt` is the route-review date, while `provenance` is `config-reviewed`, `live-reserve-display`, `proof-of-reserves`, or `preferred-link`                              |
| `notes`                      | `string[] \| undefined`                                                                                                                                    | Runtime notes such as stale reserve metadata fallback                                                                                                                                                                         |
| `capsApplied`                | `string[] \| undefined`                                                                                                                                    | Applied score caps (`queue-route-cap`, `offchain-route-cap`, `config-cap`)                                                                                                                                                    |

**Response (503):** `{ "error": "Data not yet available" }` or `{ "error": "Redemption backstop snapshot unavailable" }`

---

### `GET /api/safety-score-history`

Per-coin Safety Score grade transition history (seed row + grade changes only). Rows are written by the daily `snapshot-safety-grade-history` cron and returned in ascending date order.

**Cache:** slow

**Required query parameter**

| Param        | Type     | Description                     |
| ------------ | -------- | ------------------------------- |
| `stablecoin` | `string` | Pharos stablecoin ID (required) |

**Optional query parameters**

| Param  | Type      | Default | Bounds | Description             |
| ------ | --------- | ------- | ------ | ----------------------- |
| `days` | `integer` | `365`   | 1–3650 | Lookback window in days |

**Response:** Array sorted by `date` ascending.

```json
[
  {
    "date": 1771977600,
    "grade": "B+",
    "score": 78,
    "prevGrade": "B",
    "prevScore": 74,
    "methodologyVersion": "5.5"
  }
]
```

| Field                | Type             | Description                                               |
| -------------------- | ---------------- | --------------------------------------------------------- |
| `date`               | `number`         | UTC day bucket (Unix seconds) when the event was recorded |
| `grade`              | `string`         | Current Safety Score letter grade at `date`               |
| `score`              | `number \| null` | Current numeric score (0–100); `null` when grade is `NR`  |
| `prevGrade`          | `string \| null` | Previous grade before this event; `null` for the seed row |
| `prevScore`          | `number \| null` | Previous score before this event; `null` for the seed row |
| `methodologyVersion` | `string`         | Safety Score methodology version used for this event row  |

---

### `GET /api/yield-rankings`

Cache-backed yield rankings written by the `sync-yield-data` cron. The endpoint rehydrates `safetyScore`, `safetyGrade`, `yieldToRisk`, and `pharosYieldScore` from the cron-published report-card snapshot so Yield Intelligence stays aligned with `/api/report-cards` without rebuilding the full Safety Score envelope on every read. Compute-on-read is used only when the published snapshot is unavailable. Royco Dawn structured-tranche rows use the attached underlying asset's report-card snapshot as input, then expose an opportunity-level tranche Safety Score in the yield row. PYS v8 is benchmark-aware and source-risk-aware: it starts from cached APY inputs, adds a weighted slice of the row's benchmark spread, divides by the nested `sourceRisk.sourceRiskPenalty` populated from measured source evidence, then applies the current Safety Score and volatility multiplier. Missing source-risk evidence is neutral. The response also includes source-selection provenance, the default USD benchmark (`riskFreeRate`), and the structured benchmark registry used for row-level excess-yield selection. If a ranking row has no matching live report-card snapshot, the API now retains the row and falls back to `DEFAULT_SAFETY_SCORE` (`40`) and grade `NR` instead of dropping coverage.

**Cache:** standard — `X-Data-Age` and `Warning` headers included. Freshness threshold: 3600 s (1 hour, aligned to the hourly `sync-yield-data` publisher).

**Error responses:** `503` when the cached rankings payload is missing, unparseable JSON, or parseable JSON that fails the `YieldRankingsResponseSchema` cache-read validation. Schema-invalid cached objects are not served because the endpoint cannot safely hydrate or trust their row shape.

**Response**

```text
{
  "rankings": [YieldRanking, ...],
  "riskFreeRate": 4.25,
  "benchmarks": {
    "USD": { "key": "USD", "label": "USD 3M T-Bill", "currency": "USD", "rate": 4.25, "recordDate": "2026-03-25", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "fred-dgs3mo", "isFallback": false, "fallbackMode": null, "isProxy": false },
    "EUR": { "key": "EUR", "label": "EUR 3M compounded €STR", "currency": "EUR", "rate": 1.9358, "recordDate": "2026-03-26", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "ecb-estr-3m", "isFallback": false, "fallbackMode": null, "isProxy": false },
    "CHF": { "key": "CHF", "label": "CHF 3M compounded SARON", "currency": "CHF", "rate": -0.0539, "recordDate": "2026-03-25", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "six-sar3mc", "isFallback": false, "fallbackMode": null, "isProxy": false }
  },
  "scalingFactor": 8,
  "medianApy": 4.21,
  "updatedAt": 1772000000,
  "provenance": {
    "selectionMethod": "confidence-weighted",
    "benchmark": { "key": "USD", "label": "USD 3M T-Bill", "currency": "USD", "rate": 4.25, "recordDate": "2026-03-25", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "fred-dgs3mo", "isFallback": false, "fallbackMode": null, "isProxy": false },
    "benchmarks": {
      "USD": { "key": "USD", "label": "USD 3M T-Bill", "currency": "USD", "rate": 4.25, "recordDate": "2026-03-25", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "fred-dgs3mo", "isFallback": false, "fallbackMode": null, "isProxy": false },
      "EUR": { "key": "EUR", "label": "EUR 3M compounded €STR", "currency": "EUR", "rate": 1.9358, "recordDate": "2026-03-26", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "ecb-estr-3m", "isFallback": false, "fallbackMode": null, "isProxy": false },
      "CHF": { "key": "CHF", "label": "CHF 3M compounded SARON", "currency": "CHF", "rate": -0.0539, "recordDate": "2026-03-25", "fetchedAt": 1774425600, "ageSeconds": 0, "source": "six-sar3mc", "isFallback": false, "fallbackMode": null, "isProxy": false }
    },
    "dlPools": { "mode": "dex-cache", "ageSeconds": 240, "poolCount": 812 },
    "safetySnapshot": { "kind": "ok", "coverageRatio": 0.98 }
  },
  "warnings": [],
  "publication": {
    "generationId": "yield-1772000000",
    "updatedAt": 1772000000,
    "cutoffAt": 1772000000,
    "schemaVersion": 1,
    "status": "published"
  },
  "methodology": {
    "version": "8.19",
    "currentVersion": "8.19",
    "changelogPath": "/methodology/yield-changelog/"
  },
  "_meta": { "updatedAt": 1710500000, "ageSeconds": 42, "status": "fresh" }
}
```

| Field           | Type                     | Description                                                                                                                                                                      |
| --------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rankings`      | `YieldRanking[]`         | All ranked stablecoins, sorted by Pharos Yield Score descending                                                                                                                  |
| `riskFreeRate`  | `number`                 | Default USD benchmark rate (%) retained for backward compatibility and mixed-view fallback                                                                                       |
| `benchmarks`    | `object \| null`         | Benchmark registry keyed by currency (`USD`, `EUR`, `CHF`, plus v8.13: `GBP`, `JPY`, `MXN`, `BRL`, `AUD`, `CAD`; `SGD` is reserved without a fetcher) with label, rate, freshness, fallback, and proxy metadata |
| `scalingFactor` | `number`                 | Scaling factor applied in yield score computation                                                                                                                                |
| `medianApy`     | `number`                 | TVL-weighted median APY (30d) across best-source rows, used as a peer reference in warning heuristics                                                                            |
| `updatedAt`     | `number`                 | Unix seconds when the rankings were last computed                                                                                                                                |
| `provenance`    | `object \| null`         | Snapshot-level provenance for default benchmark freshness, full benchmark registry, DeFiLlama pool input freshness, safety coverage, and selection method                        |
| `warnings`      | `YieldResponseWarning[]` | Optional body-level warnings for degraded but still served payloads, such as live safety hydration gaps. Clients should surface these separately from row-level `warningSignals` |
| `publication`   | `object \| null`         | Optional publication metadata for generation-aware payloads; omitted on legacy payloads                                                                                          |
| `methodology`   | `object \| undefined`    | Optional Yield Intelligence methodology envelope for rankings payloads when emitted by the publisher                                                                             |

Optional v8 fields are nullable and omittable. Publication-generation fields are populated by the generation-aware publisher when available; legacy rows and old payloads may still omit them. Public source-risk values are nested under the `sourceRisk` object; flattened top-level fields such as `sourceRiskPenalty` or `rewardShare` are not part of the public API contract.

| Field                             | Surface                     | Type                                                                                                                                                                                      | Population status                                                                                                                                       |
| --------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publication.generationId`        | rankings/history root       | `string \| null \| undefined`                                                                                                                                                             | Publisher generation identifier, e.g. `yield-1774526400`; omitted on legacy payloads                                                                    |
| `publication.updatedAt`           | rankings/history root       | `number \| null \| undefined`                                                                                                                                                             | Unix seconds when the generation was computed                                                                                                           |
| `publication.cutoffAt`            | rankings/history root       | `number \| null \| undefined`                                                                                                                                                             | Latest history timestamp approved for public reads                                                                                                      |
| `publication.schemaVersion`       | rankings/history root       | `number \| null \| undefined`                                                                                                                                                             | Payload-generation schema version                                                                                                                       |
| `publication.status`              | rankings/history root       | `"staged" \| "published" \| "failed" \| null \| undefined`                                                                                                                                | Public cache payloads should expose `published`; failed or CAS-skipped generations do not replace current `yield_data` rows                             |
| `warnings[]`                      | rankings root               | `YieldResponseWarning[] \| undefined`                                                                                                                                                     | Body-level degraded-response advisories; row source warnings remain in `warningSignals`                                                                 |
| `publicationGenerationId`         | ranking/history rows        | `string \| null \| undefined`                                                                                                                                                             | Row-to-generation join identifier; `null` on legacy rows                                                                                                |
| `publishedRank`                   | ranking rows                | `integer >= 1 \| null \| undefined`                                                                                                                                                       | Stable rank from the published cache order before live Safety Score hydration                                                                           |
| `liveRank`                        | ranking rows                | `integer >= 1 \| null \| undefined`                                                                                                                                                       | Post-hydration rank assigned after live Safety Score recomputation                                                                                      |
| `sourceRisk.sourceRiskScore`      | ranking/history/source rows | `0..100 number \| null \| undefined`                                                                                                                                                      | Optional source-risk score when populated by the source-risk worker                                                                                     |
| `sourceRisk.sourceRiskPenalty`    | ranking/history/source rows | `number >= 1 \| null \| undefined`                                                                                                                                                        | Active PYS v8 source-risk multiplier derived from reliable source evidence. Missing/invalid values are neutral (`1`); runtime clamps values to `1..2.5` |
| `sourceRisk.sourceDepthRatio`     | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Optional venue-depth ratio                                                                                                                              |
| `sourceRisk.rewardShare`          | ranking/history/source rows | `0..1 number \| null \| undefined`                                                                                                                                                        | Optional reward APY share                                                                                                                               |
| `sourceRisk.sourceAgeSeconds`     | ranking/history/source rows | `integer seconds >= 0 \| null \| undefined`                                                                                                                                               | Optional source-observation age                                                                                                                         |
| `sourceRisk.observationCount30d`  | ranking/history/source rows | `integer >= 0 \| null \| undefined`                                                                                                                                                       | Optional 30-day observation count for the source                                                                                                        |
| `sourceRisk.sourceSwitchCount30d` | ranking/history/source rows | `integer >= 0 \| null \| undefined`                                                                                                                                                       | Optional 30-day selected-source switch count                                                                                                            |
| `sourceRisk.deploymentPlace`      | ranking/history/source rows | `"native-wrapper" \| "issuer-savings" \| "lending-market" \| "strategy-vault" \| "structured-tranche" \| "lp-or-dex" \| "rwa-fund" \| "reward-program" \| "rate-derived" \| "price-derived" \| null \| undefined` | Optional sourced deployment-place label                                                                                                                 |
| `sourceRisk.venueProtocol`        | ranking/history/source rows | `string \| null \| undefined`                                                                                                                                                             | Optional venue protocol label                                                                                                                           |
| `sourceRisk.venueChain`           | ranking/history/source rows | `string \| null \| undefined`                                                                                                                                                             | Optional venue chain label                                                                                                                              |
| `sourceRisk.venueRiskTier`        | ranking/history/source rows | `"low" \| "medium" \| "high" \| "unknown" \| null \| undefined`                                                                                                                           | Optional sourced venue tier; unknown remains neutral                                                                                                    |
| `sourceRisk.investabilityFlags`   | ranking/history/source rows | `string[] \| undefined`                                                                                                                                                                   | Optional investability caveats                                                                                                                          |
| `sourceRisk.trancheSide`          | ranking/history/source rows | `"senior" \| "junior" \| null \| undefined`                                                                                                                                               | Royco Dawn tranche side for structured-tranche rows                                                                                                     |
| `sourceRisk.trancheSafetyScore`   | ranking/history/source rows | `0..100 number \| null \| undefined`                                                                                                                                                      | Opportunity-level Safety Score used by Royco Dawn tranche rows after underlying-score and tranche-risk adjustments                                      |
| `sourceRisk.trancheSafetyPenalty` | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Difference between the underlying report-card Safety Score and the final tranche Safety Score                                                           |
| `sourceRisk.underlyingSafetyScore` | ranking/history/source rows | `0..100 number \| null \| undefined`                                                                                                                                                     | Current underlying report-card Safety Score input used for opportunity-level tranche scoring                                                            |
| `sourceRisk.marketCoverageRatio`  | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Current Royco market coverage ratio                                                                                                                     |
| `sourceRisk.marketMinCoverageRatio` | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                      | Royco market minimum required coverage ratio                                                                                                            |
| `sourceRisk.marketUtilizationRatio` | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                      | Current Royco market utilization ratio                                                                                                                  |
| `sourceRisk.marketUtilizationLimitRatio` | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                               | Royco market utilization target or limit ratio when supplied                                                                                            |
| `sourceRisk.marketDrawdownRatio`  | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Current Royco market drawdown ratio                                                                                                                     |
| `sourceRisk.marketTotalDrawdowns` | ranking/history/source rows | `integer >= 0 \| null \| undefined`                                                                                                                                                       | Royco market drawdown count when supplied                                                                                                               |
| `sourceRisk.marketStatus`         | ranking/history/source rows | `"normal" \| "protected" \| "unhealthy" \| "critical" \| null \| undefined`                                                                                                               | Normalized Royco market status used by tranche scoring                                                                                                  |
| `sourceRisk.marketTvlUsd`         | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Royco market-level TVL in USD                                                                                                                           |
| `sourceRisk.trancheTvlUsd`        | ranking/history/source rows | `number >= 0 \| null \| undefined`                                                                                                                                                        | Royco vault/tranche TVL in USD                                                                                                                          |
| `sourceRisk.trancheShareTokenAddress` | ranking/history/source rows | `string \| null \| undefined`                                                                                                                                                         | Share-token address for the Royco tranche vault                                                                                                         |
| `sourceRisk.trancheDepositTokenAddress` | ranking/history/source rows | `string \| null \| undefined`                                                                                                                                                       | Deposit-token address used to attach the tranche row to a tracked underlying stablecoin                                                                 |
| `sourceRisk.withdrawalDelaySeconds` | ranking/history/source rows | `integer seconds >= 0 \| null \| undefined`                                                                                                                                             | Withdrawal/redemption delay for the tranche when supplied                                                                                               |
| `sourceRisk.kycRequired`          | ranking/history/source rows | `boolean \| null \| undefined`                                                                                                                                                           | Whether the source marks KYC as required                                                                                                                |
| `sourceRisk.accessRestricted`     | ranking/history/source rows | `boolean \| null \| undefined`                                                                                                                                                           | Whether the source marks jurisdictional or other access restrictions                                                                                    |
| `rankChangeAttribution`           | ranking rows                | `object \| null \| undefined`                                                                                                                                                             | Optional previous-rank/PYS delta attribution with primary driver and contribution hints                                                                 |

Current `v8.19` scoring treats missing source-risk evidence as neutral: omitted or `null` `sourceRisk`, `sourceRisk.sourceRiskPenalty`, or `sourceRisk.venueRiskTier` values resolve to a neutral source-risk penalty and do not change PYS or report-card scoring. `sourceRisk.sourceRiskScore` is now derived from the resolved source-risk penalty when no upstream value is provided. Royco Dawn structured-tranche rows additionally carry opportunity-level Safety Score evidence under `sourceRisk.tranche*`; this changes only the yield row's safety input and PYS, not the underlying stablecoin's report-card Safety Score. DEWS methodology v5.99 consumes only populated structured yield stress evidence inside its Yield Anomaly sub-signal; neutral, malformed, or missing structured rows remain no-ops. Saved payloads used by calibration tooling should normalize from the nested `sourceRisk.*` fields before analysis rather than assuming flattened row properties.

**`YieldRanking`**

| Field                     | Type                                                           | Description                                                                                                                                                        |
| ------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                      | `string`                                                       | Pharos stablecoin ID                                                                                                                                               |
| `symbol`                  | `string`                                                       | Token symbol                                                                                                                                                       |
| `name`                    | `string`                                                       | Full name                                                                                                                                                          |
| `currentApy`              | `number`                                                       | Current APY (%)                                                                                                                                                    |
| `apy7d`                   | `number`                                                       | 7-day average APY (%)                                                                                                                                              |
| `apy30d`                  | `number`                                                       | 30-day average APY (%)                                                                                                                                             |
| `apyBase`                 | `number \| null`                                               | Base APY component (%)                                                                                                                                             |
| `apyReward`               | `number \| null`                                               | Reward APY component (%), `null` if none                                                                                                                           |
| `yieldSource`             | `string`                                                       | Human-readable yield source description                                                                                                                            |
| `yieldSourceUrl`          | `string \| null`                                               | Official URL for the selected source when Pharos has a curated or metadata-derived link                                                                            |
| `yieldType`               | `string`                                                       | Yield type classification (e.g. `"lending-vault"`, `"staking"`)                                                                                                    |
| `dataSource`              | `string`                                                       | Data source identifier (e.g. `"defillama"`)                                                                                                                        |
| `sourceTvlUsd`            | `number \| null`                                               | TVL of the yield source pool (USD)                                                                                                                                 |
| `pharosYieldScore`        | `number \| null`                                               | Composite Pharos Yield Score (0–100), recomputed at read time from cached APY + benchmark inputs plus the current Safety Score                                     |
| `safetyScore`             | `number \| null`                                               | Current Safety Score input used by Yield Intelligence. Rated coins match `/api/report-cards`; Royco structured-tranche rows expose the row-level tranche score; unrated coins use the default NR penalty input (`40`) |
| `safetyGrade`             | `string \| null`                                               | Current Safety Score letter grade (`"A+"` through `"F"`, or `"NR"`); Royco structured-tranche rows derive the grade from the opportunity-level tranche score       |
| `yieldToRisk`             | `number \| null`                                               | Yield-to-risk ratio recomputed at read time from cached APY inputs plus the current Safety Score                                                                   |
| `excessYield`             | `number \| null`                                               | 30-day average APY above the row benchmark (percentage points)                                                                                                     |
| `benchmarkKey`            | `"USD" \| "EUR" \| "CHF" \| "GBP" \| "JPY" \| "MXN" \| "BRL" \| "AUD" \| "CAD" \| "SGD" \| undefined` | Benchmark selected for this row's `excessYield` and any rate-derived APY logic (v8.13 expanded the set; `SGD` is reserved without a fetcher and routes to USD fallback) |
| `benchmarkLabel`          | `string \| undefined`                                          | Human-readable benchmark label for the row                                                                                                                         |
| `benchmarkCurrency`       | `string \| undefined`                                          | Benchmark currency code used for the row                                                                                                                           |
| `benchmarkRate`           | `number \| undefined`                                          | Benchmark rate (%) applied to this row                                                                                                                             |
| `benchmarkRecordDate`     | `string \| null \| undefined`                                  | Market or policy record date for the selected benchmark                                                                                                            |
| `benchmarkIsFallback`     | `boolean \| undefined`                                         | Whether the row benchmark is currently on a fallback path                                                                                                          |
| `benchmarkFallbackMode`   | `string \| null \| undefined`                                  | Fallback reason for the row benchmark when applicable                                                                                                              |
| `benchmarkSelectionMode`  | `"native" \| "fallback-usd" \| "manual-override" \| undefined` | How the row benchmark was selected                                                                                                                                 |
| `benchmarkIsProxy`        | `boolean \| undefined`                                         | True when the selected benchmark is an explicit proxy rather than the exact reference rate                                                                         |
| `yieldStability`          | `number \| null`                                               | Yield stability metric (0–1; higher = more stable)                                                                                                                 |
| `apyVariance30d`          | `number \| null`                                               | 30-day APY variance                                                                                                                                                |
| `apyMin30d`               | `number \| null`                                               | Minimum APY in last 30 days (%)                                                                                                                                    |
| `apyMax30d`               | `number \| null`                                               | Maximum APY in last 30 days (%)                                                                                                                                    |
| `warningSignals`          | `string[]`                                                     | Active warning-signal flags for the selected best source                                                                                                           |
| `altSources`              | `AltYieldSource[]`                                             | Additional non-selected source rows for the same coin                                                                                                              |
| `provenance`              | `object \| null`                                               | Source-level provenance: confidence tier, selection reason, benchmark state, source-switch metadata, source freshness, and optional anchor timing                  |
| `publicationGenerationId` | `string \| null \| undefined`                                  | Publication-generation identifier, or `null`/omitted for legacy rows                                                                                               |
| `publishedRank`           | `number \| null \| undefined`                                  | Stable publication-order rank from the cached generation                                                                                                           |
| `liveRank`                | `number \| null \| undefined`                                  | Post-hydration rank from the response order after live Safety Score recomputation                                                                                  |
| `sourceRisk`              | `object \| null \| undefined`                                  | Optional nested source-risk payload. Runtime rows derive or resolve `sourceRisk.sourceRiskPenalty` before PYS v8 scoring; missing or unknown values remain neutral |
| `rankChangeAttribution`   | `object \| null \| undefined`                                  | Optional rank-change attribution with previous rank/PYS, delta, primary driver, and driver contribution hints                                                      |

When present, `YieldRanking.provenance` includes:

- `sourceObservedAt` / `sourceAgeSeconds`: the timestamp and age of the latest observation actually backing the row
- `comparisonAnchorObservedAt` / `comparisonAnchorAgeSeconds`: optional prior-anchor timing for APYs derived from two observations, such as price-derived and on-chain exchange-rate rows
- `benchmarkKey`, `benchmarkLabel`, `benchmarkRate`, `benchmarkIsFallback`, `benchmarkSelectionMode`, and related fields for the exact benchmark applied to that row

---

### `GET /api/yield-adapter-manifest`

Yield adapter manifest for every yield-bearing asset. The route is public-read, uses the standard cache profile (`s-maxage=300`), and requires `X-API-Key` on the public API lane.

`sourceKey` is an exact runtime key only when it can join to `/api/yield-history?sourceKey=...`, rankings provenance, or decision-ledger rows. Runtime-resolved DeFiLlama variant strategies and disabled/quarantined readers return `sourceKey: null` with `sourceKeyPattern` set to the runtime pattern or would-be disabled key instead of a synthetic non-runtime value.

**Response**

```text
{
  "methodologyVersion": "v8.16",
  "updatedAt": 1779210000,
  "entries": [
    {
      "stablecoinId": "susde-ethena",
      "coinSymbol": "sUSDe",
      "family": "defillama",
      "sourceKey": "66985a81-9c51-46ca-9977-42b4fe7bc6df",
      "sourceKeyPattern": null,
      "label": "Curated DeFiLlama pool UUID",
      "chain": null,
      "project": null,
      "lifecycle": "active",
      "quarantineReason": null,
      "methodologyVersion": "v8.16",
      "updatedAt": 1779210000
    }
  ]
}
```

---

### `GET /api/yield-history`

Historical yield data for a single stablecoin. If a stored `warning_signals` payload is malformed, the API treats it as an empty array rather than failing the entire response. Generation-aware rows are returned only after their publication generation is marked `published`; legacy rows remain readable through the existing cutoff fallback. Returned rows are capped at the latest published `/api/yield-rankings` snapshot so history cannot advance past an unpublished yield cache state. If the cached rankings payload is missing or malformed, the cap degrades to the latest successful `sync-yield-data` cron timestamp instead of wall-clock `now`.

For tracked savings-wrapper handoffs (`USDe`, `USDS`, `DAI`, `frxUSD`, `crvUSD`, `avUSD`), legacy parent-owned wrapper rows are filtered immediately at read time and are also purged by the hourly publisher plus the operator cleanup tool. The discontinuity is intentional: those old child-owned series no longer remain queryable through the parent id or through `mode=source&sourceKey=...`. New linked parent rows use `linked-variant:<variantId>:<sourceKey>` source keys when a tracked variant's eligible native/wrapper source is intentionally exposed on the active parent for comparison and coverage context.

**Cache:** slow — `X-Data-Age` and `Warning` headers included. Freshness threshold: 3600 s (1 hour, aligned to the hourly `sync-yield-data` publisher).

**Required query parameter**

| Param        | Type     | Description                     |
| ------------ | -------- | ------------------------------- |
| `stablecoin` | `string` | Pharos stablecoin ID (required) |

**Optional query parameters**

| Param       | Type      | Default | Bounds           | Description                                                                      |
| ----------- | --------- | ------- | ---------------- | -------------------------------------------------------------------------------- |
| `days`      | `integer` | `90`    | 1–365            | Lookback window in days                                                          |
| `mode`      | `string`  | `best`  | `best`, `source` | `best` for historically selected best-source rows; `source` requires `sourceKey` |
| `sourceKey` | `string`  | —       | —                | Required with `mode=source`; returns source-specific history for that source key |

**Response**

```text
{
  "current": {
    "date": 1772000000,
    "apy": 4.21,
    "sourceKey": "onchain:susde-ethena",
    "yieldSource": "Ethena staking (sUSDe)"
  },
  "history": [YieldHistoryPoint, "..."],
  "publication": {
    "generationId": "yield-1772000000",
    "updatedAt": 1772000000,
    "cutoffAt": 1772000000,
    "schemaVersion": 1,
    "status": "published"
  },
  "methodology": {
    "version": "8.13",
    "currentVersion": "8.13",
    "changelogPath": "/methodology/yield-changelog/"
  }
}
```

| Field         | Type                      | Description                                                                      |
| ------------- | ------------------------- | -------------------------------------------------------------------------------- |
| `current`     | `YieldHistoryPoint\|null` | Latest row in the returned history window, or `null` when no history exists      |
| `history`     | `YieldHistoryPoint[]`     | History rows sorted by `date` ASC                                                |
| `methodology` | `object`                  | Yield methodology envelope for the response                                      |
| `warning`     | `string \| undefined`     | Present when freshness lookup fails and the handler falls back to cache metadata |
| `publication` | `object \| null`          | Optional published-generation metadata; omitted for legacy rankings payloads     |

**`YieldHistoryPoint`**

```json
{
  "date": 1771500000,
  "apy": 12.4,
  "apyBase": 10.2,
  "apyReward": 2.2,
  "exchangeRate": 1.052,
  "sourceTvlUsd": 5200000000,
  "warningSignals": [],
  "sourceKey": "rate-derived",
  "yieldSource": "T-bill proxy",
  "yieldSourceUrl": "https://ondo.finance/usdy",
  "yieldType": "nav-appreciation",
  "dataSource": "rate-derived",
  "isBest": true,
  "sourceSwitch": false,
  "publicationGenerationId": "yield-1771500000"
}
```

| Field                     | Type                          | Description                                                                                                                                       |
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `date`                    | `number`                      | Unix seconds                                                                                                                                      |
| `apy`                     | `number`                      | Total APY at snapshot time (%)                                                                                                                    |
| `apyBase`                 | `number \| null`              | Base APY component (%)                                                                                                                            |
| `apyReward`               | `number \| null`              | Reward APY component (%); `null` if none                                                                                                          |
| `exchangeRate`            | `number \| null`              | Exchange rate at snapshot time (e.g. sUSDe/USDe); `null` if not applicable                                                                        |
| `sourceTvlUsd`            | `number \| null`              | TVL of the yield source pool at snapshot time (USD)                                                                                               |
| `warningSignals`          | `string[]`                    | Active warning-signal flags at that snapshot                                                                                                      |
| `sourceKey`               | `string \| null`              | Stable source identifier for this history row (for example a DL pool UUID, `onchain:<stablecoinId>`, or `linked-variant:<variantId>:<sourceKey>`) |
| `yieldSource`             | `string \| null`              | Human-readable source label at that snapshot                                                                                                      |
| `yieldSourceUrl`          | `string \| null`              | Official URL for that source when Pharos has a curated or metadata-derived link                                                                   |
| `yieldType`               | `string \| null`              | Yield type classification at that snapshot                                                                                                        |
| `dataSource`              | `string \| null`              | Underlying data-source family                                                                                                                     |
| `isBest`                  | `boolean`                     | Whether this row was the selected best source at that timestamp                                                                                   |
| `sourceSwitch`            | `boolean`                     | True when the historically selected best source changed at this row                                                                               |
| `publicationGenerationId` | `string \| null \| undefined` | Published generation identifier for generation-aware rows; `null`/omitted on legacy rows                                                          |
| `sourceRisk`              | `object \| null \| undefined` | Optional nested source-risk payload for historical rows; missing or unknown values are neutral                                                    |

---

### `GET /api/mint-burn-flows`

Mint/burn flow data across tracked stablecoins — aggregate gauge score, per-coin net-flow + pressure-shift signals, and hourly timeseries. Updated every 30 minutes by the sync cron. Aggregate responses without `stablecoin` are served cache-first; the critical sync lane pre-publishes the default 24h and 168h windows after successful runs so public reads avoid rescanning hourly mint/burn aggregates.

**Cache:** standard

**Error responses:** `503` when the cached fallback payload is missing or malformed and live recomputation cannot satisfy the request. Malformed embedded freshness fields inside an otherwise valid cached payload no longer reset freshness to synthetic values; the API logs the corruption and falls back to the cache row timestamp.

**Optional query parameters**

| Param        | Type      | Default | Bounds | Description                                                               |
| ------------ | --------- | ------- | ------ | ------------------------------------------------------------------------- |
| `stablecoin` | `string`  | —       | —      | Filter to a single stablecoin ID. Changes response shape to per-coin mode |
| `hours`      | `integer` | `24`    | 1–720  | Lookback window for the returned `hourly[]` series                        |

**Response (aggregate mode — no `stablecoin` param)**

```text
{
  "gauge": {
    "score": 2.3,
    "band": "NEUTRAL",
    "flightToQuality": false,
    "flightIntensity": 0,
    "trackedCoins": 8,
    "trackedMcapUsd": 215000000000,
    "intensitySemantics": "signed-v2",
    "classificationSource": "report-card-cache"
  },
  "coins": [CoinFlow, ...],
  "hourly": [HourlyFlow, ...],
  "updatedAt": 1772000000,
  "windowHours": 24,
  "scope": { "chainIds": ["ethereum", "arbitrum"], "label": "Configured issuance chains" },
  "sync": { "lastSuccessfulSyncAt": 1772000200, "freshnessStatus": "fresh", "warning": null, "criticalLaneHealthy": true }
}
```

**`gauge`**

| Field                  | Type             | Description                                                                                          |
| ---------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
| `score`                | `number \| null` | Market-cap-weighted pressure-shift composite (-100 to +100). `null` when insufficient data           |
| `band`                 | `string \| null` | Gauge band: `"CRISIS"`, `"STRESS"`, `"CAUTIOUS"`, `"NEUTRAL"`, `"HEALTHY"`, `"CONFIDENT"`, `"SURGE"` |
| `flightToQuality`      | `boolean`        | Whether flight-to-quality conditions are active                                                      |
| `flightIntensity`      | `number`         | Flight-to-quality intensity (0–100). 0 when not active                                               |
| `trackedCoins`         | `number`         | Number of stablecoins tracked for mint/burn flows                                                    |
| `trackedMcapUsd`       | `number`         | Combined market cap of tracked coins (USD)                                                           |
| `intensitySemantics`   | `string`         | Scoring semantics version identifier (currently `"signed-v2"`)                                       |
| `classificationSource` | `string`         | Source of flight-to-quality classification (`"report-card-cache"` or `"unavailable"`)                |

**Top-level metadata**

| Field         | Type     | Description                                                                                                        |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `windowHours` | `number` | Requested chart window for `hourly[]`                                                                              |
| `scope`       | `object` | Current ingestion scope, for example `{ chainIds: ["ethereum", "arbitrum"], label: "Configured issuance chains" }` |
| `sync`        | `object` | Latest critical-lane freshness metadata, warning state, and optional `classificationWarning`                       |

**`CoinFlow`**

| Field                 | Type                                             | Description                                                                                                                                                                                       |
| --------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `stablecoinId`        | `string`                                         | Pharos stablecoin ID                                                                                                                                                                              |
| `symbol`              | `string`                                         | Token symbol                                                                                                                                                                                      |
| `flowIntensity`       | `number \| null`                                 | Deprecated alias for `pressureShiftScore`; retained for compatibility                                                                                                                             |
| `pressureShiftScore`  | `number \| null`                                 | Canonical baseline-relative pressure score (-100 to +100). `null` if < 7 days of data or no current activity                                                                                      |
| `pressureShiftState`  | `"improving" \| "stable" \| "worsening" \| "nr"` | Interpreted pressure state from `pressureShiftScore`                                                                                                                                              |
| `netFlowDirection24h` | `"minting" \| "burning" \| "flat" \| "inactive"` | Current 24h direction derived from raw net flow + activity                                                                                                                                        |
| `has24hActivity`      | `boolean`                                        | Whether any 24h mint/burn events were recorded for the coin                                                                                                                                       |
| `baselineDailyNetUsd` | `number \| null`                                 | Average daily net flow over the baseline window used for scoring                                                                                                                                  |
| `baselineDailyAbsUsd` | `number \| null`                                 | Average daily absolute flow over the baseline window used for scoring                                                                                                                             |
| `baselineDataDays`    | `number \| null`                                 | Number of tracked days contributing to the baseline window                                                                                                                                        |
| `netFlow24hUsd`       | `number`                                         | Raw 24h net flow (USD, positive = net minting, negative = net burning). Fixed to the canonical 24h window even when `hours` changes                                                               |
| `mintVolume24hUsd`    | `number`                                         | Total mint volume in the canonical 24h window (USD)                                                                                                                                               |
| `burnVolume24hUsd`    | `number`                                         | Total burn volume in the canonical 24h window (USD)                                                                                                                                               |
| `mintCount24h`        | `number`                                         | Number of mint events in the canonical 24h window                                                                                                                                                 |
| `burnCount24h`        | `number`                                         | Number of burn events in the canonical 24h window                                                                                                                                                 |
| `netFlow7dUsd`        | `number`                                         | 7-day net flow (USD)                                                                                                                                                                              |
| `netFlow30dUsd`       | `number`                                         | 30-day net flow (USD)                                                                                                                                                                             |
| `netFlow90dUsd`       | `number`                                         | 90-day net flow (USD)                                                                                                                                                                             |
| `largestEvent24h`     | `object \| null`                                 | Largest event in the last 24h: `{ direction, amountUsd, txHash, timestamp }`                                                                                                                      |
| `coverage`            | `object \| undefined`                            | Coverage metadata: `startBlock`, `lastSyncedBlock`, `lagBlocks`, `historyStartAt`, window booleans, adapter provenance (`adapterKinds`, `startBlockSource`, `startBlockConfidence`), and `status` |

**`HourlyFlow`**

| Field           | Type     | Description                     |
| --------------- | -------- | ------------------------------- |
| `hourTs`        | `number` | Unix seconds (start of hour)    |
| `netFlowUsd`    | `number` | Net flow for this hour (USD)    |
| `mintVolumeUsd` | `number` | Mint volume for this hour (USD) |
| `burnVolumeUsd` | `number` | Burn volume for this hour (USD) |

**Response (per-coin mode — with `stablecoin` param)**

Returns per-chain breakdown and hourly timeseries for a single coin. Returns `404` if the stablecoin is not tracked for mint/burn flows.

```text
{
  "stablecoinId": "usdt-tether",
  "symbol": "USDT",
  "mintVolumeUsd": 50000000,
  "burnVolumeUsd": 30000000,
  "netFlowUsd": 20000000,
  "mintCount": 12,
  "burnCount": 8,
  "chains": [{ "chainId": "ethereum", "mintVolumeUsd": 40000000, ... }],
  "hourly": [HourlyFlow, ...],
  "updatedAt": 1772000000,
  "windowHours": 24,
  "scope": { "chainIds": ["ethereum"], "label": "Ethereum-only" },
  "sync": { "lastSuccessfulSyncAt": 1772000200, "freshnessStatus": "fresh", "warning": null, "criticalLaneHealthy": true }
}
```

---

### `GET /api/mint-burn-events`

Paginated list of individual mint/burn events for a specific stablecoin. Events are sourced from on-chain logs via Alchemy JSON-RPC.

**Cache:** realtime

**Required query parameter**

| Param        | Type     | Description                     |
| ------------ | -------- | ------------------------------- |
| `stablecoin` | `string` | Pharos stablecoin ID (required) |

**Optional query parameters**

| Param          | Type      | Default | Bounds                                                   | Description                                                                                                             |
| -------------- | --------- | ------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `direction`    | `string`  | —       | `"mint"` or `"burn"`                                     | Filter by direction                                                                                                     |
| `chain`        | `string`  | —       | tracked chain IDs for the requested stablecoin           | Filter by chain ID within the stablecoin's configured issuance scope                                                    |
| `burnType`     | `string`  | —       | `"effective_burn"`, `"bridge_burn"`, `"review_required"` | Filter burn rows by classification                                                                                      |
| `scope`        | `string`  | `"all"` | `"all"` or `"counted"`                                   | `counted` returns only rows used in economic-flow aggregates (`flow_type='standard'` and mint/effective-burn semantics) |
| `minAmount`    | `number`  | —       | —                                                        | Minimum USD amount; unpriced rows are excluded when this filter is used                                                 |
| `limit`        | `integer` | `50`    | 1–500                                                    | Max results                                                                                                             |
| `offset`       | `integer` | `0`     | 0–25,000                                                 | Pagination offset; cannot be combined with `cursor`                                                                     |
| `cursor`       | `string`  | —       | opaque                                                   | Keyset cursor from `nextCursor`                                                                                         |
| `includeTotal` | `boolean` | `true`  | `true` or `false`                                        | When `false`, skips the exact `COUNT(*)`; `total` becomes a page lower bound and `totalExact` is `false`                |

**Response**

```text
{
  "events": [MintBurnEvent, ...],
  "total": 1234,
  "totalExact": true,
  "nextCursor": "eyJ2IjoxLCJ2YWx1ZXMiOlsxNzcyMDAwMDAwLDE5MDAwMDAwLCJtYi0xIl19"
}
```

Results are ordered by `timestamp DESC, blockNumber DESC, id DESC`. Prefer `cursor`/`nextCursor` for deep pagination; offset pagination is capped for D1 safety.

**`MintBurnEvent`**

| Field              | Type                                                             | Description                                                                                                       |
| ------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `id`               | `string`                                                         | Composite ID: `{chainId}-{txHash}-{logIndex}`                                                                     |
| `stablecoinId`     | `string`                                                         | Pharos stablecoin ID                                                                                              |
| `symbol`           | `string`                                                         | Token symbol                                                                                                      |
| `chainId`          | `string`                                                         | Chain identifier (e.g. `"ethereum"`)                                                                              |
| `direction`        | `"mint" \| "burn"`                                               | Whether tokens were minted or burned                                                                              |
| `flowType`         | `"standard" \| "bridge_transfer" \| "atomic_roundtrip"`          | Flow-noise classification; `bridge_transfer` and `atomic_roundtrip` rows are excluded from aggregate flow metrics |
| `amount`           | `number`                                                         | Amount in native token units                                                                                      |
| `amountUsd`        | `number \| null`                                                 | USD value at time of event                                                                                        |
| `burnType`         | `"effective_burn" \| "bridge_burn" \| "review_required" \| null` | Burn classification; `null` for mint rows                                                                         |
| `burnReviewReason` | `string \| null`                                                 | Reason emitted when a burn requires manual review classification                                                  |
| `counterparty`     | `string \| null`                                                 | Non-zero address (recipient for mint, sender for burn)                                                            |
| `txHash`           | `string`                                                         | Transaction hash                                                                                                  |
| `blockNumber`      | `number`                                                         | Block number                                                                                                      |
| `timestamp`        | `number`                                                         | Unix seconds                                                                                                      |
| `explorerTxUrl`    | `string`                                                         | Block explorer URL for the transaction                                                                            |
| `priceUsed`        | `number \| null`                                                 | Price used to derive `amountUsd`                                                                                  |
| `priceTimestamp`   | `number \| null`                                                 | Unix seconds of the price snapshot used                                                                           |
| `priceSource`      | `string \| null`                                                 | Valuation provenance (`supply_history`, `price_cache`, `price_cache_heal`, etc.)                                  |

---

### `GET /api/stress-signals`

Returns Depeg Early Warning Score (DEWS) data for active tracked stablecoins.

**All coins (no params):** Latest DEWS score + signal breakdown per coin.

**Single coin:** Add `?stablecoin=ID&days=30` for latest + daily history.

`stablecoin` must be an active tracked Pharos stablecoin ID. Unknown IDs return `404` with `{ "error": "Unknown stablecoin" }`; tracked-but-non-active IDs return `404` with `{ "error": "Stablecoin not tracked" }`.

**Cache:** standard (`public, s-maxage=300, max-age=60`). Freshness threshold: 1800 s (30 minutes, aligned to `compute-dews`).

**Query parameters**

| Param        | Type      | Default | Description                                     |
| ------------ | --------- | ------- | ----------------------------------------------- |
| `stablecoin` | `string`  | —       | Single coin mode: return latest + daily history |
| `days`       | `integer` | `30`    | History lookback (max 365)                      |

Aggregate responses are filtered to active tracked stablecoin IDs only, even if stale rows for non-active or de-tracked IDs still exist in storage. The aggregate response keeps `updatedAt` as the newest returned current row, but `X-Data-Age` / `Warning` freshness headers are based on `oldestComputedAt` so a stale per-coin row cannot be hidden by fresher rows for other coins.

**Response (all coins)**

```json
{
  "signals": {
    "usdt-tether": {
      "score": 5,
      "band": "CALM",
      "signals": {
        "supply": { "value": 2, "available": true },
        "price": { "value": 1, "available": true }
      },
      "amplifiers": { "psi": 1, "contagion": 1 },
      "computedAt": 1740000000,
      "methodologyVersion": "6.0"
    }
  },
  "updatedAt": 1740000000,
  "oldestComputedAt": 1740000000,
  "malformedRows": 0,
  "methodology": {
    "version": "6.0",
    "versionLabel": "v6.0",
    "currentVersion": "6.0",
    "currentVersionLabel": "v6.0",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1740000000,
    "isCurrent": true
  }
}
```

**Response (single coin)**

```json
{
  "current": {
    "score": 5,
    "band": "CALM",
    "signals": {
      "supply": { "value": 2, "available": true },
      "price": { "value": 1, "available": true }
    },
    "amplifiers": { "psi": 1, "contagion": 1 },
    "computedAt": 1740000000,
    "methodologyVersion": "6.0"
  },
  "history": [
    {
      "date": 1739900000,
      "score": 3,
      "band": "CALM",
      "signals": {
        "supply": { "value": 1, "available": true },
        "price": { "value": 1, "available": true }
      },
      "amplifiers": { "psi": 1, "contagion": 1 },
      "methodologyVersion": "6.0"
    }
  ],
  "malformedRows": 0,
  "methodology": {
    "version": "6.0",
    "versionLabel": "v6.0",
    "currentVersion": "6.0",
    "currentVersionLabel": "v6.0",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1740000000,
    "isCurrent": true
  }
}
```

**`malformedRows`** — count of DB rows with unparseable JSON signal data (expected 0 under normal operation)

**`oldestComputedAt`** — aggregate mode only; oldest returned current row, exposed as a body-only lag diagnostic

**`amplifiers`** — clamped multipliers that were applied on top of the base weighted score. `psi` is the systemic PSI amplifier (range `[1.0, 1.3]`); `contagion` is the per-peg-type cross-asset amplifier (range `[1.0, 1.2]`). Both default to `1.0` for legacy cached rows written before v5.95.

**`methodology`** — same fields and semantics as `/api/depeg-events`

---

### `POST /api/api-key-requests`

Public self-serve API key request endpoint used by `https://pharos.watch/api/`. It records the request, reserves the normalized email claim, and sends an email verification link through Resend. It does not issue a key until the email verification endpoint succeeds.

**Authentication:** exempt

**Cache:** no-store

**Default key policy after verification**

- `tier`: `"self-serve"`
- `trafficClass`: `"external"`
- `rateLimitPerMinute`: `30`
- `expiresAt`: 60 days after issuance
- one active or pending self-serve key per normalized email

**Request body**

```json
{
  "email": "dev@example.com",
  "requesterName": "Optional name",
  "organization": "Optional organization",
  "projectUrl": "https://example.com",
  "useCase": "Required, 10-1200 characters",
  "intendedEndpoints": ["/api/stablecoins", "/api/stablecoin/:id"],
  "expectedCadence": "hourly",
  "expectedVolume": "Optional free-form estimate",
  "acceptedTerms": true,
  "website": ""
}
```

| Field               | Type                                                                | Required | Notes                                                                                                                           |
| ------------------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `email`             | `string`                                                            | Yes      | Normalized to lowercase for the one-key-per-email claim                                                                         |
| `requesterName`     | `string`                                                            | No       | Private operator context only                                                                                                   |
| `organization`      | `string`                                                            | No       | Private operator context only                                                                                                   |
| `projectUrl`        | `string`                                                            | No       | Must start with `https://` when provided                                                                                        |
| `useCase`           | `string`                                                            | Yes      | 10-1200 characters                                                                                                              |
| `intendedEndpoints` | `string[]`                                                          | No       | Public API paths, known dynamic patterns such as `/api/stablecoin/:id`, or `unknown`; admin/API-key/backfill paths are rejected |
| `expectedCadence`   | `"hourly" \| "every_5_min" \| "every_1_min" \| "manual" \| "other"` | Yes      | Used for review context                                                                                                         |
| `expectedVolume`    | `string`                                                            | No       | Private operator context only                                                                                                   |
| `acceptedTerms`     | `true`                                                              | Yes      | Fair-use acknowledgement                                                                                                        |
| `website`           | `string`                                                            | No       | Honeypot field; non-empty submissions are silently accepted without issuing work                                                |

**Success response:** `202 Accepted`

```json
{
  "status": "pending_verification",
  "message": "If this address can receive verification email, check your inbox to continue."
}
```

**Error responses**

- `400` invalid body, invalid email, invalid project URL, unknown/admin intended endpoint, or missing fair-use acknowledgement
- duplicate active or pending self-serve key claims receive the same `202` response shape as a new pending request and do not send a second verification link
- `429` request throttle exceeded; responses include `Retry-After`
- `503` self-serve env/email dependency unavailable (`Retry-After: 60`)

### `POST /api/api-key-requests/verify`

Public self-serve verification endpoint used by the email link. Links carry the token only in the URL fragment as raw `/api/#akv_...`, which is not sent to the server in the page request, logged by intermediaries, or leaked via Referer. The browser strips the fragment before posting and exchanges the token here. Query-string and legacy fragment-parameter verification forms are not accepted. A successful response creates the key, marks the request issued, and returns the plaintext API token exactly once.

**Authentication:** exempt

**Cache:** no-store

**Request body**

```json
{ "token": "akv_..." }
```

**Success response:** `201 Created`

```json
{
  "status": "issued",
  "key": {
    "keyPrefix": "0123456789abcdef",
    "maskedToken": "ph_live_0123456789abcdef_...",
    "tier": "self-serve",
    "trafficClass": "external",
    "rateLimitPerMinute": 30,
    "expiresAt": 1715686400
  },
  "token": "ph_live_...",
  "usage": {
    "baseUrl": "https://api.pharos.watch",
    "headerName": "X-API-Key",
    "retryGuidance": "Respect Retry-After on 429 responses and add jitter to polling intervals."
  }
}
```

**Error responses**

- `400` invalid, expired, used, or no-longer-pending verification token
- `429` verification attempt throttle exceeded or daily issuance limit for the salted IP hash reached; responses include `Retry-After`
- `503` self-serve dependency unavailable or issuance consistency compensation triggered (`Retry-After: 60`)

### `POST /api/feedback`

Public feedback ingestion endpoint used by the in-app feedback modal. Validates payloads, applies IP-based rate limiting, and forwards submissions to GitHub Issues.

**Authentication:** exempt

**Cache:** no edge cache (POST passthrough)

**Rate limits**

- Feedback endpoint limiter: `3 submissions / 10 minutes` per salted IP hash in D1.
- Feedback limiter dependency failure: `503` with `Retry-After: 60` and `{ "error": "Feedback service temporarily unavailable. Please try again." }`.

**Request body**

```json
{
  "type": "bug",
  "title": "Optional short title",
  "description": "Required, 10-2000 characters",
  "expectedValue": "Optional expected behavior/value",
  "stablecoinId": "Optional canonical stablecoin id",
  "stablecoinName": "Optional stablecoin name",
  "pageUrl": "/stablecoin/usdt-tether",
  "pegValue": "Optional UI value snapshot",
  "contactHandle": "@pharos_user",
  "website": ""
}
```

| Field                                                         | Type                                              | Required    | Notes                                                                                                                                      |
| ------------------------------------------------------------- | ------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                                                        | `"bug" \| "data-correction" \| "feature-request"` | Yes         | Submission category                                                                                                                        |
| `title`                                                       | `string`                                          | Conditional | Required for `bug` and `feature-request` (3–100 chars); optional for `data-correction`                                                     |
| `description`                                                 | `string`                                          | Yes         | 10–2000 chars                                                                                                                              |
| `pageUrl`                                                     | `string`                                          | Yes         | Relative app path (must be a single-slash internal path such as `/stablecoin/usdc-circle/`; protocol-relative `//...` values are rejected) |
| `website`                                                     | `string`                                          | No          | Honeypot field; non-empty is silently accepted/dropped                                                                                     |
| `expectedValue`, `stablecoinId`, `stablecoinName`, `pegValue` | `string`                                          | No          | Optional metadata                                                                                                                          |
| `contactHandle`                                               | `string`                                          | No          | Optional Telegram/X handle that appears publicly on GitHub                                                                                 |

**Response**

```json
{ "ok": true }
```

**Error responses**

- `400` invalid payload
- `429` rate limited (3 submissions / 10 minutes per salted IP hash)
- `500` forwarding/processing failure
- `503` service misconfigured (missing `FEEDBACK_IP_SALT` or `GITHUB_PAT`) or feedback limiter/storage dependency failure (`Retry-After: 60`)

---

### `POST /api/telegram-mini-app/session`

Returns the current private-chat Mini App control-panel state for a Telegram user.

**Authentication:** exempt from `X-API-Key`; requires Telegram Mini App `initData` signed with the bot token. The worker excludes Telegram's transport `hash` field from the HMAC data-check string, includes every other signed field, rejects missing/invalid hashes, and accepts sessions up to 24 hours old for read-only state loading.

**Site-data lane:** denied. The frontend calls the public API host through `src/lib/api.ts`; `/_site-data/*` never proxies this route.

**Rate limiting:** cache-backed cooldown per Telegram user (`mini-app:session`, 2 seconds). Invalid-auth requests are rejected before cooldown or analytics writes.

**Cache:** no-store.

**Request body:**

```json
{
  "initData": "query-string-from-Telegram.WebApp.initData"
}
```

The request schema is strict. Launch context such as `start_param` must come from signed Telegram `initData`.

**Response:** JSON Mini App state. Private fresh sessions return `viewer.canMutate=true`, including Telegram direct-link launches where `chat_type="sender"` identifies the user's private context. Private sessions older than the 5-minute mutation window but younger than 24 hours return state with `viewer.canMutate=false` and `viewer.mutationBlockReason="stale-auth"`. Group, supergroup, and channel launches return read-only state with `viewer.mutationBlockReason="not-private"`.

Key fields:

- `viewer` — Telegram user, optional `chatId`, chat type, `startParam`, and mutation eligibility.
- `subscriber` — global alert flags, quiet-hours settings, and chat-level snooze.
- `subscriptions` — explicit per-coin follows with alert flags and per-coin thresholds.
- `presets` — followed preset watchlists.
- `catalog` — recommended presets and searchable tracked stablecoins for the UI.
- `health` — last successful delivery/reply, recent failure class, and queued alert count.

Errors: `400` invalid request shape, `401` invalid or stale Telegram session, `429` cooldown, `503` missing bot-token configuration.

### `POST /api/telegram-mini-app/mutate`

Applies one private-chat Mini App setting mutation, then returns the refreshed Mini App state.

**Authentication:** exempt from `X-API-Key`; requires signed Telegram Mini App `initData` no older than 5 minutes. Mutations are private-user-context only (`chat_type` absent, `private`, or Telegram direct-link `sender`). The same fresh launch can perform multiple mutations inside that 5-minute window; stale auth returns `401`.

**Site-data lane:** denied.

**Rate limiting:** cache-backed cooldown per Telegram user across all mutation kinds (`mini-app:mutation:any`, 5 seconds).

**Cache:** no-store.

**Request body:**

```json
{
  "initData": "query-string-from-Telegram.WebApp.initData",
  "operation": {
    "kind": "set-global",
    "alertType": "safety",
    "enabled": true
  }
}
```

Supported `operation.kind` values:

- `recommended-setup` — canonical first-run setup only: `presetId="usd-top25"` and `alertTypes=["dews","depeg"]`.
- `follow-preset` — follow any supported preset with selected alert types.
- `set-global` — toggle one global alert family (`dews`, `depeg`, `safety`, `launch`).
- `set-global-depeg-step` — set or clear the global depeg severity and worsening-step threshold (`100`, `250`, `500`, or `null`).
- `set-quiet-hours` — enable or disable UTC quiet hours.
- `clear-snooze` — clear chat-level snooze.
- `set-snooze` — set chat-level snooze for `1h`, `4h`, or `24h`.
- `set-coin-snooze` — set or clear one explicit coin subscription's snooze.
- `set-timezone` — set the chat timezone used for quiet-hours display.
- `unsubscribe-all` — clear all global, per-coin, and preset alert settings.
- `forget-me` — delete the private subscriber row and mutable alert settings.
- `set-coin` — add or tune one explicit coin subscription.
- `remove-coin` — remove one explicit coin subscription.
- `follow-preset` / `unfollow-preset` — add or remove a dynamic preset watchlist.

Errors: `400` invalid operation, unknown coin/preset, or empty alert type selection; `401` invalid or stale Telegram session; `403` group mutation attempt; `429` cooldown; `503` preset cache unavailable or missing bot-token configuration.

### `POST /api/telegram-webhook`

Telegram Bot API webhook endpoint. Receives user messages, processes bot commands, and manages subscriptions.

**Authentication:** exempt from `X-API-Key`; requires `X-Telegram-Bot-Api-Secret-Token` for processing. Missing or invalid webhook secrets are acknowledged with `200 ok` and ignored to prevent Telegram retry storms. The webhook never uses the operator Cloudflare Access lane.

**Rate limiting:** Exempt from IP rate limiter (Telegram sends from fixed IPs).

**Cache:** no edge cache (POST passthrough)

**Request body:** Telegram Update object (JSON, sent by Telegram servers).

**Response:** Normal authenticated, unauthenticated/ignored, and command-processing paths return `200 OK` with plain-text body `ok` so Telegram does not retry routine bot decisions. Uncaught handler errors still use the standard API error wrapper and can return `500`.

**Commands handled:**

- `/start` — Welcome/setup wizard with onboarding examples plus `@pharoswatch` and `@pharoswatchers` links
- `/presets` — List the preset watchlist catalog and example commands
- `/subscribe <types> <targets>` — Subscribe to alerts for explicit coins or preset watchlists (types: dews, depeg, safety, launch)
- `/subscribe <types> all` — Enable one or more alert types across all tracked stablecoins
- `/unsubscribe <targets>` — Remove explicit coin subscriptions or the concrete coin rows covered by a preset watchlist
- `/unsubscribe all` — Remove all per-coin subscriptions, disable every current alert flag including launch, and clear the global depeg worsening step
- `/set <ticker> <setting> <value>` — Tune per-coin thresholds and modes
- `/set all <setting> <value>` — Toggle global all-stablecoin alert types
- `/settings` — Open the inline-keyboard settings menu
- `/mute <start>-<end>` — Enable quiet hours (in the subscriber's configured timezone, defaulting to UTC)
- `/unmutehours` — Disable quiet hours
- `/timezone [<IANA zone>]` — Show or set the subscriber's timezone for quiet hours; without an argument it offers a quick-pick keyboard
- `/status <ticker>` — Read-only per-coin status summary
- `/brief` (alias: `/market`) — Market brief built from current cached datasets
- `/top <view>` — Top movers/leaders for `depeg`, `dews`, `yield`, `liquidity`, `chains`, or `safety`
- `/why <ticker>` — Explain the current Safety Score for a coin
- `/coverage <ticker>` — Per-coin coverage diagnostics for the subscriber surface
- `/health` — Chat self-diagnostics: last successful delivery/reply, queued alerts, recent failure class, quiet-hours/snooze state, and alert readiness
- `/unsnooze` — Clear an active alert snooze without waiting for it to expire
- `/cancel` — Cancel a pending disambiguation flow
- `/list` — Show current subscriptions, per-coin settings, and quiet hours
- `/help` — Command reference

Preset watchlists are stored in `telegram_preset_subscriptions` and resolved dynamically at dispatch/list/status time. Supported aliases are `usd-top10`, `usd-top25`, `usd-top50`, `non-usd-top10`, `non-usd-top25`, `non-usd-top50`, `eur-top10`, `gold-top5`, `mcap-ge-1b`, and `mcap-ge-100m`. Presets are supported for `dews`, `depeg`, and `safety`; `launch` still requires explicit tickers or Pharos coin IDs.

---

## Pages Function endpoints

These endpoints are served by Cloudflare Pages Functions from the website host (`pharos.watch`), not by the Worker API host (`api.pharos.watch`). They are out of scope for the public `X-API-Key` regime: no API key is required, they do not appear in the OpenAPI artifact, and they do not honor `Idempotency-Key`.

Same-origin only. Browser CORS blocks cross-origin POST before the function executes; foreign-origin requests receive `404`. Documented for completeness and for external tooling that reads share URLs. These endpoints are snapshot storage for the website UI, not a public integration API.

### `GET /selector-snapshot/:sid`

Returns a previously stored Stablecoin Picker output JSON identified by content-addressed `sid` (32 hex chars). The returned artifact is frozen; clients that offer "Compare to today's data" must compute a separate live Picker run and keep the stored artifact unchanged.

**Authentication:** exempt — same-origin gated via `Origin` / `Referer` allowlist.

**Path parameter:** `sid` — 32 lowercase hex chars, content-addressed SHA-256 truncation. The server recomputes the identifier from the canonicalized selector output before storing or reading a snapshot.

**Response (200):**

```json
{
  "profile": "treasury",
  "engineVersion": "selector-v1.3",
  "datasetHash": "<content hash>",
  "timestamp": 1715000000,
  "input": {
    "profile": "treasury",
    "pegCurrency": "EUR",
    "horizon": "6mplus",
    "depegTolerance": "zero",
    "composability": "none",
    "venuePreferences": ["custody"],
    "exitSpeed": "any",
    "minApy": null,
    "yieldNativeOnly": false,
    "decentralization": "any",
    "custodyOk": "any"
  },
  "universe": { "active": 392, "surviving": 12 },
  "recommended": [ /* ranked shortlist entries */ ],
  "lowerRanked": [ /* lower-ranked entries */ ],
  "usedRelaxedFallback": false,
  "relaxedReasons": [],
  "coverageWarnings": {
    "skippedForCoverageCount": 0,
    "sparse": false,
    "uneven": false,
    "skippedForCoverage": [],
    "newListingCount": 0,
    "redistributionCount": 0
  },
  "exclusionSummary": [],
  "closestSurvivors": [],
  "relaxableConstraints": [],
  "lowConfidence": false,
  "methodologyVersions": { "safetyScore": "v7.25" }
}
```

The full `SelectorOutput` shape is owned by `shared/lib/selector/types.ts`. The Pages Function rejects snapshots missing the frontend replay fields (`input.pegCurrency`, `universe`, `lowConfidence`, `usedRelaxedFallback`, `relaxedReasons`, `exclusionSummary`, `closestSurvivors`, `relaxableConstraints`, coverage warning counts, and authored recommendation/lower-ranked prose). Semantic validation rejects impossible component/score ranges, unknown enum values, wrong-profile `venuePreferences`, unknown `whyKeys`, malformed confidence/rank diagnostics, unknown lower-ranked reason keys, malformed `recommendedSource` objects, malformed `perInputStaleness`, and impossible rank/slot values. Readers should treat unknown fields permissively; `datasetHash` is scoped to the selected-peg decision universe and must change when any exclusion, scoring, tie-break, explanation, source-selection, or version-affecting field changes. Freshness-only metadata is excluded unless it affects output semantics. `engineVersion` / selector version carries the bump on deterministic behavior, weight, exclusion-rule, missing-data, tie-break, or explanation changes.

On GET, the Pages Function parses the stored payload, recomputes the canonical sid, and verifies it matches the requested `sid`. A mismatch is treated as corrupt storage and returns `502`.

**Cache:** `private, no-store` — reads are same-origin gated with `Origin` / `Referer`, so stored snapshots are intentionally not served from a public shared cache.

**Failure modes:**

| Status | When |
| --- | --- |
| 404 | Origin disallowed, sid not 32 hex chars, or KV miss. |
| 500 | `SELECTOR_SNAPSHOTS` KV binding missing on the Pages project. |
| 502 | Stored KV value is corrupt, fails semantic validation, or recomputes to a different sid. |
| 503 | KV read throws transiently (Cloudflare KV outage). |

### `POST /selector-snapshot`

Stores a Stablecoin Picker output JSON under a server-recomputed `sid`. Idempotent — re-POSTing the same canonical payload returns the same `sid`. Documented here for completeness; external integrations should not call this endpoint (it is bound to the Picker wizard at `https://pharos.watch/screener/picker/`).

**Authentication:** exempt — same-origin gated.

**Body:** `application/json`, a complete `SelectorOutput`. Max 100 KB defensive cap. Debug traces are stripped before canonical sid computation and storage.

**Response (200):** `{ "sid": "<32 hex chars>" }`. The sid is content-addressed: SHA-256 over a canonicalized JSON payload with debug/freshness-derived fields stripped (`timestamp`, `debug`, `perInputStaleness`, plus fields matching the suffixes `ageSeconds` / `capturedAt` / `stalenessMs` / `updatedAt` / `fetchedAt`), with keys lexicographically sorted at every depth. `coverageWarnings.newListingCount` is not stripped because the implemented engine derives it from content-level recent-listing flags. Engine and integration agree on the same strip-list, so a sid computed client-side matches the server's authoritative value.

Share-link privacy property: the stored payload contains the Picker answers and output rows, not IP addresses, browser fingerprints, wallet addresses, or account identifiers. The website UI must disclose that anyone with the resulting link can view the frozen artifact and that the KV entry is retained for 5 years.

**Validation matrix:**

| Case | Status / client contract |
| --- | --- |
| Invalid `sid` path syntax | `404`; clients should surface invalid-link/not-found state without replaying unrelated live output. |
| Missing required replay fields | `400` on POST, `502` on GET. |
| Unknown enum, reason key, source shape, or impossible score/rank | `400` on POST, `502` on GET. |
| Stored payload canonical sid differs from requested `sid` | `502`. |
| Clipboard denied after a successful POST | Endpoint still returns `200`; UI shows a selectable URL fallback. |
| Trading profile has stale share-blocking inputs | UI should not POST until refreshed; endpoint remains shape-focused and does not recompute live staleness. |

**Failure modes:**

| Status | When |
| --- | --- |
| 400 | Body parse error, unsupported JSON, missing required replay fields, malformed recommendation / coverage-warning basics, or semantic validation failure. |
| 404 | Origin disallowed. |
| 405 | Method on the wrong path — POST is accepted only at `/selector-snapshot` without a path segment. |
| 413 | Payload exceeds 100 KB defensive cap. |
| 500 | `SELECTOR_SNAPSHOTS` KV binding missing. |
| 503 | KV write throws transiently. |

---