{"openapi":"3.1.0","info":{"title":"Strasmore API","summary":"SQL access to 22 years of US equities and 12 years of options, in one schema.","description":"\nRun SQL directly against a full US market-data warehouse: **global_markets**.\n\nEquities minute bars and tick data from 2003, options trades from 2014, options NBBO\nquotes from 2022, plus corporate actions, SEC filings, short interest, fundamentals,\nTreasury yields and macro series — all in **one schema**, so you can join across them in\na single query.\n\nDerived option **greeks and implied volatility** are available in the Strasmore Terminal,\nbut not yet through this API — they come from a derived view whose queries must be shaped\nto stay inside the execution cap, and we would rather hand you a straight answer there than\na timeout here. Tell us if you need them programmatically and we will prioritise it. We do\nnot carry open interest at all (it is an OCC end-of-day figure, not derivable from trades).\n\n## Quickstart\n\n```bash\ncurl -X POST https://api.strasmore.com/v1/query \\\n  -H \"Authorization: Bearer $STRASMORE_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sql\": \"SELECT ticker, sum(volume) AS shares FROM global_markets.delayed_stocks_minute_aggs WHERE ticker IN ('AAPL','MSFT') AND window_start >= now() - INTERVAL 5 DAY GROUP BY ticker\"}'\n```\n\nGet a key at https://ai.strasmore.com → API keys. The key is shown **once**; we store only its SHA-256 hash.\n\n## The schema\n\n`GET /v1/schema` returns the live table/column catalogue, read from the warehouse itself\n(so it cannot drift from what your query will see). It reflects **your** tier: tables you\ncannot query are flagged, and it names the column each table's history window applies to.\n\n## Limits (all enforced server-side)\n\n| | |\n|---|---|\n| Result rows | **20,000** per query. Beyond that the result is **truncated, not errored** — check the `truncated` flag. |\n| Query timeout | **60 seconds**. A slower query is rejected. |\n| Request body | 100,000 characters of SQL. |\n| Rate limit | Per **organization**, per minute, by tier: free 10, starter 60, pro 300, quotes 600, enterprise 1200. Minting extra keys does not raise it. |\n| Free-tier daily cap | **100 queries/day** (in addition to the rate limit). |\n| History window | free 2y · starter 5y · pro / quotes / enterprise **full history**. Rows older than your window are simply **absent** — not an error. |\n\n## SQL restrictions\n\nThe API executes your SQL under a **read-only** database user, after a gate that requires:\n\n* Exactly **one** statement, and it must be a `SELECT` or a `WITH … SELECT`.\n* No multiple statements (a `;` separating statements is rejected; a semicolon inside a\n  string literal is fine).\n* No `SETTINGS` clause — it is stripped, because it could otherwise override the\n  server-side caps.\n* No table functions (`remote`, `url`, `merge`, `file`, …) anywhere, including in\n  subqueries.\n* No `system.*` or any database other than `global_markets`.\n* No DDL/DML of any kind.\n\nBlocked tables return **400** with a reason. Some datasets are unavailable to **every**\ntier for licensing reasons; the tick-level trade/quote firehoses are available on paid\ntiers only (see `GET /v1/schema`, field `requires_paid_tier`).\n\n## Errors\n\nEvery error is a JSON object: `{\"detail\": \"<human-readable reason>\"}`.\n\n| Status | Meaning |\n|---|---|\n| **400** | The SQL was rejected by the gate (not a single read-only SELECT, forbidden table, blocked dataset for your tier) **or** it failed to execute (syntax, unknown column). |\n| **401** | Missing, malformed, unknown, or revoked API key. |\n| **422** | Malformed request body (e.g. `sql` missing, or longer than 100,000 chars). |\n| **429** | Rate limit exceeded, **or** the free-tier daily cap is reached. |\n| **500** | Internal error. Details are logged server-side and never returned. |\n\nNote that a query returning zero rows is a **200**, not a 404.\n","contact":{"name":"Strasmore","url":"https://www.strasmore.com/","email":"terminal@strasmore.com"},"version":"1.0"},"servers":[{"url":"https://api.strasmore.com","description":"live environment"}],"paths":{"/v1/health":{"get":{"tags":["Service"],"summary":"Liveness check (no authentication)","description":"Returns `{\"status\": \"ok\"}` if the service is up. Does not check the warehouse.","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/schema":{"get":{"tags":["Schema"],"summary":"The live table/column catalogue, as YOUR tier sees it","description":"Every table and column you can query, read from the warehouse itself at request\ntime — so it can never drift from what a query will actually see.\n\nReflects your entitlements:\n\n* Datasets that are unlicensed for redistribution are **never listed**, for any tier.\n* Tick-level trade/quote tables carry `requires_paid_tier: true`; a free-tier key\n  querying one gets a 400.\n* `history_column` names the column your tier's history window filters on. Rows older\n  than your window are **absent**, not an error.\n* Some columns carry a `caveat` — a known data defect worth reading before you trust\n  the column (e.g. `filing_date` on the financial-statement tables is unreliable; use\n  `period_end`).","operationId":"v1_schema_v1_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/query":{"post":{"tags":["Query"],"summary":"Run one read-only SQL query","description":"Execute a single `SELECT` (or `WITH … SELECT`) against `global_markets`.\n\nRuns under a read-only database user, with server-side caps on rows, bytes and\nruntime, and a per-tier history window. See the API description for the full list of\nlimits, SQL restrictions and the error contract, and `GET /v1/schema` for the tables.","operationId":"v1_query_v1_query_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponse"}}}},"400":{"description":"SQL rejected by the gate, or failed to execute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded, or free-tier daily cap reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error (details are never returned).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable reason.","examples":["Only a single read-only SELECT/WITH query is allowed."]}},"type":"object","required":["detail"],"title":"ErrorResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"QueryRequest":{"properties":{"sql":{"type":"string","maxLength":100000,"title":"Sql","description":"One read-only SELECT (or WITH … SELECT) against global_markets.*.","examples":["SELECT ticker, round(sum(toFloat64(close) * toFloat64(volume)) / 1e9, 2) AS dollars_bn FROM global_markets.delayed_stocks_minute_aggs WHERE window_start >= now() - INTERVAL 5 DAY GROUP BY ticker ORDER BY dollars_bn DESC LIMIT 10"]}},"type":"object","required":["sql"],"title":"QueryRequest"},"QueryResponse":{"properties":{"columns":{"items":{"type":"string"},"type":"array","title":"Columns","description":"Column names, in result order."},"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Rows","description":"One OBJECT per row, keyed by column name — not positional arrays. Use `columns` for ordering when you need it.","examples":[[{"dollars_bn":41.2,"ticker":"AAPL"},{"dollars_bn":38.9,"ticker":"NVDA"}]]},"row_count":{"type":"integer","title":"Row Count","description":"Rows returned (after any truncation)."},"truncated":{"type":"boolean","title":"Truncated","description":"True if the result hit the 20,000-row cap. The result is truncated, NOT an error — narrow the query or paginate with LIMIT/OFFSET."},"elapsed":{"type":"number","title":"Elapsed","description":"Server-side execution time, seconds."},"generated_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Sql","description":"Reserved. Always null today: this endpoint is SQL-only. It exists so that adding natural-language querying later is additive, not breaking."}},"type":"object","required":["columns","rows","row_count","truncated","elapsed"],"title":"QueryResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"Query","description":"Run SQL against the warehouse."},{"name":"Schema","description":"The live table/column catalogue, as your tier sees it."},{"name":"Service","description":"Liveness."}]}