Run SQL directly against a full US market-data warehouse. Equities minute bars and tick data
from 2003, options trades from 2014, options NBBO quotes from 2022, plus corporate actions,
SEC filings, short interest, fundamentals, Treasury yields and macro series. It all lives in
one schema, so you can join across datasets in a single query.
There is a single data endpoint. You send SQL, you get rows. There is no per-dataset
endpoint to learn, no pagination cursor scheme, and no client library required.
Quickstart
Create a key in the terminal (https://ai.strasmore.com → API keys),
then send a query. The key is displayed once: we store only its SHA-256 hash and
cannot show it to you again.
The response is JSON. Rows come back as objects keyed by column name.
Try it — no key
Run read-only SQL against global_markets right here. This posts to
https://ai.strasmore.com/api/demo/sql, the public demo endpoint: no key, no signup,
500 rows, 20s, 1 year of history, no tick trades, and 5 days of the NBBO quote tapes. It is the same gate and the same warehouse your key would query.
POSThttps://ai.strasmore.com/api/demo/sql
⌘/Ctrl + Enter
Authentication
Every endpoint except /v1/health takes a bearer token:
Authorization: Bearer sk_live_…
Keys resolve to an organization, and limits are applied per organization, not
per key. Minting extra keys does not raise your rate limit. Revoking a key takes effect
immediately.
Run a query
POST/v1/queryrequires key
Execute one read-only SELECT (or WITH … SELECT) against global_markets.
Body
Field
Description
sqlrequired string
One read-only statement. Maximum 100,000 characters.
Response
Field
Description
columns string[]
Column names, in result order.
rows object[]
One object per row, keyed by column name. Not positional arrays.
row_count integer
Rows returned, after any truncation.
truncated boolean
True if the result hit the 20,000-row cap. The result is truncated, not
errored — narrow the query, aggregate, or page with LIMIT/OFFSET.
elapsed float
Server-side execution time, in seconds.
generated_sql string | null
Always null today. Reserved so that adding natural-language querying later is
additive rather than a breaking change.
Fetch the schema
GET/v1/schemarequires key
Every table and column you can query, read from the warehouse itself at request time — so it
can never drift from what your query will actually see. It reflects your tier:
tables you cannot query are flagged, each table names the column its history window filters on,
and columns with known data defects carry a caveat.
Health
GET/v1/healthno auth
Liveness only. Returns {"status": "ok"}. Does not check the warehouse.
SQL restrictions
Your SQL runs under a read-only database user, behind a gate that requires:
Exactly one statement, and it must be a SELECT or a WITH … SELECT.
Multiple statements are rejected with a 400 — we will not silently execute the first and drop the rest.
No SETTINGS clause. It is stripped: it could otherwise override the server-side caps.
No table functions (remote, url, merge, file, …), anywhere, including inside subqueries.
No system.*, and no database other than global_markets.
No DDL or DML of any kind.
A semicolon inside a string literal is fine (WHERE name = 'Smith; Jones'), and so is a single
trailing semicolon. The engine is ClickHouse, so its SQL dialect and functions apply.
Limits
Limit
Value
Result rows
20,000 per query. Beyond this the result is truncated, not errored —
check the truncated flag.
Query timeout
60 seconds, enforced server-side.
Request body
100,000 characters of SQL. Larger returns 422.
Free-tier daily cap
100 queries/day, in addition to the rate limit.
Rate limits
Requests per minute, applied per organization. Exceeding it returns 429.
Tier
Requests / minute
free
10
starter
60
pro
300
quotes
600
enterprise
1200
Error contract
Every error is a JSON object with a single human-readable field:
{"detail": "…"}. A query that returns zero rows is a 200, not a 404.
Status
Meaning
400
The SQL was rejected by the gate (not a single read-only SELECT, a forbidden table, a
dataset your tier cannot access), or it failed to execute (syntax, unknown column).
401
Missing, malformed, unknown, or revoked API key.
422
Malformed request body — sql missing, or over 100,000 characters.
429
Rate limit exceeded, or the free-tier daily cap is reached. The message says which.
500
Internal error. Details are logged server-side and never returned to you.
History windows
Each tier sees a uniform depth of history across all datasets. The window is enforced
server-side, on the column named by history_column in the schema catalogue.
This is not an error
Rows older than your window are simply absent from results. You will not get a 400;
you will get fewer rows. If a historical query returns less than you expect, check your tier's
window before you check your SQL.
Tier
History
free
1 years
starter
3 years
pro
Full history
quotes
Full history
enterprise
Full history
Schema catalogue
36 tables in global_markets, read live from the warehouse.
Query them as global_markets.<table>. Tables marked
paid are unavailable on the free tier.
Equities — prices
delayed_stocks_minute_aggs9 columnsMinute OHLCV bars for US equities. The main price table. `window_start` is UTC.
history window applies to: window_start
tickerLowCardinality(String)
window_startDateTime
openDecimal(12, 4)
closeDecimal(12, 4)
highDecimal(12, 4)
lowDecimal(12, 4)
volumeDecimal(15, 6)
transactionsUInt32
_ingest_timeDateTime
stocks_daily_aggs11 columnsDaily OHLCV bars for US equities, with `vwap` and `transactions`. The end-of-day counterpart to delayed_stocks_minute_aggs; use this for anything longer than a few sessions.
cache_stocks_quotes15 columns5-day preview · full history on ProTick-level equity NBBO quotes. Paid tiers. From 2022.
history window applies to: sip_timestamp
tickerString
sip_timestampDateTime64(9)
participant_timestampDateTime64(9)
trf_timestampNullable(DateTime64(9))
sequence_numberInt64
ask_priceDecimal(12, 4)
bid_priceDecimal(12, 4)
ask_sizeInt32
bid_sizeInt32
ask_exchangeInt16
bid_exchangeInt16
tapeInt8
conditionsArray(Int16)
indicatorsArray(Int16)
_ingest_timeDateTime
Options
options_greeks18 columnspaid tierDaily Black-Scholes IV + greeks per contract (EOD close, delta/gamma/vega/theta/rho). One row per contract per session, 2014 onward, refreshed nightly. Paid tiers.
history window applies to: date
dateDate
tickerString
underlying_symbolString
option_typeString
strike_priceFloat64
expiration_dateNullable(Date)
volumeUInt64
option_closeFloat64
underlying_closeFloat64
risk_free_rateNullable(Float64)
days_to_expiryNullable(Float64)
implied_volatilityNullable(Float64)Solved from the EOD option close by bisection. Screen implied_volatility > 0.02 to drop degenerate fits.
iv_convergedNullable(UInt8)Filter iv_converged = 1: rows where the IV solve did not converge carry NULL greeks (deep ITM/OTM or stale closes).
deltaNullable(Float64)
gammaNullable(Float64)
vegaNullable(Float64)
thetaNullable(Float64)
rhoNullable(Float64)
options_trades13 columnspaid tierTick-level options trades (OPRA). Paid tiers. From 2014.
history window applies to: sip_timestamp
tickerLowCardinality(String)
sip_timestampDateTime64(9)
participant_timestampDateTime64(9)
priceDecimal(10, 4)
sizeUInt32
exchangeUInt8
conditionsArray(UInt16)
correctionUInt8
underlying_symbolLowCardinality(String)
expiration_dateDateUnreliable. Parse the expiry from the option ticker instead.
strike_priceDecimal(18, 3)
option_typeLowCardinality(String)
_ingest_timeDateTime
cache_options_quotes10 columns5-day preview · full history on ProTick-level options NBBO quotes. Paid tiers. From 2022. The largest table by far.
history window applies to: sip_timestamp
tickerLowCardinality(String)
sip_timestampDateTime64(9)
sequence_numberInt64
ask_priceDecimal(10, 2)
bid_priceDecimal(10, 2)
ask_sizeInt32
bid_sizeInt32
ask_exchangeInt16
bid_exchangeInt16
_ingest_timeDateTime
options_minute_aggs9 columnsMinute OHLCV bars per options contract.
history window applies to: window_start
tickerLowCardinality(String)
window_startDateTime
openDecimal(10, 4)
closeDecimal(10, 4)
highDecimal(10, 4)
lowDecimal(10, 4)
volumeUInt64
transactionsUInt32
_ingest_timeDateTime
Corporate actions
stocks_dividends13 columnsDeclared dividends: ex-date, record date, pay date, cash amount.
history window applies to: ex_dividend_date
ex_dividend_dateDate
tickerString
idString
cash_amountNullable(Float64)
currencyString
declaration_dateNullable(Date)
pay_dateNullable(Date)
record_dateNullable(Date)
frequencyNullable(Int32)
distribution_typeString
split_adjusted_cash_amountNullable(Float64)
historical_adjustment_factorNullable(Float64)
_ingest_timeDateTime
stocks_splits8 columnsStock splits (forward and reverse) with execution date and ratio.
stocks_income_statements35 columnsQuarterly/annual income statements.
history window applies to: filing_date
period_endDate
filing_dateDateUNRELIABLE. Vendor defect: ~89% of quarterly rows carry a filing_date more than 200 days after period_end (older fiscal years are stamped with recent years' dates). Use period_end as the time key.
stocks_13f_filings21 columnsInstitutional holdings from 13F-HR filings — one row per position per manager per quarter (`filer_cik`, `cusip`, `market_value`, `shares_or_principal_amount`, voting authority). Back to 2000. Join to a quarter with `period`, to the filing with `filing_date`.
accession_numberString
cusipNullable(String)
file_numberNullable(String)
filer_cikString
filing_dateDate
filing_urlNullable(String)
film_numberNullable(String)
form_typeNullable(String)
investment_discretionNullable(String)
issuer_nameNullable(String)
market_valueNullable(Int64)
other_managersArray(String)
periodNullable(Date)334 rows carry an out-of-range period (as filed — they range to 1987 and to 2149). Tiny against 160m rows, but enough to poison the obvious idiom: max(period) returns 2149-06-06 and a 'latest quarter' query then matches one row. Bound it — max(period) WHERE period <= today().
put_callNullable(String)
shares_or_principal_amountNullable(Int64)
shares_or_principal_typeNullable(String)
title_of_classNullable(String)
voting_authority_noneNullable(Int64)
voting_authority_sharedNullable(Int64)
voting_authority_soleNullable(Int64)
_ingest_timeDateTime
stocks_form441 columnsInsider transactions (SEC Form 4): open-market buys and sells, grants and exercises by officers, directors and 10% owners. `transaction_code` is the action, `transaction_shares`/`transaction_price_per_share` the size. `tickers` is an ARRAY — filter with has(tickers, 'AAPL'). Back to 2003.
transaction_price_per_shareNullable(Float64)Carries a small tail of mis-parsed values as filed — ~0.3% of open-market purchases price above $100,000/share, and in some rows the SHARE COUNT is repeated into this field. Harmless to ignore until you ORDER BY value, which puts them first. Bound it (e.g. BETWEEN 1 AND 10000) for any ranking. transaction_value is derived from this column and inherits the defect.
transaction_sharesNullable(Float64)
transaction_timelinessNullable(String)
transaction_valueNullable(Float64)
underlying_security_sharesNullable(Float64)
underlying_security_titleNullable(String)
_ingest_timeDateTime
stocks_form330 columnsInitial statements of beneficial ownership (SEC Form 3) — filed when someone first becomes an insider. The starting position that Form 4 transactions then move. `tickers` is an ARRAY.
accession_numberString
aff_10b5_oneNullable(UInt8)
date_of_original_submissionNullable(Date)
direct_or_indirectNullable(String)
exercise_dateNullable(Date)
exercise_priceNullable(Float64)
filing_dateDate
filing_urlNullable(String)
footnotesNullable(String)
form_typeNullable(String)
is_directorNullable(UInt8)
is_officerNullable(UInt8)
is_otherNullable(UInt8)
is_ten_percent_ownerNullable(UInt8)
issuer_cikString
issuer_nameNullable(String)
nature_of_ownershipNullable(String)
not_subject_to_section_16Nullable(UInt8)
officer_titleNullable(String)
owner_cikNullable(String)
owner_nameNullable(String)
period_of_reportNullable(Date)
remarksNullable(String)
security_titleNullable(String)
security_typeNullable(String)
shares_ownedNullable(Float64)
tickersArray(String)
underlying_security_sharesNullable(Float64)
underlying_security_titleNullable(String)
_ingest_timeDateTime
SEC filings
stocks_sec_edgar_index8 columnsThe EDGAR filing index: every filing, its form type and filing date.
history window applies to: filing_date
cikString
tickerString
issuer_nameString
form_typeString
filing_dateDate
accession_numberString
filing_urlString
_ingest_timeDateTime
stocks_10k_sections8 columnsParsed sections of 10-K filings.
history window applies to: filing_date
cikString
tickerString
filing_dateDate
period_endDate
filing_urlString
sectionString
textString
_ingest_timeDateTime
stocks_8k_disclosures10 columns8-K material-event filings, categorised (`primary_category` … `tertiary_category`) with the supporting text. `tickers` is an ARRAY. See stocks_8k_text for the full filing body.
accession_numberString
cikString
filing_dateDate
filing_urlNullable(String)
primary_categoryNullable(String)
secondary_categoryNullable(String)
tertiary_categoryNullable(String)
supporting_textNullable(String)
tickersArray(String)
_ingest_timeDateTime
stocks_8k_text8 columns8-K filing text.
history window applies to: filing_date
cikString
tickerString
form_typeString
filing_dateDate
accession_numberString
filing_urlString
items_textString
_ingest_timeDateTime
stocks_13f_text7 columnsRaw 13F filing text. NOT YET POPULATED — the table exists and is empty; use stocks_13f_filings for the parsed positions.
stocks_market_holidays7 columnsExchange calendar: holidays and early closes.
dateDate
exchangeString
nameString
statusString
openNullable(DateTime)
closeNullable(DateTime)
_ingest_timeDateTime
stocks_market_status8 columnsReference: market status codes.
afterHoursNullable(Bool)
currenciesString
earlyHoursNullable(Bool)
exchangesString
indicesGroupsString
marketString
serverTimeDateTime
_ingest_timeDateTime
stocks_exchanges11 columnsReference: exchange codes and names.
idInt64
typeString
asset_classString
localeString
nameString
acronymString
micString
operating_micString
participant_idString
urlString
_ingest_timeDateTime
stocks_condition_codes12 columnsReference: trade/quote condition codes (needed to read the tick tables).
idInt64
nameString
abbreviationString
typeString
descriptionString
asset_classString
data_typesArray(String)
legacyNullable(Bool)
exchangeNullable(Int64)
sip_mappingString
update_rulesString
_ingest_timeDateTime
Data caveats
Known defects in the underlying data. We publish them rather than let you discover them: a
silently wrong number is worse than a missing one. They are also attached to the affected
columns in GET /v1/schema.
options_greeks.iv_converged
Filter iv_converged = 1: rows where the IV solve did not converge carry NULL greeks (deep ITM/OTM or stale closes).
options_greeks.implied_volatility
Solved from the EOD option close by bisection. Screen implied_volatility > 0.02 to drop degenerate fits.
options_trades.expiration_date
Unreliable. Parse the expiry from the option ticker instead.
stocks_13f_filings.period
334 rows carry an out-of-range period (as filed — they range to 1987 and to 2149). Tiny against 160m rows, but enough to poison the obvious idiom: max(period) returns 2149-06-06 and a 'latest quarter' query then matches one row. Bound it — max(period) WHERE period <= today().
stocks_balance_sheets.filing_date
PARTLY UNRELIABLE (~43% of rows filed >200d after period_end). Prefer period_end.
stocks_cash_flow_statements.filing_date
UNRELIABLE (same vendor defect as stocks_income_statements). Use period_end.
stocks_form4.transaction_price_per_share
Carries a small tail of mis-parsed values as filed — ~0.3% of open-market purchases price above $100,000/share, and in some rows the SHARE COUNT is repeated into this field. Harmless to ignore until you ORDER BY value, which puts them first. Bound it (e.g. BETWEEN 1 AND 10000) for any ranking. transaction_value is derived from this column and inherits the defect.
stocks_income_statements.filing_date
UNRELIABLE. Vendor defect: ~89% of quarterly rows carry a filing_date more than 200 days after period_end (older fiscal years are stamped with recent years' dates). Use period_end as the time key.
stocks_short_volume.date
This table can contain duplicate rows for a ticker/date. Dedupe with GROUP BY + max().