Docs navigation +
Data reference

SQL queries

/query runs read-only DuckDB SQL directly against the options snapshot. It’s the escape hatch for anything the chain and screener shapes can’t express: GROUP BY aggregations (gamma exposure, put/call ratios, OI distributions), window functions, joins between the tables, or ad-hoc analytics.

Calling it

cvApi (in-app)
// Total gamma exposure by strike for SPY
const gex = await cvApi.query(`
  SELECT strike_price,
         SUM(gamma * open_interest * shares_per_contract *
             CASE WHEN contract_type = 'call' THEN 1 ELSE -1 END) AS net_gamma
  FROM options_snapshots
  WHERE underlying_ticker = 'SPY'
  GROUP BY strike_price
  ORDER BY strike_price
`);
curl (REST)
curl -X POST -H "Authorization: Bearer cv_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "sql": "SELECT underlying_ticker, COUNT(*) AS contracts FROM options_snapshots GROUP BY 1 ORDER BY 2 DESC LIMIT 20" }' \
  https://tap.convexvalue.com/api/data/query

From a coding agent, the MCP tool is query_sql — see the MCP reference.

Request shape

Parameter Type Description
sql required string A single read-only statement. Must start with SELECT or WITH; a trailing semicolon is allowed, a second statement is not.
max_rows integer Row cap for this query. Defaults to the server maximum (10,000) and is clamped to it; the response sets truncated: true if more rows matched.

Rows come back as JSON objects keyed by column name, with row_count, truncated, and elapsed_ms alongside:

JSON response
{
  "rows": [
    { "underlying_ticker": "SPY", "contracts": 12488 },
    { "underlying_ticker": "SPX", "contracts": 11020 }
  ],
  "row_count": 2,
  "truncated": false,
  "elapsed_ms": 132
}

Tables

options_snapshots

One row per option contract — the entire live snapshot. Use the documented supported fields: text columns (underlying_ticker, ticker, contract_type, expiration_date, …), numeric columns (FMV, Greeks, session aggregates), and timestamps. Raw storage columns are not a promise of product availability: cvforge does not provide option bid/ask quotes, NBBO, or last trades. The table is indexed by underlying_ticker, by ticker, and by (underlying_ticker, expiration_date, strike_price, contract_type) — filter on those for the fastest queries.

option_symbols

Column Type Description
ticker text Stock/ETF symbol from the daily universe refresh.
has_options boolean Whether the symbol has listed options (only optionable symbols are ingested into the snapshot).
updated_at timestamp When the universe row was last refreshed.

Rules & limits

  • SELECT / WITH only. DDL, DML, PRAGMA, and multiple statements are rejected with 400.
  • 10,000-row ceiling. max_rows above the server cap is clamped; aggregate server-side rather than paginating raw contracts.
  • The database is a snapshot. It’s swapped atomically about once a minute; there is no history in these tables — for time series use historical bars.
  • DuckDB’s SQL dialect applies — date functions, window functions, QUALIFY, and list aggregates all work.

Recipes

Put/call volume ratio, top names
SELECT underlying_ticker,
       SUM(CASE WHEN contract_type = 'put'  THEN day_volume ELSE 0 END) /
       NULLIF(SUM(CASE WHEN contract_type = 'call' THEN day_volume ELSE 0 END), 0) AS pc_ratio,
       SUM(day_volume) AS total_volume
FROM options_snapshots
GROUP BY underlying_ticker
HAVING total_volume > 10000
ORDER BY total_volume DESC
LIMIT 25
IV term structure for one underlying
SELECT expiration_date,
       AVG(implied_volatility) FILTER (WHERE ABS(delta) BETWEEN 0.45 AND 0.55) AS atm_iv
FROM options_snapshots
WHERE underlying_ticker = 'AAPL'
GROUP BY expiration_date
ORDER BY expiration_date

Get example prompts & new data guides

Occasional emails with prompts worth trying and what’s new in the data.