OptionSpace.ai

Reference

Every method and route.

Every public method of the optionspace Python package (version 0.9.0) and every route of the REST API: arguments, defaults and what comes back. Generated from the code. In Python, help(osp.Client.smile) (or client.smile? in Jupyter) shows the same description where you work.

Contents

How to read this page

Python: connect

Every data method is called on a Client. Create one per script or notebook.

osp.Client()

A connection to the OptionSpace data API, using your saved key.

osp.Client(
    api_key=None,
    *,
    base_url=None,
    profile=None,
    timeout=60.0,
    job_timeout=1800.0,
)
Argument Type Default Meaning
api_key str or None None Your sk_os_... key. None uses OPTIONSPACE_API_KEY, then the key saved by optionspace login. It never prompts: with no key found it raises CredentialError saying what to run.
base_url str or None None The server to send requests to, for a key passed here or set in the environment (or set OPTIONSPACE_BASE_URL). A saved key works only with the server it was saved for.
profile str or None None Which saved login to use. None uses OPTIONSPACE_PROFILE, else "default". Ignored when a key is passed here or set in the environment.
timeout float 60.0 Seconds to wait for each request.
job_timeout float 1800.0 Total seconds to wait for a research call that became a background job. After that, JobTimeout gives you the job_id to collect it later with client.job().

Returns: A client. client.credential_source says where the key came from ("argument", "env", "keyring" or "file"), client.profile names the saved login used, and client.base_url is the only server the key is sent to.
Requests: None: creating a client makes no API request.

Python: account and background jobs

Your account, the price list and background jobs. These answer even when today's allowance is used up.

client.entitlements()

Your plan, what your key can access, your daily limits and today's usage, with the price list.

client.entitlements() -> dict

Returns: A dictionary: tier, status, entitlements (your grants), key_id, limits (credits_per_day, rows_per_day, concurrent), usage (credits_today, credits_reserved: credits held by research calls still running, rows_today, ...) and prices. Always answers, even when today's allowance is used up.
REST: GET /entitlements

client.prices()

What each call costs, in credits.

client.prices() -> dict

Returns: A dictionary: lookups (the price of each lookup route), research (how research routes are charged), version and effective (the date the list took effect).
Requests: One account check (it reads the price list from entitlements()).

client.job()

Collect a research call that became a background job.

client.job(job_id, *, wait=True, timeout=None)
Argument Type Default Meaning
job_id str required The id from JobTimeout.job_id, or from a REST 202 response.
wait bool True True waits until the job finishes and returns its answer. False returns the job's state now.
timeout float or None None Seconds to wait. None uses the client's job_timeout.

Returns: With wait=True, the same result the research call would have returned. With wait=False, a dictionary whose status is queued, running, done, failed or canceled. Results are kept for one hour after the job finishes.
REST: GET /jobs/{job_id}

Python: discover what exists

Check what exists before a larger pull: symbols, the snapshot times your plan covers, and the listed expirations.

client.symbols()

Every symbol in the product, with the sessions it covers.

client.symbols() -> DataFrame

Returns: A DataFrame with symbol, start_date, end_date and n_days. When your access is limited to a sample window, .attrs["sample_window"] gives it.
REST: GET /symbols

DataFrame columns: symbol, start_date, end_date, n_days. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, sample_window. Each means the same as the REST field of the same name (GET /symbols).

client.snapshots()

The snapshot times your key can retrieve for one session.

client.snapshots(symbol, data_date) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.

Returns: A DataFrame with symbol, data_date, snapshot_time, after_close (a time after an early close) and is_eod (the time "EOD" resolves to). .attrs holds eod, session_close and granularity.
REST: GET /snapshots

DataFrame columns: symbol, data_date, snapshot_time, after_close, is_eod. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, eod. Each means the same as the REST field of the same name (GET /snapshots); the ones that differ:

Columns that differ from REST (2)
Column or attr Type and unit Can be empty Meaning Example
after_close true or false True when the time falls after an early close. false
is_eod true or false True on the time EOD resolves to. false

client.expirations()

The expirations listed at one snapshot.

client.expirations(
    symbol,
    data_date,
    *,
    snapshot="EOD",
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
snapshot str "EOD" "EOD" (the last snapshot at or before the official close that your plan covers) or a time "HH:MM" in US Eastern time. The time used comes back as snapshot_time.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per root and expiration: root, expiration, dte (calendar days to expiry). An expiry whose fit did not pass our quality checks at that snapshot is not listed; .attrs["withheld_for_quality"] names those. When every expiry was withheld, NotFoundError with reason "withheld_for_quality".
REST: GET /expirations

DataFrame columns: symbol, root, data_date, snapshot_time, expiration, dte. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, withheld_for_quality. Each means the same as the REST field of the same name (GET /expirations).

Python: data lookups

Each lookup is one request. A chain() over several snapshots makes several requests and says so before it starts.

client.smile()

The fitted smile for one expiry at one snapshot.

client.smile(
    symbol,
    data_date,
    *,
    expiration,
    snapshot="EOD",
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
snapshot str "EOD" "EOD" (the last snapshot at or before the official close that your plan covers) or a time "HH:MM" in US Eastern time. The time used comes back as snapshot_time.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per strike and side: fitted_vol (decimal), z, the greeks, is_wing_extrapolated, and on past sessions the quotes (bid, ask, mid, iv_bid, iv_ask, iv_mid). Quote columns are absent until the quotes are released, at the next session's 09:30 ET open. Expiry values such as forward, atm_vol, rate, q, T_vol, rmse and the quality block are in .attrs; the fitted curve is .attrs["fitted_curve"] (see curve_frame()).
REST: GET /smile

DataFrame columns: symbol, root, data_date, snapshot_time, expiration, strike, put_call, fitted_vol, z, delta, gamma, vega, theta, theta_1d, theta_next_open, rho, vanna, volga, is_otm, is_wing_extrapolated, iv_bid, iv_ask, iv_mid, bid, ask, mid, volume, open_interest, quote_state. .attrs keys: symbol, root, data_date, snapshot_time, session_close, expiration, dte, forward, spot, s_implied, rate, q, T, T_vol, T_borrow, atm_vol, rmse, n_options, n_fitted, gated, granularity, notes, z_lo, z_hi, fitted_curve. Each means the same as the REST field of the same name (GET /smile); the ones that differ:

Columns that differ from REST (3)
Column or attr Type and unit Can be empty Meaning Example
strike number, USD Strike price (the REST strikes list, one row per strike and side). 520.0
put_call text Call or put: which of the REST calls and puts lists the row came from. Values: call, put. "call"
fitted_vol number, decimal vol Fitted implied volatility (the REST fitted list). 0.2144

client.chain()

Every fitted option for a session at one snapshot, across all expiries unless filtered.

client.chain(
    symbol,
    data_date,
    snapshot="EOD",
    *,
    snapshots=None,
    root=None,
    expiration=None,
    strike_min=None,
    strike_max=None,
    put_call=None,
    columns=None,
    check_budget=True,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
snapshot str "EOD" One snapshot: "EOD" or "HH:MM" US Eastern time. Pass either snapshot or snapshots, not both.
snapshots str or list of str or None None Several snapshots: a list of "HH:MM" times, or "all" for every snapshot your key can retrieve. Each one is a separate request.
root str or None None Only this listed root. None returns every root the symbol lists; each row names its root.
expiration date or str or None None Only this expiration. None returns every expiration.
strike_min float or None None Only strikes at or above this price (USD).
strike_max float or None None Only strikes at or below this price (USD).
put_call str or None None "call" or "put" only. None returns both.
columns str or list of str or None None Return only these columns: a list, or one comma-separated text. Identity columns are always included. Quote columns exist for past sessions only. Allowed: data_date, snapshot_time, symbol, expiration, strike, put_call, company_name, sector, security_type, dte, spot, forward, q, r, T_vol, z, fitted_vol, fitted_price, delta, gamma, vega, theta, rho, vanna, volga, is_wing_extrapolated, bid, ask, bid_size, ask_size, iv_bid, iv_ask, iv_mid, theta_1d, theta_next_open. On request (not in the default columns), for past sessions only: volume, open_interest. volume is the contracts traded so far that session up to the snapshot minute; the official end-of-day figure is in /contract-volume. Filtering by expiration keeps a large chain fast.
check_budget bool True Before a pull over several snapshots, check that its credits and projected rows fit what is left today, and raise QuotaExceeded (nothing more fetched) if they do not. False skips the check.

Returns: A DataFrame of option rows: identity columns (symbol the product, root the listed root, data_date, snapshot_time, expiration, strike, put_call), then the fitted values and greeks, and quotes on past sessions. .attrs["budget"] says what a multi-snapshot pull cost. One snapshot over the per-request row limit raises ResponseTooLarge (never a cut-off frame): narrow it with the filters.
Requests: One per snapshot.
REST: GET /chain

DataFrame columns: symbol, root, data_date, snapshot_time, expiration, strike, put_call, company_name, sector, security_type, dte, spot, forward, q, r, T_vol, z, fitted_vol, fitted_price, delta, gamma, vega, theta, rho, vanna, volga, is_wing_extrapolated, bid, ask, bid_size, ask_size, iv_bid, iv_ask, iv_mid, theta_1d, theta_next_open, quote_state. .attrs keys: symbol, root, data_date, session_close, granularity, columns, snapshot_time, snapshots, returned_rows, notes, budget. Each means the same as the REST field of the same name (GET /chain); the ones that differ:

Columns that differ from REST (4)
Column or attr Type and unit Can be empty Meaning Example
symbol text The product symbol you asked for (in REST rows, symbol is the root). "SPX"
root text The listed root of the option (the REST row's symbol). "SPXW"
.attrs["snapshots"] list of text, HH:MM, US Eastern The snapshots fetched, in order.
.attrs["budget"] object What a multi-request call cost: the requests and credits used, and (for chain()) the rows fetched and projected.

client.atm_intraday()

At-the-money volatility through one session for one expiry.

client.atm_intraday(
    symbol,
    data_date,
    *,
    expiration,
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
root str or None None The listed root. None serves the root that lists the expiry, or SPXW on the SPX monthlies where both do. Roots are never mixed; .attrs["root"] names the one served.

Returns: A DataFrame with one row per snapshot your plan covers: snapshot_time, sigma_atm (decimal), forward, n_obs (options in the fit). Minutes where this expiry's fit did not pass our quality checks are left out and listed in .attrs["withheld_snapshots"].
REST: GET /atm-timeseries

DataFrame columns: symbol, root, data_date, snapshot_time, expiration, sigma_atm, forward, n_obs. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, expiration, withheld_snapshots. Each means the same as the REST field of the same name (GET /atm-timeseries); the ones that differ:

Columns that differ from REST (1)
Column or attr Type and unit Can be empty Meaning Example
expiration text, YYYY-MM-DD The expiry. "2025-04-17"

client.term_structure()

At-the-money volatility for every listed expiry at one snapshot.

client.term_structure(
    symbol,
    data_date,
    *,
    snapshot="EOD",
    root=None,
    max_expiries=60,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
snapshot str "EOD" "EOD" (the last snapshot at or before the official close that your plan covers) or a time "HH:MM" in US Eastern time. The time used comes back as snapshot_time.
root str or None None The listed root. Needed when the symbol lists under several roots that day: a term structure uses one root.
max_expiries int 60 The most expiries to fetch, from 1 to 60. With more listed, it raises InvalidRequestError rather than leave any out.

Returns: A DataFrame with one row per expiry, shortest first: symbol, root, data_date, snapshot_time, expiration, dte, atm_vol, forward, z_lo, z_hi. .attrs["budget"] gives the requests and credits used.
Requests: One for the expirations lookup plus one per expiry (one smile each). Checked against what is left today before the smiles are fetched.
REST: GET /term-structure

DataFrame columns: symbol, root, data_date, snapshot_time, expiration, dte, atm_vol, forward, z_lo, z_hi. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, budget. Each means the same as the REST field of the same name (GET /smile, GET /expirations); the ones that differ:

Columns that differ from REST (2)
Column or attr Type and unit Can be empty Meaning Example
atm_vol number, decimal vol The expiry's at-the-money volatility (the atm_vol of its smile). 0.1423
.attrs["budget"] object What a multi-request call cost: the requests and credits used, and (for chain()) the rows fetched and projected.

client.tenor_history()

Constant-maturity volatility measures through time for one tenor.

client.tenor_history(
    symbol,
    tenor_days,
    start,
    end,
    *,
    snapshot=None,
    root=None,
    fields=None,
    sampling=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
tenor_days int required The constant maturity in calendar days: one of 30, 60, 90, 180, 360.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
snapshot str or None None None: every snapshot your plan covers. "EOD": each session's last snapshot at or before its close. "HH:MM": that time each session.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.
fields str or list of str or None None Return only these measures: a list, or one comma-separated text. None returns all. Allowed: atf_iv, skew_25d, curvature_25d, straddle_pct, strangle_25d_pct, rr_25d_pct, atf_iv_clean, atf_iv_clean_se, straddle_pct_clean, event_dates.
sampling str or None None How often each session is sampled: "eod", "30m", "5m" or "1m". None: your plan's frequency. Your plan must cover it (EntitlementError otherwise). Give either snapshot or sampling, not both.

Returns: A DataFrame with one row per session and snapshot: identity columns, tenor_days and the measures (each one is described under GET /tenor-history). A range too wide for your plan's frequency raises InvalidRequestError with estimated_rows: narrow the dates or pass snapshot="EOD".
Requests: One call covers at most a set number of periods (one snapshot of one session; client.prices() gives the limit); a longer range is fetched in several calls, checked against what is left today before the first one.
REST: GET /tenor-history

DataFrame columns: symbol, root, data_date, snapshot_time, tenor_days, atf_iv, skew_25d, curvature_25d, straddle_pct, strangle_25d_pct, rr_25d_pct, atf_iv_clean, atf_iv_clean_se, straddle_pct_clean, event_dates. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, start, end, tenor_days, snapshot_mode, fields, calls, budget. Each means the same as the REST field of the same name (GET /tenor-history); the ones that differ:

Columns that differ from REST (3)
Column or attr Type and unit Can be empty Meaning Example
symbol text The product symbol. "SPY"
.attrs["calls"] integer How many requests the range took: one, or several when it covered more periods than one call may. 1
.attrs["budget"] object What a multi-request call cost: the requests and credits used, and (for chain()) the rows fetched and projected.

client.rates()

The risk-free rate implied by SPX options, per session and listed expiry.

client.rates(start, end, *, expiration=None) -> DataFrame
Argument Type Default Meaning
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
expiration date or str or None None Only this listed expiry. None returns every expiry.

Returns: A DataFrame with data_date, expiration, rate (continuously compounded, annualized, decimal) and rate_se (its standard error).
Requests: One call covers at most a set number of periods (one snapshot of one session; client.prices() gives the limit); a longer range is fetched in several calls, checked against what is left today before the first one.
REST: GET /rates

DataFrame columns: symbol, data_date, expiration, rate, rate_se. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes, start, end, calls, budget. Each means the same as the REST field of the same name (GET /rates); the ones that differ:

Columns that differ from REST (3)
Column or attr Type and unit Can be empty Meaning Example
symbol text Always SPX. "SPX"
.attrs["calls"] integer How many requests the range took: one, or several when it covered more periods than one call may. 1
.attrs["budget"] object What a multi-request call cost: the requests and credits used, and (for chain()) the rows fetched and projected.

client.iv_rv()

At-the-money implied volatility, realized volatility and the gap between them at 1 to 30 days, through time.

client.iv_rv(
    symbol,
    tenor_days,
    start,
    end,
    *,
    snapshot=None,
    root=None,
    fields=None,
    sampling=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
tenor_days int required The horizon in calendar days: one of 1, 2, 3, 5, 7, 10, 14, 20, 30.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
snapshot str or None None None: every snapshot your plan covers. "EOD": each session's last snapshot at or before its close. "HH:MM": that time each session.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.
fields str or list of str or None None Return only these measures: a list, or one comma-separated text. None returns all.
sampling str or None None How often each session is sampled: "eod", "30m", "5m" or "1m". None: your plan's frequency. Your plan must cover it (EntitlementError otherwise). Give either snapshot or sampling, not both.

Returns: A DataFrame with one row per session and snapshot: identity columns, tenor_days and the measures (each one is described under GET /iv-rv).
Requests: One call covers at most a set number of periods (one snapshot of one session; client.prices() gives the limit); a longer range is fetched in several calls, checked against what is left today before the first one.
REST: GET /iv-rv

client.expiry_history()

One expiry's values across sessions, at one snapshot each session.

client.expiry_history(
    symbol,
    expiration,
    start,
    end,
    *,
    snapshot="EOD",
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
snapshot str "EOD" "EOD" (each session's last snapshot at or before its close that your plan covers) or a time "HH:MM" in US Eastern time, the same each session.
root str or None None The listed root. None: for an SPX monthly listed under both SPX and SPXW, SPXW is served (.attrs["notes"] says so).

Returns: A DataFrame with one row per session, oldest first: the values surface() gives for an expiry, from sigma_atm to r_squared (each one is described under GET /expiry-history). .attrs["withheld_sessions"] lists sessions with no row because the expiry's fit did not pass our quality checks.
REST: GET /expiry-history

client.contract_history()

One option across sessions: fitted volatility and price, greeks and, on past sessions, its quotes, at one snapshot each session.

client.contract_history(
    symbol,
    expiration,
    strike,
    put_call,
    start,
    end,
    *,
    snapshot="EOD",
    root=None,
    columns=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
strike float required The strike price (USD).
put_call str required "call" or "put".
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
snapshot str "EOD" "EOD" (each session's last snapshot at or before its close that your plan covers) or a time "HH:MM" in US Eastern time, the same each session.
root str or None None The listed root. None: for an SPX monthly listed under both SPX and SPXW, SPXW is served.
columns str or list of str or None None Return only these columns, as for chain(): a list, or one comma-separated text. "volume" and "open_interest" are added only when named, for a range that ends on a past session.

Returns: A DataFrame with one row per session, oldest first: identity columns, then chain()'s columns for this option, and quote_state where the quotes are included. .attrs["withheld_sessions"] lists sessions with no row because the expiry's fit did not pass our quality checks.
REST: GET /contract-history

client.event_outcomes()

For each earnings, Fed, CPI or jobs-report day, the move the options priced, the move that followed, and 30-day at-the-money volatility before and after.

client.event_outcomes(
    symbol,
    start,
    end,
    *,
    event_type=None,
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
event_type str or None None One of earnings, fomc, cpi, nfp. None: every type.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per event, oldest first (each column is described under GET /event-outcomes). Moves are decimals of the share price.
REST: GET /event-outcomes

client.event_runup()

The move priced for each event on the sessions before it.

client.event_runup(
    symbol,
    start,
    end,
    *,
    event_type=None,
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
event_type str or None None One of earnings, fomc, cpi, nfp. None: every type.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per event and session before it (each column is described under GET /event-runup).
REST: GET /event-runup

client.volume_summary()

Daily volume and open interest for calls, puts and both, with the put/call ratios. Past sessions only.

client.volume_summary(symbol, start, end, *, root=None) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per session (each column is described under GET /volume-summary). A session not yet available raises EntitlementError with reason "not_yet_historical".
REST: GET /volume-summary

client.contract_volume()

One option's official daily volume and open interest. Past sessions only.

client.contract_volume(
    symbol,
    expiration,
    strike,
    put_call,
    start,
    end,
    *,
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
strike float required The strike price (USD).
put_call str required "call" or "put".
start date or str required The first session of the range: "YYYY-MM-DD" or a datetime.date.
end date or str required The last session of the range: "YYYY-MM-DD" or a datetime.date.
root str or None None The listed root. None: for an SPX monthly listed under both SPX and SPXW, SPXW is served.

Returns: A DataFrame with one row per session the option was listed, oldest first: open_interest (at the end of the previous session) and volume (the official end-of-day figure), in contracts.
REST: GET /contract-volume

client.surface()

A legacy end-of-day summary: one row per listed expiry. For new work use term_structure() or smile().

client.surface(
    symbol,
    data_date,
    snapshot="EOD",
    *,
    root=None,
) -> DataFrame
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
snapshot str "EOD" "EOD" (the last snapshot at or before the official close that your plan covers) or a time "HH:MM" in US Eastern time. The time used comes back as snapshot_time.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.

Returns: A DataFrame with one row per listed expiry: expiration, dte, sigma_atm, forward, rate, q, spot, the fit statistics, quality and its root. .attrs holds the resolved snapshot_time and session_close.
REST: GET /surface

DataFrame columns: snapshot_time, expiration, dte, sigma_atm, forward, rate, q, spot, s_implied, s_basis, T, T_vol, T_borrow, n_obs, rmse, r_squared, root, quality. .attrs keys: symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, date, spot. Each means the same as the REST field of the same name (GET /surface).

Python: research calls

Each research call answers a whole question in one request and returns a dictionary with its values, the observations it used, coverage and a status. Pass estimate=True first to see the most a call can cost, or max_credits=N to have it refused, unrun, if it could cost more. A long call becomes a background job that the client waits for. They work on completed sessions only, and except for expiry_metrics() they need a symbol that lists under one root.

client.expiry_metrics()

At-the-money volatility and 25-delta skew for one listed expiry at one snapshot, optionally beside the previous session.

client.expiry_metrics(
    symbol,
    data_date,
    *,
    expiration,
    snapshot="EOD",
    root=None,
    compare_previous=False,
    max_credits=None,
    estimate=False,
) -> dict
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
expiration date or str required The option expiration date: "YYYY-MM-DD" or a datetime.date. client.expirations() lists the ones that exist.
snapshot str "EOD" "EOD" (the last snapshot at or before the official close that your plan covers) or a time "HH:MM" in US Eastern time. The time used comes back as snapshot_time.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.
compare_previous bool False Also return the previous trading session, and the change in skew.
max_credits int or None None Refuse the call, unrun, if it could cost more than this many credits (raises MaxCreditsExceeded). None sets no limit. At least 1.
estimate bool False True returns the cost estimate (credits_estimate, credits_cap) without running the call.

Returns: A dictionary with one entry per session: sigma_atm, put_25d_iv, call_25d_iv, put_25d_skew, call_25d_skew, skew_25d and status; skew_change when the previous session is compared; credits (what the call cost).
REST: GET /expiry-metrics

client.exposure_distribution()

Where today's fitted volatility for a delta-selected option sits against the same selection on past sessions: same clock time, same days to expiry.

client.exposure_distribution(
    symbol,
    data_date,
    *,
    snapshot,
    put_call,
    delta=0.15,
    dte=1,
    delta_tolerance=0.01,
    lookback_years=1,
    root=None,
    max_credits=None,
    estimate=False,
) -> dict
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
data_date date or str required The trading session: "YYYY-MM-DD" text or a datetime.date.
snapshot str required An explicit "HH:MM" US Eastern time. "EOD" is refused, so every session is compared at the same clock time.
put_call str required "call" or "put".
delta float 0.15 The absolute delta to select (0.15 = a 15-delta option). Greater than 0 and less than 1.
dte int 1 Days to expiry, in calendar days, matched exactly. From 0 to 365.
delta_tolerance float 0.01 How far a stored option's delta may be from delta and still be selected. From 0 to 0.05.
lookback_years float 1 How many years of past sessions to compare against. From 0.1 to 1.
root str or None None The listed root, for a symbol that lists under several (SPX lists as SPX and SPXW). None lets the server choose, and the result names the root it served. See SPX and SPXW.
max_credits int or None None Refuse the call, unrun, if it could cost more than this many credits (raises MaxCreditsExceeded). None sets no limit. At least 1.
estimate bool False True returns the cost estimate (credits_estimate, credits_cap) without running the call.

Returns: A dictionary: current (the option selected on data_date), history (one selected option per past session, with expiration, strike, delta, fitted_vol), statistics (n, median, percentile from 0 to 100, robust_z_score), coverage, status and credits.
REST: GET /exposure-distribution

client.clean_atm_history()

Constant-maturity at-the-money volatility through time, with scheduled events removed (clean) and without (dirty).

client.clean_atm_history(
    symbol,
    start,
    end,
    *,
    tenor_days=30,
    granularity="eod",
    max_credits=None,
    estimate=False,
) -> dict
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
start date or str required The first session: "YYYY-MM-DD" or a date. At most three years before end.
end date or str required The last session, before today: "YYYY-MM-DD" or a date.
tenor_days int 30 The constant maturity in calendar days. From 7 to 180.
granularity str "eod" "eod" (one value per session) or "5m" (every five minutes; needs five-minute or finer access).
max_credits int or None None Refuse the call, unrun, if it could cost more than this many credits (raises MaxCreditsExceeded). None sets no limit. At least 1.
estimate bool False True returns the cost estimate (credits_estimate, credits_cap) without running the call.

Returns: A dictionary: rows (data_date, snapshot_time, dirty_iv, clean_iv, clean_iv_se, status, event_dates, expirations), earnings (report dates in the window), notes and credits. For a stock only the next scheduled earnings report is removed; for an index or ETF, scheduled macro releases. Where an event cannot be removed, clean_iv repeats the unadjusted value and status says why.
REST: GET /clean-atm-history

client.earnings_history()

Past earnings reports: the move the options priced, the move that followed, and how the priced move built up beforehand.

client.earnings_history(
    symbol,
    as_of,
    *,
    count=8,
    runup_sessions=30,
    granularity="eod",
    max_credits=None,
    estimate=False,
) -> dict
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
as_of date or str required Use the reports strictly before this date ("YYYY-MM-DD", before today).
count int 8 How many past reports. From 1 to 12.
runup_sessions int 30 How many sessions before each report the run-up path covers. From 5 to 60.
granularity str "eod" "eod" or "5m" for the run-up path (five-minute needs five-minute or finer access).
max_credits int or None None Refuse the call, unrun, if it could cost more than this many credits (raises MaxCreditsExceeded). None sets no limit. At least 1.
estimate bool False True returns the cost estimate (credits_estimate, credits_cap) without running the call.

Returns: A dictionary with events, each with earnings_date, timing (AMC after the close or BMO before the open), implied_move (the average size of the move priced at the last close before the report, about 0.80 times the event's volatility; a decimal, not annualized) and implied_move_se, actual_return (signed), actual_move (its size), return_overnight, return_intraday, status and samples (the run-up path, by sessions_to_event); plus notes and credits.
REST: GET /earnings-history

client.earnings_backtest()

A bounded earnings put-spread simulation: for each recent report, sell a put spread before the report and close it after, with fills, fees, P&L and risk.

client.earnings_backtest(
    symbol,
    as_of,
    *,
    count=8,
    entry_offset=-5,
    exit_offset=1,
    fill_model="quote_side",
    fee_per_contract=0.65,
    granularity="eod",
    max_credits=None,
    estimate=False,
) -> dict
Argument Type Default Meaning
symbol str required The product symbol, such as "SPY" or "SPX". client.symbols() lists them.
as_of date or str required Use the reports strictly before this date ("YYYY-MM-DD", before today).
count int 8 How many past reports to trade, one spread each. From 1 to 12.
entry_offset int -5 When to open: sessions relative to the last close before the report (-5 = five sessions earlier). From -20 to 0.
exit_offset int 1 When to close: sessions after the report. From 1 to 5.
fill_model str "quote_side" "quote_side" (sell at the bid, buy at the ask), "midpoint", or "theoretical" (the fitted price). Quote-based fills need an as_of whose quotes are released.
fee_per_contract float 0.65 Fee in USD per contract per fill. From 0 to 20.
granularity str "eod" "eod" or "1m" for the position path (one-minute needs one-minute access).
max_credits int or None None Refuse the call, unrun, if it could cost more than this many credits (raises MaxCreditsExceeded). None sets no limit. At least 1.
estimate bool False True returns the cost estimate (credits_estimate, credits_cap) without running the call.

Returns: A dictionary: assumptions (the trade rules), events (each with status, entry_date, exit_date, expiration, legs, fills, gross_pnl, fees, net_pnl and path: P&L, delta, gamma, vega and theta at each observation), summary and credits. P&L is in USD for one spread.
REST: GET /earnings-backtest

Python: saved keys

The same steps as optionspace login, whoami and logout in the terminal, from Python.

osp.login()

Check a key with the server and save it on this computer. The same as optionspace login in the terminal.

osp.login(
    api_key=None,
    *,
    profile=None,
    store="keyring",
    base_url="https://optionspace.ai",
    stdin=False,
) -> LoginStatus
Argument Type Default Meaning
api_key str or None None The key to save. None asks for it without showing what you type.
profile str or None None The name to save it under (letters, digits, - and _). None uses OPTIONSPACE_PROFILE, else "default".
store str "keyring" "keyring" (your operating system's credential store) or "file" (a private file in your user folder).
base_url str "https://optionspace.ai" The server the key is checked against. The saved key works only with this server.
stdin bool False Read the key from one line of standard input instead of asking; for scripts.

Returns: A LoginStatus. A rejected key, or a server that cannot be reached, saves nothing and raises CredentialError.
Requests: One account check.

osp.whoami()

Which key this computer would use, where it came from, and what the server says about it. The same as optionspace whoami.

osp.whoami(*, profile=None, api_key=None, base_url=None) -> dict
Argument Type Default Meaning
profile str or None None Which saved login to check. None uses the one Client() would use.
api_key str or None None Check this key instead of a saved one.
base_url str or None None The server to check against, for a key passed here or set in the environment.

Returns: A dictionary: source, profile, base_url, key_id, org_name, tier, status, entitlements, limits, usage and notes. Never the key itself. A rejected key raises AuthenticationError.
Requests: One account check.

osp.logout()

Remove the saved key from this computer. The key stays valid on the server: revoke it on your account page.

osp.logout(profile=None, *, all_profiles=False) -> dict
Argument Type Default Meaning
profile str or None None Which saved login to remove. None uses OPTIONSPACE_PROFILE, else "default".
all_profiles bool False Remove every saved login.

Returns: A dictionary: removed (what was deleted for each profile), env_key_still_set (whether OPTIONSPACE_API_KEY is still set) and a note.
Requests: No API request.

osp.LoginStatus

What login() did. Safe to print and to keep in a notebook: it never contains the key.

Field Type Default Meaning
profile str required The profile the key was saved under.
store str required Where it was saved: "keyring" or "file".
base_url str required The server it was checked against.
key_id str or None required The public key identifier (osk_...).
org_id str or None required Your account's identifier.
tier str or None required Your plan.
entitlements dict required What the key can access.
limits dict required Your daily limits.
usage dict required Today's usage.
removed_copies tuple () Older saved copies of this profile that were removed.
warnings tuple () Anything to act on, such as an environment key that would take priority.

Python: working with results

Helpers for frames you have already fetched. None of them makes an API request.

osp.curve_frame()

The fitted smile curve from a smile() result, as a DataFrame.

osp.curve_frame(smile) -> DataFrame
Argument Type Default Meaning
smile DataFrame required A frame from client.smile().

Returns: A DataFrame with the identity columns plus z, strike, sigma (decimal) and is_wing_extrapolated (the point lies outside the fitted range). Empty for older curve types that carry no curve.
Requests: No API request.

osp.FrameCache()

A folder cache of the frames the client returns, so a notebook can rerun without new requests. Each entry is stored under its full query and checked when read: a frame that does not match raises CacheMismatch instead of being returned. Entries are Python pickle files: only open a cache folder you created.

osp.FrameCache(directory, *, base_url="https://optionspace.ai")
cache = osp.FrameCache("study_cache", base_url=client.base_url)
query = dict(symbol="SPY", data_date="2025-04-10",
             expiration="2025-04-17", snapshot_time="15:57")
sm = cache.get("smile", **query)
if sm is None:
    sm = client.smile("SPY", "2025-04-10", expiration="2025-04-17", snapshot="15:57")
    cache.put(sm, "smile", **query)
Argument Type Default Meaning
directory str or Path required The folder to keep the cache in. Created when needed.
base_url str "https://optionspace.ai" The server the data came from; data from different servers is kept apart. Pass client.base_url.

Returns: A cache. Its methods follow.
Requests: No API request.

cache.get()

The cached frame for exactly this query, or None.

cache.get(route, **identity) -> DataFrame or None
Argument Type Default Meaning
route str required The method the frame came from, such as "smile".
**identity str The query: symbol and data_date (both required), and root, expiration and snapshot_time where they apply.

Returns: The DataFrame, or None when nothing is stored. An entry that does not match its query or has changed on disk raises CacheMismatch.

cache.put()

Store a frame under its query.

cache.put(frame, route, **identity) -> Path
Argument Type Default Meaning
frame DataFrame required The DataFrame to store. Its own identity must match the query.
route str required The method the frame came from, such as "smile".
**identity str The query, as for get().

Returns: The path of the stored file.

Python: charts

from optionspace import charts (install with pip install "optionspace[charts]"). Every chart takes results you already fetched and returns an editable Plotly figure: drawing makes no API request. Charts show volatility in vol points (20 = 20%) and never fill in missing observations. fig.show() displays one; charts.save() writes a file.

charts.smile_chart()

A smile card from one client.smile() result: observed mids, bid/ask bars on every quoted strike, and the fitted curve (solid inside the fitted range, dotted outside).

charts.smile_chart(
    sm,
    theme="light",
    *,
    title=None,
    xlim=None,
    width=1100,
    mark=True,
) -> Figure
Argument Type Default Meaning
sm DataFrame required A frame from client.smile().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
title str or None None A title for the card. None uses the default title.
xlim tuple or None None (low, high) strike range for the x-axis. None shows the quoted strikes.
width int 1100 Figure width in pixels.
mark bool True Draw the OptionSpace logo mark on the card.

Returns: A Plotly figure.

charts.compare_chart()

Smiles side by side, one panel each, on shared axes.

charts.compare_chart(
    smiles,
    theme="light",
    *,
    labels=None,
    title=None,
    xlim=None,
    width=1100,
    mark=True,
) -> Figure
Argument Type Default Meaning
smiles list of DataFrame required A list of client.smile() frames (two to four work best).
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
labels list of str or None None A label per panel. None labels each panel by what differs (time, date or expiry).
title str or None None A title for the card. None uses the default title.
xlim tuple or None None (low, high) strike range for every panel. None covers the quoted strikes.
width int 1100 Figure width in pixels.
mark bool True Draw the OptionSpace logo mark on the card.

Returns: A Plotly figure.

charts.atm_chart()

The at-the-money volatility path through a session.

charts.atm_chart(
    atm,
    theme="light",
    *,
    highlight=(),
    title=None,
    width=1100,
    mark=True,
    cadence=None,
) -> Figure
Argument Type Default Meaning
atm DataFrame required A frame from client.atm_intraday().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
highlight list of str () Snapshot times ("HH:MM") to mark on the path.
title str or None None A title for the card. None uses the default title.
width int 1100 Figure width in pixels.
mark bool True Draw the OptionSpace logo mark on the card.
cadence int or None None Minutes between snapshots, used to show gaps. None uses your plan's frequency, else the spacing in the data (stated on the chart).

Returns: A Plotly figure.

charts.term_chart()

At-the-money volatility and the forward across expiries.

charts.term_chart(
    term,
    theme="light",
    *,
    title=None,
    width=1100,
    mark=True,
) -> Figure
Argument Type Default Meaning
term DataFrame required A frame from charts.term_frame() or client.term_structure().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
title str or None None A title for the card. None uses the default title.
width int 1100 Figure width in pixels.
mark bool True Draw the OptionSpace logo mark on the card.

Returns: A Plotly figure.

charts.history_chart()

One to four constant-maturity histories on one chart. Gaps between sessions break the line; missing values are never filled.

charts.history_chart(
    histories,
    metric="atf_iv",
    theme="light",
    *,
    width=1100,
)
Argument Type Default Meaning
histories list of DataFrame required One to four client.tenor_history() frames at the same tenor.
metric str "atf_iv" "atf_iv" (at-the-forward volatility) or "skew_25d" (25-delta skew).
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.term_comparison_chart()

Term structures from different dates on one chart.

charts.term_comparison_chart(terms, theme="light", *, width=1100)
Argument Type Default Meaning
terms list of DataFrame required One to four client.term_structure() frames for the same symbol and root.
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.skew_comparison_chart()

25-delta skew by symbol, one bar per session.

charts.skew_comparison_chart(
    rows,
    tenor_days=None,
    theme="light",
    *,
    expiration=None,
    width=1100,
)
Argument Type Default Meaning
rows list of dict required A list of dictionaries, each with symbol, data_date, snapshot_time and skew_25d (decimal), for example client.tenor_history(..., fields=["skew_25d"]).to_dict("records").
tenor_days int or None None The tenor the rows come from, for the label.
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
expiration str or None None The listed expiry the rows come from, for the label (instead of tenor_days).
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.exposure_distribution_chart()

The past distribution of fitted volatility, with today's value marked.

charts.exposure_distribution_chart(result, theme="light", *, width=1100)
Argument Type Default Meaning
result dict required The dictionary from client.exposure_distribution().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.clean_atm_chart()

At-the-money volatility with and without scheduled events, with the earnings dates marked.

charts.clean_atm_chart(result, theme="light", *, width=1100)
Argument Type Default Meaning
result dict required The dictionary from client.clean_atm_history().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.earnings_comparison_chart()

The move each report priced beside the move that followed.

charts.earnings_comparison_chart(result, theme="light", *, width=1100)
Argument Type Default Meaning
result dict required The dictionary from client.earnings_history().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.earnings_runup_chart()

How the priced earnings move built up in the sessions before each report.

charts.earnings_runup_chart(result, theme="light", *, width=1100)
Argument Type Default Meaning
result dict required The dictionary from client.earnings_history().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.earnings_backtest_chart()

P&L per event from an earnings put-spread simulation.

charts.earnings_backtest_chart(result, theme="light", *, width=1100)
Argument Type Default Meaning
result dict required The dictionary from client.earnings_backtest().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.backtest_path_chart()

One simulated trade from entry to exit: P&L and delta at each observation.

charts.backtest_path_chart(
    result,
    theme="light",
    *,
    earnings_date=None,
    width=1100,
)
Argument Type Default Meaning
result dict required The dictionary from client.earnings_backtest().
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
earnings_date str or None None Which report's trade to draw ("YYYY-MM-DD"). None draws the last completed trade.
width int 1100 Figure width in pixels.

Returns: A Plotly figure.

charts.save()

Write a figure to a file.

charts.save(fig, path, *, scale=2) -> Path
Argument Type Default Meaning
fig Figure required A figure from any chart function.
path str or Path required The file to write. The extension picks the format: .png, .svg or .pdf (need the chart export extras and Chrome), .html (an interactive page that loads Plotly from the internet) or .json (the figure itself).
scale float 2 Image scale for .png, .svg and .pdf. 2 gives 2200-pixel-wide cards.

Returns: The path written.

Python: session movies

from optionspace.movies import DayMovie. A replay of one expiry's smile through a session, built from smiles you fetched (one request each, so count the snapshots first with client.snapshots()). Building and saving the movie makes no further request.

DayMovie()

A replay of one expiry's smile through a session.

DayMovie(
    smiles,
    *,
    atm=None,
    theme="light",
    stride=1,
    xlim="band",
    title=None,
    fps=12,
    trail=20,
    width=1100,
    cadence=None,
    source_snapshots=None,
    include_last=False,
    coverage_evidence=None,
)
from optionspace.movies import DayMovie
times = client.snapshots("SPY", "2025-04-10")["snapshot_time"][::30]
smiles = [client.smile("SPY", "2025-04-10", expiration="2025-04-17", snapshot=t)
          for t in times]
movie = DayMovie(smiles)
movie.write_html("spy-day.html")
Argument Type Default Meaning
smiles list of DataFrame required client.smile() frames for one expiry through one session, one per snapshot.
atm DataFrame or None None A client.atm_intraday() frame, drawn in a second panel. None leaves it out.
theme str or Theme "light" "light", "dark" or "institutional" (reduced branding), or a charts.Theme.
stride int 1 Play every Nth snapshot (counts snapshots, not minutes).
xlim str, tuple or None "band" "band": the strikes the fits are supported by quotes on, padded 5%. ("z", low, high): strikes whose standardized moneyness z lies between low and high. (low, high): explicit strikes. None: every quoted strike.
title str or None None A title for the card. None uses the default title.
fps int 12 Frames per second in a video.
trail int 20 How many earlier frames fade behind the current one.
width int 1100 Figure width in pixels.
cadence int or None None Minutes between snapshots, for reporting gaps. None uses your plan's frequency, else the spacing in the data.
source_snapshots list of str or None None Every snapshot time the session holds, when you fetched only some of them. Gaps are judged on this list, so a deliberate skip is not reported as missing data.
include_last bool False Add the session's last snapshot when the stride would skip it, so the movie ends at the close.
coverage_evidence str or None None A note on where source_snapshots came from, kept in the metadata.

Returns: A movie. Its methods follow.
Requests: No API request (the smiles you pass were already fetched).

movie.interactive()

The movie as one interactive figure, with a timeline slider, play and pause, and three speeds.

movie.interactive() -> Figure

Returns: A Plotly figure.

movie.write_html()

Save the interactive movie as a web page.

movie.write_html(path) -> Path
Argument Type Default Meaning
path str or Path required The .html file to write. The page loads Plotly from the internet.

Returns: The path written.

movie.encode()

Turn rendered frames into video files (needs FFmpeg).

movie.encode(
    frames_dir,
    out_stem,
    *,
    formats=("webm", "mp4"),
    fps=None,
    crf=30,
) -> dict
Argument Type Default Meaning
frames_dir str or Path required The folder render_frames() wrote.
out_stem str or Path required The output path without an extension; each format adds its own.
formats list of str ("webm", "mp4") Which videos to write: "webm", "mp4" or both.
fps int or None None Frames per second. None uses the movie's fps.
crf int 30 Video quality: lower is sharper and larger.

Returns: A dictionary of the files written: the videos, a poster image (the last frame) and a metadata file.

Python: errors

Every error is importable as osp.<Name> and catchable as osp.OptionSpaceError. The HTTP status is the REST API's answer that the Python error stands for; "none" means the package raises it itself.

Error HTTP status When Attributes
OptionSpaceError any The base class of every error below: except osp.OptionSpaceError catches them all.
InvalidRequestError 400, 422 The request is not valid: an unknown root, column or field, a snapshot that is not "EOD" or "HH:MM", a value out of range, or a date range too wide for your plan's frequency. A 422 means a parameter is missing or has the wrong type. The message names each problem. detail: The server's hints, such as roots, estimated_rows or errors.
status: The HTTP status, 400 or 422.
AuthenticationError 401 The key is missing, invalid, revoked or expired. The server does not say which.
EntitlementError 403 The key is valid but your plan does not include this data: a date outside your window, a snapshot outside your frequency, a route your plan does not include, or quotes asked for before they are released. Volume and open interest for a session not out yet give reason "not_yet_historical". reason: The server's reason code, when it sends one, such as not_yet_historical.
detail: The server's other hints, such as first_unavailable (the first date not out yet).
NotFoundError 404 No data for that symbol, date, snapshot or expiration. When the fit exists but did not pass our quality checks, reason is "withheld_for_quality"; a route or event type not switched on yet gives "not_enabled". reason: The server's reason code, when it sends one: withheld_for_quality or not_enabled.
detail: The server's other hints, such as withheld_for_quality (the expiries withheld) or withheld_snapshots.
MaxCreditsExceeded 412 A research call could cost more than the max_credits you passed. It was not run. credits_estimate: The server's estimate of the call's cost.
credits_cap: The most the call could cost.
max_credits: The limit you passed.
ResponseTooLarge 413 One request would return more rows than the per-request limit. Narrow it (an expiration, strike range or side for chain(); fewer dates for tenor_history() and rates()). A response is never cut off. cap: The per-request row limit.
QuotaExceeded 429 A limit was reached (today's credits, rows, calls at a time, or the research queue), or the client refused to start a multi-request pull that would not fit what is left today. reason: Which limit, such as daily_credits or projected_credits.
retry_after_seconds: When to try again, if the server said.
usage: Today's usage.
limits: Your limits.
ServerError 5xx A server error that persisted after the client's retries.
JobTimeout none A research call became a background job and was still running when job_timeout ran out. The job keeps going: collect it with client.job(job_id) within an hour of it finishing. job_id: The job to collect.
poll_url: The job's REST address.
status: Its last state: queued or running.
ContractError none A response did not have the shape this package version expects. Upgrade the package, or report it to [email protected].
CredentialError none No usable key: none saved or set, the credential store is locked, the key file is unsafe, or a saved key belongs to another server. The message says what to do.
CacheMismatch none A FrameCache entry does not match its query, or has changed on disk. Raised instead of returning the wrong data.

Constants: osp.__version__: The installed package version.

Response fields: shared rules

Every route's own fields are listed under the route below, with each field's type, unit, when it can be empty and an example. Field names are the same in REST responses, Python results and MCP results. These rules apply to all of them.

Snapshot times and EOD

Intraday snapshots run from 09:32 to 15:57 ET each full session. The 30m frequency covers 09:32, 10:02 and every 30 minutes to 15:32, plus 15:57; 5m covers every five minutes from 09:32 plus 15:57; eod covers 15:57 only. EOD means the last snapshot at or before the official close within your frequency. On an early close (13:00) that is 13:00 for 1m, 12:57 for 5m and 12:32 for 30m. A minute missing from the data is never filled in; /snapshots lists what exists.

Quote availability

Quote fields (bid, ask, mid, bid_size, ask_size, iv_bid, iv_ask, iv_mid, quote_state) for a session are served from the next session's 09:30 ET open. Wednesday's quotes become available Thursday at 09:30; Friday's on Monday at 09:30 (Tuesday after a Monday holiday). Until then the quote fields are absent from the response rather than null, and asking /chain for one explicitly is refused with 403. Fitted values are not affected.

quote_state says how to read a quote: two_sided (bid and ask volatilities both exist), ask_only (only the ask bounds the volatility), no_vol (the price is past the early-exercise boundary, so no volatility exists), no_quote (neither side quoted). The raw prices are passed through unchanged.

Fit quality

Each fitted expiry carries a quality block: on /smile it is a field of the response, on /chain a list with one entry per root and expiration, and on /surface a field of each expiry. status is checked, or not_yet_checked (today's live fits, and any fit not checked yet: checks run after the close). Its other fields are listed under /smile. The codes list gives reasons the expiry's fit may be less reliable:

Code What it means
term_structure_outlier This expiry's at-the-money level is out of line with the expiries around it, or its total variance is lower than a shorter expiry's. Check for an event (earnings, dividends) before treating the difference as a signal.
forward_jump The expiry's forward or borrow jumped at this snapshot and reverted shortly after. It may be a brief market move or a data glitch; for a stable series, use the neighbouring snapshots.
vol_jump The expiry's volatility level, skew or a wing jumped at this snapshot and reverted shortly after. As above, check the neighbouring snapshots.
call_put_disagree Calls and puts at similar strikes imply different volatilities beyond what their spreads explain. This often points to a dividend, borrow or forward that does not match the market; use the forward, borrow and in-the-money values for this expiry with caution.
one_quote_dominates A single quote carried most of the weight in this fit, so the smile mostly reflects that one strike. Confirm against other strikes or neighbouring snapshots.
borrow_at_limit The implied borrow (dividend and borrow cost) could not be pinned down from the quotes and was set at a bound. The smile and prices are usable; treat the borrow and forward for this expiry with caution.
smile_arbitrage Part of the fitted smile implies a small negative probability, so a butterfly priced off the fit would have negative value. Avoid this expiry for density or butterfly calculations at the affected strikes.
quotes_not_convex The market quotes themselves contain a butterfly that could be bought for a credit after paying every spread, which usually means a stale or wrong quote. The fit stays smooth rather than follow it, so it may not match every quote; check the affected strikes.
spot_moved The stock price implied by the option quotes differs from the recorded stock price by more than 1%. Exercise values, Greeks hedged against the stock, and the borrow and dividend series may be affected; the smile itself still fits its quotes. Stocks and ETFs only.
stale_spot The recorded stock price at this snapshot looks out of date, because several short expiries agree on the same gap from it. The forwards and smiles are fine, but the implied borrow for short expiries will look unusually large; skip this snapshot for borrow series. Set on every expiry of the snapshot. Stocks and ETFs only.
wide_quotes Too few call and put pairs had spreads tight enough to pin down the forward precisely. The forward, and with it the borrow, is less certain than usual for this expiry. Stocks and ETFs only.

Common fields

Fields that many answers carry, described once here. Each route says which of them it carries; "the identity fields" are symbol, root, data_date, snapshot_time, session_close, granularity, returned_rows, notes. The credits block comes with research answers; the quality block with /smile, /chain (one per root and expiration) and /surface (inside each expiry).

Field Type and unit Can be empty Meaning Example
symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
gated true or false True when the quote fields (bid, ask, mid, iv_*, quote_state) are included. False on the current session, before the quotes are released. true
session_close text, HH:MM, US Eastern On answers that are not tied to one session. The official close of that session. "16:00"
granularity text How often your plan's snapshots are taken (on research answers: the frequency the call used). Values: 1m, 5m, 30m, eod. "30m"
returned_rows integer How many rows (or options, points, events) the answer holds. 386
notes list of text An empty list when there is nothing to say. Specific limitations of this answer, as sentences, such as a snapshot after the official close or a field not yet filled in. Read them before using the numbers.
credits object What a research call cost.
credits.charged integer Credits charged for this call. 3
credits.estimate integer, credits The server's estimate of the call's cost, made from its parameters before it ran. 4
credits.cap integer, credits The most the call can cost. 4
credits.work_seconds number, seconds Server time spent working on the call, not counting time waiting in a queue. 2.41
credits.setting text Which estimate rule applied: the observation frequency of the call, or default for a route with one rule. Values: default, eod, 5m, 1m. "eod"
credits.price_version text The price list the charge used. "2026-10-beta-3"
quality object The fit-quality block for the expiry: see fit quality.
quality.status text Whether the expiry's fit has been checked yet: checks run after the close. Values: checked, not_yet_checked. "checked"
quality.share_inside number, fraction from 0 to 1 When no quote could be checked, or the fit is not checked yet. Share of checked quotes where the fitted price lies within the market (between the bid minus half a cent and the ask plus half a cent). 0.97
quality.miss_spreads number, half-spreads When every checked quote is inside, or the fit is not checked yet. For the checked quotes whose fitted price lies outside that range: how far outside, in half-spreads, as the median (1.0 = one half-spread beyond the bid or ask). 0.6
quality.miss_usd number, USD per share When every checked quote is inside, or the fit is not checked yet. The same distance in dollars, median over the same quotes. 0.03
quality.n_checked integer When the fit is not checked yet. How many quotes were checked: out-of-the-money quotes with a positive bid and an ask above it, inside the fitted strike range. 64
quality.codes list of text An empty list when there are none; null when the fit is not checked yet. Reasons the expiry's fit may be less reliable; see fit quality. Values: term_structure_outlier, forward_jump, vol_jump, call_put_disagree, one_quote_dominates, borrow_at_limit, smile_arbitrage, quotes_not_convex, spot_moved, stale_spot, wide_quotes.

REST: account and discovery

GET /entitlements

Your plan, access, limits, today's usage and the price list. Always answers, even when today's allowance is used up.

GET https://optionspace.ai/api/v1/data/entitlements

No parameters.

Returns: tier, status, entitlements, key_id, limits, usage, prices.
Python: client.entitlements()

Response fields (34)
Field Type and unit Can be empty Meaning Example
org_id text Your account's identifier. "org_8f2c91"
org_name text Your account's name. "Acme Research"
tier text Your plan's name, such as free, basic, value or pro. "value"
status text Your account's status. Keys work while it is active or past_due. Values: active, past_due, and others when access has ended. "active"
entitlements object Your grants, such as granularity (your snapshot frequency) and a sample_window on a sample plan. Keys depend on the plan.
key_id text The public identifier of the key you used. It is not the secret. "osk_06ce5c253284"
meter text What your daily allowance is counted in. Values: credits (normally), requests. "credits"
limits object Your limits.
limits.credits_per_day integer, credits Daily credit allowance, shared by every key on the account. It resets at midnight UTC. 2500
limits.requests_per_day integer Daily request count limit. Enforced only when meter is requests. 2500
limits.rows_per_day integer, rows Daily row limit across all calls. 1000000
limits.concurrent integer How many calls may run at a time. 2
limits.research_running integer How many research calls may run at a time. 1
limits.research_queued integer How many more research calls may wait behind the running one. 2
usage object Today's usage (UTC day).
usage.credits_today integer, credits Credits used today. 214
usage.credits_reserved integer, credits Credits held by research calls still running. The unused part is released when they finish. 4
usage.requests_today integer Calls made today. 198
usage.rows_today integer, rows Rows returned today. 40512
usage.concurrent_now integer Calls running right now. 0
usage.research_jobs_now integer Research calls running or waiting right now. 0
prices object The price list in force.
prices.version text The price list's version. "2026-10-beta-3"
prices.effective text, YYYY-MM-DD The date the price list took effect. "2026-10-21"
prices.lookups object The price of each lookup route, in credits, keyed by route name.
prices.periods_per_call object How much one call of a time-series lookup (/tenor-history, /iv-rv, /rates) may cover. A period is one snapshot of one session (and one root, where a symbol lists under several).
prices.periods_per_call.rule text The rule, in words. "a period is one snapshot of one session (and one root); a call covers at most max periods, and a longer range is the same data in more calls"
prices.periods_per_call.max object The most periods one call may cover, keyed by route name. A longer range returns 400 too_many_periods with suggested_end. "{\"tenor-history\": 10000}"
prices.periods_per_call.per_session object Periods in one full session at each sampling: eod 1, 30m 14, 5m 78, 1m 386 (fewer on an early close). snapshot=EOD or HH:MM counts 1 a session. "{\"eod\": 1, \"30m\": 14, \"5m\": 78, \"1m\": 386}"
prices.research object How research routes are charged.
prices.research.rule text The charging rule, in words.
prices.research.cap_multiple integer The cap as a multiple of the estimate. At 1 a call never costs more than its estimate. 1
prices.research.estimate_seconds object The numbers each research route's estimate is made from, keyed by route and setting (such as default or 1m). Each entry has base_seconds and per, the seconds added for each unit of the request (for example sessions: each session in the date range). Estimated seconds = base_seconds plus each unit count times its per value, times the number of symbols; the estimate in credits is that rounded up, at least 1. Use estimate=true on a call to get its estimate directly. "{\"default\": {\"base_seconds\": 0.646, \"per\": {\"sessions\": 0.0185}}}"
prices.research.fixed_credits object Research settings priced per call instead of by measured time, keyed by route and setting. A call at such a setting costs that price when it succeeds, however long it runs.

GET /symbols

Every symbol with the sessions it covers.

GET https://optionspace.ai/api/v1/data/symbols

No parameters.

Returns: symbols (each with its first and last session and number of sessions); sample_window when your access is limited to one.
Python: client.symbols()

Also carries the common fields: the identity fields.

Response fields (6)
Field Type and unit Can be empty Meaning Example
symbols list of objects Every symbol with its coverage. Coverage is for the product as a whole; your plan's dates may be narrower.
symbols[].symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
symbols[].start_date text, YYYY-MM-DD The first session with data. "2017-01-03"
symbols[].end_date text, YYYY-MM-DD The last session with data. "2026-10-19"
symbols[].n_days integer How many sessions have data. 2461
sample_window object When your plan is not limited to a window. The date window your plan can read, when it is limited to one.

GET /snapshots

The snapshot times your key can retrieve for one session.

GET https://optionspace.ai/api/v1/data/snapshots?symbol=SPY&data_date=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.

Returns: snapshots, after_close (times after an early close), eod (what EOD resolves to), session_close, granularity.
Python: client.snapshots()

Also carries the common fields: the identity fields.

Response fields (3)
Field Type and unit Can be empty Meaning Example
snapshots list of text, list of HH:MM, US Eastern The snapshot times your key can retrieve for the session, in order. A minute missing from the data is never filled in. "09:32"
after_close list of text, HH:MM, US Eastern An empty list on a full session. Times in snapshots that fall after an early close.
eod text, HH:MM, US Eastern When no snapshot at or before the close is available to you. The time EOD resolves to for your key on this session. "15:57"

GET /expirations

The expirations listed at one snapshot.

GET https://optionspace.ai/api/v1/data/expirations?symbol=SPY&data_date=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
snapshot text none EOD or HH:MM US Eastern time. Leaving it out means EOD: the last snapshot at or before the official close that your plan covers. The time used comes back as snapshot_time.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.

Returns: expirations: one entry per root and expiration, with dte.
Python: client.expirations()

Also carries the common fields: the identity fields.

Response fields (7)
Field Type and unit Can be empty Meaning Example
expirations list of objects One entry per listed root and expiration at the snapshot.
expirations[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
expirations[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
expirations[].dte integer, calendar days Calendar days from data_date to expiration. 7
withheld_for_quality list of objects An empty list when none was withheld. Expirations fitted at this snapshot whose fit did not pass our quality checks, so they are not in expirations. Each names its root and expiration.
withheld_for_quality[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
withheld_for_quality[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"

REST: data lookups

GET /smile

The fitted smile for one expiry at one snapshot.

GET https://optionspace.ai/api/v1/data/smile?symbol=SPY&data_date=2025-04-10&expiration=2025-04-17
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
expiration date required The option expiration date, YYYY-MM-DD. /expirations lists them.
snapshot text none EOD or HH:MM US Eastern time. Leaving it out means EOD: the last snapshot at or before the official close that your plan covers. The time used comes back as snapshot_time.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.

Returns: Expiry values (forward, atm_vol, rate, q, T_vol, rmse, quality, fitted_curve, ...) and one entry per strike and side with fitted_vol, z, the greeks and, on past sessions, the quotes.
Python: client.smile()

Also carries the common fields: the identity fields, gated, the quality block.

Response fields (45)
Field Type and unit Can be empty Meaning Example
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
dte integer, calendar days Calendar days from data_date to expiration. 7
forward number, USD The forward price implied by the expiry's options. For VIX, use forward: each expiry's forward sits at its own futures level. 525.31
spot number, USD The underlying price at the snapshot. For VIX this is the VIX index, not a futures price. 524.58
s_implied number, USD When it could not be determined for the expiry. The spot price implied by that expiry's options. 524.61
rate number, decimal (0.041 = 4.1%) The risk-free rate for that expiry, continuously compounded and annualized. 0.0412
q number, decimal per year (0.012 = 1.2%) The continuous dividend or borrow yield implied for that expiry. On VIX it restates the spot-to-futures gap and is not a borrow cost. 0.0118
T number, years Time to expiry used for discounting. 0.0192
T_vol number, years Time to expiry used for volatility. It counts trading hours more than nights and weekends. 0.0168
T_borrow number, years Time to expiry used for the dividend and borrow term. 0.0192
atm_vol number, decimal vol The expiry's at-the-money volatility. 0.1423
rmse number, decimal vol (0.001 = 0.1 vol point) Typical gap between the quotes' implied volatility and the fitted curve at the same strikes. Smaller means the curve fits the quotes more closely. 0.001
n_options integer How many options (strikes times sides) the answer holds. 236
n_fitted integer How many of those options lie inside the fitted range. 198
calls
puts
object The calls, as parallel lists: entry i of every list is the same strike. puts has the same lists for the puts.
calls.strikes
puts.strikes
list of number, USD Strike prices, lowest first.
calls.fitted
puts.fitted
list of number, decimal vol Fitted implied volatility at each strike.
calls.delta
puts.delta
list of number Change in option value per $1 move in the underlying. 0.48
calls.gamma
puts.gamma
list of number Change in delta per $1 move in the underlying. 0.0215
calls.vega
puts.vega
list of number, USD per share Change in option value per 1 volatility point (1%). 0.29
calls.theta
puts.theta
list of number, USD per share per day On v3.1 fits. Change in option value per calendar day. Fits made before methodology v3.1 only: v3.1 fits carry theta_1d and theta_next_open instead. -0.31
calls.theta_1d
puts.theta_1d
list of number, USD per share On fits made before methodology v3.1. Change in option value if held, unexercised, to the same time on the next trading day, with the underlying price and volatility unchanged. An amount, not a rate: before a weekend or holiday it covers the days the market is closed. It includes carry, so in-the-money puts can show a positive value, mostly on Thursdays. Never larger than the time value: an option worth exercising is not assumed held, and on the day before an ex-dividend date the underlying opens lower by the dividend. -0.44
calls.theta_next_open
puts.theta_next_open
list of number, USD per share On fits made before methodology v3.1. The same change, until 09:30 ET on the next trading day. It shrinks through the trading day. -0.24
calls.rho
puts.rho
list of number, USD per share Change in option value per 1% change in rates. 0.05
calls.vanna
puts.vanna
list of number Change in delta per 1 volatility point. 0.004
calls.volga
puts.volga
list of number Change in vega per 1 volatility point. 0.012
calls.z
puts.z
list of number Standardized moneyness: the log of strike over forward, divided by ATM volatility times the square root of T_vol. 0 is at the forward; negative is below it. -0.42
calls.is_otm
puts.is_otm
list of true or false Null when the forward is unknown. True when the option is out of the money against the forward (a call above it, a put below it).
calls.volume
puts.volume
list of integer, contracts Contracts traded so far that session at each strike, through the snapshot minute. Empty for now: per-strike volume is on /chain with columns=volume,open_interest.
calls.open_interest
puts.open_interest
list of integer, contracts Open contracts at each strike at the end of the previous session. Empty for now: per-strike open interest is on /chain with columns=volume,open_interest.
calls.iv_bid
puts.iv_bid
list of number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Read it with quote_state. Implied volatility of the bid price. 0.2121
calls.iv_ask
puts.iv_ask
list of number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Implied volatility of the ask price. 0.2168
calls.bid
puts.bid
list of number, USD per share Absent until the session's quotes are released (the next session's 09:30 ET open); null where that side was not quoted. Quoted bid price. 6.8
calls.ask
puts.ask
list of number, USD per share Absent until the session's quotes are released; null where that side was not quoted. Quoted ask price. 6.9
calls.iv_mid
puts.iv_mid
list of number, decimal vol Absent until the session's quotes are released; null without a usable mid. Implied volatility of the mid price. 0.2144
calls.mid
puts.mid
list of number, USD per share Absent until the session's quotes are released; null without a two-sided quote. Midpoint of bid and ask. 6.85
calls.is_wing_extrapolated
puts.is_wing_extrapolated
list of true or false True when the strike lies outside the fitted range, so the value extends the wing rather than sitting among the quotes used. Values: true, false (1, 0 on /chain rows). false
calls.quote_state
puts.quote_state
list of text Absent until the session's quotes are released. How to read the quote: two_sided (bid and ask volatilities both exist), ask_only (only the ask bounds the volatility), no_vol (the price is past the early-exercise boundary, so no volatility exists), no_quote (neither side quoted). "two_sided"
z_lo number For older curve types with no evaluated curve. The lower end of the fitted range, in z. -2.9
z_hi number For older curve types with no evaluated curve. The upper end of the fitted range, in z. 2.4
fitted_curve object Null for older curve types, which have only the per-strike values. The fitted smile evaluated on a grid, for drawing a smooth curve. curve_frame() turns it into a DataFrame.
fitted_curve.z list of number Grid points in standardized moneyness.
fitted_curve.strike list of number, USD The strike at each grid point.
fitted_curve.sigma list of number, decimal vol The fitted volatility at each grid point.
fitted_curve.is_wing_extrapolated list of true or false True where the grid point lies outside the fitted range (z_lo to z_hi).

GET /term-structure

At-the-money volatility, forward and fitted strike range for every listed expiry at one snapshot, in one request. The same values /smile gives for each expiry.

GET https://optionspace.ai/api/v1/data/term-structure?symbol=SPY&data_date=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
snapshot text none EOD or HH:MM US Eastern time. Leaving it out means EOD: the last snapshot at or before the official close that your plan covers. The time used comes back as snapshot_time.
root text none The listed root. Needed when the symbol lists under several roots that day: a term structure uses one root.

Returns: expiries: one entry per listed expiry, shortest first, with expiration, dte, atm_vol, forward, z_lo and z_hi. If any listed expiry has no fit, the request fails and names it in missing, rather than leave it out.
Python: client.term_structure()

Also carries the common fields: the identity fields.

Response fields (14)
Field Type and unit Can be empty Meaning Example
expiries list of objects One entry per listed expiry, shortest first.
expiries[].symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
expiries[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
expiries[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
expiries[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
expiries[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
expiries[].dte integer, calendar days Calendar days from data_date to expiration. 7
expiries[].atm_vol number, decimal vol The expiry's at-the-money volatility (the atm_vol of its smile). 0.1423
expiries[].forward number, USD The forward price implied by the expiry's options. For VIX, use forward: each expiry's forward sits at its own futures level. 525.31
expiries[].z_lo number For older curve types with no evaluated curve. The lower end of the expiry's fitted range, in z (the z_lo of its smile). -2.9
expiries[].z_hi number For older curve types with no evaluated curve. The upper end of the expiry's fitted range, in z (the z_hi of its smile). 2.4
withheld_for_quality list of objects An empty list when none was withheld. Expirations fitted at this snapshot whose fit did not pass our quality checks, so they are not in expiries. Each names its root and expiration.
withheld_for_quality[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
withheld_for_quality[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"

GET /chain

Every fitted option for a session at one snapshot, across all expiries unless filtered. One snapshot per request.

GET https://optionspace.ai/api/v1/data/chain?symbol=SPY&data_date=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
snapshot text none One snapshot: EOD or HH:MM US Eastern time. Leaving it out means EOD.
root text none Only this listed root. Left out, every root the symbol lists is returned and each row names its root.
expiration date none Only this expiration.
strike_min number none Only strikes at or above this price (USD).
strike_max number none Only strikes at or below this price (USD).
put_call text none call or put only.
columns list none Only these columns, comma-separated or repeated. Identity columns are always included; quote columns exist for past sessions only. Allowed: data_date, snapshot_time, symbol, expiration, strike, put_call, company_name, sector, security_type, dte, spot, forward, q, r, T_vol, z, fitted_vol, fitted_price, delta, gamma, vega, theta, rho, vanna, volga, is_wing_extrapolated, bid, ask, bid_size, ask_size, iv_bid, iv_ask, iv_mid, theta_1d, theta_next_open. On request (not in the default columns), for past sessions only: volume, open_interest. volume is the contracts traded so far that session up to the snapshot minute; the official end-of-day figure is in /contract-volume. Filtering by expiration keeps a large chain fast.
format text json json, or csv for a CSV file download.

Returns: rows: identity columns, fitted values and greeks, and quotes on past sessions; quality per root and expiration. More rows than the per-request limit returns 413, never a cut-off response.
Python: client.chain()

Also carries the common fields: symbol, data_date, snapshot_time, session_close, granularity, returned_rows, notes, the quality block.

Response fields (44)
Field Type and unit Can be empty Meaning Example
root text When you did not pass root; each row's symbol then names its root. The root you asked for. "SPXW"
columns list of text, list of field names The columns each row carries, in order.
rows list of objects One entry per option (expiration, strike and side).
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].symbol text The listed root of the option (for SPX, SPX or SPXW). "SPXW"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].strike number, USD Strike price. 520.0
rows[].put_call text Call or put. Values: call, put. "call"
rows[].company_name text When no name is on file. The underlying's name. "SPDR S&P 500 ETF Trust"
rows[].sector text When no label is on file. The underlying's sector label. "Technology"
rows[].security_type text When no label is on file. What kind of underlying it is, such as stock, ETF or index. "ETF"
rows[].dte integer, calendar days Calendar days from data_date to expiration. 7
rows[].spot number, USD The underlying price at the snapshot. For VIX this is the VIX index, not a futures price. 524.58
rows[].forward number, USD The forward price implied by the expiry's options. For VIX, use forward: each expiry's forward sits at its own futures level. 525.31
rows[].q number, decimal per year (0.012 = 1.2%) The continuous dividend or borrow yield implied for that expiry. On VIX it restates the spot-to-futures gap and is not a borrow cost. 0.0118
rows[].r number, decimal (0.041 = 4.1%) The risk-free rate for that expiry, continuously compounded and annualized (the same value as rate on other routes). 0.0412
rows[].T_vol number, years Time to expiry used for volatility. It counts trading hours more than nights and weekends. 0.0168
rows[].z number Standardized moneyness: the log of strike over forward, divided by ATM volatility times the square root of T_vol. 0 is at the forward; negative is below it. -0.42
rows[].fitted_vol number, decimal vol (0.20 = 20%) The fitted implied volatility at the strike. 0.2144
rows[].fitted_price number, USD per share (multiply by the contract multiplier, usually 100, for one contract) The option price at the fitted volatility. 6.85
rows[].delta number Change in option value per $1 move in the underlying. 0.48
rows[].gamma number Change in delta per $1 move in the underlying. 0.0215
rows[].vega number, USD per share Change in option value per 1 volatility point (1%). 0.29
rows[].theta number, USD per share per day On v3.1 fits. Change in option value per calendar day. Fits made before methodology v3.1 only: v3.1 fits carry theta_1d and theta_next_open instead. -0.31
rows[].rho number, USD per share Change in option value per 1% change in rates. 0.05
rows[].vanna number Change in delta per 1 volatility point. 0.004
rows[].volga number Change in vega per 1 volatility point. 0.012
rows[].is_wing_extrapolated integer True when the strike lies outside the fitted range, so the value extends the wing rather than sitting among the quotes used. Values: true, false (1, 0 on /chain rows). false
rows[].bid number, USD per share Absent until the session's quotes are released (the next session's 09:30 ET open); null where that side was not quoted. Quoted bid price. 6.8
rows[].ask number, USD per share Absent until the session's quotes are released; null where that side was not quoted. Quoted ask price. 6.9
rows[].bid_size integer, contracts Absent until the session's quotes are released; null where not quoted. Contracts quoted at the bid. 120
rows[].ask_size integer, contracts Absent until the session's quotes are released; null where not quoted. Contracts quoted at the ask. 85
rows[].iv_bid number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Read it with quote_state. Implied volatility of the bid price. 0.2121
rows[].iv_ask number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Implied volatility of the ask price. 0.2168
rows[].iv_mid number, decimal vol Absent until the session's quotes are released; null without a usable mid. Implied volatility of the mid price. 0.2144
rows[].theta_1d number, USD per share On fits made before methodology v3.1. Change in option value if held, unexercised, to the same time on the next trading day, with the underlying price and volatility unchanged. An amount, not a rate: before a weekend or holiday it covers the days the market is closed. It includes carry, so in-the-money puts can show a positive value, mostly on Thursdays. Never larger than the time value: an option worth exercising is not assumed held, and on the day before an ex-dividend date the underlying opens lower by the dividend. -0.44
rows[].theta_next_open number, USD per share On fits made before methodology v3.1. The same change, until 09:30 ET on the next trading day. It shrinks through the trading day. -0.24
rows[].quote_state text Absent until the session's quotes are released. How to read the quote: two_sided (bid and ask volatilities both exist), ask_only (only the ask bounds the volatility), no_vol (the price is past the early-exercise boundary, so no volatility exists), no_quote (neither side quoted). "two_sided"
rows[].volume integer, contracts When the day's volume has not been loaded; 0 means no trades. On /chain: contracts traded so far that session, through the snapshot minute. On /contract-volume: the official end-of-day volume for the session. 63
rows[].open_interest integer, contracts When the day's open-interest figure has not been loaded; 0 means none. Open contracts at the end of the previous session (the figure published before this session opened). 5236
quality list of objects The fit-quality block for each root and expiration in the answer; see fit quality.
quality[].root text The root the block belongs to. "SPXW"
quality[].expiration text, YYYY-MM-DD The expiration the block belongs to. "2025-04-17"

GET /atm-timeseries

At-the-money volatility through one session for one expiry.

GET https://optionspace.ai/api/v1/data/atm-timeseries?symbol=SPY&data_date=2025-04-10&expiration=2025-04-17
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
expiration date required The option expiration date, YYYY-MM-DD. /expirations lists them.
root text none The listed root. Left out, the root that lists the expiry is served, or SPXW on the SPX monthlies where both do.

Returns: points: one per snapshot, with snapshot_time, sigma_atm, forward, n_obs; and root, the root served.
Python: client.atm_intraday()

Also carries the common fields: symbol, data_date, snapshot_time, session_close, granularity, returned_rows, notes.

Response fields (9)
Field Type and unit Can be empty Meaning Example
root text The root served. Roots are never mixed. "SPY"
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
points list of objects One entry per snapshot your plan covers, in time order.
points[].snapshot_time text, HH:MM, US Eastern The snapshot time. "10:02"
points[].root text The root of the point (always the root served). "SPY"
points[].sigma_atm number, decimal vol When unavailable at that snapshot. At-the-money volatility at that snapshot. 0.1431
points[].forward number, USD When unavailable at that snapshot. The expiry's forward at that snapshot. 525.1
points[].n_obs integer When unavailable at that snapshot. How many options went into the fit at that snapshot. 180
withheld_snapshots list of text, list of HH:MM, US Eastern An empty list when none was withheld. Snapshot times your plan covers at which this expiry's fit did not pass our quality checks; they are left out of points.

GET /tenor-history

Constant-maturity volatility measures through time for one tenor.

GET https://optionspace.ai/api/v1/data/tenor-history?symbol=SPY&tenor_days=30&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
tenor_days integer required The constant maturity in calendar days: one of 30, 60, 90, 180, 360.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
snapshot text none Left out: every snapshot your plan covers. EOD: each session's last snapshot at or before its close. HH:MM: that time each session.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.
fields list none Only these measures, comma-separated or repeated. Allowed: atf_iv, skew_25d, curvature_25d, straddle_pct, strangle_25d_pct, rr_25d_pct, atf_iv_clean, atf_iv_clean_se, straddle_pct_clean, event_dates.
sampling text none Left out: your plan's frequency. eod, 30m, 5m or 1m: every snapshot at that frequency, up to your plan's. Coarser sampling covers a longer range in one call. Give snapshot or sampling, not both.

Returns: rows: one per session and snapshot with the measures. One call covers at most 10,000 periods (a period is one snapshot of one session): about 40 years at end of day, 2.8 years at 30m, 6 months at 5m, 5 weeks at 1m. A longer range returns 400 too_many_periods with suggested_end, the last session that fits; ask for the rest in further calls.
Python: client.tenor_history()

Also carries the common fields: the identity fields.

Response fields (20)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
tenor_days integer, calendar days The constant maturity, in calendar days. 30
snapshot_mode text Which snapshots the rows use: every snapshot your plan covers, each session's end of day, or one time each session. Values: bucket (every snapshot your plan covers), EOD, or an HH:MM time. "EOD"
fields list of text The measures each row carries. Values: atf_iv, skew_25d, curvature_25d, straddle_pct, strangle_25d_pct, rr_25d_pct, atf_iv_clean, atf_iv_clean_se, straddle_pct_clean, event_dates.
rows list of objects One entry per session and snapshot.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].root text The root the row was read from. "SPY"
rows[].tenor_days integer, calendar days The constant maturity, in calendar days. 30
rows[].atf_iv number, decimal vol When no listed expiries span that maturity on that date (expected, not missing data). At-the-forward implied volatility at that maturity. 0.1912
rows[].skew_25d number, decimal vol When no listed expiries span that maturity, or the value is unavailable. 25-delta put volatility minus 25-delta call volatility. 0.0451
rows[].curvature_25d number, decimal vol When no listed expiries span that maturity. Average of the 25-delta put and call volatilities minus ATM volatility. 0.0083
rows[].straddle_pct number, percent of spot (3.0 = 3%) When no listed expiries span that maturity. The at-the-money straddle's price as a percent of spot. 4.38
rows[].strangle_25d_pct number, percent of spot When no listed expiries span that maturity. The 25-delta strangle's price as a percent of spot. 1.12
rows[].rr_25d_pct number, percent of spot When no listed expiries span that maturity. The 25-delta risk reversal's price as a percent of spot. -0.41
rows[].atf_iv_clean number, decimal vol Where atf_iv is empty, and on history not yet filled in (then notes says so). ATM volatility with scheduled events removed. For a stock, the next scheduled earnings report when it falls within 90 days; for an index or ETF, Fed decisions within 30 days and CPI and payrolls releases within 15 days. Equal to atf_iv when nothing was removed. 0.1874
rows[].atf_iv_clean_se number, decimal vol When the removal cannot be vouched for; the unadjusted value is then repeated in atf_iv_clean. One-standard-error band of atf_iv_clean. 0 when no event was in range. 0.0021
rows[].straddle_pct_clean number, percent of spot Where atf_iv_clean is empty. The straddle priced at the clean volatility. 4.29
rows[].event_dates list of text, list of YYYY-MM-DD An empty list when nothing was removed. The scheduled events removed from the clean value. "2025-05-28"

GET /rates

The risk-free rate implied by SPX options, per session and listed expiry.

GET https://optionspace.ai/api/v1/data/rates?start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
expiration date none Only this listed expiry.

Returns: rows: data_date, expiration, rate, rate_se. One call covers at most 10,000 sessions.
Python: client.rates()

Also carries the common fields: root, data_date, snapshot_time, session_close, granularity, returned_rows, notes.

Response fields (8)
Field Type and unit Can be empty Meaning Example
symbol text Always SPX: rates are implied from SPX options. "SPX"
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
rows list of objects One entry per session and listed expiry.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].rate number, decimal (0.041 = 4.1%) The risk-free rate for that expiry, continuously compounded and annualized. 0.0412
rows[].rate_se number, decimal Standard error of rate: how precisely the rate is pinned down. 0.0003

GET /iv-rv

At-the-money implied volatility, realized volatility and the gap between them, at one short tenor, through time.

GET https://optionspace.ai/api/v1/data/iv-rv?symbol=SPY&tenor_days=30&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
tenor_days integer required The tenor in calendar days: one of 1, 2, 3, 5, 7, 10, 14, 20, 30. The implied side looks that many calendar days ahead; the realized side looks back over that many trading sessions.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
snapshot text none Left out: every snapshot your plan covers. EOD: each session's last snapshot at or before its close. HH:MM: that time each session.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.
fields list none Only these measures, comma-separated or repeated. Allowed: atf_iv, rv, vrp, atf_iv_clean, atf_iv_clean_se, event_dates, rv_clean, vrp_clean.
sampling text none Left out: your plan's frequency. eod, 30m, 5m or 1m: every snapshot at that frequency, up to your plan's. Coarser sampling covers a longer range in one call. Give snapshot or sampling, not both.

Returns: rows: one per session and snapshot with the measures. Short tenors are often empty for symbols without daily expiries (notes says so). One call covers at most 10,000 periods (a period is one snapshot of one session): about 40 years at end of day, 2.8 years at 30m, 6 months at 5m, 5 weeks at 1m. A longer range returns 400 too_many_periods with suggested_end, the last session that fits; ask for the rest in further calls.
Python: client.iv_rv()

Also carries the common fields: the identity fields.

Response fields (18)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
tenor_days integer, calendar days The constant maturity, in calendar days. 30
snapshot_mode text Which snapshots the rows use: every snapshot your plan covers, each session's end of day, or one time each session. Values: bucket (every snapshot your plan covers), EOD, or an HH:MM time. "EOD"
fields list of text The measures each row carries. Values: atf_iv, rv, vrp, atf_iv_clean, atf_iv_clean_se, event_dates, rv_clean, vrp_clean.
rows list of objects One entry per session and snapshot.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].root text The root the row was read from. "SPY"
rows[].tenor_days integer, calendar days The constant maturity, in calendar days. 30
rows[].atf_iv number, decimal vol When no listed expiries span that maturity on that date (expected, not missing data). At-the-forward implied volatility at that maturity. 0.1912
rows[].rv number, decimal vol Early in a symbol's history (fewer sessions than the tenor), or when the window reaches a session with no stored prices. Realized volatility of the underlying over the last tenor_days trading sessions up to that snapshot, overnight moves included, annualized. (The implied side's tenor is calendar days; this one is sessions.) 0.088
rows[].vrp number, decimal vol When atf_iv or rv is empty. atf_iv minus rv: positive when options price more movement than the underlying has had. 0.0729
rows[].atf_iv_clean number, decimal vol Where atf_iv is empty, and on history not yet filled in (then notes says so). ATM volatility with scheduled events removed. For a stock, the next scheduled earnings report when it falls within 90 days; for an index or ETF, Fed decisions within 30 days and CPI and payrolls releases within 15 days. Equal to atf_iv when nothing was removed. 0.1874
rows[].atf_iv_clean_se number, decimal vol When the removal cannot be vouched for; the unadjusted value is then repeated in atf_iv_clean. One-standard-error band of atf_iv_clean. 0 when no event was in range. 0.0021
rows[].event_dates list of text, list of YYYY-MM-DD An empty list when nothing was removed. The scheduled events removed from the clean value. "2025-05-28"
rows[].rv_clean number, decimal vol When rv is empty, or the event calendar is incomplete for a session in the window (a report missing). rv with scheduled events taken out: the overnight move into the session after an earnings report (before the open or after the close), a CPI or jobs report, and 2:00 to 3:00 pm on a Fed day. The window counts that much less time. 0.081
rows[].vrp_clean number, decimal vol When atf_iv_clean or rv_clean is empty. Check atf_iv_clean_se on the same row: when it is empty, the implied side could not be fully cleaned. atf_iv_clean minus rv_clean: the gap with scheduled events taken out of both sides. 0.0612

GET /expiry-history

One expiry's values across sessions, at one snapshot each session: the per-expiry values /surface gives, through time.

GET https://optionspace.ai/api/v1/data/expiry-history?symbol=SPY&expiration=2025-04-17&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
expiration date required The option expiration date, YYYY-MM-DD. /expirations lists them.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
snapshot text none EOD (the default: each session's last snapshot at or before its close that your plan covers) or HH:MM, the same each session.
root text none The listed root. Left out, SPXW is served for an SPX monthly listed under both.

Returns: rows: one per session, oldest first. withheld_sessions lists sessions with no row because the expiry's fit did not pass our quality checks.
Python: client.expiry_history()

Also carries the common fields: the identity fields.

Response fields (25)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
snapshot_mode text Which snapshot each row uses: each session's end of day, or one time each session. Values: EOD, or an HH:MM time. "EOD"
fields list of text The values each row carries, after its identity fields.
rows list of objects One entry per session, oldest first.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].dte integer, calendar days Calendar days from data_date to expiration. 7
rows[].sigma_atm number, decimal vol When unavailable for that snapshot. At-the-money volatility of the expiry. 0.1423
rows[].forward number, USD The forward price implied by the expiry's options. For VIX, use forward: each expiry's forward sits at its own futures level. 525.31
rows[].spot number, USD The underlying price at the snapshot. For VIX this is the VIX index, not a futures price. 524.58
rows[].s_implied number, USD When it could not be determined for the expiry. The spot price implied by that expiry's options. 524.61
rows[].s_basis number, USD When s_implied is empty. s_implied minus spot: how far the options' implied spot sits from the quoted spot. 0.03
rows[].rate number, decimal (0.041 = 4.1%) The risk-free rate for that expiry, continuously compounded and annualized. 0.0412
rows[].q number, decimal per year (0.012 = 1.2%) The continuous dividend or borrow yield implied for that expiry. On VIX it restates the spot-to-futures gap and is not a borrow cost. 0.0118
rows[].T number, years Time to expiry used for discounting. 0.0192
rows[].T_vol number, years Time to expiry used for volatility. It counts trading hours more than nights and weekends. 0.0168
rows[].T_borrow number, years Time to expiry used for the dividend and borrow term. 0.0192
rows[].n_obs integer How many options went into the expiry's fit. 182
rows[].rmse number, decimal vol (0.001 = 0.1 vol point) Typical gap between the quotes' implied volatility and the fitted curve at the same strikes. Smaller means the curve fits the quotes more closely. 0.001
rows[].r_squared number When not available. How much of the variation in the quotes the fit explains (R squared). Closer to 1 is a tighter fit. 0.998
withheld_sessions list of text, list of YYYY-MM-DD An empty list when none was withheld. Sessions in the range with no row because the expiry's fit did not pass our quality checks.

GET /contract-history

One option across sessions: /chain's columns for that option, at one snapshot each session.

GET https://optionspace.ai/api/v1/data/contract-history?symbol=SPY&expiration=2025-04-17&strike=520&put_call=put&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
expiration date required The option expiration date, YYYY-MM-DD. /expirations lists them.
strike number required The strike price (USD).
put_call text required call or put.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
snapshot text none EOD (the default: each session's last snapshot at or before its close that your plan covers) or HH:MM, the same each session.
root text none The listed root. Left out, SPXW is served for an SPX monthly listed under both.
columns list none Only these columns, comma-separated or repeated, as for /chain. volume and open_interest are added only when named, for a range ending on a past session (403 otherwise).

Returns: rows: one per session, oldest first, with quote_state where the quotes are included; columns names them. withheld_sessions lists sessions with no row because the expiry's fit did not pass our quality checks.
Python: client.contract_history()

Also carries the common fields: the identity fields.

Response fields (26)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
strike number, USD Strike price. 520.0
put_call text Call or put. Values: call, put. "call"
snapshot_mode text Which snapshot each row uses: each session's end of day, or one time each session. Values: EOD, or an HH:MM time. "EOD"
columns list of text, list of field names The columns each row carries, in order.
rows list of objects One entry per session, oldest first.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].strike number, USD Strike price. 520.0
rows[].put_call text Call or put. Values: call, put. "call"
rows[].fitted_vol number, decimal vol (0.20 = 20%) The fitted implied volatility at the strike. 0.2144
rows[].fitted_price number, USD per share (multiply by the contract multiplier, usually 100, for one contract) The option price at the fitted volatility. 6.85
rows[].delta number Change in option value per $1 move in the underlying. 0.48
rows[].bid number, USD per share Absent until the session's quotes are released (the next session's 09:30 ET open); null where that side was not quoted. Quoted bid price. 6.8
rows[].ask number, USD per share Absent until the session's quotes are released; null where that side was not quoted. Quoted ask price. 6.9
rows[].iv_bid number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Read it with quote_state. Implied volatility of the bid price. 0.2121
rows[].iv_ask number, decimal vol Absent until the session's quotes are released; null where no volatility matches the price. Implied volatility of the ask price. 0.2168
rows[].iv_mid number, decimal vol Absent until the session's quotes are released; null without a usable mid. Implied volatility of the mid price. 0.2144
rows[].volume integer, contracts When the day's volume has not been loaded; 0 means no trades. On /chain: contracts traded so far that session, through the snapshot minute. On /contract-volume: the official end-of-day volume for the session. 63
rows[].open_interest integer, contracts When the day's open-interest figure has not been loaded; 0 means none. Open contracts at the end of the previous session (the figure published before this session opened). 5236
rows[].quote_state text Absent until the session's quotes are released. How to read the quote: two_sided (bid and ask volatilities both exist), ask_only (only the ask bounds the volatility), no_vol (the price is past the early-exercise boundary, so no volatility exists), no_quote (neither side quoted). "two_sided"
withheld_sessions list of text, list of YYYY-MM-DD An empty list when none was withheld. Sessions in the range with no row because the expiry's fit did not pass our quality checks.

GET /event-outcomes

For each earnings, Fed, CPI or jobs-report day: the implied move the options priced, the move that happened, and 30-day ATM volatility before and after.

GET https://optionspace.ai/api/v1/data/event-outcomes?symbol=NVDA&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
start date required The first event date of the range, YYYY-MM-DD.
end date required The last event date of the range, YYYY-MM-DD.
event_type text none One of earnings, fomc, cpi, nfp. Left out: every type.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.

Returns: rows: one per event, oldest first. Moves are decimals of the share price (0.05 = 5%).
Python: client.event_outcomes()

Also carries the common fields: the identity fields.

Response fields (18)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
event_type text When every type was asked for. The event type asked for. "earnings"
rows list of objects One entry per event, oldest first.
rows[].root text The ticker the event is stored under: an older ticker before a rename (FB for Meta before 2022-06-09). "NVDA"
rows[].event_type text The kind of event. Values: earnings (earnings reports, single stocks), fomc (Fed rate decisions), cpi (consumer price index releases), nfp (the monthly jobs report). "earnings"
rows[].event_date text, YYYY-MM-DD The date of the announcement. "2026-08-26"
rows[].timing text When the announcement came. Values: BMO (before the open), AMC (after the close), INTRADAY (during the session; Fed decisions). "AMC"
rows[].implied_move number, decimal (0.05 = 5%) When the options priced no measurable extra move for the event, or no figure exists for the session before it; notes says so. Common for CPI and jobs-report days. The implied move: the average size of the move the options priced for the event, up or down, as a fraction of the price. It is about 0.80 times the event's volatility (one standard deviation of the move); see the API guide's "Implied move and event volatility". 0.0543
rows[].implied_move_se number, decimal When implied_move is empty. One standard error of implied_move. 0.0002
rows[].actual_return number, decimal; negative = down Across a stock split, or when a price is missing. The move that followed, with its sign: from the last close before the announcement to the first close after it. It equals the overnight and intraday parts compounded. -0.0162
rows[].actual_move number, decimal (0.086 = 8.6%) Across a stock split, or when a price is missing. The size of actual_return, without its sign. 0.086
rows[].return_overnight number, decimal; negative = down Across a stock split, or when a price is missing. The signed change from the last close before the announcement to the price at the next open (09:32 ET). 0.0671
rows[].return_intraday number, decimal; negative = down Across a stock split, or when a price is missing. The signed change from that open to the close of the same session. 0.0177
rows[].atm_vol_30d_before number, decimal vol When unavailable that session. 30-day at-the-money implied volatility at the last close before the announcement. 0.4711
rows[].atm_vol_30d_after number, decimal vol When unavailable that session. The same at the first close after it. 0.3928
rows[].vol_crush number, decimal When either volatility is empty. How far that volatility fell, as a fraction of the value before: 0.17 = it fell by 17%; negative = it rose. 0.1662
rows[].actual_minus_implied number, decimal of the price When either move is empty. actual_move minus implied_move: positive when the stock moved more than the options priced. A difference of moves, not a profit in dollars. 0.0317

GET /event-runup

For each event in the range, the implied move priced for it on each session before it.

GET https://optionspace.ai/api/v1/data/event-runup?symbol=NVDA&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
start date required The first event date of the range, YYYY-MM-DD.
end date required The last event date of the range, YYYY-MM-DD.
event_type text none One of earnings, fomc, cpi, nfp. Left out: every type.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.

Returns: rows: one per event and session before it, oldest event first. An upcoming event appears with the sessions up to the latest one available.
Python: client.event_runup()

Also carries the common fields: the identity fields.

Response fields (12)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
event_type text When every type was asked for. The event type asked for. "earnings"
rows list of objects One entry per event and session before it, oldest event first.
rows[].root text The ticker the event is stored under: an older ticker before a rename. "NVDA"
rows[].event_type text The kind of event. Values: earnings (earnings reports, single stocks), fomc (Fed rate decisions), cpi (consumer price index releases), nfp (the monthly jobs report). "earnings"
rows[].event_date text, YYYY-MM-DD The date of the announcement. "2026-08-26"
rows[].data_date text, YYYY-MM-DD The session the implied move was read at. "2026-10-19"
rows[].sessions_to_event number, sessions Trading sessions before the announcement: 0 is the last close before it (the event day itself for an announcement after the close; the session before for one before the open or during the session), -1 the session before that. Exchange holidays are not sessions. -21
rows[].implied_move number, decimal (0.05 = 5%) When the options priced no measurable extra move for the event, or no figure exists for the session before it; notes says so. Common for CPI and jobs-report days. The implied move: the average size of the move the options priced for the event, up or down, as a fraction of the price. It is about 0.80 times the event's volatility (one standard deviation of the move); see the API guide's "Implied move and event volatility". 0.0543
rows[].implied_move_se number, decimal When implied_move is empty. One standard error of implied_move. 0.0002
rows[].atm_vol_30d number, decimal vol When unavailable that session. 30-day at-the-money implied volatility at that session's close. 0.4191

GET /volume-summary

Per session: official volume and open interest for calls, puts and both, and the put/call ratios. Past sessions only.

GET https://optionspace.ai/api/v1/data/volume-summary?symbol=SPY&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.

Returns: rows: one per session. A session's figures are available from the next session's 09:30 ET open; a range reaching a session not yet available returns 403 with reason not_yet_historical.
Python: client.volume_summary()

Also carries the common fields: the identity fields.

Response fields (13)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
rows list of objects One entry per session.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].root text The root the row was read from; SPX returns SPX and SPXW rows separately. "SPY"
rows[].total_volume integer, contracts When the day's official figures have not been loaded. Official end-of-day volume, calls and puts together. 16617125
rows[].call_volume integer, contracts As total_volume. Official end-of-day call volume. 6838428
rows[].put_volume integer, contracts As total_volume. Official end-of-day put volume. 9778697
rows[].total_open_interest number, contracts When the day's open-interest figures have not been loaded. Open interest, calls and puts together, at the end of the previous session. 19645860
rows[].call_open_interest number, contracts As total_open_interest. Call open interest at the end of the previous session. 5973153
rows[].put_open_interest number, contracts As total_open_interest. Put open interest at the end of the previous session. 13672707
rows[].put_call_volume_ratio number, ratio When call volume is 0 or empty. put_volume divided by call_volume. 1.43
rows[].put_call_open_interest_ratio number, ratio When call open interest is 0 or empty. put_open_interest divided by call_open_interest. 2.29

GET /contract-volume

One option's official daily volume and open interest. Past sessions only.

GET https://optionspace.ai/api/v1/data/contract-volume?symbol=SPY&expiration=2025-04-17&strike=520&put_call=put&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
expiration date required The option's expiration date, YYYY-MM-DD.
strike number required The strike price (USD).
put_call text required call or put.
start date required The first session of the range, YYYY-MM-DD.
end date required The last session of the range, YYYY-MM-DD.
root text none The listed root. Left out, SPXW is served for an SPX monthly listed under both.

Returns: rows: one per session the option was listed. A range reaching a session not yet available returns 403 with reason not_yet_historical.
Python: client.contract_volume()

Also carries the common fields: the identity fields.

Response fields (13)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
strike number, USD Strike price. 520.0
put_call text Call or put. Values: call, put. "call"
rows list of objects One entry per session the option was listed, oldest first.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].strike number, USD Strike price. 520.0
rows[].put_call text Call or put. Values: call, put. "call"
rows[].open_interest integer, contracts When the day's open-interest figure has not been loaded; 0 means none. Open contracts at the end of the previous session (the figure published before this session opened). 5236
rows[].volume number, contracts When the day's volume has not been loaded; 0 means no trades. On /chain: contracts traded so far that session, through the snapshot minute. On /contract-volume: the official end-of-day volume for the session. 63

GET /surface

A legacy end-of-day summary, one entry per listed expiry. For new work use /expirations and /smile.

GET https://optionspace.ai/api/v1/data/surface?symbol=SPY&data_date=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
snapshot text EOD EOD or HH:MM US Eastern time. Leaving it out means EOD: the last snapshot at or before the official close that your plan covers. The time used comes back as snapshot_time.
root text none Only this root. Left out, each expiry comes from the root that lists it, SPXW where both do.

Returns: expiries: expiration, dte, sigma_atm, forward, rate, q, the fit statistics, quality and root; and spot.
Python: client.surface()

Also carries the common fields: the identity fields, the expiries[].quality block.

Response fields (20)
Field Type and unit Can be empty Meaning Example
date text, YYYY-MM-DD The session (the same as data_date). "2025-04-10"
spot number, USD The underlying price at the snapshot. 524.58
expiries list of objects One entry per listed expiry, shortest first.
expiries[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
expiries[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
expiries[].dte integer, calendar days Calendar days from data_date to expiration. 7
expiries[].sigma_atm number, decimal vol When unavailable for that snapshot. At-the-money volatility of the expiry. 0.1423
expiries[].forward number, USD The forward price implied by the expiry's options. For VIX, use forward: each expiry's forward sits at its own futures level. 525.31
expiries[].rate number, decimal (0.041 = 4.1%) The risk-free rate for that expiry, continuously compounded and annualized. 0.0412
expiries[].q number, decimal per year (0.012 = 1.2%) The continuous dividend or borrow yield implied for that expiry. On VIX it restates the spot-to-futures gap and is not a borrow cost. 0.0118
expiries[].spot number, USD The underlying price at the snapshot. For VIX this is the VIX index, not a futures price. 524.58
expiries[].s_implied number, USD When it could not be determined for the expiry. The spot price implied by that expiry's options. 524.61
expiries[].s_basis number, USD When s_implied is empty. s_implied minus spot: how far the options' implied spot sits from the quoted spot. 0.03
expiries[].T number, years Time to expiry used for discounting. 0.0192
expiries[].T_vol number, years Time to expiry used for volatility. It counts trading hours more than nights and weekends. 0.0168
expiries[].T_borrow number, years Time to expiry used for the dividend and borrow term. 0.0192
expiries[].n_obs integer How many options went into the expiry's fit. 182
expiries[].rmse number, decimal vol (0.001 = 0.1 vol point) Typical gap between the quotes' implied volatility and the fitted curve at the same strikes. Smaller means the curve fits the quotes more closely. 0.001
expiries[].r_squared number When not available. How much of the variation in the quotes the fit explains (R squared). Closer to 1 is a tighter fit. 0.998
expiries[].root text The root the expiry was taken from. "SPXW"

REST: research routes

estimate=true returns the most a call can cost without running it, and max_credits=N refuses a call that could cost more (412, not run). A call still working after about 20 seconds answers 202 with a job_id: see background jobs. Completed sessions only; except for /expiry-metrics the symbol must list under one root.

GET /expiry-metrics

At-the-money volatility and 25-delta skew for one listed expiry, optionally beside the previous session.

GET https://optionspace.ai/api/v1/data/expiry-metrics?symbol=SPY&data_date=2025-04-10&expiration=2025-04-17
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
expiration date required The option expiration date, YYYY-MM-DD. /expirations lists them.
snapshot text EOD EOD or HH:MM US Eastern time. Leaving it out means EOD: the last snapshot at or before the official close that your plan covers. The time used comes back as snapshot_time.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.
compare_previous true or false false true also returns the previous trading session and the change in skew.
estimate true or false false true returns credits_estimate and credits_cap without running the call.
max_credits integer none Refuse the call with 412, unrun, if it could cost more than this. At least 1.

Returns: Per session: sigma_atm, put_25d_iv, call_25d_iv, put_25d_skew, call_25d_skew, skew_25d, status; skew_change when compared; credits.
Python: client.expiry_metrics()

Also carries the common fields: the identity fields, the credits block.

Response fields (17)
Field Type and unit Can be empty Meaning Example
expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows list of objects One entry per session: the requested session, then the previous one when compared.
rows[].symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
rows[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
rows[].session_close text, HH:MM, US Eastern The official close of that session. "16:00"
rows[].sigma_atm number, decimal vol When status is not ok. At-the-money volatility of the expiry. 0.1423
rows[].put_25d_iv number, decimal vol When status is not ok. Volatility of the 25-delta put. 0.1611
rows[].call_25d_iv number, decimal vol When status is not ok. Volatility of the 25-delta call. 0.1259
rows[].put_25d_skew number, decimal vol When status is not ok. 25-delta put volatility minus at-the-money volatility. 0.0188
rows[].call_25d_skew number, decimal vol When status is not ok. 25-delta call volatility minus at-the-money volatility. -0.0164
rows[].skew_25d number, decimal vol When status is not ok. 25-delta put volatility minus 25-delta call volatility. 0.0352
rows[].status text Whether the session's values are available. Values: ok, metric_unavailable (the expiry was found but the skew could not be read), unavailable (no fitted expiry at that snapshot). "ok"
skew_change number, decimal vol Without compare_previous, or when either session's skew is unavailable. skew_25d on the requested session minus the previous session. 0.0041
requested_snapshot text The snapshot you asked for, before it was resolved. "EOD"

GET /exposure-distribution

Where the fitted volatility of a delta-selected option sits against the same selection on past sessions, at the same clock time and days to expiry.

GET https://optionspace.ai/api/v1/data/exposure-distribution?symbol=SPY&data_date=2025-04-10&snapshot=09:45&put_call=put
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
data_date date required The trading session, YYYY-MM-DD.
snapshot text required An explicit HH:MM US Eastern time. EOD is refused.
put_call text required call or put.
delta number 0.15 The absolute delta to select (0.15 = a 15-delta option). Greater than 0 and less than 1.
dte integer 1 Days to expiry, in calendar days, matched exactly. From 0 to 365.
delta_tolerance number 0.01 How far a stored option's delta may be from delta and still be selected. From 0 to 0.05.
lookback_years number 1 How many years of past sessions to compare against. From 0.1 to 1.
root text none The listed root, for a symbol that lists under several. Leave it out to let the server choose; see SPX and SPXW.
estimate true or false false true returns credits_estimate and credits_cap without running the call.
max_credits integer none Refuse the call with 412, unrun, if it could cost more than this. At least 1.

Returns: current, history, statistics (n, median, percentile, robust_z_score), coverage, status, credits.
Python: client.exposure_distribution()

Also carries the common fields: the identity fields, the credits block.

Response fields (54)
Field Type and unit Can be empty Meaning Example
status text Whether a statistic is given, or why not. Values: no_current_observation, insufficient_history, zero_dispersion, ok. "ok"
metric text The value being compared. Values: fitted_vol. "fitted_vol"
current object When no option matches on that session (status says so). The option selected on data_date.
current.symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
current.root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
current.data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
current.snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
current.expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
current.dte integer, calendar days Calendar days from data_date to expiration. 7
current.put_call text Call or put. Values: call, put. "call"
current.strike number, USD Strike price. 520.0
current.delta number Change in option value per $1 move in the underlying. 0.48
current.fitted_vol number, decimal vol (0.20 = 20%) The fitted implied volatility at the strike. 0.2144
current.candidate_count integer How many stored options on that session matched the delta band before one was selected. 3
history list of objects One selected option per past session, oldest first.
history[].symbol text On answers not about one symbol (/symbols). The product symbol you asked for. "SPY"
history[].root text When the answer mixes roots or has none (a discovery answer, or SPX without root=); then each row or expiry names its own. The listed root the data belongs to. One product can list under several roots (SPX lists as SPX and SPXW). "SPXW"
history[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
history[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
history[].expiration text, YYYY-MM-DD The contract's expiration date. "2025-04-17"
history[].dte integer, calendar days Calendar days from data_date to expiration. 7
history[].put_call text Call or put. Values: call, put. "call"
history[].strike number, USD Strike price. 520.0
history[].delta number Change in option value per $1 move in the underlying. 0.48
history[].fitted_vol number, decimal vol (0.20 = 20%) The fitted implied volatility at the strike. 0.2144
history[].candidate_count integer How many stored options on that session matched the delta band before one was selected. 2
statistics object Where current sits against history.
statistics.n integer How many past sessions are in history. 247
statistics.median number, decimal vol When status is not ok. Median fitted volatility over history. 0.2011
statistics.scaled_mad number, decimal vol When status is not ok. The spread of history: its median absolute deviation from the median, scaled to read like a standard deviation. 0.021
statistics.robust_z_score number When status is not ok. How far current is from the median, in units of scaled_mad. Not bounded, and not a normal-tail probability. 1.35
statistics.percentile number When status is not ok. Share of history at or below current, from 0 to 100. 84.2
statistics.percentile_rule text The percentile's definition, in words.
statistics.z_score_rule text The z-score's definition, in words.
statistics.minimum_observations integer The fewest past sessions needed for a statistic. 30
coverage object How complete history is.
coverage.expected_sessions integer Trading sessions in the lookback where the snapshot time existed. 251
coverage.matched_sessions integer Sessions with a selected option. 247
coverage.missing_sessions list of text, YYYY-MM-DD An empty list when every session matched. Sessions with no selected option.
selection object The selection you asked for, as applied.
selection.delta_convention text Which delta the selection uses. "stored option spot delta"
selection.target_abs_delta number The absolute delta you asked for. 0.15
selection.delta_tolerance number The tolerance you asked for. 0.01
selection.dte integer, calendar days The days to expiry you asked for. 1
selection.dte_unit text The unit of dte. Values: calendar days.
selection.snapshot_time text, HH:MM, US Eastern The clock time compared on every session. "09:45"
selection.put_call text The side you asked for. Values: call, put. "put"
selection.rule text How one option is picked when several match, in words.
selection.filters text Which stored options are eligible, in words.
lookback object The window of past sessions.
lookback.start_inclusive text, YYYY-MM-DD The first day of the window. "2024-04-10"
lookback.end_exclusive text, YYYY-MM-DD The day after the window (your data_date, which is not part of history). "2025-04-10"
lookback.years number, years The lookback you asked for. 1.0
lookback.sampling text How sessions are sampled, in words.

GET /clean-atm-history

Constant-maturity at-the-money volatility with scheduled events removed (clean_iv) and without (dirty_iv).

GET https://optionspace.ai/api/v1/data/clean-atm-history?symbol=NVDA&start=2025-04-01&end=2025-04-10
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
start date required The first session, YYYY-MM-DD. At most three years before end.
end date required The last session, YYYY-MM-DD, before today.
tenor_days integer 30 The constant maturity in calendar days. From 7 to 180.
granularity text eod eod (one value per session), or every 30m, 5m or 1m (minute) through each session. Your plan must cover that frequency or a finer one. At 1m, at most one year per call.
estimate true or false false true returns credits_estimate and credits_cap without running the call.
max_credits integer none Refuse the call with 412, unrun, if it could cost more than this. At least 1.

Returns: rows (data_date, snapshot_time, dirty_iv, clean_iv, clean_iv_se, status, event_dates, expirations), earnings, notes, credits.
Python: client.clean_atm_history()

Also carries the common fields: symbol, root, notes, returned_rows, the credits block.

Response fields (17)
Field Type and unit Can be empty Meaning Example
start text, YYYY-MM-DD The first session of the range you asked for. "2025-01-02"
end text, YYYY-MM-DD The last session of the range you asked for. "2025-03-31"
tenor_days integer, calendar days The constant maturity, in calendar days. 30
snapshot text Which observations the rows use. Values: EOD (one per session), 5m (every five minutes). "EOD"
granularity text The observation frequency you asked for. Values: eod, 5m. "eod"
rows list of objects One entry per observation, in time order.
rows[].data_date text, YYYY-MM-DD On answers that cover a date range (the range is in start and end). The trading session. "2025-04-10"
rows[].snapshot_time text, HH:MM, US Eastern On answers that are not tied to one snapshot (a range, or a whole session). The time of the quote sample. A request for EOD returns the time it resolved to. "15:57"
rows[].dirty_iv number, decimal vol When status is tenor_not_bracketed or observation_unavailable. Constant-maturity ATM volatility with nothing removed. 0.5412
rows[].clean_iv number, decimal vol When dirty_iv is empty. The same volatility with scheduled events removed. Equals dirty_iv when nothing was removed or removal was not possible. 0.4987
rows[].clean_iv_se number, decimal vol When the removal cannot be vouched for (status says why). One-standard-error band of clean_iv. 0.0062
rows[].status text What happened to the scheduled events for this observation. Values: no_material_event, adjusted, event_size_unresolved, adjustment_unavailable, event_estimate_unavailable, tenor_not_bracketed, observation_unavailable. "adjusted"
rows[].event_dates list of text, list of YYYY-MM-DD An empty list when nothing was removed; absent when there is no value. The scheduled events removed.
rows[].expirations list of text, list of YYYY-MM-DD Absent when status is tenor_not_bracketed or observation_unavailable. The listed expiries the tenor was read from.
earnings list of objects The earnings reports in the window.
earnings[].date text, YYYY-MM-DD The report date. "2025-02-26"
earnings[].timing text When the company reported: after the close or before the open. Values: AMC (after market close), BMO (before market open). "AMC"

GET /earnings-history

Past earnings: the move the options priced, the move that followed, and the run-up before each report.

GET https://optionspace.ai/api/v1/data/earnings-history?symbol=NVDA&as_of=2026-09-16
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
as_of date required Use the reports strictly before this date, YYYY-MM-DD, before today.
count integer 8 How many past reports. From 1 to 12.
runup_sessions integer 30 How many sessions before each report the run-up path covers. From 5 to 60.
granularity text eod eod, 30m, 5m or 1m for the run-up path. Your plan must cover that frequency or a finer one.
estimate true or false false true returns credits_estimate and credits_cap without running the call.
max_credits integer none Refuse the call with 412, unrun, if it could cost more than this. At least 1.

Returns: events (earnings_date, timing, pre_event_session, post_event_session, event_vol, realized_return, realized_abs_move, status, samples), notes, credits.
Python: client.earnings_history()

Also carries the common fields: symbol, root, notes, returned_rows, the credits block.

Response fields (27)
Field Type and unit Can be empty Meaning Example
as_of text, YYYY-MM-DD The as-of date you asked for: reports strictly before it are used. "2026-09-16"
granularity text The run-up observation frequency you asked for. Values: eod, 5m. "eod"
events list of objects One entry per report, oldest first.
events[].earnings_date text, YYYY-MM-DD The earnings report date. "2025-02-26"
events[].timing text When the company reported: after the close or before the open. Values: AMC (after market close), BMO (before market open). "AMC"
events[].pre_event_session text, YYYY-MM-DD The last session closing before the report. "2025-02-26"
events[].post_event_session text, YYYY-MM-DD When that session has not happened yet. The first session after the report. "2025-02-27"
events[].implied_move number, decimal (0.05 = 5%) When the options priced no measurable extra move for the event, or no figure exists for the session before it; notes says so. Common for CPI and jobs-report days. The implied move: the average size of the move the options priced for the event, up or down, as a fraction of the price. It is about 0.80 times the event's volatility (one standard deviation of the move); see the API guide's "Implied move and event volatility". 0.0543
events[].implied_move_se number, decimal When implied_move is empty. One standard error of implied_move. 0.0002
events[].estimate_date text, YYYY-MM-DD When implied_move is empty. The session implied_move was read on: the last close before the report. "2025-02-26"
events[].estimate_snapshot_time text When implied_move is empty. EOD: the report-level estimate is the end-of-day one at every granularity. "EOD"
events[].actual_return number, decimal; negative = down Across a stock split, or when a price is missing. The move that followed, with its sign: from the last close before the announcement to the first close after it. It equals the overnight and intraday parts compounded. -0.0162
events[].actual_move number, decimal (0.086 = 8.6%) Across a stock split, or when a price is missing. The size of actual_return, without its sign. 0.086
events[].return_overnight number, decimal; negative = down Across a stock split, or when a price is missing. The signed change from the last close before the announcement to the price at the next open (09:32 ET). 0.0671
events[].return_intraday number, decimal; negative = down Across a stock split, or when a price is missing. The signed change from that open to the close of the same session. 0.0177
events[].status text Whether the event is complete. Values: ok, incomplete (the priced or realized move is missing), event_timing_unavailable (the report's timing is unknown; the event has no values). "ok"
events[].samples list of objects The run-up: the priced move at each observation before the report.
events[].samples[].data_date text, YYYY-MM-DD The session of the observation. "2025-02-12"
events[].samples[].snapshot_time text, HH:MM, US Eastern, or EOD The time of the observation: EOD on end-of-day run-ups. "EOD"
events[].samples[].sessions_to_event integer, sessions Trading sessions before the announcement: 0 is the last close before it (the event day itself for an announcement after the close; the session before for one before the open or during the session), -1 the session before that. Exchange holidays are not sessions. -21
events[].samples[].implied_move number, decimal (0.05 = 5%) When the options priced no measurable extra move for the event, or no figure exists for the session before it; notes says so. Common for CPI and jobs-report days. The implied move: the average size of the move the options priced for the event, up or down, as a fraction of the price. It is about 0.80 times the event's volatility (one standard deviation of the move); see the API guide's "Implied move and event volatility". 0.0543
events[].samples[].implied_move_se number, decimal When implied_move is empty. One standard error of implied_move. 0.0002
events[].samples[].status text Whether this observation has a value. Values: ok, no_positive_event_estimate, event_estimate_unavailable. "ok"
events[].samples[].session_offset number On five-minute run-ups: the observation's position in sessions, with the fraction of the session added (for example -2.5 is midway through the session two before). -2.5
requested_events integer How many reports you asked for. 8
returned_events integer How many reports were found and returned. 8
runup_sessions integer, sessions The run-up length you asked for. 30

GET /earnings-backtest

A bounded earnings put-spread simulation with fills, fees, P&L and risk.

GET https://optionspace.ai/api/v1/data/earnings-backtest?symbol=NVDA&as_of=2026-09-16
Parameter Type Default Meaning and allowed values
symbol text required The product symbol, such as SPY or SPX. /symbols lists them.
as_of date required Use the reports strictly before this date, YYYY-MM-DD, before today.
count integer 8 How many past reports to trade, one spread each. From 1 to 12.
entry_offset integer -5 When to open: sessions relative to the last close before the report. From -20 to 0.
exit_offset integer 1 When to close: sessions after the report. From 1 to 5.
fill_model text quote_side quote_side (sell at the bid, buy at the ask), midpoint or theoretical (the fitted price).
fee_per_contract number 0.65 USD per contract per fill. From 0 to 20.
granularity text eod eod or 1m for the position path (needs one-minute access).
estimate true or false false true returns credits_estimate and credits_cap without running the call.
max_credits integer none Refuse the call with 412, unrun, if it could cost more than this. At least 1.

Returns: assumptions, events (status, entry_date, exit_date, expiration, legs, fills, gross_pnl, fees, net_pnl, path), summary, credits.
Python: client.earnings_backtest()

Also carries the common fields: symbol, root, notes, returned_rows, the credits block.

Response fields (66)
Field Type and unit Can be empty Meaning Example
as_of text, YYYY-MM-DD The as-of date you asked for: reports strictly before it are used. "2026-09-16"
granularity text The position-path frequency you asked for. Values: eod, 1m. "eod"
strategy text The simulated strategy. Values: earnings_put_credit_spread. "earnings_put_credit_spread"
assumptions object The trade rules the simulation used.
assumptions.fill_model text The fill model you asked for. Values: quote_side, midpoint, theoretical. "quote_side"
assumptions.fee_per_contract number, USD per contract per fill The fee you asked for. 0.65
assumptions.multiplier integer Shares per contract used for P&L. 100
assumptions.entry_sessions integer Your entry_offset. -5
assumptions.exit_sessions integer Your exit_offset. 1
assumptions.expiry text Which expiry is traded, in words.
assumptions.selection text How the two strikes are picked, in words.
assumptions.marking text How the position is valued along the way, in words.
events list of objects One entry per report, oldest first.
events[].earnings_date text, YYYY-MM-DD The earnings report date. "2025-02-26"
events[].timing text When the company reported: after the close or before the open. Values: AMC (after market close), BMO (before market open). "AMC"
events[].status text Whether the trade was simulated. Values: complete, unavailable (see reason). "complete"
events[].legs list of objects An empty list when the event was not traded. The two options: the short put first, then the long put.
events[].legs[].quantity integer Contracts: -1 short, +1 long. -1
events[].legs[].target_delta number The absolute delta the leg aimed for. 0.25
events[].legs[].strike number, USD The leg's strike. 120.0
events[].legs[].delta number The selected option's delta at entry. -0.24
events[].legs[].fitted_price number, USD per share The selected option's fitted price at entry. 3.1
events[].legs[].fitted_vol number, decimal vol The selected option's fitted volatility at entry. 0.62
events[].path list of objects An empty list when the event was not traded. The position at each observation from entry to exit.
events[].path[].data_date text, YYYY-MM-DD The session. "2025-02-20"
events[].path[].snapshot_time text, HH:MM, US Eastern The time of the observation. "15:57"
events[].path[].pnl number, USD When a leg's price is unavailable (status says so). Profit or loss so far, for one spread. -11.3
events[].path[].delta number, share-equivalent When a leg's value is unavailable. The position's delta. 15.0
events[].path[].gamma number, shares per $1 of spot When a leg's value is unavailable. The position's gamma. -0.4
events[].path[].vega number, USD per 1 volatility point When a leg's value is unavailable. The position's vega. -8.2
events[].path[].theta number, USD per calendar day When a leg's value is unavailable. The position's theta. 4.1
events[].path[].status text Whether the observation is complete. Values: complete, mark_unavailable, risk_incomplete, closed (the exit). "complete"
events[].fills list of objects An empty list when the event was not traded. Every fill: two at entry, two at exit.
events[].fills[].id text The fill's identifier: report date, phase and leg number. "2025-02-26-entry-0"
events[].fills[].trade_id text The trade the fill belongs to (the report date). "2025-02-26"
events[].fills[].symbol text The product symbol. "NVDA"
events[].fills[].root text The listed root. "NVDA"
events[].fills[].leg_kind text What was traded. Values: option. "option"
events[].fills[].expiration text, YYYY-MM-DD The option's expiry. "2025-03-21"
events[].fills[].strike number, USD The option's strike. 120.0
events[].fills[].put_call text Always put in this strategy. Values: put. "put"
events[].fills[].qty integer The leg's position: -1 short, +1 long. -1
events[].fills[].executed_qty integer Contracts traded in this fill, signed: negative sells, positive buys. -1
events[].fills[].fill_price number, USD per share The fill price under the fill model. 3.05
events[].fills[].side text Opening or closing. Values: open, close. "open"
events[].fills[].fill_ts text, YYYY-MM-DDTHH:MM:SS, US Eastern When the fill is assumed to happen. "2025-02-19T15:57:00"
events[].fills[].session_date text, YYYY-MM-DD The fill's session. "2025-02-19"
events[].fills[].fee number, USD The fee charged on the fill. 0.65
events[].fills[].snapshot_time text, HH:MM, US Eastern The snapshot whose prices were used. "15:57"
events[].entry_date text, YYYY-MM-DD Absent when the sessions could not be placed. The session the spread was opened. "2025-02-19"
events[].exit_date text, YYYY-MM-DD Absent when the sessions could not be placed. The session the spread was closed. "2025-02-27"
events[].expiration text, YYYY-MM-DD Absent when the event was not traded. The expiry traded. "2025-03-21"
events[].gross_pnl number, USD Absent when the event was not traded. Profit or loss before fees, for one spread. -20.0
events[].fees number, USD Absent when the event was not traded. Fees on the four fills. 2.6
events[].net_pnl number, USD Absent when the event was not traded. gross_pnl minus fees. -22.6
events[].initial_credit number, USD Absent when the event was not traded. Cash received when the spread was opened, after the entry fees. 108.7
events[].spread_width number, USD (strike difference times 100) Absent when the event was not traded. The distance between the strikes, for one spread. 1000.0
events[].reason text Absent when status is complete. Why the event was not traded. Values: unknown_report_timing, session_unavailable, split_spanning_trade, no_monthly_expiry_after_exit, no_valid_stored_delta_put_spread, ambiguous_contract_rows, entry_or_exit_fill_unavailable.
summary object Totals over the traded events.
summary.requested_events integer How many reports you asked for. 8
summary.events integer How many reports were found. 8
summary.traded_events integer How many were traded. 7
summary.excluded_events integer How many were not traded. 1
summary.net_pnl number, USD Net profit or loss over the traded events. -67.8
summary.gross_pnl number, USD Profit or loss before fees. -60.0
summary.fees number, USD All fees. 7.8

Estimate-only answer

What any research route returns when called with estimate=true: nothing is run.

Fields (12)
Field Type and unit Can be empty Meaning Example
route text The route estimated. "clean-atm-history"
setting text Which estimate rule applied. Values: default, eod, 5m, 1m. "eod"
credits_estimate integer, credits The estimated cost. 4
credits_cap integer, credits The most a run can cost. 4
estimate_seconds number, seconds The estimated server time. 3.2
features object The sizes the estimate was made from, such as years of history or events.
symbols integer How many symbols the call covers. 1
price_version text The price list used. "2026-10-beta-3"
fixed_price true or false True when this setting has a fixed price per call: the estimate and the cap are that price, and a successful run is charged it. false
estimate_only true or false Always true: nothing was run. true
credits_charged integer Credits charged for the estimate itself. 0
note text The charging rule, in words.

REST: background jobs

GET /jobs/{job_id}

The state of a background research job. Only your account can read its jobs.

GET https://optionspace.ai/api/v1/data/jobs/JOB_ID
Parameter Type Default Meaning and allowed values
job_id text required The job_id from a 202 response.

Returns: status: queued, running, done (with result: the body the call would have returned), failed (with error) or canceled. Kept for one hour after the job finishes.
Python: client.job()

Response fields (20)
Field Type and unit Can be empty Meaning Example
job_id text The job's identifier. "Jq3xR1c8vT"
status text The job's state. Values: queued, running, done, failed, canceled. "done"
route text The research route that started the job. "/clean-atm-history"
setting text The estimate rule the job's cost uses. Values: default, eod, 5m, 1m. "eod"
created_at text, ISO 8601 time, UTC When the job was created. "2026-10-20T14:00:00Z"
started_at text, ISO 8601 time, UTC Until it starts. When the job started running. "2026-10-20T14:00:01Z"
ended_at text, ISO 8601 time, UTC Until it finishes. When the job finished. "2026-10-20T14:00:31Z"
queue_seconds number, seconds Until it starts. Time spent waiting before it started. 1.0
work_seconds number, seconds Until it finishes. Time spent working. 29.6
credits object The job's cost.
credits.estimate integer, credits The server's estimate of the call's cost, made from its parameters before it ran. 4
credits.cap integer, credits The most the call can cost. 4
credits.charged integer Until it finishes. Credits charged. 30
poll_url text Where to collect the job. "/api/v1/data/jobs/Jq3xR1c8vT"
result object Absent unless status is done. When status is done: exactly the body the research call would have returned (see that route's fields).
result_kept_seconds integer, seconds Absent unless status is done. How long the result is kept after the job finishes. 3600
error object Absent otherwise. When status is failed or canceled: the status and message the call would have returned.
error.status_code integer The HTTP status the call would have returned (499 for a cancelled job). 503
error.detail text The message. "the data store is unavailable"
poll_after_seconds integer, seconds Absent once the job has finished. How long to wait before polling again. 2

DELETE /jobs/{job_id}

Cancel a job that has not started. A running job cannot be stopped (409).

DELETE https://optionspace.ai/api/v1/data/jobs/JOB_ID
Parameter Type Default Meaning and allowed values
job_id text required The job_id from a 202 response.

Returns: The job's state, canceled.

Also carries the common fields: the credits block.

Response fields (14)
Field Type and unit Can be empty Meaning Example
job_id text The job's identifier. "Jq3xR1c8vT"
status text Always canceled after a successful cancel. Values: canceled. "canceled"
route text The research route that started the job. "/clean-atm-history"
setting text Which estimate rule applied: the observation frequency of the call, or default for a route with one rule. Values: default, eod, 5m, 1m. "eod"
created_at text, ISO 8601 time, UTC When the job was created. "2026-10-20T14:00:00Z"
started_at null Always. Only a job that has not started can be cancelled.
ended_at null Always. When the job ended; not set at the time of the cancel.
queue_seconds number, seconds Until it starts. Time spent waiting before it started. 1.0
work_seconds null Always. The job never ran.
credits.charged null Always. Empty for a cancelled job.
poll_url text Where to collect the job. "/api/v1/data/jobs/Jq3xR1c8vT"
error object Says the job was cancelled.
error.status_code integer Always 499 for a cancelled job. 499
error.detail text The message. "cancelled by the caller"

Accepted (202) answer

What a research route returns when it is still working after about 20 seconds. Collect the answer from poll_url.

Fields (11)
Field Type and unit Can be empty Meaning Example
job_id text The job's identifier. "Jq3xR1c8vT"
status text The job's state when the call answered. Values: queued, running. "queued"
route text The research route. "/clean-atm-history"
poll_url text Where to collect the answer. "/api/v1/data/jobs/Jq3xR1c8vT"
poll_after_seconds integer, seconds How long to wait before the first poll. 2
result_kept_seconds integer, seconds How long the answer is kept after the job finishes. 3600
credits object The job's cost so far.
credits.estimate integer, credits The server's estimate of the call's cost, made from its parameters before it ran. 4
credits.cap integer, credits The most the call can cost. 4
credits.charged null Always. Empty here; the finished job reports it (GET /jobs/{job_id}).
message text What to do next, in words.