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.
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
2 years
starter
5 years
pro
Full history
quotes
Full history
enterprise
Full history
Schema catalogue
30 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_minute_aggs9 columnsBase table behind delayed_stocks_minute_aggs. Prefer the delayed_ view.
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_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_trades.expiration_date
Unreliable. Parse the expiry from the option ticker instead.
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_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().