---
title: API Reference
product: tide
doc_type: api_reference
version: master
source: git2docs (code-derived, validation-filtered)
canonical: https://git2docs.com/wabam/docs/tide/tide-capital-pressure-observatory/api-reference
---

# API Reference

_FastAPI endpoint documentation_

## Description

The TIDE API is a read-only FastAPI service that exposes all data the SvelteKit frontend needs to render the Capital Pressure Observatory dashboard. Use these endpoints directly when you want to integrate TIDE data into your own tooling, debug ingestion health, or inspect raw readings without opening a browser. Every endpoint returns JSON; all endpoints except `/health` are mounted under the `/api` prefix.

## Parameters

Parameters vary by endpoint. The table below covers every endpoint in the API.

| Endpoint | Method | Path parameter | Query parameter | Type | Required | Description |
|---|---|---|---|---|---|---|
| `GET /health` | GET | — | — | — | — | No parameters. |
| `GET /api/dashboard` | GET | — | — | — | — | No parameters. Returns the full dashboard payload in one call. |
| `GET /api/metrics/{metric_id}/history` | GET | `metric_id` | — | `string` | Yes | ID of the metric whose raw observation history you want. Must match a registered metric ID (e.g. `hy_spread`, `m2_yoy`). |
| `GET /api/metrics/{metric_id}/history` | GET | — | `since` | `date` (`YYYY-MM-DD`) | No | If provided, only observations on or after this date are returned. Omit to receive the full history. |
| `GET /api/metrics/{metric_id}/detail` | GET | `metric_id` | — | `string` | Yes | ID of the metric you want full detail for. Returns registry metadata, the latest reading, and the computed indicator + z-score series. |
| `GET /api/tier/{tier}/history` | GET | `tier` | — | `integer` | Yes | Tier number: `1`, `2`, `3`, or `4`. |
| `GET /api/sources` | GET | — | — | — | — | No parameters. Returns ingestion health for every registered metric. |
| `GET /api/scheduler` | GET | — | — | — | — | No parameters. Returns status for every configured cron job. |
| `GET /api/watchlist` | GET | — | — | — | — | No parameters. Returns current readings for all eight healthcare watchlist tickers. |

## Returns

All endpoints return JSON. The shapes below match the Pydantic schemas that form the wire contract between the API and the SvelteKit frontend.

---

### `GET /health`
Returns a simple liveness object. Use this to confirm the backend process is running before making other requests.

```json
{ "status": "ok" }
```

---

### `GET /api/dashboard` → `DashboardOut`
The largest response in the API. The SvelteKit page server calls this once per request and distributes the payload to every component on the page. Fields:

| Field | Type | Description |
|---|---|---|
| `composite` | `CompositeOut \| null` | The composite Capital Pressure reading. `null` if no metrics have data yet. |
| `composite.z` | `float` | Equal-weight average of the four tier z-score averages, using each metric's `directional_z`. |
| `composite.z_label` | `string` | Human-readable label for the current z-score level (e.g. `"Elevated"`). |
| `composite.sign_class` | `string` | CSS class hint for coloring (e.g. `"bear"`, `"bull"`, `"neutral"`). |
| `composite.tally` | `TallyOut` | Counts of Bullish / Neutral / Bearish votes across all composite-eligible metrics. |
| `composite.tally.bull` | `int` | Number of indicators voting Bullish. |
| `composite.tally.neutral` | `int` | Number of indicators voting Neutral. |
| `composite.tally.bear` | `int` | Number of indicators voting Bearish. |
| `composite.metrics_total` | `int` | Total number of indicators included in the composite tally. |
| `tiers` | `list[TierOut]` | One entry for each of the four tiers, always present (may have an empty `metrics` list if no data has been ingested). |
| `tiers[].tier` | `int` | Tier number: 1–4. |
| `tiers[].name` | `string` | Tier display name (e.g. `"Macro Liquidity"`). |
| `tiers[].tag` | `string` | Short descriptor (e.g. `"The base · multi-year"`). |
| `tiers[].cadence` | `string` | Typical update frequency summary (e.g. `"5 series · monthly–weekly cadence"`). |
| `tiers[].avg_z` | `float \| null` | Simple mean of the z-scores of all metrics in this tier that returned a reading. `null` if the tier has no readings. |
| `tiers[].metrics` | `list[ReadingOut]` | Per-metric cards for this tier. See `ReadingOut` below. |
| `composite_history` | `list[CompositeHistoryPoint]` | Up to 252 business days of composite z-score history, used to draw the SVG history chart. |
| `composite_history[].ts` | `date` | Observation date. |
| `composite_history[].z` | `float` | Composite z-score on that date. |
| `composite_history[].spy_close` | `float \| null` | SPY closing price on that date, for overlay. `null` if not yet populated. |
| `composite_history[].tier_zs` | `dict[str, float]` | Per-tier average z-scores keyed by string (`"1"`–`"4"`). Keys are absent if that tier had no data for that date. |

**`ReadingOut` fields** (appears inside `tiers[].metrics`):

| Field | Type | Description |
|---|---|---|
| `metric_id` | `string` | Unique registry identifier. |
| `name` | `string` | Display name. |
| `tier` | `int` | Tier number (1–4). |
| `source` | `string` | Data source label (e.g. `"FRED"`, `"CFTC"`). |
| `unit` | `string` | Unit of the value (e.g. `"bps"`, `"%"`). |
| `value` | `string` | Formatted current value. |
| `delta` | `string` | Formatted change from the previous period. |
| `delta_class` | `string` | CSS class hint for the delta direction. |
| `z` | `float` | Rolling z-score (3-year window by default; some metrics use 1-year — see notes). |
| `z_label` | `string` | Human-readable z-score label. |
| `z_class` | `string` | CSS class hint for the z-score level. |
| `as_of` | `date` | Observation date for this reading. |
| `sparkline` | `list[float]` | Short recent price/value series for the inline sparkline chart. |
| `vote` | `VoteOut` | The indicator's current vote. |
| `vote.direction` | `string` | `"bull"`, `"neutral"`, or `"bear"`. |
| `vote.reason` | `string` | One-line explanation of why this vote was cast. |
| `include_in_composite` | `bool` | `true` if this metric's directional z enters the composite average; `false` for loadedness/fragility gauges that appear in tier cards but are excluded from the tally. |
| `description` | `string \| null` | Longer text description from the metric registry. |

---

### `GET /api/metrics/{metric_id}/history` → `HistoryOut`
Returns the raw observation series for a single metric — the values exactly as stored in the `observations` table, without z-score transformation.

| Field | Type | Description |
|---|---|---|
| `metric_id` | `string` | Echoes back the requested metric ID. |
| `points` | `list[HistoryPoint]` | Chronologically ordered list of observations. |
| `points[].ts` | `date` | Observation date. |
| `points[].value` | `float` | Raw observed value on that date. |

---

### `GET /api/metrics/{metric_id}/detail` → `MetricDetailOut`
Returns everything the API knows about one metric: its registry definition, the latest rendered reading card, and the full computed indicator + rolling z-score series.

| Field | Type | Description |
|---|---|---|
| `metric_id` | `string` | Unique registry ID. |
| `name` | `string` | Display name. |
| `tier` | `int` | Tier number (1–4). |
| `source` | `string` | Data source label. |
| `source_kind` | `string` | Source category (e.g. `"fred"`, `"yahoo"`, `"cftc"`). |
| `source_series` | `string \| null` | Upstream series identifier where applicable (e.g. a FRED series ID). `null` for sources that do not use a named series. |
| `unit` | `string` | Unit of measurement. |
| `cadence` | `string` | Typical publication frequency. |
| `description` | `string \| null` | Longer explanation from the metric registry. |
| `direction_kind` | `string` | Whether a higher value is bullish or bearish (governs directional z sign). |
| `indicator_kind` | `string` | `"yoy"` (year-over-year % change) or `"level"` (raw level). |
| `indicator_window` | `int` | Rolling window in days used to compute the indicator. |
| `indicator_lag` | `int` | Lag in days applied before computing the indicator (used for `yoy`). |
| `zscore_years` | `int` | Rolling z-score lookback in years. Default is `3`; some metrics use `1` (see notes). |
| `include_in_composite` | `bool` | Whether this metric's z-score enters the composite average. |
| `reading` | `ReadingOut \| null` | The latest rendered reading card. `null` for stubbed metrics or metrics with no data. |
| `series` | `list[DetailPoint]` | Full computed indicator + z-score series, matching what the reading card uses. |
| `series[].ts` | `date` | Observation date. |
| `series[].indicator` | `float` | Computed indicator value (yoy% or raw level). |
| `series[].z` | `float \| null` | Rolling z-score on that date. `null` before the lookback window is filled. |
| `obs_count` | `int` | Total number of raw observations stored for this metric. |

---

### `GET /api/tier/{tier}/history` → `TierHistoryOut`
Returns the per-tier average z-score series populated by `tide-ingest backfill-composite`. Use this to draw regime charts for a single tier over time.

| Field | Type | Description |
|---|---|---|
| `tier` | `int` | Tier number (1–4). |
| `name` | `string` | Tier display name. |
| `tag` | `string` | Short tier descriptor. |
| `points` | `list[TierHistoryPoint]` | Chronologically ordered tier z-score history. |
| `points[].ts` | `date` | Observation date. |
| `points[].z` | `float` | Tier average z-score on that date. |
| `latest_z` | `float \| null` | Most recent z-score in `points`. `null` if no history has been backfilled yet. |

---

### `GET /api/sources` → `SourcesOut`
Returns ingestion health metadata for every metric registered in the system. Use this to check which metrics are live vs. stubbed, how recently each was ingested, and how many observations are stored.

| Field | Type | Description |
|---|---|---|
| `rows` | `list[SourceRow]` | One entry per registered metric, including stubbed metrics. |
| `rows[].metric_id` | `string` | Registry ID. |
| `rows[].name` | `string` | Display name. |
| `rows[].tier` | `int` | Tier number (1–4). |
| `rows[].source` | `string` | Data source label. |
| `rows[].source_kind` | `string` | Source category. |
| `rows[].cadence` | `string` | Publication frequency. |
| `rows[].status` | `string` | `"live"` if the metric has an ingest function; `"stub"` if it is registered but not yet fetching data. |
| `rows[].last_observation` | `date \| null` | Date of the most recent stored observation. `null` if no data has been ingested. |
| `rows[].last_ingested_at` | `datetime \| null` | Timestamp when the most recent observation was written to the database. `null` if no data has been ingested. |
| `rows[].obs_count` | `int` | Total observation count stored for this metric. |

---

### `GET /api/scheduler` → `SchedulerOut`
Returns the current state of every configured cron job. State is read from the `scheduler_status` DuckDB table, which the Scheduler process writes to after each run.

| Field | Type | Description |
|---|---|---|
| `jobs` | `list[SchedulerJob]` | One entry per configured job. |
| `jobs[].id` | `string` | Job identifier (matches the key used in `scheduler_status`). |
| `jobs[].label` | `string` | Short display name. |
| `jobs[].description` | `string` | What the job does. |
| `jobs[].schedule_repr` | `string` | Human-readable cron schedule string. |
| `jobs[].last_run_at` | `datetime \| null` | When the job last started. `null` if the Scheduler has never run. |
| `jobs[].last_success_at` | `datetime \| null` | When the job last completed without error. `null` if it has never succeeded. |
| `jobs[].last_error` | `string \| null` | Error message from the most recent failed run. `null` if no errors have occurred. |
| `jobs[].last_error_at` | `datetime \| null` | When the most recent error occurred. `null` if no errors have occurred. |
| `jobs[].next_run_at` | `datetime \| null` | When the job is next scheduled to run. `null` if the Scheduler is not running. |

---

### `GET /api/watchlist` → `WatchlistOut`
Returns current readings for all eight healthcare watchlist tickers, each benchmarked against XLV.

| Field | Type | Description |
|---|---|---|
| `rows` | `list[WatchlistRowOut]` | One entry per watchlist ticker. |
| `rows[].ticker` | `string` | Stock ticker symbol. |
| `rows[].name` | `string` | Company display name. |
| `rows[].sub` | `string` | Sub-sector or brief description. |
| `rows[].price` | `float \| null` | Most recent closing price. `null` if price data is unavailable. |
| `rows[].target` | `float` | Analyst price target. |
| `rows[].fwd_pe` | `string` | Forward P/E ratio, formatted as a string. |
| `rows[].status` | `string` | Qualitative status label. |
| `rows[].return_30d` | `float \| null` | Absolute 30-day return for this ticker. `null` if insufficient history. |
| `rows[].return_30d_relative` | `float \| null` | 30-day return of this ticker minus the 30-day return of XLV — the relative return. `null` if insufficient history. |
| `rows[].sparkline` | `list[float]` | Recent closing prices for the inline sparkline chart. |
| `rows[].as_of` | `date \| null` | Date of the most recent price observation. |
| `benchmark` | `string` | Always `"XLV"` — the healthcare sector ETF used as the relative-return benchmark. |

## Errors

All endpoints return standard HTTP error responses.

| Endpoint | Status code | When it occurs |
|---|---|---|
| Any | `500 Internal Server Error` | Unhandled exception in the route handler, typically a DuckDB read error or a data shape mismatch. Check the backend process logs. |
| `GET /api/metrics/{metric_id}/history` | `404 Not Found` | `metric_id` is not registered in the metric registry. The response body contains `{ "detail": "Unknown metric: <metric_id>" }`. |
| `GET /api/metrics/{metric_id}/detail` | `404 Not Found` | Same as above — `metric_id` is not in the registry. |
| `GET /api/tier/{tier}/history` | `404 Not Found` | `tier` is not one of `1`, `2`, `3`, `4`. The response body contains `{ "detail": "Unknown tier: <tier>" }`. |

Note that all other endpoints (`/health`, `/api/dashboard`, `/api/sources`, `/api/scheduler`, `/api/watchlist`) have no path parameters and therefore cannot produce `404` errors for bad identifiers. If the database file is missing or unreadable, those endpoints will return `500`.

## Notes

**Base URL and CORS**

The FastAPI backend is a separate process from the SvelteKit frontend. In development, CORS is enabled for `http://localhost:5173` and `http://127.0.0.1:5173` so that client-side fetches (for example, timeframe tab switches and history endpoint calls) work without a proxy. In production, SSR fetches go server-to-server and do not cross origins. All API methods are `GET`-only; there are no mutation endpoints.

---

**The API is read-only by design**

There are no `POST`, `PUT`, or `DELETE` endpoints. Data enters TIDE only through `tide-ingest` (run manually or by the Scheduler). The API reflects whatever is currently in the DuckDB database file.

---

**Database isolation**

The backend and the ingest process share only the DuckDB file — they do not share memory or a network connection. A wedged or slow `tide-ingest` run cannot take the API down, and a running API cannot interfere with an in-progress ingest.

---

**Stale data is surfaced, not hidden**

Every `ReadingOut` includes an `as_of` date. Because different data sources publish on different schedules (FRED series may lag by days or weeks; CFTC data publishes weekly), the `as_of` date on a metric card may be older than today's date. This is expected behavior — TIDE shows the last known value with its observation date rather than suppressing the reading. Use `/api/sources` to see `last_observation` for every metric in one place.

---

**Stubbed metrics appear in API responses**

The four stubbed metrics (`ici_etf_flows`, `buyback_yield`, `put_call`, `uvol_dvol`) are registered in the metric registry and therefore appear in `/api/sources` responses with `"status": "stub"`. They do not appear in `/api/dashboard` `tiers[].metrics` because their `compute_fn` is `None` and they are skipped during the dashboard read loop. Similarly, `/api/metrics/{metric_id}/detail` will return a `MetricDetailOut` for a stubbed metric, but `reading` will be `null` and `series` will be empty.

---

**Composite history requires a backfill step**

The `composite_history` array in `GET /api/dashboard` and the points returned by `GET /api/tier/{tier}/history` are populated by running `tide-ingest backfill-composite`. If you have never run this command, both arrays will be empty and the 252-day history chart on the dashboard will not render. Run the backfill after your first ingest.

---

**Z-score lookback window varies by metric**

Z-scores default to a 3-year rolling window. The FRED series `BAMLH0A0HYM2` (HY spread) is an exception: its `zscore_years` is set to `1` because the series was truncated to a 3-year window in April 2026, making a longer lookback misleading. The `zscore_years` field in `MetricDetailOut` tells you exactly which window each metric uses.

---

**Composite z-score uses directional z, not raw z**

The composite score shown in `composite.z` is the equal-weight average of the four tier averages, where each metric contributes its `directional_z`. Metrics with inverted conventions — where a lower raw value signals stress (for example, tight HY spreads are bullish, low VIX is bullish) — are sign-flipped before entering the average. This ensures all metrics push the composite in the same direction when conditions are stressed.

---

**Yahoo Finance data does not use yfinance**

If you inspect source calls in the ingest layer, Yahoo Finance data is fetched by hitting `query1.finance.yahoo.com/v8/finance/chart/` directly over HTTP. The `yfinance` package is intentionally not a dependency, to avoid recurrent authentication and crumb-handling breakage.

---

**TLS verification exception for CFTC**

TLS certificate verification is disabled only for requests to `cftc.gov`. All other upstream HTTP calls — including FRED, Yahoo Finance, and any other source — use standard TLS verification.
