# Market Atlas API v1 Base: `https://www.market-atlas.co/v1` (same origin as the web app; in development `http://127.0.0.1:8391/v1`). Envelope: `{ "data": …, "meta": { "request_id", "timestamp", … } }`; errors: `{ "error": { "code", "message" } }`. Public rate limit on /v1 only: 20 requests/s per IP (burst 300), `429` + `retry-after`. Admin routes need `x-ma-admin-token`. Quote object (`publicQuote`): `instrument_id, symbol, name, asset_class, exchange_id, country, price, open, high, low, previous_close, change, change_percent, volume, bid, ask, currency, source_count, dispersion_bps, confidence, freshness_ms, data_status, market_state, rights_status, withheld, updated_at, source_timestamp, session_high, session_low`. `data_status` ∈ `REALTIME | DELAYED | END_OF_DAY | AT_CLOSE | STALE | WITHHELD` — never present STALE/AT_CLOSE as live. Quotes also carry `observation_count`, `proxy_count`, `validator_count`, `comparability` (LIVE/FIX/EOD); provenance contributions carry `observation_type`, `delta_bps`, `reason` ∈ stale | not_comparable | temporal_mismatch | validation_only | validator_disagrees | outlier. | Method & path | Purpose | |---|---| | `GET /health` | liveness (db, connectors healthy, quotes cached) | | `GET /status` | public status components (website, api, stream, ingestion, source groups) | | `GET /stats` | homepage telemetry — real counters only | | `GET /metrics` | Prometheus exposition | | `GET /instruments?asset_class&exchange&country&q"ed=1&sort&limit&offset` | instrument list with quotes | | `GET /instruments/:idOrSymbol` | instrument + company + exchange status + quote + aliases + sources + related (`NASDAQ:AAPL` form accepted) | | `GET /quotes/:idOrSymbol` · `GET /quotes?ids=a,b` | canonical quotes | | `GET /quotes/:id/provenance` | **Why this price?** — contributions, weights, inclusion reasons, method, versions | | `GET /history/:id?resolution=1m|5m|15m|1h|1d&from&to&limit` | bars (`p` = producer: `consensus` or a historical connector id) | | `GET /events?type&instrument&country&asset_class&severity&since&before&limit` · `GET /events/:id` | canonical market events | | `GET /filings?form&cik&q&limit&offset` | regulatory filings | | `GET /search?q` | hybrid search (exact/prefix/alias/name), grouped by asset class / exchange / country | | `GET /markets` | global overview: indices, crypto, forex, rates, commodities, equities, gainers/losers, exchanges with status, breadth | | `GET /breadth?scope=world|US|xnas` | advancers/decliners/median return | | `GET /changes?window=1m|5m|15m|1h|1d` | what changed: movers > 1 %, events by type, filings | | `GET /exchanges` · `GET /exchanges/:id` | venues, status/next transition, holidays, breadth, movers, events | | `GET /countries` · `GET /countries/:code` | country atlas page data | | `GET /compare?ids=a,b,c&resolution=1d&limit` | normalized series, return/vol/drawdown, correlations | | `GET /sources` · `GET /connectors` · `GET /data-health` | source directory (with `role` contributor/validator and `likely_shared_upstream_with`), public connector table, data-health dashboard | | `GET /coverage?asset_class&tier&limit` · `GET /coverage/:id` | Source Coverage Engine: redundancy histogram (≥5/≥3/≥2/1 families), weighted score, tier attainment, expansion queue, inferred shared upstreams; per-instrument coverage | | `GET /admin/lineage` | all statistically compared source pairs with similarity | | `GET /stream` (WebSocket) · `GET /sse?channels=` | live stream — see below | | `GET /admin/overview|connectors|connectors/:id|schema-changes|divergence|storage|raw?ref=` · `POST /admin/connectors/:id/pause|resume|restart|test` · `POST /admin/connectors/:id/schema-changes/:changeId/ack` · `POST /admin/discovery {url}` | admin console | ## Stream protocol Client → server: `{"action":"subscribe","channels":["quotes:AAPL","quotes:crypto_btc_usd","quotes:*","quotes:class:CRYPTO","events:*","events:US","events:type:TRADING_HALT","market:xnas","tape"]}`, `{"action":"unsubscribe",…}`, `{"action":"ping","id":1}`. Server → client: `hello`, `subscriptions`, `heartbeat` (25 s), `pong`, `error`, and batches at ≤ 10 Hz per client: `{"type":"batch","seq":12,"ts":…,"messages":[{"type":"quote","instrument_id","symbol","price","change","change_pct","bid","ask","volume","currency","timestamp","received","confidence","sources","status"}, {"type":"event","event":{…}}, {"type":"market_state","exchangeId","state","at"}]}`. `quotes:*` is throttled to one quote per instrument per 500 ms; explicit `quotes:` subscriptions receive every recomputation. `seq` is per client; a gap means the client missed batches (backpressure drops oldest). Withheld (non-redistributable) values are never streamed.