bullinv-quant-data API
REST API for the internal quant market-data platform: aggregate bars, raw ticks, symbol metadata, pipeline status, bar coverage and admin backfills. All timestamps are Asia/Taipei (+08:00). Every response (except /health) is wrapped in a standard envelope:
{ "code": 200, "message": "ok", "data": { ... } }Authentication
Authenticate by passing your key in the X-API-Key header on every request. Keys are issued per service by the platform team.
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/symbols"
http://localhost:8080Auth headerX-API-Key: $BULLINV_API_KEYTimezoneAsia/Taipei (+08:00)The “Try it” panels below send requests through this console’s server-side proxy, which attaches the API key for you — the key never reaches the browser.
/v1/bars/{symbol}Aggregate Bars
OHLCV bars for a symbol over a date range, aggregated from raw ticks. Supports 1m to 1d timeframes and day/night/full session filters. Timestamps are Asia/Taipei.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol, e.g. the continuous front-month TXF contract. |
| timeframe | query | string | Bar aggregation window.one of: 1m | 5m | 15m | 30m | 1h | 1ddefault: 1m |
| start | query | string (ISO 8601) | Inclusive range start. Defaults to the current trading day's open. |
| end | query | string (ISO 8601) | Exclusive range end. Defaults to now. |
| session | query | string | Session filter. day = 08:45-13:45, night = 15:00-05:00 (crosses midnight), full = both.one of: day | night | fulldefault: full |
| limit | query | integer | Maximum number of bars returned (max 5000). Use next_start to paginate.default: 1500 |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/bars/TXFR1?timeframe=1m&start=2026-06-30T08%3A45%3A00%2B08%3A00&end=2026-06-30T13%3A45%3A00%2B08%3A00&session=day&limit=500"
Response
{
"code": 200,
"message": "ok",
"data": {
"symbol": "TXFR1",
"timeframe": "1m",
"session": "day",
"tz": "Asia/Taipei",
"count": 300,
"results": [
{
"ts": "2026-06-30T08:45:00+08:00",
"open": 23012,
"high": 23021,
"low": 23008,
"close": 23017,
"volume": 412,
"amount": 9482804,
"tick_count": 287,
"buy_volume": 221,
"sell_volume": 191
}
],
"next_start": null
}
}- —Bars are keyed by their opening minute; a bar covering 08:45:00-08:45:59 has ts 08:45:00.
- —When more bars exist than limit allows, next_start holds the ts to pass as start on the next call.
/v1/ticks/{symbol}Raw Ticks
Raw trade ticks for a symbol. Ascending (oldest first) by default; pass order=desc for a newest-first tape. tick_type 1 = outer (traded at ask), 2 = inner (traded at bid), 0 = indeterminate.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| start | query | string (ISO 8601) | Inclusive range start. |
| end | query | string (ISO 8601) | Exclusive range end. Defaults to now. |
| limit | query | integer | Maximum number of ticks returned (max 1000).default: 200 |
| include_simtrade | query | boolean | Include simulated pre-open matching ticks (simtrade=true).one of: 0 | 1default: 0 |
| order | query | string | Sort order. desc returns newest first; next_start pagination is only supported for asc (desc always returns next_start=null).one of: asc | descdefault: asc |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/ticks/TXFR1?start=2026-06-30T09%3A00%3A00%2B08%3A00&end=2026-06-30T09%3A05%3A00%2B08%3A00&limit=50&include_simtrade=0&order=desc"
Response
{
"code": 200,
"message": "ok",
"data": {
"count": 50,
"results": [
{
"ts": "2026-06-30T09:04:59.812+08:00",
"recv_ts": "2026-06-30T09:04:59.819+08:00",
"contract": "TXFR1",
"price": 23015,
"volume": 3,
"total_volume": 48211,
"tick_type": 1,
"bid_side_total_vol": 22903,
"ask_side_total_vol": 25308,
"simtrade": false
}
],
"next_start": "2026-06-30T09:03:12.044+08:00"
}
}- —next_start holds the ts to pass as start on the next call; it is null when order=desc.
/v1/live/{symbol}Live Snapshot
Single-call snapshot for a live trading view: running day stats, the level-5 order book and the most recent trades (newest first). Poll this instead of stitching /v1/ticks and /v1/bidask together client-side.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| tape_limit | query | integer | Number of tape entries returned, newest first (max 200).default: 40 |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/live/TXFR1?tape_limit=60"
Response
{
"code": 200,
"message": "ok",
"data": {
"symbol": "TXFR1",
"tz": "Asia/Taipei",
"market_open": true,
"stats": {
"last_price": 46231.0,
"price_chg": 35.0,
"pct_chg": 0.08,
"open": 46200.0,
"high": 46280.0,
"low": 46150.0,
"avg_price": 46210.5,
"total_volume": 41231,
"total_amount": 1904000000000,
"buy_volume_today": 21031,
"sell_volume_today": 19822,
"underlying_price": 46210.11,
"basis": 20.89,
"last_tick_ts": "2026-06-30T09:04:59.812+08:00",
"last_tick_age_s": 1.2
},
"book": {
"ts": "2026-06-30T09:05:00.104+08:00",
"bids": [{ "price": 46230.0, "volume": 12 }],
"asks": [{ "price": 46231.0, "volume": 9 }],
"bid_total_vol": 123,
"ask_total_vol": 140
},
"tape": [
{
"ts": "2026-06-30T09:04:59.812331+08:00",
"price": 46231.0,
"volume": 2,
"tick_type": 1,
"total_volume": 41231
}
]
}
}- —stats come from the latest non-simtrade tick; buy/sell_volume_today are summed from 1m bars since Taipei midnight.
- —basis = last_price - underlying_price; both are null when the feed has no underlying price.
- —book is null until bidask snapshots exist for the symbol; levels with price or volume <= 0 are dropped.
- —tape is newest first and excludes simtrade ticks.
/v1/bidask/{symbol}Order Book Snapshots
Raw level-5 bid/ask snapshots as captured from the feed, keyset-paginated in the same style as /v1/ticks. Arrays are ordered L1 (best) to L5.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| start | query | string (ISO 8601) | Inclusive range start. |
| end | query | string (ISO 8601) | Exclusive range end. Defaults to now. |
| limit | query | integer | Maximum number of snapshots returned (max 1000).default: 200 |
| order | query | string | Sort order. desc returns newest first; next_start pagination is only supported for asc (desc always returns next_start=null).one of: asc | descdefault: asc |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/bidask/TXFR1?start=2026-06-30T09%3A00%3A00%2B08%3A00&end=2026-06-30T09%3A05%3A00%2B08%3A00&limit=50&order=desc"
Response
{
"code": 200,
"message": "ok",
"data": {
"count": 1,
"results": [
{
"ts": "2026-06-30T09:04:59.900+08:00",
"recv_ts": "2026-06-30T09:04:59.904+08:00",
"contract": "TXFR1",
"bid_prices": [46230, 46229, 46228, 46227, 46226],
"bid_volumes": [12, 8, 21, 17, 30],
"ask_prices": [46231, 46232, 46233, 46234, 46235],
"ask_volumes": [9, 14, 25, 11, 19],
"bid_total_vol": 123,
"ask_total_vol": 140,
"underlying_price": 46210.11,
"simtrade": false
}
],
"next_start": null
}
}- —Snapshots dedupe on (ts, symbol, level-1 fields); identical Kafka redeliveries collapse to one row.
/v1/symbolsList Symbols
All symbols tracked by the platform, with their session schedule and stored-bar extent.
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/symbols"
Response
{
"code": 200,
"message": "ok",
"data": {
"results": [
{
"symbol": "TXFR1",
"source": "shioaji",
"display_name": "台指期近月",
"sessions": {
"day": ["08:45", "13:45"],
"night": ["15:00", "05:00"]
},
"first_bar_ts": "2026-01-05T08:45:00+08:00",
"last_bar_ts": "2026-06-30T13:44:00+08:00",
"bar_count": 137940
}
]
}
}/v1/internal/bars/{symbol}Internal Bulk Bars
Unpaginated bulk 1m bars for internal batch consumers (e.g. the backtest engine). Streams QuestDB's raw /exec JSON straight through — no envelope, no per-row transformation — so multi-hundred-thousand-row fetches run at near-direct speed while still being authenticated and visible in /usage.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| start | query | string (ISO 8601) | Inclusive range start (naive = Asia/Taipei). Defaults to end − 7d. |
| end | query | string (ISO 8601) | Inclusive range end. Defaults to now. |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/internal/bars/TXFR1?start=2026-06-20T08%3A45%3A00%2B08%3A00&end=2026-06-30T13%3A45%3A00%2B08%3A00"
Response
{
"query": "SELECT ts, open, high, low, close, volume, amount FROM bars_1m ...",
"columns": [
{ "name": "ts", "type": "TIMESTAMP" },
{ "name": "open", "type": "DOUBLE" },
{ "name": "high", "type": "DOUBLE" },
{ "name": "low", "type": "DOUBLE" },
{ "name": "close", "type": "DOUBLE" },
{ "name": "volume", "type": "LONG" },
{ "name": "amount", "type": "DOUBLE" }
],
"dataset": [
["2026-06-30T00:45:00.000000Z", 46100.0, 46120.0, 46081.0, 46095.0, 2362, 108870000.0]
],
"count": 7122
}- —Requires an admin key (DATA_API_ADMIN_KEYS).
- —Raw QuestDB /exec passthrough: NOT wrapped in the {code,message,data} envelope; ts values are UTC ISO — convert timezone client-side (vectorized).
- —Hard safety cap 5,000,000 rows (≈15 years of 1m bars) — a guardrail, not pagination.
/v1/analytics/latency/{symbol}Ingest Latency
Pipeline latency analytics over a rolling window: tick and bidask feed latency (recv_ts − ts, ms) with percentiles, a fixed-bucket histogram and a per-minute series, plus bar-write latency (minute close → ingested_at) for realtime bars.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| window | query | string | Rolling window the stats are computed over.one of: 15m | 1h | 4h | 1ddefault: 1h |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/analytics/latency/TXFR1?window=1h"
Response
{
"code": 200,
"message": "ok",
"data": {
"symbol": "TXFR1",
"window": "1h",
"tick": {
"count": 8123,
"avg_ms": 142.1,
"p50_ms": 120.0,
"p90_ms": 210.0,
"p99_ms": 480.0,
"max_ms": 2210.0,
"negative_count": 0,
"histogram": [
{ "le_ms": 50, "count": 10 },
{ "le_ms": 100, "count": 900 },
{ "le_ms": null, "count": 2 }
],
"series": [
{ "t": "2026-07-02T10:04:00+08:00", "count": 210, "avg_ms": 130.5, "p99_ms": 320.0 }
]
},
"bidask": null,
"bar_write": {
"count": 120,
"avg_s": 6.8,
"p50_s": 6.5,
"p99_s": 9.1,
"max_s": 22.0
}
}
}- —Latency = recv_ts − ts in milliseconds; simtrade ticks are excluded.
- —Negative latencies (local vs exchange clock skew) are excluded from the percentiles and reported via negative_count.
- —Histogram bucket upper bounds are fixed: 25, 50, 75, 100, 150, 200, 300, 500, 1000, 2000, 5000 and null (= above); series is SAMPLE BY 1m.
- —bidask is null when the bidask table has no rows for the symbol.
- —bar_write measures src='rt' bars as ingested_at − (ts + 60s); its window is widened to at least 4h so there are enough samples.
/v1/analytics/quality/{symbol}Data Quality
Per-trading-day bar provenance for a symbol: how many 1m bars came from the realtime stream (rt), backfill or the vendor seed, plus volume-check failures and per-day activity aggregates.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| days | query | integer | Number of trading days to return (max 60), ending at the current trading day.default: 14 |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/analytics/quality/TXFR1?days=14"
Response
{
"code": 200,
"message": "ok",
"data": {
"days": [
{
"trading_day": "2026-07-02",
"total_bars": 1108,
"rt_bars": 420,
"backfill_bars": 10,
"seed_bars": 678,
"vol_check_fail_bars": 0,
"avg_tick_count": 38.2,
"total_volume": 183422
}
]
}
}- —Bars are grouped by the src column of bars_1m; rt_bars + backfill_bars + seed_bars = total_bars.
- —trading_day attribution uses the 05:01 cut: night-session bars before 05:01 belong to the previous trading day.
- —vol_check_fail_bars counts bars whose volume disagrees with the vendor kbar during the nightly coverage check.
/v1/analytics/profile/{symbol}Intraday Profile
Average intraday structure over the last N trading days: mean volume and tick count per Taipei minute-of-day, session-filtered. Useful for spotting the open/close U-shape and the night-session US-open bump.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| days | query | integer | Number of recent trading days to average over (max 60).default: 20 |
| session | query | string | Session filter. day = 08:45-13:45, night = 15:00-05:00, full = both.one of: day | night | fulldefault: day |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/analytics/profile/TXFR1?days=20&session=day"
Response
{
"code": 200,
"message": "ok",
"data": {
"session": "day",
"days_sampled": 20,
"minutes": [
{ "minute": "08:45", "avg_volume": 812.3, "avg_tick_count": 95.1 },
{ "minute": "08:46", "avg_volume": 590.8, "avg_tick_count": 71.4 }
]
}
}- —minute is a Taipei minute-of-day (HH:mm); values are means over the sampled trading days.
/v1/analytics/basis/{symbol}Futures Basis
Futures-vs-underlying basis series over a rolling window, sampled per minute from ticks that carry an underlying price. basis = price − underlying.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| window | query | string | Rolling window of the series.one of: 1h | 4h | 1ddefault: 1d |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/analytics/basis/TXFR1?window=1d"
Response
{
"code": 200,
"message": "ok",
"data": {
"series": [
{
"t": "2026-07-02T10:04:00+08:00",
"price": 46231.0,
"underlying": 46720.1,
"basis": -489.1
}
]
}
}- —SAMPLE BY 1m average over ticks whose underlying_price is not null; minutes without such ticks are omitted.
/v1/analytics/spread/{symbol}Bid/Ask Spread
Level-1 spread analytics from bidask snapshots over a rolling window: per-minute average spread (ask L1 − bid L1) and average bid/ask total queue volumes.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| window | query | string | Rolling window of the series.one of: 1h | 4h | 1ddefault: 1d |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/analytics/spread/TXFR1?window=1d"
Response
{
"code": 200,
"message": "ok",
"data": {
"series": [
{
"t": "2026-07-02T10:04:00+08:00",
"avg_spread": 1.2,
"avg_bid_total": 123.1,
"avg_ask_total": 140.2
}
],
"count": 421
}
}- —SAMPLE BY 1m over bidask snapshots; a snapshot only counts when both L1 prices are > 0.
- —When the bidask table has no rows for the symbol the series is empty ([]), not an error.
/v1/statusPlatform Status
Live health of every pipeline service (ingestor, writer, backfill, data-api), per-symbol tick freshness with a 60-minute rate history, and recent bar gaps.
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/status"
Response
{
"code": 200,
"message": "ok",
"data": {
"services": [
{
"service": "ingestor",
"symbol": "TXFR1",
"status": "ok",
"lag_ms": 12,
"ticks_1m": 431,
"heartbeat_age_s": 1,
"detail": "shioaji stream connected"
}
],
"symbols": [
{
"symbol": "TXFR1",
"last_tick_ts": "2026-06-30T09:04:59.812+08:00",
"last_tick_age_s": 2,
"last_price": 23015,
"ticks_today": 48211,
"simtrade_ticks_today": 193,
"ticks_per_min": [{ "t": "2026-06-30T08:05:00+08:00", "v": 388 }]
}
],
"gaps": {
"detected": 2,
"filling": 1,
"unfillable": 1,
"recent": [
{
"gap_id": "gap-txfr1-20260610-1031",
"symbol": "TXFR1",
"gap_start": "2026-06-10T10:31:00+08:00",
"gap_end": "2026-06-10T10:34:00+08:00",
"expected_bars": 3,
"actual_bars": 0,
"status": "unfillable"
}
]
},
"market_open": true
}
}/v1/coverage/{symbol}Bar Coverage
Per-trading-day 1m-bar coverage for a symbol: expected vs actual bar counts and the resulting percentage, session-aware.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | path | string | Contract symbol. |
| from | query | string (date) | First trading day, yyyy-MM-dd. Defaults to 90 days ago. |
| to | query | string (date) | Last trading day, yyyy-MM-dd. Defaults to today. |
| session | query | string | Session filter used for the expected-bar count.one of: day | night | fulldefault: full |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/coverage/TXFR1?from=2026-04-01&to=2026-06-30&session=full"
Response
{
"code": 200,
"message": "ok",
"data": {
"results": [
{
"trading_day": "2026-06-10",
"expected": 1140,
"actual": 1071,
"coverage_pct": 93.95,
"is_trading_day": true,
"verified": true,
"vendor_bars": 1071,
"checked_at": "2026-06-11T05:20:00+08:00"
},
{
"trading_day": "2026-06-13",
"expected": 0,
"actual": 0,
"coverage_pct": 0,
"is_trading_day": false,
"verified": false,
"vendor_bars": null,
"checked_at": null
}
]
}
}/v1/usage/summaryAPI Usage Summary
Aggregated API-usage analytics over a rolling window, computed from the api_requests log: totals by status class, latency (avg / p99), a time series, the top endpoints and per-key counts.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| window | query | string | Rolling window the summary is computed over.one of: 1h | 4h | 1d | 7ddefault: 1d |
| exclude_keys | query | string | Comma-separated key labels to exclude from ALL stats (totals, series, endpoints, keys) — e.g. hide the dashboard's own traffic by its named key. |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/usage/summary?window=1d&exclude_keys=dash"
Response
{
"code": 200,
"message": "ok",
"data": {
"window": "1d",
"total": 8213,
"count_2xx": 8100,
"count_4xx": 100,
"count_5xx": 13,
"avg_ms": 11.2,
"p99_ms": 88.0,
"series": [
{ "t": "2026-07-02T10:05:00+08:00", "count": 140, "errors": 2, "avg_ms": 10.1 }
],
"endpoints": [
{ "route": "/v1/status", "count": 5200, "avg_ms": 8.9, "p99_ms": 40.1, "errors": 0 }
],
"keys": [
{ "api_key": "key-1", "count": 8100, "last_seen": "2026-07-02T10:09:41+08:00" }
]
}
}- —series granularity follows the window: 1h/4h → SAMPLE BY 1m, 1d → 5m, 7d → 1h.
- —endpoints are the top 20 routes by count; route is the FastAPI route template (e.g. /v1/bars/{symbol}).
- —api_key is an identity label — "key-N" / "admin-N" for configured keys, "invalid" for a bad key, "anonymous" when no key was sent (dev mode); raw keys are never stored.
- —The api_requests table keeps 30 days of data (PARTITION BY DAY TTL 30 DAYS); /health and OPTIONS requests are not logged.
/v1/usage/requestsAPI Request Log
The raw api_requests tail, newest first: one row per API call with the matched route template, actual path, truncated query string, key label, status code, duration and client IP.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Maximum number of rows returned (1–500), newest first.default: 100 |
| status | query | string | Filter by status-code class (range match on status_code).one of: all | 2xx | 4xx | 5xxdefault: all |
| route | query | string | Exact-match filter on the route template; use values from the summary's endpoints list. |
| exclude_routes | query | string | Comma-separated route templates to hide (e.g. the dashboard's own polling). Rows with route=null (unmatched 404s) are always kept. |
| exclude_keys | query | string | Comma-separated key labels to hide — e.g. exclude_keys=dash hides the dashboard's own traffic. Keys are named in DATA_API_KEYS as "name:secret". |
| q | query | string | Case-insensitive substring search over path and query string (max 64 chars; letters, digits and /_{}.,=&%?:- only). |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/usage/requests?limit=100&status=4xx&route=%2Fv1%2Fbars%2F%7Bsymbol%7D&exclude_routes=%2Fv1%2Fstatus%2C%2Fv1%2Flive%2F%7Bsymbol%7D&exclude_keys=dash&q=timeframe%3D5m"
Response
{
"code": 200,
"message": "ok",
"data": {
"count": 100,
"results": [
{
"ts": "2026-07-02T10:09:41.213+08:00",
"method": "GET",
"route": "/v1/bars/{symbol}",
"path": "/v1/bars/TXFR1",
"query": "timeframe=5m&session=day",
"api_key": "key-1",
"status_code": 200,
"duration_ms": 12.4,
"client_ip": "127.0.0.1"
}
]
}
}- —route is null when the request matched no FastAPI route (e.g. a scanner probing /admin/login); path always holds the actual request path.
- —query is truncated to 512 characters; ts is the request start time.
- —api_requests is an append-only log with a 30-day TTL; /health and OPTIONS are excluded as probe noise.
/v1/admin/backfillTrigger Backfill
Queue a backfill run that re-downloads ticks for a symbol over a time range and rebuilds the affected bars. Returns the run id.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| symbol* | body | string | Contract symbol to backfill. |
| start* | body | string (ISO 8601) | Inclusive range start. |
| end* | body | string (ISO 8601) | Exclusive range end. |
Request
curl -X POST \
-H "X-API-Key: $BULLINV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"symbol":"TXFR1","start":"2026-06-10T08:45:00+08:00","end":"2026-06-11T05:00:00+08:00"}' \
"http://localhost:8080/v1/admin/backfill"Response
{
"code": 200,
"message": "ok",
"data": {
"run_id": "bf-20260630-0912-a41c"
}
}- —Runs are idempotent per (symbol, start, end); re-posting the same range returns a new run that no-ops on already-filled bars.
/v1/backfill-runsBackfill Run History
Execution history of backfill runs (one row per run, latest status). Runs are created by the admin trigger, the seed CLI, or the automatic gap scanner, and progress requested → running → done | failed.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | int | Max runs to return (1–200), newest first.default: 20 |
| status | query | enum | Filter by current status.one of: requested | running | done | failed |
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/v1/backfill-runs?limit=20&status=done"
Response
{
"code": 200,
"message": "ok",
"data": {
"count": 1,
"results": [
{
"run_id": "9c2f1c40e2a34d0f8b1d6c1c8f0a2b7e",
"symbol": "TXFR1",
"range_start": "2026-06-10T08:45:00+08:00",
"range_end": "2026-06-11T05:00:00+08:00",
"status": "done",
"rows_written": 1108,
"requested_by": "api",
"error": null,
"ts": "2026-07-02T18:40:11+08:00"
}
]
}
}- —ts is the run's latest status-change time; a run stuck in running for >30 min is reaped and re-executed by the backfill loop.
/healthHealth Check
Liveness probe for the data-api process and its QuestDB connection. This is the only endpoint that is not wrapped in the standard envelope and requires no API key.
Request
curl -H "X-API-Key: $BULLINV_API_KEY" \ "http://localhost:8080/health"
Response
{
"status": "ok",
"questdb": "ok"
}- —No X-API-Key required.
Questions? Ping #quant-data. Method badges: GET read, POST mutate.