Docs/Option Chain API
API referenceOption Chain API
POST /api/option-chain reference: full U.S. option chains with strikes, expirations, bid/ask, OI, IV, and Greeks.
On this page
Beta. This API is in beta testing. The request and response schema (fields, units, and semantics) may change.
Start here
Follow the Option Chain quickstart, review the request filters, or jump to the response fields.
The Option Chain API exposes per-contract chain rows for an underlying, for a given trading date:
POST https://www.optiondata.io/api/option-chainIt is included with the realtime plan — both trialing and active realtime subscriptions get full access.
Authentication
Send Authorization: Bearer YOUR_API_KEY for API clients. The header takes precedence over the legacy body api_key; malformed authorization is rejected. If neither is supplied, the portal can use the signed-in session. Live access requires an active or trialing Pro subscription.
Request
JSON and form (application/x-www-form-urlencoded / multipart/form-data) bodies are supported. Field names are snake_case.
Required:
symbol: one underlying ticker (uppercased by the server; 1–16 chars).
Optional:
api_key:cus_...(raw Stripe customer id) or a generatedapikey_...token. If omitted, the signed-in session is used.date:YYYY-MM-DD. Defaults to the latest available trading session. Checkmeta.trading_datefor the date returned; an explicit date is never substituted with another session. An impossible calendar date (for example2026-02-30) returns400; a date with no stored session (weekend, holiday, future, or outside retention) returns404.expiration_date:YYYY-MM-DD; must be a real calendar date.put_call:CALLorPUT.strike: exact positive strike price; cannot be combined withstrike_minorstrike_max.strike_min,strike_max: inclusive strike bounds; either bound may be used alone.
Strike bounds must be positive, and strike_min must not exceed strike_max.
Source and semantics
Responses include the complete available chain inventory matching your filters, including contracts with no trades. During U.S. market hours, newer trade-derived values are normally incorporated about every 90 seconds: quotes, Greeks, IV, volume, open interest, and last price. Contracts without a newer trade retain their chain values until a newer chain snapshot is available. This cadence is an expected update interval, not a guarantee that every contract's quotes or Greeks refresh on each interval. Prices are in USD; Greeks/IV are decimals (e.g. IV 0.22 = 22%).
Before a new session's chain is available, requests without date can return the previous session. Always check meta.trading_date instead of assuming that the latest available session is today's date.
If intraday updates are temporarily unavailable, a response can retain earlier chain values. Check both meta.trading_date and meta.as_of before treating a response as current. An explicit historical date selects that session's stored chain; it does not replay the chain at a particular time within the session.
Response — core fields (always returned)
{ "data": [ ... ], "meta": { "trading_date": "YYYY-MM-DD", "as_of": "..." } }
| Field | Type | Notes |
|---|---|---|
option_symbol | string | OCC option symbol |
put_call | CALL | PUT | |
strike | number | Strike price |
expiration_date | string | YYYY-MM-DD |
bid | number | null | Best bid recorded with the contract's most recent trade in the session; null when the contract has not traded in the session |
ask | number | null | Best ask recorded with the contract's most recent trade in the session; null when the contract has not traded in the session |
last_price | number | null | Latest trade price |
open_interest | integer string | null | Open interest as a JSON decimal string |
open_interest_change | integer string | null | open_interest − prior-day OI; null when prior-day OI is unavailable |
volume | integer string | null | Daily volume as a JSON decimal string |
implied_volatility | number | null | Decimal; null when no model value is available |
delta / gamma / theta / vega | number | null | Greeks (decimal); null together with implied_volatility when no model value is available |
Numeric fields are null when the value is unavailable for a contract. bid and ask come from the quote attached to the contract's latest trade in the meta.trading_date session, so a contract that last traded early in the day carries that earlier quote. bid and ask are null together, and implied_volatility and the Greeks are null together. A single 0, such as a bid of 0 with a positive ask, is a real value. This endpoint returns chain fields only; use the realtime WebSocket for flow fields such as premium, size, and trade count.
open_interest, open_interest_change, and volume are serialized as decimal strings so that 64-bit values stay exact. Parse them with an integer type that is safe in your language; do not assume every 64-bit integer can be represented exactly by a JavaScript number.
Response — meta
| Field | Notes |
|---|---|
trading_date | Trading session returned, in YYYY-MM-DD format |
as_of | Newest contributing chain snapshot time or merged trade time, as a UTC ISO timestamp or null. This is a response-level freshness signal, not the request time or a per-contract quote/Greek timestamp |
Errors
All errors return { "error": { "code": "...", "message": "..." } } with an HTTP status. A chain that exists but has no contract matching your filters is not an error: it returns 200 with an empty data array.
| Status | Meaning |
|---|---|
400 | Invalid body or failed request validation, including an impossible calendar date |
401 | Could not resolve a customer (missing/invalid api_key and no session) |
403 | Resolved, but no active/trialing realtime subscription |
404 | SNAPSHOT_NOT_FOUND: no chain is stored for the requested date. SYMBOL_NOT_FOUND: no chain is stored for the symbol on that session |
405 | METHOD_NOT_ALLOWED: use POST; the Allow header lists the supported method |
422 | Request was too broad for the response guardrail |
429 | Rate limited; honor Retry-After |
504 | Query timed out |
500 | Unexpected server error |
503 | UPSTREAM_UNAVAILABLE: data or API key verification is temporarily unavailable; retry after the Retry-After seconds, with backoff |
A 403 (SUBSCRIPTION_REQUIRED) also carries reason and billing_url inside error:
{
"error": {
"code": "SUBSCRIPTION_REQUIRED",
"message": "An active or trialing subscription is required.",
"reason": "NO_ACTIVE_SUBSCRIPTION",
"billing_url": "/billing"
}
}reason is always NO_ACTIVE_SUBSCRIPTION: the customer was resolved, but has no active or trialing Pro subscription. A Pro subscription whose renewal payment failed more than 7 days into the unpaid billing period gets the same response until the invoice is paid. billing_url is the path on https://www.optiondata.io where the account owner can restore Pro access. Do not retry until access is restored.
FAQ
Q: How often should I poll?
A: The latest session updates about every 90 seconds during market hours, so poll every one to two minutes at most and compare meta.as_of before doing new work. A contract without a recent trade keeps its last chain value, so an update does not refresh every Greek. Faster polling returns the same data and may hit rate limits.
Q: Rate and size limits?
A: Keep sustained traffic at one request per second per customer and short bursts to 10 requests or fewer, for example when fetching several expirations. Current throttling is best-effort; this is client usage guidance, not a guaranteed global quota. HTTP 429 includes a Retry-After header; wait that long before retrying. See API rate limits for the complete policy.
Planned, not yet active: trial 20/minute and 600/hour; paid Pro 60/minute and 3,000/hour. Once enabled, both limits apply per customer across their keys and IPs; reaching either triggers rate limiting. No effective date has been announced. Enterprise limits are contract-specific. See API rate limits for the complete policy.
Response-size behavior is unchanged: over-broad chains are rejected instead of returning partial results—use expiration, put/call, or strike filters.
Q: History range and delay?
A: Chain sessions from about 2026-02-20 forward (grows each trading day). Use as_of for freshness. For individual trades, use Historical SQL: paid access covers the past 15 days (≥15-minute delay).
Q: Free trial / extension?
A: Complete qualification, then explicitly activate the 14-day Pro trial when you are ready. Eligible trial users can receive 50% off for life with a promotion code from Sales; contact Sales for the code, then enter it on Billing before the trial ends. Trials are not auto-extended.
Q: WebSocket full chain?
A: No. Chains are REST only; WebSocket is trades/flow.
Ask ChatGPT or Claude Code
Copy this prompt, paste it into ChatGPT, Claude, Claude Code, Cursor, or Codex, then add your question. It tells the model to read our public docs first — no API key needed for that step.
You are helping me use OptionData (https://www.optiondata.io/), an OPRA-licensed U.S. equity options data API.
Before answering, fetch these public files (no login required) and treat them as the source of truth:
- https://www.optiondata.io/llms.txt — short product map (same content as https://www.optiondata.io/llm.txt)
- https://www.optiondata.io/llms-full.txt — full API reference
- https://www.optiondata.io/openapi.json — HTTP OpenAPI
Do not invent endpoints, fields, tables, or limits. Prefer `Authorization: Bearer apikey_…` for HTTP APIs. Realtime uses `wss://ws.optiondata.io` with a `token` query parameter.
Products:
- Realtime trades WebSocket: wss://ws.optiondata.io
- Historical SQL: POST https://www.optiondata.io/api/historical/sql
- Option chain: POST https://www.optiondata.io/api/option-chain
- Market structure: GET https://www.optiondata.io/api/v1/market-structure/{symbol}
I am asking about: Option Chain API
- Markdown: https://www.optiondata.io/md/option-chain-api
- HTML docs: https://www.optiondata.io/docs/option-chain-api
My question: