Docs
Three endpoints, no auth, JSON only. Every quote is signed so a consumer can verify the price, the band and the provenance all came from the same key.
Endpoints
| Endpoint | Returns |
|---|---|
GET /api/quotes | Every tracked instrument, priced against one proxy snapshot. |
GET /api/quote/:ticker | One instrument, signed, with the digest and signer address. |
GET /api/health | Upstream reachability, signer address, current session. Returns 503 when upstream is down. |
Quote fields
| Field | Meaning |
|---|---|
price | The number to use. Equal to anchorPrice when provenance is TRADED or STALE; drifted when DERIVED. |
anchorPrice | The last price actually observed on tape, before modelling. |
confidenceBps | Two-sided uncertainty in basis points. Capped at 1500; beyond that a quote is not worth publishing. |
session | 0 REGULAR · 1 PRE · 2 POST · 3 CLOSED · 4 HOLIDAY |
provenance | 0 TRADED · 1 DERIVED · 2 STALE |
lastTradeTime | Unix seconds of the last print, reconciled against the exchange calendar rather than taken from upstream. |
driftBps | How far the anchor was moved by the model. Zero when TRADED. |
maxDeviationBps | Spread between the highest and lowest source reading. |
Provenance, and how to treat each value
| Value | Means | Safe to |
|---|---|---|
| TRADED | Observed print, session open, under 5 minutes old. | Settle, liquidate, mark. |
| DERIVED | Tape shut. Last close drifted against a 24/7 proxy. | Mark to market, display, size positions. Not liquidate. |
| STALE | No usable anchor, or the tape is open and upstream stopped printing. | Nothing automatic. During an open session this signals an upstream fault, so we deliberately do not model over it. |
The confidence model
live print: bps = BASE[session] + maxDeviationBps // 8 regular, 35 pre/post gap (DERIVED or STALE): sigma = overnightSigmaBps * (hours/17.5)^k // k and sigma both fitted bps = 1.96 * sigma + maxDeviationBps // two-sided 95% bps = max(bps, BASE[CLOSED]) // never tighter than a live one bps = clamp(bps, 1, 1500)
Both sigma and k come from calibration.json, fitted by npm run calibrate against two years of realised close-to-open gaps. The measured exponent is k ≈ 0.107, not the 0.5 a random walk in calendar time would imply. See the problem for why.
The model's own error is deliberately not added on top. The residual sigma is what remains after applying the beta, so it already contains it; adding a drift term would double-count and break the validated coverage.
The floor exists because of a bug caught in testing: without it, the band collapsed the moment the session label flipped from CLOSED to PRE on Monday morning, even though the underlying data had not improved at all. A stale anchor does not become trustworthy because the opening bell rang.
Coverage
The band claims to be a 95% interval, so that claim is tested by replaying every historical gap and counting how many landed inside it. All eight instruments fall between 93.4% and 96.4%, across roughly 500 gaps each. Re-run npm run calibrate to reproduce.
Verifying a signature
import { verifyMessage } from "viem";
const r = await fetch("/api/quote/HOOD").then((r) => r.json());
const ok = await verifyMessage({
address: r.signer,
message: { raw: r.digest },
signature: r.signature,
});The digest is keccak256 over the abi-encoded tuple (string ticker, uint128 price, uint64 confidenceBps, uint8 session, uint8 provenance, uint8 sourceCount, uint64 maxDeviationBps, uint64 lastTradeTime, uint64 publishTime), with price scaled to 8 decimals. That is exactly the tuple the on-chain verifier reconstructs, so the band cannot be stripped from the price without invalidating the signature.
Limits
- The default provider is Yahoo's unofficial endpoint: no key, real print times, but not licensed for commercial redistribution. Add a licensed vendor before production.
- Betas are fitted on two years ending September 2026 and describe that regime. They do not anticipate a structural break.
- The proxy move is measured over exactly the gap window from hourly series. Where the series does not reach back far enough, drift is zero rather than extrapolated.
sourceCountreflects providers that actually resolved. With only Yahoo enabled it is 1 andmaxDeviationBpsis necessarily 0. Add a second provider key for those fields to carry signal.