Playground — catalog, deep links, how to extend
The playground (/playground, also embedded in /dashboard/playground) is a single reusable component:
import Playground from './src/playground/Playground.jsx'
<Playground apiKey={sessionKey} embedded /> // apiKey optional → keyless modeapiKey is sent as Authorization: Bearer … on every run and on the WebSocket URL (?api_key=). It lives in React state only — never localStorage, sessionStorage, cookies or the address bar. Code exports always print $HFMD_API_KEY.
Files
| File | Role |
|---|---|
src/playground/catalog.js |
The request types (data only) + resolution/validation helpers |
src/playground/Playground.jsx |
The component: catalog nav, form, URL preview, run, result views, quota panel, code export, presets |
src/playground/RequestForm.jsx |
Form generated from entry.params |
src/playground/SymbolInput.jsx |
Debounced autocomplete (/v1/{asset}/tickers?search=, /v1/futures/roots, /v1/options/tickers) |
src/playground/views/{JsonView,TableView,ChartView}.jsx |
Collapsible JSON · virtualised sortable table (50 000 rows) · lightweight-charts (candles / line, roll markers) |
src/playground/RateLimitPanel.jsx |
X-RateLimit-* bars, reset countdown, 429 Retry-After countdown |
src/playground/WebSocketTab.jsx |
/v1/stream client (disabled without key) |
src/playground/codegen.js |
curl / Python (requests + pandas) / JavaScript (fetch) generators |
src/playground/presets.js |
One-click examples |
src/components/Pg{Tabs,CodeBlock,Callout,CopyButton}.jsx |
Local UI primitives (swap for web-core's shared ones when available) |
Catalog schema
{
id: 'futures-continuous', // stable, URL-safe — used in deep links and tests
group: 'Futures', // one of GROUPS: Bars · Futures · Options · Fundamentals · Symbols · Stream
title: 'Futures continuous (v2)',
method: 'GET', // or 'WS' for the stream tab
path: '/v1/futures/{root}/continuous',
operationIds: ['futures_continuous_v1_futures__root__continuous_get'], // OpenAPI ids that map here
description: 'Markdown-free one-liner shown above the form',
fixed: { asset: 'crypto' }, // optional: values injected in the path/query but hidden from the form
requiresKey: false, // stream only
params: [
{ name: 'root', in: 'path', type: 'symbol', required: true, source: 'futures_root', default: 'ES', placeholder: 'ES', help: '…' },
{ name: 'roll', in: 'query', type: 'enum', enum: ['', 'volume', 'open_interest', 'calendar'], default: 'volume' },
{ name: 'depth', in: 'query', type: 'number', default: 1, min: 1, max: 12 },
{ name: 'from', in: 'query', type: 'date' }, // native <input type=date>
{ name: 'start', in: 'query', type: 'datetime' }, // native datetime-local, sent as "YYYY-MM-DD HH:MM:SS"
{ name: 'adjustment', in: 'query', type: 'enum', enumBy: { asset: { stock: [...], futures: [...] } }, enum: [''] }, // dependent enum
{ name: 'symbol', in: 'path', type: 'text', pattern: '^[A-Z]{1,3}[FGHJKMNQUVXZ]\\d{2}$' },
],
result: {
kind: 'bars' | 'series' | 'table' | 'list' | 'object' | 'ws' | 'auto',
timeKey: 'datetime', // x axis hint for the chart
valueKeys: ['close', 'settle'], // preferred y column(s) for line charts
listKey: 'tickers', // for `list` kinds: JSON key holding a flat array
envelope: 'v2', // rows live in `data`, extras in `meta` (roll_dates → chart markers)
},
}Param types → controls: symbol (autocomplete), enum (select; '' = server default), number, date, datetime, text (optional pattern), bool. Validation (validate()) enforces required, numeric bounds, date formats, patterns and enum membership; Run is disabled until the form is valid.
Result routing after a run: bars/series with a detectable time column → Chart tab first; any rows → Table; otherwise JSON. format=csv parses the CSV for the table and offers a download; format=parquet offers the download only.
Deep-link contract
/playground?ep=<ref>&<param>=<value>&…refis either a catalog id (stock-bars,futures-chain, …) or an OpenAPIoperationId(bars_v1_bars__asset___ticker__get,opt_chain_v1_options_chain__ticker__get, …). FastAPI's default naming is<function>_<path with / and {} → _>_<method>.- Every other query parameter is a form value (path or query param of the entry). Unknown names are ignored for curated entries and kept for spec-generated ones.
- Resolution order (
resolveEntry()inPlayground.jsx):- catalog
idmatch; - entry whose
operationIdscontainsref— when several entries share an operation (e.g.bars_v1_bars__asset___ticker__get→ Stock/ETF, Crypto, FX, Index, legacy futures) the one whosefixedvalues match the supplied params wins (asset=crypto→ Crypto bars); - otherwise
/openapi.jsonis fetched,refis looked up, and the entry with the same method + path template is used; - otherwise a generic form is generated from the spec (path/query parameters, enums from
schema.enumor^(a|b)$patterns, defaults, descriptions) — a notice says so; - otherwise a warning is shown and the default entry (
stock-bars) is loaded.
- catalog
- The address bar is rewritten (
history.replace) to?ep=<catalog id>&<non-default values>so links copied from the browser are short and stable. Spec-generated entries keepep=<operationId>. - Docs "Try it" buttons should therefore link to
/playground?ep=${operation.operationId}&${exampleParams}. No coordination with the catalog is needed: unknown operations still get a working form. - The dashboard mounts the same component under
/dashboard/playground— the same query contract applies there.
Presets (presets.js)
| Label | Entry | Values |
|---|---|---|
| AAPL 1-min today | stock-bars |
ticker=AAPL timeframe=1min start=<today> |
| CL chain | futures-chain |
root=CL |
| ES continuous back-adjusted 2015-2025 | futures-continuous |
root=ES roll=volume adjust=back_adjusted depth=1 interval=1d from=2015-01-01 to=2025-12-31 |
| NG term structure | futures-term-structure |
root=NG |
| AAPL income statements (quarterly) | fundamentals-statements |
ticker=AAPL statement=income period=quarterly |
| Screener: PE<15 & ROE>15% | fundamentals-screener |
filter=pe<15,roe>0.15 sort=-market_cap |
Adding a request type
- Append an entry to
CATALOGincatalog.js(copy the closest one). Put the FastAPIoperationIdinoperationIds— check it in/openapi.jsononce the route exists. - If it belongs to a new area, add the group name to
GROUPS(ordering of the left column). - Set
resultso the right view opens first (barsfor OHLC,series+timeKey/valueKeysfor one line,list+listKeyfor flat arrays). - Optional: a preset in
presets.js, a symbolsourceinSymbolInput.jsxif a new autocomplete endpoint is needed. - Add an assertion in
e2e/playground.spec.js(deep link → URL preview) and runnpm run test:e2e.
Errors: any non-2xx JSON envelope {"error":{code,message,docs}} is displayed with a Docs button; a bare FastAPI {"detail":"Not Found"} 404 is rendered as "Endpoint not available yet — coming soon" (the v2 modules are deployed independently).
Tests
npm run test:e2e (Playwright, Chromium). The config builds the site and serves vite preview on :4173; specs mock every /v1/* and /openapi.json call with page.route, so the suite runs offline.