Skip to main content
Pharos
PHAROS

External Integrations

API Reference

The public integration lane is https://api.pharos.watch. In production, protected public routes require X-API-Key. The website itself does not use that lane directly; it talks to the internal site-data proxy instead.

Quick Facts

  • Public auth: X-API-Key
  • Exempt public routes: health, OG images, feedback, Telegram webhook
  • Admin auth: Cloudflare Access on the ops hosts

For integrations

External API

Call `https://api.pharos.watch` directly. Protected public routes require `X-API-Key`; missing or invalid keys receive `401`.

For pharos.watch only

Website lane

Browsers on the site use same-origin `/_site-data/*`, which proxies to the internal Worker lane. External consumers should not use this path.

For operators

Ops lane

Admin routes live behind Cloudflare Access on `ops.pharos.watch` and `ops-api.pharos.watch`. They do not use public API keys.

Need A Key?

Request API access on Telegram

If you want a public API key, join the Pharos Telegram channel and ask for one there.

Include your intended usage in the request: what you are building, which endpoints you expect to call, your approximate polling cadence, and the expected request volume. That makes it possible to issue the right key and rate-limit profile up front.

Base Contract

Before You Call The API

The Pharos API is a REST API served by a Cloudflare Worker backed by a D1 database. It powers the 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/* returns image/png, 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. Protected public /api/* traffic on api.pharos.watch is API-key-gated via X-API-Key; production runs with PUBLIC_API_AUTH_MODE=enforce, so missing or invalid keys receive 401 Unauthorized.

Reference Section

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, and Pages preview hosts.

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.

Reference Section

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

Anonymous public access is limited to these exempt routes:

  • GET /api/health
  • GET /api/og/*
  • POST /api/feedback
  • POST /api/telegram-webhook

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.

Reference Section

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:

ExampleAsset
"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/index.ts, with the raw metadata stored in shared/data/stablecoins/*.json and validated by shared/lib/stablecoins/schema.ts. The API currently accepts canonical IDs only; legacy route redirects are handled at the frontend/static-export layer, not by the worker ID resolver.

Reference Section

Response Headers

Endpoints backed by the cron cache include these additional headers:

HeaderDescription
X-Data-AgeSeconds elapsed since the cron last wrote this data to D1
WarningRFC 7234 stale-data warning, present when X-Data-Age exceeds the endpoint's max age

When a cache-backed response is stale (X-Data-Age > max age), the worker also downgrades Cache-Control to no-store for that response so edge/browser caches do not keep serving a stale payload after the underlying cron data recovers.

Reference Section

Response Body Freshness (_meta)

Endpoints backed by createCacheHandler() inject a _meta object into plain-object (non-array) response bodies 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"
  }
}
FieldTypeDescription
updatedAtnumberUnix epoch seconds when the cron last wrote this data to D1
ageSecondsnumberfloor(now / 1000) - updatedAt
statusstring"fresh" (age/max <= 1.0), "degraded" (1.0 < ratio <= 1.5), or "stale" (ratio > 1.5)

Endpoints with `_meta`:

EndpointMax Age (sec)Source
GET /api/stablecoins600createCacheHandler
GET /api/bluechip-ratings43200createCacheHandler
GET /api/usds-status86400createCacheHandler
GET /api/yield-rankings3600Manual injection after live safety hydration
GET /api/treasury-stable-exposure86400Manual validation + _meta injection

Array-typed responses (e.g., endpoints returning a JSON array at the top level) receive only the HTTP headers (X-Data-Age, Warning) and do not include _meta.

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.

Reference Section

Cache-Control Profiles

These profiles apply while the dataset is within its endpoint freshness budget. Once a cache-backed response becomes stale, the worker overrides that response to Cache-Control: no-store until a fresh response is generated.

ProfileCache-ControlUsed by
realtimepublic, s-maxage=60, max-age=10stablecoins, stablecoin-summary, blacklist, depeg-events, peg-summary, mint-burn-events, chains
standardpublic, s-maxage=300, max-age=60stablecoin-charts, redemption-backstops, usds-status, daily-digest, digest-archive, report-cards, stability-index, yield-rankings, mint-burn-flows, stress-signals
custompublic, s-maxage=300, max-age=300dex-liquidity (browser-side max-age extended to match CDN TTL)
per-coinpublic, s-maxage=300, max-age=10stablecoin/:id (cache-aside with 5-min per-coin TTL in D1)
slowpublic, s-maxage=3600, max-age=300supply-history, dex-liquidity-history, bluechip-ratings, yield-history, safety-score-history, treasury-stable-exposure, non-usd-share
archivepublic, s-maxage=86400, max-age=3600digest-snapshot
no-storeno-storehealth plus admin GET routes after router override (status, status-history, request-source-stats, debug-sync-state, backfill-dews, audit-depeg-history?dry-run=true, discovery-candidates)

POST /api/feedback, POST /api/telegram-webhook, and admin POST endpoints bypass edge caching because they are non-GET request paths. They are not part of a cacheable Cache-Control profile and do not currently rely on an emitted Cache-Control: no-store header.

Reference Section

Polling Guidance

Recommended minimum polling cadence for external integrations:

Cache profileMinimum poll intervalNotes
realtime60 secondsPolling faster usually re-fetches the same edge-cached payload
standard300 secondsPreferred baseline for most dashboards
per-coin300 secondsGET /api/stablecoin/:id is history-heavy; avoid short loops
slow3600 secondsHistorical/timeline endpoints should generally be polled hourly
archive86400 secondsHistorical snapshot payload for digest SSG and recap pages
no-storeOn-demand onlyHealth/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.
  • Back off exponentially on 429 and 5xx responses.

Reference Section

Error Response Conventions

All error responses use { "error": "message" } JSON format.

StatusMeaningWhen
400Bad RequestInvalid query parameter syntax (missing required parameter, invalid enum value, malformed numeric input, or rejected filter values that no longer coerce silently)
401UnauthorizedProtected public 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)
404Not FoundUnknown stablecoin ID or missing resource
429Too Many RequestsRate limit exceeded (global public API limiter or feedback-specific limiter)
500Internal Server ErrorUnhandled exception (caught by withErrorHandler)
502Bad GatewayUpstream (DefiLlama / CoinGecko) fetch failed
503Service UnavailableCache-passthrough endpoint where cache has never been populated, where the cached payload is corrupt / rejected by validation, or MAINTENANCE_MODE=true (global kill switch via wrangler secret put)

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 requests immediately return 503 with { "error": "maintenance", "message": "..." } — used during DB migrations.

Reference Section

Method Gating Policy

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

  • GET is accepted for read endpoints (plus admin debug/status endpoints and GET /api/backfill-dews).
  • POST is accepted for mutating admin endpoints, POST /api/feedback, and POST /api/telegram-webhook.
  • 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.
  • Unknown POST paths return 405 with Allow: GET; unsupported verbs return 405 with Allow: GET, POST.

The same shared endpoint descriptors now also carry static worker dependency-hydration hints consumed by worker/src/route-registry.ts, where the worker now 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.

Reference Section

Admin Auth And Idempotency

Admin endpoints are authenticated only on the ops-api.pharos.watch host. Cloudflare Access must authenticate the caller first, then inject Cf-Access-Jwt-Assertion for the worker. worker/src/lib/auth.ts verifies that JWT against the configured Access audience (CF_ACCESS_OPS_API_AUD) and team domain (CF_ACCESS_TEAM_DOMAIN) via worker/src/lib/jwt-verify.ts, including signature, aud, exp, and iss checks. Browser operators should use https://ops.pharos.watch/admin/, which talks to same-origin /api/admin/* Pages Functions routes behind Cloudflare Access.

The website-internal read lane is separate from Cloudflare Access. site-api.pharos.watch accepts only allowlisted GET public-read paths and requires X-Pharos-Site-Proxy-Secret, which the Pages /_site-data/* proxy injects server-to-server from SITE_API_SHARED_SECRET. Until that dedicated host is provisioned, the Pages proxy can temporarily fall back to api.pharos.watch; do not move PUBLIC_API_AUTH_MODE past off until SITE_API_ORIGIN is pointed at the dedicated site-api host. Public browser traffic must not call site-api.pharos.watch directly.

Many router-dispatched mutating admin endpoints also support optional Idempotency-Key handling. Current idempotent routes are:

  • POST /api/backfill-depegs
  • POST /api/backfill-supply-history
  • POST /api/backfill-stability-index
  • POST /api/backfill-cg-prices
  • POST /api/backfill-mint-burn-prices
  • POST /api/backfill-mint-burn
  • POST /api/reclassify-atomic-roundtrips
  • POST /api/audit-depeg-history
  • POST /api/trigger-digest
  • POST /api/reset-blacklist-sync
  • POST /api/remediate-blacklist-amount-gaps
  • POST /api/backfill-blacklist-current-balances

When an Idempotency-Key is supplied on one of those routes, successful responses echo Idempotency-Key plus X-Idempotent-Replay, and conflicting reuse returns 409.

The worker’s idempotent admin route helpers now authenticate first and only then enter idempotency bookkeeping. That keeps the helper contract aligned with its name and prevents future admin endpoints from accidentally becoming “idempotent but unauthenticated” through wrapper misuse.

The /admin/ UI now sends an Idempotency-Key automatically for supported manual actions so double-submits from the operator surface replay safely.

Reference Section

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.

Endpoint

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.

Response

json
{
  "peggedAssets": [StablecoinData, ...],
  "fxFallbackRates": { "peggedEUR": 1.082, "peggedGBP": 1.26 }
}

fxFallbackRates is present when the ECB FX-rate cron has run; keys are pegType strings (e.g. "peggedEUR"), values are rates in USD.

`StablecoinData` fields

FieldTypeDescription
idstringPharos stablecoin ID
namestringFull name (e.g. "Tether")
symbolstringTicker (e.g. "USDT")
geckoId`string \null`CoinGecko ID (normalized output key; upstream DefiLlama uses gecko_id)
pegTypestringDefiLlama peg type (e.g. "peggedUSD", "peggedEUR")
pegMechanismstring"fiat-backed", "crypto-backed-algorithmic", etc.
priceSourcestringSource 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)
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", "coingecko-gap-fill" (used when tracked deployments are missing from DefiLlama chain coverage and CoinGecko repairs the total/history buckets), "coingecko-fallback", or "onchain-total-supply" (used when a supplemental asset is normalized from on-chain total supply instead of an upstream market-cap field)
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.
circulatingRecord<string, number>Current supply in USD, keyed by pegType (e.g. { "peggedUSD": 138000000 })
circulatingPrevDayRecord<string, number>Supply 24 h ago
circulatingPrevWeekRecord<string, number>Supply 7 days ago
circulatingPrevMonthRecord<string, number>Supply ~30 days ago
chainCirculatingRecord<string, ChainCirculating>Per-chain breakdown. For "coingecko-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.
chainsstring[]List of chain names where the token is deployed
consensusSourcesstring[]Source names that returned a valid price for this coin during the sync cycle. Defaults to [] when absent.

`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.

Endpoint

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.

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

json
{
  "tokens": [TokenPoint, ...]
}

`TokenPoint`

FieldTypeDescription
datenumberUnix timestamp (seconds)
totalCirculatingUSDRecord<string, number>Supply in USD per pegType key
totalCirculatingRecord<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.

Endpoint

GET /api/treasury-stable-exposure

Daily snapshot of reviewed protocol and DAO treasury stablecoin sleeves. This endpoint is intentionally scoped to public onchain treasury wallets, EVM-first, and best-effort coverage.

Cache: slow — X-Data-Age, Warning, and _meta included.

Response

json
{
  "entities": [
    {
      "protocolId": "maker",
      "name": "Sky / Maker",
      "treasuryUsd": 123456789,
      "stablecoinSleeveUsd": 45678901,
      "trackedStableUsd": 43000000,
      "decentralizedStableUsd": 21000000,
      "decentralizedStablePctOfTreasury": 17.0,
      "decentralizedStablePctOfStableSleeve": 46.0,
      "weightedSafetyScore": 82.4,
      "weightedSafetyGrade": "B+",
      "holdings": []
    }
  ],
  "updatedAt": 1743321600,
  "coverage": {
    "entityCount": 12,
    "registryCount": 14,
    "launchEligibleCount": 12,
    "ownerChainTuples": 54,
    "launchOwnerChainTuples": 42,
    "evmOnly": true,
    "extractionModes": {
      "staticSeeded": 14,
      "customReviewed": 0,
      "dynamicUnresolved": 0,
      "missing": 0
    }
  }
}

Important response semantics:

  • treasuryUsd is the full wallet-balance denominator from the same provider snapshot used for the stable sleeve.
  • stablecoinSleeveUsd combines direct stablecoin balances from Sim's stablecoin filter with supported LP, vault, and lending positions decomposed to their underlying stablecoins.
  • trackedStableUsd is the subset of the stable sleeve that Pharos can map to tracked stablecoins by chain + contract.
  • decentralizedStablePctOfStableSleeve therefore uses the full stable sleeve denominator, not only the tracked subset.
  • coverage.untrackedStableUsd discloses the stablecoin value that could not be mapped into a Pharos stablecoin ID.
  • coverage.notes may state when LP, vault, or lending positions were decomposed into underlying stablecoin exposure, or when that supplement failed and treasury-only coverage remained.
  • weightedSafetyScore is USD-weighted across tracked stable holdings that have a current report card; coverage.ratedTrackedStablePct shows how much of the tracked sleeve that score covers.

Cold-start behavior: if no treasury snapshot has been published yet, the endpoint returns 200 with entities: [] plus stale _meta freshness fields so UI and smoke checks can treat the dataset as temporarily empty rather than transport-failed.

Error responses: 503 only when the cached treasury snapshot exists but is structurally malformed.

For integrations that only need current per-coin metrics (without full historical arrays), prefer GET /api/stablecoin-summary/:id.

Endpoint

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
}
FieldTypeDescription
idstringPharos stablecoin ID
namestringAsset name
symbolstringTicker symbol
pegTypestringPeg type key (peggedUSD, peggedEUR, etc.)
pegMechanismstringBacking/mechanism classification
priceUsd`number \null`Current price in USD
priceSourcestringPrice 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
supplyByPegUsdRecord<string, number>Current supply by peg bucket (USD)
supplyUsdobjectAggregate USD supply values and deltas (current, prevDay, prevWeek, prevMonth, change1d, change7d, change30d)
chainCountnumberNumber of chains where the asset is deployed
updatedAtnumberUnix seconds of the stablecoins snapshot used for this response

Endpoint

GET /api/non-usd-share

Returns historical non-USD stablecoin market share data from supply_history, split into commodity-pegged (gold/silver) and fiat non-USD buckets. 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

ParamTypeDefaultConstraintsDescription
daysnumber1825min 30, max 1825Lookback window in days

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

FieldTypeDescription
datenumberUnix seconds (snapshot date)
commoditySharenumberCommodity-pegged share as % of total supply
fiatNonUsdSharenumberFiat non-USD share as % of total supply
commoditynumberCommodity-pegged circulating USD
fiatNonUsdnumberFiat non-USD circulating USD
totalnumberTotal circulating USD across all tracked coins

Endpoint

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.

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

Freshness threshold: 600 seconds. Returns 503 when the stablecoins cache is unavailable.

Status codes:

StatusMeaning
200Chain aggregates computed successfully
503Stablecoins cache unavailable (missing or structurally corrupt)

Response (`ChainsResponse`):

json
{
  "chains": [ChainSummary, ...],
  "globalTotalUsd": 230000000000,
  "updatedAt": 1710500000,
  "healthMethodologyVersion": "1.1"
}
FieldTypeDescription
chainsChainSummary[]Chains sorted by totalUsd descending
globalTotalUsdnumberSum of all chain supply in USD
updatedAtnumberUnix epoch seconds of the underlying stablecoins snapshot
healthMethodologyVersionstringChain Health Score methodology version (currently "1.1")

`ChainSummary` fields:

FieldTypeDescription
idstringCanonical chain identifier (DefiLlama chain name)
namestringHuman-readable chain name
logoPath`string \null`Path to chain logo asset
type`"evm" \"tron" \"other"`Chain runtime family from CHAIN_META
totalUsdnumberTotal stablecoin supply on this chain in USD
change24hnumberAbsolute 24h supply change in USD
change24hPctnumber24h supply change as a percentage
change7dnumberAbsolute 7d supply change in USD
change7dPctnumber7d supply change as a percentage
change30dnumberAbsolute 30d supply change in USD
change30dPctnumber30d supply change as a percentage
stablecoinCountnumberNumber of distinct stablecoins on this chain
dominantStablecoin{ id, symbol, share }Largest stablecoin by supply on the chain
dominanceSharenumberChain share of the global tracked stablecoin supply (0–1)
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)
healthFactorsChainHealthFactorsRaw sub-factor scores (0–100 each; quality may still be null)

`ChainHealthFactors` fields:

FieldTypeDescription
concentrationnumberHHI-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
chainEnvironmentnumberResilience-tier score for the chain itself (100 tier 1, 60 tier 2, 20 tier 3)
pegStabilitynumberSupply-weighted average peg deviation score
backingDiversitynumberShannon entropy of backing types across the chain

Endpoint

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.

Cache: dynamic

  • Live snapshots: slow (public, s-maxage=3600, max-age=300)
  • Bootstrap / fallback / stale 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):

FieldTypeDescription
stablecoinIdstringPharos coin ID
modestringOne of live, live-stale, curated-fallback, template-fallback, unavailable. This is snapshot transport/freshness state, not the user-facing reserve badge semantics.
reservesReserveSlice[]Reserve slices currently being shown to the user
estimatedbooleantrue only when using the classification template fallback
liveAtnumber?Unix seconds of the last successful live snapshot. Present only when live data exists
sourcestring?Adapter key (for example "infinifi", "m0", "openeden-usdo", or "accountable"). Present only when live data exists
displayUrlstring?Human-readable source link shown in the UI. Present only when configured
displayBadgeobject?User-facing reserve badge semantics for authoritative live snapshots (live, curated-validated, or proof)
metadataobject?Adapter snapshot metadata for authoritative live snapshots. This can include feed-specific context such as yieldBasisCollateralPct for crvusd
provenanceobject?Evidence-quality envelope for authoritative live snapshots (evidenceClass, sourceModel, optional freshnessMode, scoringEligible)
syncobject?Live sync state (status, bootstrap, stale, lastAttemptedAt, lastSuccessAt, warnings, lastError). 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.

When present, displayBadge has:

FieldTypeDescription
kind`"live" \"curated-validated" \"proof"`User-facing reserve badge classification
labelstringBadge label rendered by the frontend (Live, Curated-Validated, or Proof)

When present, provenance has:

FieldTypeDescription
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
scoringEligiblebooleanWhether this exact snapshot is currently eligible for collateral-quality passthrough

Response (404): { "error": "Not found" }

Endpoint

GET /api/stablecoin-charts

Aggregate historical supply chart data across all stablecoins, broken down by peg type. 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
    }
  }
]
FieldTypeDescription
datenumberUnix timestamp (seconds)
totalCirculatingUSDRecord<string, number>Aggregate supply in USD per peg type

Endpoint

GET /api/blacklist

Freeze, blacklist, and token-destruction events currently ingested for USDC, USDT, PAXG, XAUT, PYUSD, and USD1. EURC is intentionally excluded from the live filter set for now because Circle frequently mirrors the same blacklist actions on both USDC and EURC, which creates many zero-balance EURC rows. 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 hourly sync-blacklist writer timestamp. Public freshness stays fresh through that hourly budget and only degrades once the scheduled blacklist sync is actually late.

Query parameters

ParamTypeDefaultDescription
stablecoinstringFilter by token symbol: USDC, USDT, PAXG, XAUT, PYUSD, USD1
chainstringFilter by chain name (e.g. Ethereum, Tron)
eventTypestringFilter by type: blacklist, unblacklist, destroy
qstringCase-insensitive address substring search
sortBystringdateSort field: date, stablecoin, chain, event
sortDirectionstringdescSort direction: asc, desc
limitinteger1000Max results (1–1000; 0 maps to default 1000)
offsetinteger0Pagination offset

Response

json
{
  "events": [BlacklistEvent, ...],
  "total": 13422,
  "methodology": {
    "version": "3.6",
    "versionLabel": "v3.6",
    "currentVersion": "3.6",
    "currentVersionLabel": "v3.6",
    "changelogPath": "/methodology/blacklist-tracker-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}

`BlacklistEvent`

FieldTypeDescription
idstringComposite ID: {chainId}-{txHash}-{logIndex}
stablecoinstringToken symbol (USDC, USDT, etc.)
chainIdstringChain identifier (e.g. "ethereum", "tron")
chainNamestringHuman-readable chain name (e.g. "Ethereum")
eventTypestring"blacklist", "unblacklist", or "destroy"
addressstringAffected 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
amountSourcestringevent, historical_balance, derived, or unavailable
amountStatusstringresolved, recoverable_pending, permanently_unavailable, provider_failed, ambiguous
txHashstringTransaction hash
blockNumbernumberBlock number
timestampnumberUnix seconds
methodologyVersionstringMethodology 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
explorerTxUrlstringBlock explorer URL for the transaction
explorerAddressUrlstringBlock explorer URL for the address

`methodology`

FieldTypeDescription
versionstringMethodology version of the latest returned event in this response
versionLabelstringDisplay label (e.g. "v3.2")
currentVersionstringLatest methodology version
currentVersionLabelstringDisplay label for latest methodology version
changelogPathstringRelative URL to the methodology changelog page
asOfnumberUnix timestamp of latest event used for freshness
isCurrentbooleanWhether version matches currentVersion

Endpoint

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.activeFrozenTotal reflects Pharos' local 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. 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.

Cache: realtime

Freshness note: Shares the same 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,
    "recoverableGapCount": 17
  },
  "chart": [
    { "quarter": "Q1 '24", "USDT": 1200000, "USDC": 850000, "PAXG": 0, "XAUT": 0, "total": 2050000 }
  ],
  "chains": [
    { "id": "ethereum", "name": "Ethereum" },
    { "id": "tron", "name": "Tron" }
  ],
  "totalEvents": 13422,
  "methodology": {
    "version": "3.6",
    "versionLabel": "v3.6",
    "currentVersion": "3.6",
    "currentVersionLabel": "v3.6",
    "changelogPath": "/methodology/blacklist-tracker-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}

Endpoint

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

ParamTypeDefaultDescription
stablecoinstringFilter by Pharos stablecoin ID
active"true"When "true", return only ongoing (unresolved) depeg events
limitinteger100Max results (1–1000)
offsetinteger0Pagination offset

Response

json
{
  "events": [DepegEvent, ...],
  "total": 4080,
  "methodology": {
    "version": "4.9",
    "versionLabel": "v4.9",
    "currentVersion": "4.9",
    "currentVersionLabel": "v4.9",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}

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

`DepegEvent`

FieldTypeDescription
idnumberAuto-increment DB ID
stablecoinIdstringPharos stablecoin ID
symbolstringToken symbol
pegTypestringDefiLlama peg type (e.g. "peggedUSD")
direction`"above" \"below"`Whether the price was above or below the peg
peakDeviationBpsnumberLargest deviation observed (basis points, signed; negative = below peg, positive = above peg)
startedAtnumberUnix seconds when depeg was first detected
endedAt`number \null`Unix seconds when price returned to peg; null if still active
startPricenumberPrice at event start (USD)
peakPrice`number \null`Price at worst deviation
recoveryPrice`number \null`Price at recovery
pegReferencenumberReference peg value used (USD)
source`"live" \"backfill"`Detection method

`methodology`

FieldTypeDescription
versionstringMethodology version attributed from the latest returned event timestamp
versionLabelstringDisplay label (e.g. "v4.9")
currentVersionstringLatest methodology version
currentVersionLabelstringDisplay label for latest methodology version
changelogPathstringRelative URL to the methodology changelog page
asOfnumberUnix timestamp used for methodology attribution
isCurrentbooleanWhether version matches currentVersion

Endpoint

GET /api/peg-summary

Composite peg scores and aggregate statistics for all tracked stablecoins. Scores are computed over a 4-year window from live depeg events, DEX prices, and current prices.

Cache: realtime

Response

json
{
  "coins": [PegSummaryCoin, ...],
  "summary": PegSummaryStats,
  "methodology": {
    "version": "4.9",
    "versionLabel": "v4.9",
    "currentVersion": "4.9",
    "currentVersionLabel": "v4.9",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1772606400,
    "isCurrent": true
  }
}

`PegSummaryCoin`

FieldTypeDescription
idstringPharos stablecoin ID
symbolstringToken symbol
namestringFull name
pegTypestringDefiLlama peg type
pegCurrencystringPeg currency code (USD, EUR, GOLD, etc.)
governancestring"centralized", "centralized-dependent", "decentralized"
currentDeviationBps`number \null`Live price deviation from peg (basis points, signed). null for coins with supply < $1M or missing price.
priceSourcestringPrimary 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)
pegPctnumber% of tracked time within ±100 bps
severityScorenumberSeverity sub-score (0–100)
spreadPenaltynumberSpread/liquidity penalty applied to score
eventCountnumberNumber of depeg events in the 4-year window
worstDeviationBps`number \null`Worst single deviation seen (basis points)
activeDepegbooleanWhether a depeg event is currently open
lastEventAt`number \null`Unix seconds of most recent depeg event
trackingSpanDaysnumberDays of history used for score computation
methodologyVersionstringMethodology version attributed to this coin snapshot
dexPriceCheck`DexPriceCheck \null`Optional cross-validation against DEX price (shown when coin supply ≥ $1M, DEX data is ≤ 60 minutes old, and aggregate source TVL is ≥ $250K)
consensusSourcesstring[]Source names that returned a valid price for this coin. Defaults to [] when absent.

`DexPriceCheck`

FieldTypeDescription
dexPricenumberDEX-derived price (USD)
dexDeviationBpsnumberDEX price deviation from peg (basis points, signed)
agreesbooleanWhether primary and DEX prices are within 50 bps
sourcePoolsnumberNumber of DEX pools contributing to the price
sourceTvlnumberCombined TVL of those pools (USD)

`PegSummaryStats`

FieldTypeDescription
activeDepegCountnumberCoins with an open depeg event
medianDeviationBpsnumberMedian absolute deviation across all tracked coins
worstCurrent`{ id, symbol, bps } \null`Coin with the largest current deviation
coinsAtPegnumberCoins with current deviation below their live depeg threshold (100 bps for USD pegs, 150 bps for non-USD pegs)
totalTrackednumberTotal coins in the response
depegEventsTodaynumberNumber of depeg events whose startedAt is in the current UTC day
depegEventsYesterdaynumberNumber of depeg events whose startedAt is in the previous UTC day
fallbackPegRatesstring[]_(optional)_ pegType keys using stale FX fallback rates

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

Endpoint

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
}
FieldTypeDescription
freezeActivebooleanWhether the USDS freeze module is currently enabled
implementationAddressstringAddress of the current USDS implementation contract
lastCheckednumberUnix seconds when this was last fetched on-chain

Endpoint

GET /api/bluechip-ratings

Safety ratings from 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.

json
{
  "usdt-tether": BluechipRating,
  "usdc-circle": BluechipRating
}

`BluechipRating`

FieldTypeDescription
gradestringLetter grade: "A+", "A", "A-", "B+""F"
slugstringBluechip report slug (e.g. "usdt")
collateralizationnumberCollateralization percentage
smartContractAuditbooleanWhether an audit exists
dateOfRatingstringISO 8601 date of rating
dateLastChange`string \null`ISO 8601 date of last grade change
smidgeBluechipSmidgePlain-text evaluation summaries (HTML stripped)

`BluechipSmidge` — each field is string | null:

FieldDescription
stabilityReserves management and stabilization mechanisms
managementPersonnel restrictions and track records
implementationSmart contract implementation assessment
decentralizationDecentralization posture
governanceGovernance and redemption terms
externalsExternal risk factors

Endpoint

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.

json
{
  "usdt-tether": DexLiquidityData,
  "usdc-circle": DexLiquidityData
}

`DexLiquidityData`

FieldTypeDescription
totalTvlUsdnumberTotal DEX TVL (USD)
totalVolume24hUsdnumber24 h trading volume (USD)
totalVolume7dUsdnumber7-day trading volume (USD)
poolCountnumberNumber of liquidity pools
pairCountnumberNumber of unique trading pairs
chainCountnumberNumber of chains with active pools
protocolTvlRecord<string, number>TVL per DEX protocol (e.g. { "uniswap-v3": 100000 })
chainTvlRecord<string, number>TVL per chain (e.g. { "Ethereum": 500000 })
topPoolsDexLiquidityPool[]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
updatedAtnumberUnix 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)
effectiveTvlUsdnumberTVL 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"`Coverage-confidence classification for the retained pool set; primary includes pure dl and pure direct_api rows
coverageConfidencenumberEvidence-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
hasMeasuredLiquidityEvidencebooleanWhether any retained liquidity evidence for the row includes measured pool balances
trendworthybooleanWhether this row is suitable for trend baselines (coverageConfidence >= 0.75, positive TVL, and primary/mixed coverage)
sourceMixRecord<string, { poolCount: number; tvlUsd: number }>TVL/pool-count mix across source families (dl, direct_api, cg_onchain, gecko_terminal, dexscreener, cg_tickers)
balanceMeasuredTvlUsdnumberTVL denominator actually used for weightedBalanceRatio
organicMeasuredTvlUsdnumberTVL 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
methodologyVersionstringMethodology version attributed to this row

`ScoreComponents`

FieldTypeDescription
tvlDepthnumberTVL depth sub-score
volumeActivitynumberVolume activity sub-score
poolQualitynumberPool quality sub-score
durabilitynumberDurability sub-score
pairDiversitynumberPair diversity sub-score

`DexLiquidityPool`

FieldTypeDescription
projectstringProtocol slug (e.g. "curve-dex", "uniswap-v3")
chainstringChain name
tvlUsdnumberPool TVL (USD)
symbolstringPool pair name (e.g. "USDC-USDT")
volumeUsd1dnumber24 h volume (USD)
poolTypestringPool 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:

FieldTypeDescription
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`

FieldTypeDescription
protocolstringDEX protocol name
chainstringChain name
pricenumberPrice from this source
tvlnumberTVL of this pool (USD)

Endpoint

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: archive

Required query parameter

ParamTypeDescription
stablecoinstringPharos stablecoin ID (required)

Optional query parameters

ParamTypeDefaultBoundsDescription
daysinteger901–365Lookback 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"
  }
]
FieldTypeDescription
tvlnumberTotal DEX TVL snapshot (USD)
volume24hnumber24 h volume at time of snapshot (USD)
score`number \null`Liquidity score at time of snapshot
datenumberUnix seconds
coverageClassstringSnapshot confidence class (primary, mixed, fallback, legacy, unobserved)
coverageConfidencenumberSnapshot confidence score
liquidityEvidenceClassstringSnapshot evidence class (measured, partial_measured, observed_unmeasured, unobserved)
hasMeasuredLiquidityEvidencebooleanWhether the snapshot qualifies as balance-measured liquidity evidence
trendworthybooleanWhether the snapshot is suitable for trend baselines rather than informational use
methodologyVersionstringMethodology version attributed to this snapshot

Endpoint

GET /api/supply-history

Per-coin circulating supply and price history, snapshotted once daily at 08:00 UTC.

Cache: archive

Required query parameter

ParamTypeDescription
stablecoinstringPharos stablecoin ID (required)

Optional query parameters

ParamTypeDefaultBoundsDescription
daysinteger3651–1825Lookback window in days

Response: Array sorted by date ascending.

json
[
  {
    "date": 1771500000,
    "circulatingUsd": 138000000000,
    "price": 1.0001
  }
]
FieldTypeDescription
datenumberUnix seconds
circulatingUsdnumberCirculating supply in USD
price`number \null`Price at snapshot time (USD); may be null for older records

Endpoint

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 (max 2 h). 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
}

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

FieldTypeDescription
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
generatedAtnumberUnix 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)

Endpoint

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,
      "digestType": "daily",
      "editionNumber": 214
    }
  ]
}

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

FieldTypeDescription
digestTextstringTweet-ready summary
digestTitle`string \null`Short headline
digestExtended`string \null`Extended commentary
generatedAtnumberUnix 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
digestType`"daily" \"weekly"`Digest cadence for this archived entry
editionNumbernumberSequential edition number within that digest cadence

Endpoint

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

ParamTypeDescription
datestringDate in YYYY-MM-DD format, or YYYY-MM-DD-weekly for weekly recap pages (required)

Response

json
{
  "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", ... }]
}
FieldTypeDescription
datestringThe requested date
inputData`object \null`Digest input data (mcap, depegs, supply changes, PSI) for this date
prevInputData`object \null`Previous day's input data for delta computation
depegEventsarrayUp to 20 depeg events active on that date, ordered by severity
blacklistEventsarrayUp to 50 blacklist events on that date

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

Endpoint

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).

Authentication: exempt

Response

json
{
  "status": "healthy",
  "timestamp": 1771856453,
  "warnings": [],
  "caches": {
    "stablecoins": { "ageSeconds": 323, "maxAge": 600, "healthy": true },
    "stablecoin-charts": { "ageSeconds": 323, "maxAge": 3600, "healthy": true },
    "usds-status": { "ageSeconds": 47118, "maxAge": 86400, "healthy": true },
    "fx-rates": {
      "ageSeconds": 1223,
      "maxAge": 1800,
      "healthy": true,
      "mode": "live",
      "sourceUpdatedAt": 1771855200,
      "sourceAgeSeconds": 323,
      "sourceStatus": "fresh",
      "warning": null,
      "consecutiveFallbackRuns": 0
    },
    "bluechip-ratings": { "ageSeconds": 22815, "maxAge": 86400, "healthy": true },
    "dex-liquidity": { "ageSeconds": 290, "maxAge": 43200, "healthy": true },
    "yield-data": { "ageSeconds": 820, "maxAge": 3600, "healthy": true },
    "dews": { "ageSeconds": 240, "maxAge": 1800, "healthy": true }
  },
  "blacklist": {
    "totalEvents": 13422,
    "missingAmounts": 0,
    "recentMissingAmounts": 0,
    "recentWindowSec": 86400,
    "missingRatio": 0
  },
  "mintBurn": {
    "totalEvents": 112345,
    "latestEventTs": 1771856430,
    "latestHourlyTs": 1771855200,
    "freshnessAgeSec": 23,
    "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": "success"
  }
}
FieldTypeDescription
statusstring"healthy" / "degraded" / "stale"
timestampnumberUnix seconds at time of response
warningsstring[]Best-effort diagnostics when health subqueries fail but the endpoint can still return a non-500 payload
cachesRecord<string, CacheStatus>Per-cache freshness status
caches["fx-rates"]CacheStatusFX cache freshness plus source-cadence diagnostics (mode, sourceUpdatedAt, sourceAgeSeconds, sourceStatus, warning, consecutiveFallbackRuns)
blacklist.totalEventsnumberTotal events in blacklist table
blacklist.missingAmountsnumberEvents where amount is null (should be 0)
blacklist.recentMissingAmountsnumberMissing-amount events inside the recent monitoring window used by status logic
blacklist.recentWindowSecnumberSize of the recent monitoring window in seconds
blacklist.missingRationumbermissingAmounts / 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.totalChatsnumberTotal subscribed Telegram chats currently stored
telegramSummary.pendingDeliveriesnumberPending 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
mintBurn.totalEventsnumberTotal mint+burn event count (aggregated from mint_burn_hourly)
mintBurn.latestEventTs`number \null`Latest raw event timestamp from mint_burn_events (observability only; does not drive endpoint health on its own)
mintBurn.latestHourlyTs`number \null`Latest hourly bucket timestamp from mint_burn_hourly
mintBurn.freshnessAgeSec`number \null`Seconds since latest mint/burn event (observability only)
mintBurn.majorStaleCountnumberNumber of configured major symbols still considered stale after sync-health gating
mintBurn.staleMajorSymbolsstring[]Symbol list still considered stale after sync-health gating
mintBurn.syncobjectCritical-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 20-minute critical-lane cadence (fresh <= 40m, degraded <= 60m, stale > 60m)
mintBurn.sync.warning`string \null`Human-readable warning when the critical lane is stale, degraded, or errored
mintBurn.sync.criticalLaneHealthybooleantrue when the latest critical-lane run is ok, degraded, or skipped_locked
circuitsRecord<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, treasury-rates, etherscan, alchemy, twitter-api, telegram-api, pyth-prices, binance-prices, coinbase-prices, redstone-prices, curve-onchain, curve-liquidity-api, fx-realtime

`CacheStatus`

FieldTypeDescription
ageSeconds`number \null`Seconds since last cron update; null if never populated
maxAgenumberExpected max-age in seconds for this cache key
healthybooleantrue when ageSeconds / maxAge ≤ 1.5
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`FX cache only: human-readable warning for fallback mode, stale source cadence, or hardcoding
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.

Overall status logic:

  • healthy — every cache impact is healthy, the public mint/burn lane is healthy, fewer than 3 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+ circuit groups are open
  • stale — any cache impact is stale, or the public mint/burn lane is stale versus its critical-lane cadence

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.

Endpoint

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).

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

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

Optional query parameters

ParamTypeDefaultDescription
detail"true"When "true", returns full history with 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
  }
}
FieldTypeDescription
current`object \null`Latest PSI score and components. null if cron has not yet run
current.scorenumberPSI score 0–100
current.bandstringCondition 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.componentsobjectComponent breakdown: severity, breadth, stressBreadth, trend
current.contributorsarrayTop per-coin contributors from input_snapshot.contributors (empty when unavailable)
current.totalMcapUsdnumberTotal ecosystem market cap from the latest input snapshot (0 when unavailable)
current.computedAtnumberUnix seconds of computation
current.methodologyVersionstringMethodology version used to compute the current score
historyarrayHistorical scores, newest first. With detail=true, each entry includes components
malformedRowsnumberCount of historical rows dropped from detail=true because persisted components JSON was malformed
history[].methodologyVersionstringMethodology version used for that history point
methodologyobjectVersion metadata for current PSI methodology context
methodology.versionstringMethodology version used by current score
methodology.changelogPathstringRelative path to full methodology changelog

Endpoint

GET /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 for unknown coin IDs or unknown OG routes
  • 503 when required cached data is not yet available

/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.

Endpoint

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

json
{
  "cards": [ReportCard, ...],
  "dependencyGraph": {
    "edges": [{ "from": "usde-ethena", "to": "usdc-circle", "weight": 0.9, "type": "collateral" }, ...]
  },
  "methodology": {
    "version": "6.8",
    "weights": { "pegStability": 0, "liquidity": 0.30, "resilience": 0.20, "decentralization": 0.15, "dependencyRisk": 0.25 },
    "pegMultiplierExponent": 0.2,
    "thresholds": [{ "grade": "A+", "min": 87 }, { "grade": "A", "min": 83 }, ...]
  },
  "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. Low-confidence redemption routes stay visible but do not uplift the score, and stale DEX inputs are not blended.

GET /api/report-cards treats the stablecoins cache and redemption-backstop snapshot as hard dependencies. DEX liquidity, bluechip ratings, and live-reserve inputs are soft dependencies: if one of those loaders is temporarily unavailable, the endpoint continues serving a degraded snapshot instead of failing closed.

`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`

FieldTypeDescription
idstringPharos stablecoin ID
namestringFull name
symbolstringTicker
overallGradestringLetter grade: "A+" through "F", or "NR"
overallScore`number \null`Weighted score 0–100. null for unrated coins
dimensionsRecord<DimensionKey, DimensionScore>Per-dimension grade, score, and detail text
ratedDimensionsnumberNumber of dimensions with data (max 5)
dependencies`DependencyWeight[] \undefined`Upstream stablecoin dependencies with collateral weights (for CeFi-Dependent coins)
rawInputsRawDimensionInputsRaw scoring inputs for client-side grade recomputation (stress testing)
isDefunctbooleantrue for cemetery coins (permanent F grade)

`DependencyWeight`: { id: string, weight: number } — upstream stablecoin ID + fraction of collateral from that source (0–1). Weights sum to ≤ 1.0; the remainder represents non-stablecoin collateral.

`RawDimensionInputs`

FieldType
pegScore`number \null`
activeDepegboolean
depegEventCountnumber
lastEventAt`number \null`
liquidityScore`number \null`
effectiveExitScore`number \null`
redemptionBackstopScore`number \null`
redemptionRouteFamily`RedemptionRouteFamily \null`
redemptionModelConfidence`"high" \"medium" \"low" \null`
redemptionUsedForLiquidityboolean
redemptionImmediateCapacityUsd`number \null`
redemptionImmediateCapacityRatio`number \null`
concentrationHhi`number \null`
bluechipGrade`BluechipGrade \null`
canBeBlacklisted`boolean \"possible" \"inherited"`
chainTierChainTier
deploymentModelDeploymentModel
collateralQualityCollateralQuality
custodyModelCustodyModel
governanceTierGovernanceType
governanceQualityGovernanceQuality
dependenciesDependencyWeight[]
navTokenboolean

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.

Dimensions: pegStability, liquidity, resilience, decentralization, dependencyRisk

Endpoint

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.

Response

json
{
  "coins": {
    "cusd-cap": {
      "stablecoinId": "cusd-cap",
      "score": 88,
      "effectiveExitScore": 56,
      "dexLiquidityScore": 29,
      "routeFamily": "basket-redeem",
      "accessModel": "permissionless-onchain",
      "settlementModel": "atomic",
      "outputAssetType": "stable-basket",
      "immediateCapacityUsd": null,
      "immediateCapacityRatio": null,
      "sourceMode": "estimated",
      "resolutionState": "resolved",
      "capacityConfidence": "heuristic",
      "capacitySemantics": "eventual-only",
      "feeConfidence": "undisclosed-reviewed",
      "feeModelKind": "undisclosed-reviewed",
      "modelConfidence": "low",
      "updatedAt": 1773350400,
      "methodologyVersion": "1.2"
    }
  },
  "methodology": {
    "version": "1.2",
    "componentWeights": {
      "access": 0.2,
      "settlement": 0.15,
      "executionCertainty": 0.15,
      "capacity": 0.25,
      "outputAssetQuality": 0.15,
      "cost": 0.1
    },
    "effectiveExitWeights": {
      "liquidity": 0.55,
      "redemption": 0.45
    }
  },
  "updatedAt": 1773350400
}

score is the direct redemption-quality score.

effectiveExitScore is the blended exit score written into the redemption snapshot when the route resolved cleanly and the reused DEX liquidity input was fresh. Report cards may still recompute liquidity from the same underlying redemption score with additional confidence gating.

methodology.version is attributed from the latest stored redemption snapshot row. 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

Top-level fields:

FieldTypeDescription
coinsRecord<string, RedemptionBackstopEntry>Current snapshot keyed by Pharos stablecoin ID
methodologyobjectVersion metadata plus component weights, effective-exit blend weights, and route-family caps
updatedAtnumberFreshest updated_at timestamp across all current rows

RedemptionBackstopEntry highlights:

FieldTypeDescription
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
routeFamilystringstablecoin-redeem, basket-redeem, collateral-redeem, psm-swap, queue-redeem, or offchain-issuer
accessModelstringpermissionless-onchain, whitelisted-onchain, issuer-api, or manual
settlementModelstringatomic, immediate, same-day, days, or queued
outputAssetTypestringstable-single, stable-basket, bluechip-collateral, mixed-collateral, or nav
sourceModestringdynamic, estimated, or static capacity provenance
resolutionStatestringresolved, missing-cache, missing-capacity, or failed
capacityConfidencestringlive-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
capacitySemanticsstringimmediate-bounded or eventual-only, distinguishing current redeemable buffer from eventual redeemability
feeConfidencestringfixed, formula, or undisclosed-reviewed fidelity tag for the fee model
feeModelKindstringfixed-bps, formula, documented-variable, or undisclosed-reviewed
modelConfidencestringOverall 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
queueEnabledbooleanWhether 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" }

Endpoint

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

ParamTypeDescription
stablecoinstringPharos stablecoin ID (required)

Optional query parameters

ParamTypeDefaultBoundsDescription
daysinteger3651–3650Lookback window in days

Response: Array sorted by date ascending.

json
[
  {
    "date": 1771977600,
    "grade": "B+",
    "score": 78,
    "prevGrade": "B",
    "prevScore": 74,
    "methodologyVersion": "5.5"
  }
]
FieldTypeDescription
datenumberUTC day bucket (Unix seconds) when the event was recorded
gradestringCurrent 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
methodologyVersionstringSafety Score methodology version used for this event row

Endpoint

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 current report-card snapshot at read time so Yield Intelligence stays aligned with /api/report-cards. PYS is benchmark-aware: it starts from cached APY inputs, adds a weighted slice of the row's benchmark spread, and then applies the current Safety Score. 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 or malformed.

Response

json
{
  "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 }
  }
}
FieldTypeDescription
rankingsYieldRanking[]All ranked stablecoins, sorted by Pharos Yield Score descending
riskFreeRatenumberDefault USD benchmark rate (%) retained for backward compatibility and mixed-view fallback
benchmarks`object \null`Benchmark registry keyed by currency (USD, EUR, CHF) with label, rate, freshness, fallback, and proxy metadata
scalingFactornumberScaling factor applied in yield score computation
medianApynumberTVL-weighted median APY (30d) across best-source rows, used as a peer reference in warning heuristics
updatedAtnumberUnix 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

`YieldRanking`

FieldTypeDescription
idstringPharos stablecoin ID
symbolstringToken symbol
namestringFull name
currentApynumberCurrent APY (%)
apy7dnumber7-day average APY (%)
apy30dnumber30-day average APY (%)
apyBase`number \null`Base APY component (%)
apyReward`number \null`Reward APY component (%), null if none
yieldSourcestringHuman-readable yield source description
yieldSourceUrl`string \null`Official URL for the selected source when Pharos has a curated or metadata-derived link
yieldTypestringYield type classification (e.g. "lending-vault", "staking")
dataSourcestringData 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; unrated coins use the default NR penalty input (40)
safetyGrade`string \null`Current Safety Score letter grade ("A+" through "F", or "NR") from /api/report-cards
yieldToRisk`number \null`Yield-to-risk ratio recomputed at read time from cached APY inputs plus the current Safety Score
excessYield`number \null`APY above risk-free rate (percentage points)
benchmarkKey`"USD" \"EUR" \"CHF" \undefined`Benchmark selected for this row's excessYield and any rate-derived APY logic
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 (%)
warningSignalsstring[]Active warning-signal flags for the selected best source
altSourcesAltYieldSource[]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

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

Endpoint

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. 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.

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

ParamTypeDescription
stablecoinstringPharos stablecoin ID (required)

Optional query parameters

ParamTypeDefaultBoundsDescription
daysinteger901–365Lookback window in days
modestringbestbest for historically selected best-source rows
sourceKeystringWhen present, returns source-specific history for that source key

Response

json
{
  "current": {
    "date": 1772000000,
    "apy": 4.21,
    "sourceKey": "onchain:usde-ethena",
    "yieldSource": "Ethena staking (sUSDe)"
  },
  "history": [YieldHistoryPoint, "..."],
  "methodology": {
    "version": "5.5",
    "currentVersion": "5.5",
    "changelogPath": "/methodology/yield-changelog/"
  }
}
FieldTypeDescription
current`YieldHistoryPoint\null`Latest row in the returned history window, or null when no history exists
historyYieldHistoryPoint[]History rows sorted by date ASC
methodologyobjectYield methodology envelope for the response

`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
}
FieldTypeDescription
datenumberUnix seconds
apynumberTotal 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)
warningSignalsstring[]Active warning-signal flags at that snapshot
sourceKey`string \null`Stable source identifier for this history row (for example a DL pool UUID or onchain:<stablecoinId>)
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
isBestbooleanWhether this row was the selected best source at that timestamp
sourceSwitchbooleanTrue when the historically selected best source changed at this row

Endpoint

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 20 minutes by the sync cron.

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

ParamTypeDefaultBoundsDescription
stablecoinstringFilter to a single stablecoin ID. Changes response shape to per-coin mode
hoursinteger241–720Lookback window for the returned hourly[] series

Response (aggregate mode — no `stablecoin` param)

json
{
  "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"], "label": "Ethereum-only" },
  "sync": { "lastSuccessfulSyncAt": 1772000200, "freshnessStatus": "fresh", "warning": null, "criticalLaneHealthy": true }
}

`gauge`

FieldTypeDescription
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"
flightToQualitybooleanWhether flight-to-quality conditions are active
flightIntensitynumberFlight-to-quality intensity (0–100). 0 when not active
trackedCoinsnumberNumber of stablecoins tracked for mint/burn flows
trackedMcapUsdnumberCombined market cap of tracked coins (USD)
intensitySemanticsstringScoring semantics version identifier (currently "signed-v2")
classificationSourcestringSource of flight-to-quality classification ("report-card-cache" or "unavailable")

Top-level metadata

FieldTypeDescription
windowHoursnumberRequested chart window for hourly[]
scopeobjectCurrent ingestion scope, currently { chainIds: ["ethereum"], label: "Ethereum-only" }
syncobjectLatest critical-lane freshness metadata, warning state, and optional classificationWarning

`CoinFlow`

FieldTypeDescription
stablecoinIdstringPharos stablecoin ID
symbolstringToken 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
has24hActivitybooleanWhether 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
netFlow24hUsdnumberRaw 24h net flow (USD, positive = net minting, negative = net burning). Fixed to the canonical 24h window even when hours changes
mintVolume24hUsdnumberTotal mint volume in the canonical 24h window (USD)
burnVolume24hUsdnumberTotal burn volume in the canonical 24h window (USD)
mintCount24hnumberNumber of mint events in the canonical 24h window
burnCount24hnumberNumber of burn events in the canonical 24h window
netFlow7dUsdnumber7-day net flow (USD)
netFlow30dUsdnumber30-day net flow (USD)
netFlow90dUsdnumber90-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`

FieldTypeDescription
hourTsnumberUnix seconds (start of hour)
netFlowUsdnumberNet flow for this hour (USD)
mintVolumeUsdnumberMint volume for this hour (USD)
burnVolumeUsdnumberBurn 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.

json
{
  "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 }
}

Endpoint

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

ParamTypeDescription
stablecoinstringPharos stablecoin ID (required)

Optional query parameters

ParamTypeDefaultBoundsDescription
directionstring"mint" or "burn"Filter by direction
chainstring"ethereum"Filter by chain ID (current production scope is Ethereum only)
burnTypestring"effective_burn", "bridge_burn", "review_required"Filter burn rows by classification
scopestring"all""all" or "counted"counted returns only rows used in economic-flow aggregates (flow_type='standard' and mint/effective-burn semantics)
minAmountnumberMinimum USD amount; unpriced rows are excluded when this filter is used
limitinteger501–500Max results
offsetinteger0Pagination offset

Response

json
{
  "events": [MintBurnEvent, ...],
  "total": 1234
}

Results are ordered by timestamp descending (most recent first).

`MintBurnEvent`

FieldTypeDescription
idstringComposite ID: {chainId}-{txHash}-{logIndex}
stablecoinIdstringPharos stablecoin ID
symbolstringToken symbol
chainIdstringChain identifier (e.g. "ethereum")
direction`"mint" \"burn"`Whether tokens were minted or burned
flowType`"standard" \"atomic_roundtrip"`Flow-noise classification; atomic_roundtrip rows are excluded from aggregate flow metrics
amountnumberAmount 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)
txHashstringTransaction hash
blockNumbernumberBlock number
timestampnumberUnix seconds
explorerTxUrlstringBlock 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.)

Endpoint

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 and tracked-but-non-active IDs return 404 with { "error": "Stablecoin not tracked" }.

Cache: standard (public, s-maxage=300, max-age=60)

Query parameters

ParamTypeDefaultDescription
stablecoinstringSingle coin mode: return latest + daily history
daysinteger30History 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.

Response (all coins)

json
{
  "signals": {
    "usdt-tether": {
      "score": 5,
      "band": "CALM",
      "signals": {
        "supply": { "value": 2, "available": true },
        "price": { "value": 1, "available": true }
      },
      "computedAt": 1740000000,
      "methodologyVersion": "4.9"
    }
  },
  "updatedAt": 1740000000,
  "malformedRows": 0,
  "methodology": {
    "version": "4.9",
    "versionLabel": "v4.9",
    "currentVersion": "4.9",
    "currentVersionLabel": "v4.9",
    "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 }
    },
    "computedAt": 1740000000,
    "methodologyVersion": "4.9"
  },
  "history": [
    {
      "date": 1739900000,
      "score": 3,
      "band": "CALM",
      "signals": {
        "supply": { "value": 1, "available": true },
        "price": { "value": 1, "available": true }
      },
      "methodologyVersion": "4.3"
    }
  ],
  "malformedRows": 0,
  "methodology": {
    "version": "4.9",
    "versionLabel": "v4.9",
    "currentVersion": "4.9",
    "currentVersionLabel": "v4.9",
    "changelogPath": "/methodology/depeg-changelog/",
    "asOf": 1740000000,
    "isCurrent": true
  }
}

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

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

Endpoint

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

  • Global public API limiter: D1-backed per-IP-hash limiter (300 requests / 60 seconds) for non-admin requests. If the distributed limiter path fails, the worker logs the failure and allows the request instead of switching to an isolate-local fallback limiter.
  • Feedback endpoint limiter: 3 submissions / 10 minutes per salted IP hash in D1.

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": ""
}
FieldTypeRequiredNotes
type`"bug" \"data-correction" \"feature-request"`YesSubmission category
titlestringConditionalRequired for bug and feature-request (3–100 chars); optional for data-correction
descriptionstringYes10–2000 chars
pageUrlstringYesRelative app path (must start with /)
websitestringNoHoneypot field; non-empty is silently accepted/dropped
expectedValue, stablecoinId, stablecoinName, pegValuestringNoOptional metadata
contactHandlestringNoOptional 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)

Endpoint

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 instead. Not the standard X-Admin-Key.

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: Always 200 OK with plain-text body ok (Telegram retries on non-2xx).

Commands handled:

  • /start — Welcome message
  • /subscribe <types> <tickers> — Subscribe to alerts (types: dews, depeg, safety, launch)
  • /subscribe <types> all — Enable one or more alert types across all tracked stablecoins
  • /unsubscribe <tickers> — Remove coin subscriptions
  • /unsubscribe all — Remove all per-coin subscriptions and disable the current DEWS/depeg/safety flags; launch flags are not reset by this path today
  • /set <ticker> <setting> <value> — Tune per-coin thresholds and modes
  • /set all <setting> <value> — Toggle global all-stablecoin alert types
  • /mute <start>-<end> — Enable UTC quiet hours
  • /unmutehours — Disable quiet hours
  • /cancel — Cancel a pending disambiguation flow
  • /list — Show current subscriptions, per-coin settings, and quiet hours
  • /help — Command reference

Reference Section

Admin Endpoints

Preferred operator access now splits by surface:

  • Browser / human operators: use https://ops.pharos.watch/admin/, which talks to same-origin /api/admin/* Pages Functions routes behind Cloudflare Access.
  • CLI / automation: call https://ops-api.pharos.watch/api/... with CF-Access-Client-Id and CF-Access-Client-Secret so Cloudflare Access can mint the request JWT the worker verifies. Direct ops-api requests also work with Cloudflare Access user/JWT headers.

Endpoint

GET /api/status

Full admin dashboard: cron run history, cache freshness for all keys, data quality metrics, Telegram bot subscriber stats, and operator reconciliation signals.

Preferred access:

  • Browser: https://ops.pharos.watch/admin/ -> same-origin /api/admin/status
  • CLI: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret> against https://ops-api.pharos.watch/api/status

Response shape: StatusResponse (defined in shared/types/index.ts)

json
{
  "timestamp": 1771856453,
  "dbHealthy": true,
  "availabilityStatus": "healthy",
  "dataQualityStatus": "healthy",
  "rawOverallStatus": "degraded",
  "overallStatus": "healthy",
  "confidence": 0.94,
  "causes": {
    "availability": [{ "code": "degraded_cron_warning", "severity": "info" }],
    "dataQuality": [],
    "overall": [{ "code": "degraded_cron_warning", "severity": "info" }]
  },
  "state": {
    "currentStatus": "healthy",
    "rawStatus": "degraded",
    "lastEvaluatedAt": 1771856453,
    "lastChangedAt": 1771856200,
    "consecutiveRaw": { "healthy": 3, "degraded": 0, "stale": 0 }
  },
  "staleness": { "ageSeconds": 0, "maxAgeSec": 1800, "isStale": false },
  "probe": {
    "timestamp": 1771856440,
    "status": "healthy",
    "sampleCount": 22,
    "passCount": 22,
    "failCount": 0,
    "p95LatencyMs": 301
  },
  "discrepancy": {
    "hasDivergence": false,
    "severityDelta": 0,
    "consecutiveDivergent": 0
  },
  "timeline": [
    {
      "id": 411,
      "from": "degraded",
      "to": "healthy",
      "rawStatus": "healthy",
      "transitionType": "recover",
      "reason": "raw-healthy-recovery-threshold",
      "confidence": 0.94,
      "at": 1771856200
    }
  ],
  "caches": { ... },
  "crons": {
    "sync-stablecoins": {
      "lastRun": { "startedAt": 1234567890, "durationMs": 2300, "status": "ok", "itemCount": 156 },
      "inFlight": null,
      "recentRuns": [...],
      "expectedIntervalSec": 900,
      "healthy": true
    }
  },
  "dataQuality": {
    "totalStablecoins": 156,
    "missingPrices": 3,
    "blacklistMissingAmounts": 0,
    "blacklistRecentMissingAmounts": 0,
    "blacklistRecentWindowSec": 86400,
    "blacklistMissingRatio": 0,
    "blacklistTotal": 13422,
    "blacklistOldestRecoverableAgeSec": 0,
    "blacklistNeverAttemptedCount": 0,
    "blacklistRepeatedFailureCount": 0,
    "onchainSupplyDivergences": 0,
    "onchainDivergenceRatio": 0,
    "onchainSupplyMonitoring": "active",
    "onchainSupplyLatestAt": 1771856300,
    "onchainSupplyTrackedCoins": 96,
    "activeDepegs": 12,
    "staleOnchainSupply": 0,
    "onchainStaleRatio": 0
  },
  "sectionErrors": {},
  "telegramBot": {
    "totalChats": 128,
    "alertEnabledChats": 123,
    "deliverableChats": 121,
    "subscribedChats": 124,
    "emptyAlertChats": 2,
    "mutedChatsWithSubscriptions": 3,
    "totalSubscriptions": 611,
    "avgSubscriptionsPerSubscribedChat": 4.9,
    "pendingDisambiguations": 1,
    "pendingDeliveries": 6,
    "lastSubscriberActivityAt": 1771856420,
    "customPreferenceChats": 47,
    "quietHoursEnabledChats": 18,
    "alertTypeChats": {
      "dews": 121,
      "depeg": 118,
      "safety": 102,
      "allTypes": 95
    },
    "topStablecoins": [
      { "stablecoinId": "usdc-circle", "symbol": "USDC", "subscribers": 82 },
      { "stablecoinId": "usdt-tether", "symbol": "USDT", "subscribers": 77 }
    ]
  },
  "datasetFreshness": {
    "stablecoins": 1771856400,
    "blacklist": 1771856200,
    "mintBurn": 1771856340,
    "supply": 1771804800,
    "safetyGrades": 1771804800,
    "yield": 1771856320,
    "depegs": 1771856010,
    "dews": 1771856400,
    "digest": 1771804800,
    "discoveryCandidates": 1771856400
  },
  "summary": {
    "unhealthyCrons": 0,
    "degradedCrons": 1,
    "cronErrors": 0,
    "worstCacheRatio": 1.03
  },
  "priceSourceHealth": {
    "sourceDistribution": {
      "coingecko": 14,
      "coingecko+defillama-list": 118,
      "defillama": 10,
      "defillama-list": 0,
      "protocol-redeem": 1,
      "defillama-contract": 4,
      "coinmarketcap": 2,
      "dexscreener": 1,
      "geckoterminal": 0,
      "cached": 4,
      "missing": 3
    },
    "confidenceDistribution": {
      "high": 127,
      "single-source": 15,
      "low": 8,
      "fallback": 6
    },
    "totalAssets": 156,
    "lastSync": 1771856400
  },
  "coingeckoPriceDiff": {
    "checkedAt": 1771856453,
    "trackedWithGeckoId": 152,
    "comparedCoins": 149,
    "mismatchedCount": 2,
    "thresholdPct": 5,
    "rows": [
      {
        "stablecoinId": "pyusd-paypal",
        "symbol": "PYUSD",
        "name": "PayPal USD",
        "geckoId": "paypal-usd",
        "ourPrice": 0.944,
        "coinGeckoPrice": 1.002,
        "diffPct": 5.79,
        "priceSource": "defillama",
        "priceConfidence": "single-source"
      }
    ]
  },
  "d1Usage": {
    "checkedAt": 1771856453,
    "windowStart": 1771770053,
    "windowEnd": 1771856453,
    "databaseId": "8f3f54ca-e035-4cdf-9ec5-a4fbbe48b27a",
    "databaseName": "stablecoin-db",
    "databaseSizeBytes": 1589248000,
    "numTables": 56,
    "region": "EEUR",
    "readReplicationMode": "disabled",
    "readQueries24h": 942012,
    "writeQueries24h": 709241,
    "rowsRead24h": 1633139670,
    "rowsWritten24h": 1555568
  },
  "liquidityHealth": {
    "lastRunStatus": "degraded",
    "currentCoverage": 120,
    "previousCoverage": 125,
    "currentGlobalTvl": 123000000,
    "previousGlobalTvl": 125000000,
    "currentTop10CoveredTvl": 100000000,
    "previousTop10CoveredTvl": 102000000,
    "failedSources": ["defillama-yields"],
    "nearCoverageGuard": false,
    "nearValueGuard": false,
    "nearMajorCoverageGuard": false,
    "currentCoverageClasses": { "primary": 80, "mixed": 20, "fallback": 20, "legacy": 0, "unobserved": 36 },
    "previousCoverageClasses": { "primary": 82, "mixed": 18, "fallback": 25, "legacy": 0, "unobserved": 31 }
  },
  "discoveryCandidates": [
    {
      "id": 12,
      "geckoId": "usdq",
      "llamaId": null,
      "name": "USDQ",
      "symbol": "USDQ",
      "marketCap": 18200000,
      "source": "coingecko",
      "firstSeen": 1771683600,
      "lastSeen": 1771856400,
      "daysSeen": 2,
      "dismissed": false
    }
  ],
  "mintBurnReconciliation": {
    "checkedAt": 1771856453,
    "comparedCoins": 42,
    "criticalCount": 1,
    "warnCount": 3,
    "insufficientCount": 12,
    "rows": [
      {
        "stablecoinId": "usdt-tether",
        "symbol": "USDT",
        "flowNet24hUsd": -240000000,
        "chainSupplyDelta24hUsd": -220000000,
        "absoluteDiffUsd": 20000000,
        "diffRatio": 0.08,
        "status": "warn",
        "coverageStatus": "full"
      }
    ]
  }
}

dataQuality.onchainSupplyTrackedCoins counts only coins with at least one onchain_supply row inside the current 3-day active monitoring window. Older historical rows are excluded from staleOnchainSupply and onchainStaleRatio.

Ratio-based on-chain status thresholds apply only when dataQuality.onchainSupplyTrackedCoins >= 10; below that floor, the counts remain visible but do not by themselves escalate dataQualityStatus.

itemCount and dataQuality.totalStablecoins are illustrative example values. In the live handler they reflect the current cached stablecoin payload size, not TRACKED_STABLECOINS.length.

crons[*].healthy reflects availability impact. Fresh cron runs with status="degraded" are warning-only and counted in summary.degradedCrons, but they do not mark availability unhealthy on their own.

availabilityStatus also inherits the shared public-health floor used by /api/health: cache-impact status, the critical mint/burn lane's public warning/staleness contract, and 3+ open circuit groups can degrade availability even when cron freshness alone is still green.

crons[*].inFlight is present when a leased cron is actively reporting cron_run_progress and the matching cron_leases row is still active for the same owner. It includes startedAt, updatedAt, stage, optional itemsDone/itemsTotal, optional message/metadata, and a stale flag when the heartbeat stops updating.

overallStatus is the effective (hysteresis-smoothed) status. rawOverallStatus is the immediate worst-of availability/data-quality signal.

dbHealthy=false means the DB sentinel failed (SELECT 1), so status is forced to at least degraded and data-quality/database freshness queries are skipped.

telegramBot is null when the Telegram tables are unavailable in the current environment (for example, migrations not yet applied in dev/staging). The rest of /api/status still resolves normally.

sectionErrors is a machine-readable map of subsection loader failures. When an individual status subsection fails (for example Telegram stats, discovery backlog, CoinGecko price drift, D1 usage telemetry, liquidity health, reserve drift, or mint/burn reconciliation), /api/status still returns 200, keeps the unaffected sections intact, and records the degraded subsection under sectionErrors with a stable code plus an operator-facing message.

crons["dispatch-telegram-alerts"].lastRun.metadata now carries a richer delivery breakdown, including fields such as freshAttempted, freshSent, freshRetryQueued, freshPermanentFailures, pendingAttempted, pendingDrained, pendingRetryQueued, pendingDropped, pendingEnqueued, and expanded eventsDetected counters (depegTriggered, depegResolved, depegWorsening, suppressedMethodologyChanges).

datasetFreshness covers the key operator-visible datasets written by the pipeline: cache-backed stablecoins, blacklist, mint/burn, supply snapshots, safety-grade history, yield, depeg/dews tables, daily digest, and discovery backlog timestamps.

priceSourceHealth is derived from the final sync-stablecoins asset payload and summarizes resolved price-source distribution, confidence buckets, total assets, and the timestamp of the latest successful price-health snapshot. CoinGecko-vs-Pharos divergence details live in the separate coingeckoPriceDiff block.

coingeckoPriceDiff is an admin-only live comparison block. It reads the cached tracked assets with geckoId, fetches current CoinGecko spot prices through one or more batched simple/price calls, and reports the rows where abs(pharosPrice - coinGeckoPrice) / coinGeckoPrice > 0.05. The field is null when the comparison is unavailable in the current environment or when the loader fails; failures are surfaced through sectionErrors.coingeckoPriceDiff.

d1Usage is an admin-only live D1 telemetry block. It uses Cloudflare's D1 database info endpoint plus a trailing-24h d1AnalyticsAdaptiveGroups GraphQL query to surface current storage size, table count, replication mode, and recent query/row volume. The field is null until CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_D1_STATUS_API_TOKEN, and CLOUDFLARE_D1_DATABASE_ID are configured on the worker; loader/config failures are surfaced through sectionErrors.d1Usage.

liquidityHealth is derived from the latest sync-dex-liquidity cron metadata and summarizes row coverage, value coverage, major-asset coverage, failed sources, and current/previous coverage-class distribution for the operator dashboard.

discoveryCandidates exposes the current untracked-coverage backlog from discovery_candidates, ordered by market cap for the /status operator workflow.

mintBurnReconciliation compares 24h Ethereum mint/burn net flow (mint_burn_hourly) against the cached stablecoins payload's Ethereum chain-supply delta (chainCirculating.ethereum.current - circulatingPrevDay). It is intended for operator diagnostics, not public scoring.

Endpoint

GET /api/status-history

Machine-readable status timeline endpoint for tooling and incident analysis.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
limitinteger50Number of transitions to return (1–200)
from`integer \ISO date`Optional lower bound for transition created_at (Unix seconds/milliseconds or ISO date)
to`integer \ISO date`Optional upper bound for transition created_at (Unix seconds/milliseconds or ISO date)

Response shape: StatusHistoryResponse (defined in shared/types/index.ts)

Endpoint

GET /api/request-source-stats

Admin-only public-API attribution summary. Aggregates minute-bucketed request counts into a requested window so operators can estimate what share of api.pharos.watch load is coming from browser-identified Pharos traffic versus external consumers.

This dataset excludes admin-only routes, /api/telegram-webhook, and the internal site-api / /_site-data/* website lane. First-party website traffic on the public API host is recognized from browser evidence: Origin or Referer matching https://pharos.watch, or the browser-safe frontend Accept marker combined with same-site fetch metadata. Everything else in scope is counted as external.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
hoursinteger24Window size in hours (1840, currently 35 days)
bucketSecinteger3600Time-bucket rollup size in seconds (6086400)
routeLimitinteger20Max per-route rows returned in the route breakdown

Response shape: PublicApiRequestSourceStatsResponse (defined in shared/types/index.ts)

PublicApiRequestSourceStatsResponse includes:

  • generatedAt — Unix seconds when the response was generated
  • window — requested from/to, durationSec, bucketSizeSec, routeLimit, and current retentionDays
  • totals — aggregate webRequests, externalRequests, totalRequests, webSharePct, externalSharePct
  • routes[] — normalized per-route breakdown sorted by total request volume
  • buckets[] — time-series rollups using the requested bucketSec

Endpoint

GET /api/api-keys

Admin-only API key inventory. Returns masked tokens plus metadata, but never returns stored secret material.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response shape: ApiKeyListResponse (defined in shared/types/api-keys.ts)

Endpoint

POST /api/api-keys

Admin-only API key creation route.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Body shape: ApiKeyCreateRequest

FieldTypeRequiredDescription
namestringYesDisplay name for the key
ownerEmailstringNoOptional operator / owner contact
tierstringNoFree-form tier label; defaults to "standard"
rateLimitPerMinuteintegerNoPer-key threshold (110000, default 120)

Response shape: ApiKeyCreateResponse

token is returned only once. Persist it immediately; later list/read paths expose only maskedToken.

Endpoint

POST /api/api-keys/:id/update

Admin-only metadata update for an existing API key.

Body shape: ApiKeyUpdateRequest

Accepted fields:

  • name
  • ownerEmail
  • tier
  • rateLimitPerMinute
  • isActive

Response shape: ApiKeyMutationResponse

Endpoint

POST /api/api-keys/:id/deactivate

Admin-only hard deactivation for an existing API key. This sets isActive=false; the secret cannot be used afterward.

Response shape: ApiKeyMutationResponse

Endpoint

POST /api/api-keys/:id/rotate

Admin-only secret rotation. The old token stops working immediately and a new plaintext token is returned once.

Response shape: ApiKeyRotateResponse

Endpoint

POST /api/backfill-depegs

Backfills historical depeg events from stored price data.

For coins with a registered authoritative historical price provider, the backfill uses that same provider family first (for example, replayed protocol redemption quotes) before falling back to CoinGecko/DefiLlama market history. If the authoritative provider is configured but unavailable, existing source='backfill' rows for that coin are preserved instead of being rebuilt from a weaker source.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
stablecoinstringProcess a single stablecoin ID
batchinteger0Batch offset (3 coins per batch)

Endpoint

POST /api/backfill-supply-history

Backfills per-coin supply history snapshots.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
stablecoinstringProcess a single stablecoin ID
batchinteger0Batch offset for chunked processing
batchSizeinteger10Coins per batch
allow-constant-price-fallback"true"Allow current-price fallback when historical non-USD prices are missing

Endpoint

POST /api/backfill-stability-index

Backfills historical stability index scores from stored depeg events and supply data.

The rebuild now stops at the last completed UTC day; it does not write a stability_index row for the current UTC day. Historical market-cap denominators in this replay path are bounded to the PSI-eligible universe (tracked coins plus configured shadow assets). Historical depeg severity is replayed from same-day supply_history.price when a usable day price exists, with peak_deviation_bps used only as a fallback for missing/invalid price coverage. For methodology v3.0+, the replay also derives daily stressBreadth from stress_signal_history rows in ALERT, WARNING, or DANGER bands. The response includes the evaluated startDay/endDay so operators can confirm the rebuild window.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
dry-run"true"Preview the rebuild window and change summary without mutating stability_index
startDay`integer \ISO date (YYYY-MM-DD)`earliest depeg dayLower bound for rebuilt UTC days
endDay`integer \ISO date (YYYY-MM-DD)`last completed UTC dayUpper bound for rebuilt UTC days

Endpoint

POST /api/backfill-cg-prices

Backfills CoinGecko historical prices into the price_cache table for more accurate depeg detection.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
stablecoinstringProcess a single stablecoin ID
batchSizeinteger10Coins per batch
batchinteger0Batch offset for chunked processing

Endpoint

POST /api/backfill-mint-burn-prices

Repairs incomplete mint/burn valuation metadata using a historical-first policy. The endpoint now prefers event-day supply_history prices for rows with amount_usd IS NULL and only derives audit fields for already-valued rows. It no longer bulk-fills historical amount_usd from the current price_cache snapshot.

Cron sync-mint-burn automatically heals recent NULL-price events within a 48-hour window and reports the healed count in cron metadata as nullPricesHealed; this endpoint is primarily for historical backfills beyond that window.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response

json
{
  "totalUpdated": 15000,
  "rowsValued": 14000,
  "rowsAudited": 1000,
  "rowsStillUnpriced": 85,
  "rowsStillMissingAudit": 320,
  "coins": [
    {
      "id": "usdt-tether",
      "updated": 49,
      "valued": 41,
      "audited": 8,
      "stillUnpriced": 0,
      "stillMissingAudit": 2
    }
  ]
}

Endpoint

GET /api/backfill-dews

Validates DEWS against historical depeg events. Reports true-positive rate and average lead time.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Endpoint

POST /api/backfill-mint-burn

Backfills mint/burn event ingestion for a specific contract config using the same parsing/classification pipeline as the cron. If configKey is omitted, the worker auto-selects one Ethereum config using a critical-first / major-symbol-first / most-behind policy and returns the selected config in the response.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Request body or query parameters

ParamTypeDefaultDescription
configKeystringauto-selectedOptional config key: {chainId}-{contractAddress} (currently Ethereum-only)
fromBlockintegerfrom sync stateStart block override
toBlockintegerchain headEnd block override (clamped to chain head)
chunkSizeinteger50000Block span per fetch chunk (max 50000)
maxChunksinteger24Maximum chunks to process per request

Endpoint

POST /api/reclassify-atomic-roundtrips

Retroactively tags same-transaction mint+burn pairs for the same stablecoin as flow_type='atomic_roundtrip' and recalculates the affected hourly buckets.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response

json
{
  "done": false,
  "updated": 428,
  "hoursRecalculated": 31,
  "batchSize": 1000
}

The endpoint processes up to 1000 (tx_hash, stablecoin_id) groups per request. Repeat until done=true.

Endpoint

GET /api/audit-depeg-history?dry-run=true

Dry-run preview for the depeg audit endpoint. This is the only supported GET mode for /api/audit-depeg-history; all mutating executions require POST.

The same endpoint also supports a dry-run historical repair preview with repair=synthetic-splits, which surfaces adjacent same-direction live events that were likely split by the old DEX-only auto-close behavior.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
limitinteger200Max events to audit
offsetinteger0Pagination offset
dry-run"true"requiredMust be exactly "true" for GET
min-supplynumber0Minimum supply (USD) to include in audit
symbolstringFilter by symbol (case-insensitive)
repair"synthetic-splits"Preview synthetic split-event consolidation candidates instead of the CoinGecko false-positive audit

Endpoint

POST /api/audit-depeg-history

Audits existing depeg events against CoinGecko historical price data to detect false positives.

POST /api/audit-depeg-history?repair=synthetic-splits instead runs a historical repair pass that consolidates adjacent same-direction live events when the earlier event was closed near peg and the next event reopened one sync later with a still-severe deviation. This is intended for repairing synthetic splits caused by the retired DEX-only auto-close behavior.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

GET is accepted only with dry-run=true; mutating audits require POST.

Query parameters

ParamTypeDefaultDescription
limitinteger200Max events to audit
offsetinteger0Pagination offset
deletestringComma-separated event IDs to delete directly (skips CG audit)
dry-run"true"When "true", preview deletions without touching DB. Default behavior deletes false positives
min-supplynumber0Minimum supply (USD) to include in audit
symbolstringFilter by symbol (case-insensitive)
repair"synthetic-splits"Run synthetic split-event consolidation instead of the CoinGecko false-positive audit

Endpoint

POST /api/trigger-digest

Queues a background daily-digest regeneration, bypassing the normal 1-hour dedup check. Routed through worker/src/router.ts.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response

json
{
  "ok": true,
  "accepted": true,
  "requestId": "manual-digest-..."
}

Status: 202 Accepted

The worker enqueues the digest run in waitUntil() and returns immediately so the Access-gated ops proxy does not need to hold the HTTP request open for the full Anthropic generation window. The background run is still logged against the daily-digest cron history, including manual skipped_locked outcomes when another digest run already holds the lease.

Unhandled pre-enqueue failures are wrapped by the shared error handler and return 500 with { "error": "Internal Server Error" }.

Endpoint

POST /api/reset-blacklist-sync

Rolls back blacklist sync state to re-scan missed events. EVM chains are rolled back by 50,000 blocks; Tron is rolled back by 7 days. Routed through worker/src/router.ts.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response (evmReset / tronReset are row-change counts from the blacklist_sync_state UPDATE, not block numbers)

json
{
  "ok": true,
  "evmReset": 5,
  "tronReset": 2
}

Endpoint

GET /api/debug-sync-state

Returns current blacklist sync state for all configured chains. Useful for diagnosing sync issues. Routed through worker/src/router.ts.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Response

json
[
  { "config_key": "ethereum-usdc", "last_block": 19500000 },
  { "config_key": "tron-usdt", "last_block": 1740000000000 }
]

Endpoint

POST /api/remediate-blacklist-amount-gaps

Admin-only bounded remediation endpoint for recoverable blacklist rows.

Authentication: same admin auth as other ops endpoints.

Idempotency: supported via optional Idempotency-Key.

Inputs

  • chainId?: string
  • stablecoin?: "USDC" | "USDT" | "PAXG" | "XAUT" | "PYUSD" | "USD1"
  • limit?: number default 25, max 200
  • dryRun?: boolean default true
  • onlyMissingProvenance?: boolean default true
  • maxAttempts?: number default 25

Dry-run response

json
{
  "ok": true,
  "dryRun": true,
  "candidateCount": 26,
  "resolutionCounts": {
    "resolved": 26,
    "missing_config": 0,
    "ambiguous_config": 0
  }
}

Write-enabled response

json
{
  "ok": true,
  "dryRun": false,
  "applied": {
    "resolved": 26,
    "resolvedZero": 26,
    "providerFailed": 0,
    "configMissing": 0,
    "configAmbiguous": 0,
    "budgetUsed": 26,
    "budgetLimit": 900
  }
}

Endpoint

POST /api/backfill-blacklist-current-balances

Admin-only one-shot backfill endpoint for blacklist_current_balances, intended for blacklist configs whose historical events were ingested before the current-balance cache existed.

Authentication: same admin auth as other ops endpoints.

Idempotency: supported via optional Idempotency-Key.

Query parameters

ParamTypeDefaultDescription
stablecoinstringOptional uppercase symbol filter (USDC, USDT, PAXG, XAUT, PYUSD, USD1)
chainIdstringOptional chain filter matching the blacklist contract config chainId
limitinteger500Max blacklist-event rows to load per matching config (max 2000)
dryRun"true"Preview the active-blacklisted candidate count without writing cache rows

400 is returned when the filters match no configured blacklist contracts.

Dry-run response

json
{
  "ok": true,
  "dryRun": true,
  "configs": [
    {
      "configKey": "ethereum-pyusd",
      "stablecoin": "PYUSD",
      "chainId": "ethereum",
      "candidateCount": 12,
      "updated": 0,
      "deleted": 0,
      "failed": 0
    }
  ],
  "totals": {
    "candidates": 12,
    "updated": 0,
    "deleted": 0,
    "failed": 0
  },
  "budgetUsed": 0,
  "budgetLimit": 900
}

Write-enabled response

json
{
  "ok": true,
  "dryRun": false,
  "configs": [
    {
      "configKey": "ethereum-pyusd",
      "stablecoin": "PYUSD",
      "chainId": "ethereum",
      "candidateCount": 500,
      "updated": 12,
      "deleted": 0,
      "failed": 1
    }
  ],
  "totals": {
    "candidates": 500,
    "updated": 12,
    "deleted": 0,
    "failed": 1
  },
  "budgetUsed": 37,
  "budgetLimit": 900
}

Endpoint

GET /api/discovery-candidates

Returns stablecoins tracked by CoinGecko or DefiLlama that Pharos does not yet monitor, surfaced by the Monday CoinGecko discovery scan plus quarter-hourly DefiLlama residual upserts.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Query parameters

ParamTypeDefaultDescription
status`"active" \"dismissed" \"all"`"active"Filter by candidate status
limitinteger50Max results (max 200)
offsetinteger0Pagination offset

Malformed limit / offset values return 400 instead of silently defaulting.

Response

json
{
  "candidates": [DiscoveryCandidate, ...],
  "total": 12
}

`DiscoveryCandidate` fields

FieldTypeDescription
idnumberInternal candidate ID
geckoId`string \null`CoinGecko coin ID
llamaId`number \null`DefiLlama stablecoin ID
namestringAsset name
symbolstringTicker symbol
marketCap`number \null`Latest known market cap (USD)
source`"coingecko" \"defillama" \"both"`Which discovery source detected this asset
firstSeennumberUnix seconds when first discovered
lastSeennumberUnix seconds of most recent detection
daysSeennumberNumber of days the candidate has been observed
dismissedbooleanWhether this candidate has been dismissed

Endpoint

POST /api/discovery-candidates/:id/dismiss

Dismisses a discovery candidate so it no longer appears in the active list. Dismissed candidates will not resurface unless their market cap crosses 10× the value at dismissal time.

Direct ops-api CLI example: CF-Access-Client-Id: <id> and CF-Access-Client-Secret: <secret>

Path parameter: :id — candidate ID from GET /api/discovery-candidates

Response

json
{ "ok": true }

Error responses: 404 if the candidate is not found or is already dismissed.