{"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\n# Heredoc, not -d '...': a single-quoted shell argument cannot contain a single quote, so\n# the SQL string literals ('AAPL') used to terminate it early and curl sent IN (AAPL,MSFT).\ncurl -X POST https://api.strasmore.com/v1/query \\\n  -H \"Authorization: Bearer $STRASMORE_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- <<'JSON'\n{\"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\"}\nJSON\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 plan: Free 10, Developer 60, Pro 600, Enterprise 1200. Minting extra keys does not raise it. |\n| Concurrency | Per **organization**, a share of the warehouse that rises and falls with load. Over it, a request waits briefly, then gets **429** `THROTTLED` with `Retry-After`. Writing queries that finish sooner: /docs#fast-queries. |\n| Free-tier daily cap | **100 queries/day** (in addition to the rate limit). |\n| History window | Free 1 year · Developer 3 years · Pro and Enterprise **full history**. Windows are whole calendar years, not rolling: an N-year window starts on 1 January, N years before the current year, and moves forward each 1 January (`history_window_start` in /v1/schema is the exact date). Rows older than your window are **absent** from a result, not an error — and a query that asks ONLY for dates before your window is a **402** `HISTORY_WINDOW_EXCEEDED`, because no change to the SQL can produce rows on your plan. |\n\nThe window starts on **1 January of (this year − N)**, not N years ago to the day. So it\nholds still for a whole calendar year, is always at least N years and usually more, and\nsteps forward once — on 1 January. `GET /v1/schema` returns the resolved\n`history_window_start`; pin or diff that value rather than assuming the boundary never\nmoves. It is the only way to tell that a query you ran in December covers a different\nspan in January.\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## Knowing your quota (response headers)\n\n**Every** response — success or error — carries your current quota state, so you never\nhave to discover a limit by crossing it:\n\n| Header | Meaning |\n|---|---|\n| `X-RateLimit-Limit` | Requests permitted per minute for your tier, or `unlimited`. |\n| `X-RateLimit-Remaining` | Requests left in the current minute. |\n| `X-RateLimit-Reset` | Unix epoch second at which the current window rolls over. |\n| `X-RateLimit-Policy` | The policy in force, e.g. `60;w=60; per-organization`. |\n| `X-Quota-Limit-Day` / `X-Quota-Remaining-Day` | Free tier only: the daily query cap and what is left of it. |\n| `Retry-After` | On a **429** or **503**: seconds to wait. For a per-minute limit this is the rest of the window; for the daily cap it is the time to the next UTC midnight; for `THROTTLED` it comes from your own recent query times. |\n| `X-Request-Id` | This request's identifier. Quote it to support. |\n\nQuota is counted per **organization**, not per key, and a rejected request still counts\nagainst the per-minute window — a client hammering past the limit stays capped for the\nrest of it. Requests rejected **before** execution (401, 422, and a 400 from the SQL gate)\ndo **not** consume the free-tier daily cap; only a query that reaches execution does.\n`GET /v1/schema` reports your quota without consuming any.\n\n## Errors\n\nEvery error is a JSON object with three fields:\n\n```json\n{\"detail\": \"Query failed. Check your SQL syntax and try again.\",\n  \"error_code\": \"TYPE_MISMATCH\",\n  \"request_id\": \"9f1c2b7e4a5d4c8e9a0b1c2d3e4f5a6b\"}\n```\n\n**Branch on `error_code`, never on `detail`.** `detail` is human-readable prose and may be\nreworded; `error_code` is stable. (On a **422** `detail` is FastAPI's list of field errors\nrather than a string — the other two fields are unchanged.)\n\n| Status | Meaning | `error_code` values |\n|---|---|---|\n| **400** | The SQL was rejected by the gate, or it failed to execute. A `TIER_RESTRICTED_TABLE` body also carries `upgrade_plan`, `upgrade_plan_name` and `checkout_url` — the cheapest plan that runs the query, and a link that opens checkout for it. | `QUERY_NOT_ALLOWED`, `MULTIPLE_STATEMENTS`, `TIER_RESTRICTED_TABLE`, `TABLE_NOT_ON_API`, `TYPE_MISMATCH`, `NESTED_AGGREGATE`, `MISSING_GROUP_BY`, `UNKNOWN_COLUMN`, `UNKNOWN_TABLE`, `UNSUPPORTED_SQL`, `SCALAR_SUBQUERY`, `QUERY_TIMEOUT`, `QUERY_TOO_LARGE`, `INVALID_SQL` |\n| **401** | Missing, malformed, unknown, or revoked API key. | `MISSING_API_KEY`, `INVALID_API_KEY` |\n| **402** | The query asks only for dates before your plan's history window, so it can return nothing on this plan. The body carries `history_window_start`, `requested_from`, `requested_to`, `upgrade_url`, and — when a plan would run it — `upgrade_plan`, `upgrade_plan_name` and `checkout_url`. | `HISTORY_WINDOW_EXCEEDED` |\n| **422** | Malformed request body (e.g. `sql` missing, or longer than 100,000 chars). | `INVALID_REQUEST_BODY` |\n| **429** | Rate limit exceeded, the free-tier daily cap is reached, or more queries are running at once than your share of the warehouse (`THROTTLED`; see /docs#concurrency). Wait `Retry-After` seconds and resend. | `RATE_LIMITED`, `DAILY_QUOTA_EXCEEDED`, `THROTTLED` |\n| **503** | The warehouse could not be reached. On our side, not your query; retry after `Retry-After`. | `WAREHOUSE_UNREACHABLE` |\n| **500** | Internal error. Details are logged server-side and never returned. | `INTERNAL_ERROR` |\n\nThe `error_code` names the CLASS of failure — enough to branch on, and to tell an unknown\ncolumn apart from a type error — while `detail` stays generic, so no schema, expression or\nserver internals are ever returned.\n\nNote that a query returning zero rows is a **200**, not a 404 — with one exception. Every\n`/v1/query` response on a windowed plan carries a `history_window` object: the window's\n`start`, how far back the query `requested_from`, and `clipped` (`true` when the query\nreached before the window and older rows were withheld, `false` when every bound is inside\nit, `null` when the SQL states no date bound). When the query asks ONLY for dates before\nthe window it is a **402** `HISTORY_WINDOW_EXCEEDED` instead, because the result is empty\nby construction on that plan. A `clipped: true` result is still a 200: it is the rows the\nplan can see, and `note` says what it cannot.\n\n## Pagination\n\nOrder by a stable key and page with `LIMIT`/`OFFSET`, or — better for tick data, which\ngrows underneath you — keyset pagination:\n\n```sql\nSELECT ticker, sip_timestamp, price, size\nFROM global_markets.stocks_trades\nWHERE ticker = 'AAPL'\n  AND sip_timestamp > {last_seen_timestamp}\n  AND sip_timestamp < '2026-01-02 00:00:00'\nORDER BY sip_timestamp\nLIMIT 10000\n```\n\n`OFFSET` over an append-only table can skip or repeat rows between calls; a keyset cursor\ncannot. Always give `ORDER BY` a deterministic tiebreaker (e.g. `sip_timestamp,\nsequence_number`) — without one, equal keys may come back in a different order each call.\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`).\n* Some columns carry a `note` — what the column means where the name does not say\n  (which era `participant_timestamp` is populated for, how `correction` records link\n  to the print they amend, what `tape` encodes). Measured on the warehouse.\n* `plan_name` is the pricing-page name of `your_tier` (`quotes` is Pro, or Quotes on\n  an account still on the retired Quotes plan).","operationId":"v1_schema_v1_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaResponse"}}}},"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"}}}},"402":{"description":"The query asks only for dates before this plan's history window (HISTORY_WINDOW_EXCEEDED).","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"}}}}}}},"/v1/options/contracts":{"get":{"tags":["Options"],"summary":"Option contracts quoted on a date","description":"Every option contract with at least one quote in the regular session (09:30-16:00 ET)\non `date`, with its expiry, type and strike read out of the OCC ticker.\n\nParameters (query string): `underlying` (e.g. SPY) **or** `contract` (one OCC ticker),\n`date` (YYYY-MM-DD, required), and optionally `expiry_from`, `expiry_to`, `type`\n(call|put), `strike_min`, `strike_max`, `cursor`, `format=csv`.\n\nThis is the discovery half of the pair: the raw tape has no underlying, expiry, type or\nstrike column — they are encoded in the ticker — so without this a caller has to know\nthe contract names before they can ask anything.","operationId":"v1_options_contracts_v1_options_contracts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptionsResponse"}}}},"400":{"description":"A bad parameter, a refused query, or one too broad to finish; `error_code`, and `narrow` when a narrowing would fix it."},"402":{"description":"Every date asked for lies before this plan's window for the quote tape (HISTORY_WINDOW_EXCEEDED); names the plan that reads it."},"429":{"description":"Rate limit, daily quota, or the warehouse throttle; see Retry-After."}}}},"/v1/options/quote-snapshots":{"get":{"tags":["Options"],"summary":"Clean NBBO per contract every N seconds","description":"One row per contract per interval bucket from 09:30 to 16:00 ET: the last valid quote\nat or before the bucket's end, carried forward across buckets that saw no update.\n\nParameters as `/v1/options/contracts`, plus `interval` (seconds, 1-3600, default 60).\n\nA quote is valid when both sides are quoted, neither price is negative and the ask is\nnot through the bid; an NBBO is a step function, so a bucket with no update repeats the\nquote that still stood rather than being left out.","operationId":"v1_options_quote_snapshots_v1_options_quote_snapshots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptionsResponse"}}}},"400":{"description":"A bad parameter, a refused query, or one too broad to finish; `error_code`, and `narrow` when a narrowing would fix it."},"402":{"description":"Every date asked for lies before this plan's window for the quote tape (HISTORY_WINDOW_EXCEEDED); names the plan that reads it."},"429":{"description":"Rate limit, daily quota, or the warehouse throttle; see Retry-After."}}}}},"components":{"schemas":{"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable reason. For a 422 this is FastAPI's list of field errors instead of a string.","examples":["Only a single read-only SELECT/WITH query is allowed."]},"error_code":{"type":"string","title":"Error Code","description":"Stable, machine-readable failure class. Branch on THIS, never on the `detail` text. Query failures: TYPE_MISMATCH, NESTED_AGGREGATE, MISSING_GROUP_BY, UNKNOWN_COLUMN, UNKNOWN_TABLE, UNSUPPORTED_SQL, SCALAR_SUBQUERY, QUERY_TIMEOUT, QUERY_TOO_LARGE, INVALID_SQL. Contract failures: MISSING_API_KEY, INVALID_API_KEY, MULTIPLE_STATEMENTS, QUERY_NOT_ALLOWED, TIER_RESTRICTED_TABLE, TABLE_NOT_ON_API, RATE_LIMITED, DAILY_QUOTA_EXCEEDED, THROTTLED, WAREHOUSE_UNREACHABLE, INVALID_REQUEST_BODY, INTERNAL_ERROR. Plan limit (402): HISTORY_WINDOW_EXCEEDED — the query asks only for dates before your plan's history window; the body adds history_window_start, requested_from, requested_to, upgrade_url and, when a plan would run the query, upgrade_plan / upgrade_plan_name / checkout_url (TIER_RESTRICTED_TABLE carries the same three).","examples":["TYPE_MISMATCH"]},"request_id":{"type":"string","title":"Request Id","description":"This request's id, also returned as the X-Request-Id header. Quote it to support.","examples":["9f1c2b7e4a5d4c8e9a0b1c2d3e4f5a6b"]}},"type":"object","required":["detail","error_code","request_id"],"title":"ErrorResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HistoryWindow":{"properties":{"start":{"type":"string","title":"Start","description":"First date this query could read (ISO). The plan's calendar boundary, or the quote-tape preview bound when only the tapes are involved.","examples":["2025-01-01"]},"tier_start":{"type":"string","title":"Tier Start","description":"The plan's calendar boundary (ISO) — the same value as history_window_start in GET /v1/schema."},"years":{"type":"integer","title":"Years","description":"The plan's window in years.","examples":[1,3]},"label":{"type":"string","title":"Label","description":"The window in words.","examples":["1 year"]},"tables":{"items":{"type":"string"},"type":"array","title":"Tables","description":"The windowed tables this query touched."},"tape_preview":{"additionalProperties":true,"type":"object","title":"Tape Preview","description":"Quote-tape tables read under the N-day preview, as {table: days}. Empty when none."},"requested_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested From","description":"Earliest date the SQL asked for (ISO), read from its literals, now()-relative offsets and toYear() predicates. Null when it states no lower bound."},"requested_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested To","description":"Latest date the SQL asked for, or null when the range is open to today."},"clipped":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Clipped","description":"true: the query reached before `start`, so older rows were withheld. false: every bound lies inside the window. null: no date bound was found, so the window may or may not have applied."},"outside":{"type":"boolean","title":"Outside","description":"true when the whole request lies before `start` — that case is a 402, never a 200."},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"HISTORY_WINDOW_CLIPPED when clipped, else null."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Plain-English statement of the clip, only when the window could explain the result."},"upgrade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upgrade","description":"Which plans read further back, when a note is present."},"upgrade_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upgrade Url","description":"The pricing page, when a note is present."},"upgrade_plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upgrade Plan","description":"Slug of the cheapest plan that runs this query in full, when a note is present and one exists."},"upgrade_plan_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upgrade Plan Name","description":"That plan's display name."},"checkout_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Url","description":"Opens checkout for `upgrade_plan` directly (sign-in first for a new browser; an account that already pays is offered the in-place plan change in the terminal, with the unused part of its current plan credited)."}},"type":"object","required":["start","tier_start","years","label","tables","tape_preview","outside"],"title":"HistoryWindow","description":"How this key's history window met this query. Present on every windowed plan."},"OptionsResponse":{"properties":{"request":{"additionalProperties":true,"type":"object","title":"Request","description":"What was actually run: the underlying, date, session, filters, and the validity rule a quote had to meet."},"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."},"row_count":{"type":"integer","title":"Row Count","description":"Rows on this page."},"truncated":{"type":"boolean","title":"Truncated","description":"True when more rows exist than the row cap returns; `next_cursor` continues from here."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` for the next page. Null when this page is the last."},"tape_window":{"additionalProperties":true,"type":"object","title":"Tape Window","description":"How far back this plan reads the options quote tape."},"elapsed_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed Ms","description":"Server-side execution time, ms."},"sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sql","description":"The exact SQL this answer came from. Runs unchanged on POST /v1/query."},"narrow":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Narrow","description":"Present when the page was truncated or the query was too broad: the changes that make it answerable."},"empty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Empty","description":"Present when there are no rows: why, stated rather than left to be guessed at."},"history_window":{"anyOf":[{"$ref":"#/components/schemas/HistoryWindow"},{"type":"null"}]},"trial":{"anyOf":[{"$ref":"#/components/schemas/TrialNotice"},{"type":"null"}]}},"type":"object","required":["request","columns","rows","row_count","truncated","tape_window"],"title":"OptionsResponse","description":"One page of option contracts or quote snapshots."},"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."},"history_window":{"anyOf":[{"$ref":"#/components/schemas/HistoryWindow"},{"type":"null"}],"description":"How the plan's history window met this query — null on a full-history plan or when no windowed table was touched. Read `clipped` before concluding that data is missing."},"trial":{"anyOf":[{"$ref":"#/components/schemas/TrialNotice"},{"type":"null"}],"description":"Set only while this key's 14-day trial window is about to end, and null for every other key."}},"type":"object","required":["columns","rows","row_count","truncated","elapsed"],"title":"QueryResponse"},"SchemaColumn":{"properties":{"name":{"type":"string","title":"Name","description":"Column name, as written in SQL.","examples":["window_start"]},"type":{"type":"string","title":"Type","description":"ClickHouse type, verbatim from the warehouse.","examples":["DateTime","Nullable(Float64)","LowCardinality(String)"]},"caveat":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caveat","description":"A KNOWN DEFECT in this column, when there is one. Read it before you trust the column — we publish these rather than let you find them.","examples":["filing_date is unreliable on this table; use period_end."]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"What the column MEANS where its name does not say — measured on the warehouse, not copied from vendor prose. Not a defect: a semantics note, e.g. which era a timestamp is populated for, or how a correction record links to the print it amends.","examples":["Populated from 2015-07-27; the epoch placeholder before that."]}},"type":"object","required":["name","type"],"title":"SchemaColumn"},"SchemaResponse":{"properties":{"database":{"type":"string","title":"Database","description":"The database every table lives in.","examples":["global_markets"]},"your_tier":{"type":"string","title":"Your Tier","description":"The tier this API key resolves to right now — the internal slug, stable across renames.","examples":["quotes"]},"plan_name":{"type":"string","title":"Plan Name","description":"That tier's name on the pricing page (`quotes` is Pro, `starter` is Developer). The slug and the name are the same plan, except an account still on the retired Quotes plan, which is `quotes` named Quotes.","examples":["Pro"]},"history_window_years":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"History Window Years","description":"How far back this key can read. NULL means full history. Rows outside the window are silently absent from every query, so check this before concluding that historical data does not exist.","examples":[5,null]},"history_window_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"History Window Start","description":"The exact first date this key can see (ISO), or null for full history. Steady through the calendar year, steps forward one year on 1 January. Pin or diff THIS rather than assuming the window never moves.","examples":["2021-01-01",null]},"note":{"type":"string","title":"Note","description":"How to read this catalogue."},"tables":{"items":{"$ref":"#/components/schemas/SchemaTable"},"type":"array","title":"Tables","description":"Every table this key may query."},"trial":{"anyOf":[{"$ref":"#/components/schemas/TrialNotice"},{"type":"null"}],"description":"Set only while this key's 14-day trial window is about to end, and null for every other key."}},"type":"object","required":["database","your_tier","plan_name","history_window_years","history_window_start","note","tables"],"title":"SchemaResponse","description":"The catalogue GET /v1/schema returns.\n\nThis model exists because the endpoint had NO response_model, so its OpenAPI entry was\na bare `{}` — a beta tester generating a typed client got `any`/`unknown` back for a\npayload that is stable and fully documented in prose. Everything below is exactly what\napps/api/schema.catalogue() already returns; nothing about the response changed, it is\nsimply described now."},"SchemaTable":{"properties":{"table":{"type":"string","title":"Table","description":"Table name. Query it as `global_markets.<table>`.","examples":["delayed_stocks_minute_aggs"]},"description":{"type":"string","title":"Description","description":"What the table holds, and its grain.","default":""},"history_column":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"History Column","description":"The column your tier's history window is applied to. Rows older than the window are ABSENT from results — that is not an error. Null means the table is never windowed (static reference data).","examples":["window_start"]},"requires_paid_tier":{"type":"boolean","title":"Requires Paid Tier","description":"True if a free-tier key is rejected (HTTP 400) when querying this table."},"columns":{"items":{"$ref":"#/components/schemas/SchemaColumn"},"type":"array","title":"Columns","description":"Every column, in warehouse order."}},"type":"object","required":["table","requires_paid_tier","columns"],"title":"SchemaTable"},"TrialNotice":{"properties":{"tag":{"type":"string","title":"Tag","description":"The offer this key was created under.","examples":["dev14"]},"days_left":{"type":"integer","title":"Days Left","description":"Whole days left, rounded up.","examples":[3]},"line":{"type":"string","title":"Line","description":"One sentence: what ends, when, what it falls back to, and the plan that keeps it."},"upgrade_plan":{"type":"string","title":"Upgrade Plan","description":"Slug of the plan that keeps this window.","examples":["starter"]},"upgrade_plan_name":{"type":"string","title":"Upgrade Plan Name","description":"That plan's display name.","examples":["Developer"]},"checkout_url":{"type":"string","title":"Checkout Url","description":"Opens checkout for that plan directly."}},"type":"object","required":["tag","days_left","line","upgrade_plan","upgrade_plan_name","checkout_url"],"title":"TrialNotice","description":"The key trial is ending (bet #17). Present only on a key inside a running trial, and\nonly in its last days — a notice on every response for a fortnight is furniture.\n\nSays what ENDS and what keeps it, with the numbers read from the tier rows rather than\nwritten here: an offer that quotes a window the executor does not grant is the exact\ndrift this codebase derives every published limit to avoid."},"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."}]}