# Options Intelligence API

> End-of-day options analytics ranked against each stock's own history: IV rank, put/call percentile, 25-delta skew, open-interest walls, max pain, and unusual contracts, plus a market-wide radar.

Base URL: `https://app.sentisense.ai/api/v1`
Tier: Free preview (API key required; FREE: 10 full dossiers/month, top-25 radar, 1y history. PRO: unlimited, full)

All API access requires an API key via the `X-SentiSense-API-Key` header. Keys look like `ss_live_...`.

> SDKs: [Python](https://github.com/SentiSenseApp/sentisense) · [Node](https://github.com/SentiSenseApp/sentisense-node)

## Overview

API base URL: `https://app.sentisense.ai/api/v1`.

The Options Intelligence API turns each session's full option chain into a small set of end-of-day analytics for US stocks and ETFs, and it ranks every reading against that stock's own trailing history rather than against other stocks. The same data drives the Options Radar and the per-stock Options tab in the SentiSense app.

Percentile and rank context accompanies readings when sufficient data is available, measured within the ticker's own trailing window. Readings describe what the chain looks like versus its own past. They are not forecasts, price targets, or trade recommendations.

**What the aggregate captures:**

| Group | Fields |
|-------|--------|
| Volume and open interest | `callVol`, `putVol`, `callOi`, `putOi`, `pcVol` (put/call volume), `pcOi` (put/call open interest) |
| Implied volatility | `vwIv` (volume-weighted), `atmIv` (~30d ATM), `atmIv60`, `atmIv90` (term structure), `iv25c`, `iv25p` (25-delta call/put) |
| Expected move | `expectedMove1d`, `expectedMove5d`, `expectedMove20d` (the calibrated 90% range) and `expectedMove1s1d`, `expectedMove1s5d`, `expectedMove1s20d` (the one-sigma convention), each a fraction of price over 1, 5 and 20 trading sessions. See "How the expected move is computed" |
| Skew | `skew25d` (25-delta skew, equals `iv25p - iv25c`; a fraction on the same scale as IV, so 0.03 = 3 IV points) |
| Positioning | `netDelta` (aggregate net delta exposure, open interest times delta summed across the chain; not an inference about dealer books), `notionalVol` (estimated premium activity), `contracts` (rows in the chain) |
| Structure (snapshot only) | `oiWalls` (open-interest walls + `maxPain`), `unusual` (unusually-active contracts) |

**Freshness:** this is end-of-day data, not real-time. End-of-day fields describe the latest successfully published snapshot. After market close, `asOf` may still identify the prior session. Follow-ups compare the next session's open interest and stay pending until that chain is observed. On weekends and market holidays `asOf` is the latest completed session. Intraday fields have independent timestamps (see "Intraday session fields" below).

**Coverage:** two universes, and they are discovered differently.

- **Stocks:** a bounded universe of the most actively optioned US stocks, about 1,040 names in the latest build, expanding over time. The `rows` of the full `/options/overview` are the authoritative list, and the overview's `coverageCount` reports its exact size.
- **ETFs:** `GET /etfs` lists tracked funds; the full overview's `etfRows` lists funds in the current options snapshot. FREE receives the top 25 ETF rows, so absence from that slice does not establish noncoverage. Covered funds are served from the same `/stocks/{ticker}/options/...` paths. On the overview they are a **separate board**: `etfRows`, never mixed into `rows`. `coverageCount` and the market-pulse aggregates describe the stock board only, so an ETF is never counted there.

The two boards are ranked independently and must not be merged. Every reading is a percentile of that ticker's own trailing history, so an ETF's `interestScore` is comparable to other ETFs, not to a single stock.

A ticker with no options snapshot (outside both universes, unknown, or not yet built) returns `200` with `data: null` from `/summary`; `/history` returns an empty `series` when the ticker has no stored aggregates. Neither is an error.

**Scores and percentiles:** a percentile requires a current reading and at least 60 usable observations in its window; IV rank is also omitted for a flat range. `interestScore` requires at least 60 observations, at least $100,000 of estimated premium activity (`notionalVol`) in the latest session, and at least one usable score component; otherwise it is omitted and the row is unscored. A missing percentile or score is an unavailable reading, not a zero.

**Access and how the free tier works:** every endpoint requires an API key, and each call counts against your monthly request quota (Free: 1,000 requests/month; PRO: no monthly cap) and your per-minute rate limit. Monthly request and dossier allowances reset at the start of the first day of each calendar month in America/New_York (Eastern time). On top of that, the options data itself is tiered by key:

- **PRO keys** always get the full response from every endpoint.
- **FREE access** is a limited cut of each endpoint:
  - `/options/overview` returns the **top 25 ranked rows of each board** and the **top 3 session highlights of each board**, plus every market-pulse aggregate, for both boards. `totalCount` (on the envelope) reports the full stock board; `etfTotalCount` (inside `data`) reports the full ETF board.
  - `/stocks/{ticker}/options/summary`: FREE accounts receive ten covered full-summary calls per calendar month, including repeat calls and prior account usage; summaries without a snapshot never spend this allowance. After exhaustion, summaries return headline previews (`asOf`, `sentiment`, `ivRank1y`, `atmIv`, `expectedMove1d`, `pcVol`, `pcVolPctl1y`, `maxPain`, plus the intraday session fields when present). Remaining dossier allowance and reset timestamps are not returned. Dossier exhaustion returns a preview with HTTP 200; it is distinct from the monthly request quota, whose exhaustion returns `429 quota_exceeded`.
  - Delisted tickers do not count against the FREE dossier allowance and return full summaries even after it is exhausted.
  - `/stocks/{ticker}/options/history` always serves `window=1y`.
- The intraday session fields are the same on every tier, previews included.
- Overview and summary previews set `isPreview: true`, `previewReason: "PRO_REQUIRED"` and include `upgrade`. Summary headline fields are flattened under `data`; optional intraday fields remain when available. FREE history reports `window: "1y"` with `isPreview: false`, even when `2y` or `5y` was requested. Full bodies carry `isPreview: false` and `previewReason: null`.
- The SentiSense web app is separate from this: signed-in users see the full Radar and per-stock Options tab in the browser on every plan. The tiering above applies to API-key calls only. (The `get_options` MCP connector tool is a condensed rendering with its own matching FREE/PRO cut.)

Unavailable nested metric fields are omitted. Envelope `previewReason` and `data` can be explicit null; a null dossier means no snapshot is available.

**Rate limits and errors:** standard FREE and PRO limits are 30 and 300 requests/minute. `rate_limit_exceeded` returns `Retry-After: 60`; `quota_exceeded` has no `Retry-After` and requires waiting for monthly reset or a quota change. 401 and 429 bodies contain `error` and `message`, without the success envelope. Dispatch on `error`, not on the exact message text, which can vary.

### Access examples

The values below are synthetic. The `upgrade` object illustrates the current shape; relay the `price`, `url` and `message` a response returns rather than the example text.

FREE summary after the dossier allowance is spent (HTTP 200). This example has no intraday session fields; when present, `intradayFlow`, `largePrintCount`, `largestPrintPctl` and `capabilities` sit beside the headline fields. The headline cut has no `latest`, `context`, `oiWalls` or `unusual` wrapper.

```json
{
  "isPreview": true,
  "previewReason": "PRO_REQUIRED",
  "upgrade": {
    "plan": "PRO",
    "message": "This endpoint returned a free-tier preview; PRO unlocks the full data and unlimited monthly requests.",
    "price": "PRO price and any current offer, as text",
    "url": "https://app.sentisense.ai/pricing",
    "relay": "Tell the user in one line, then continue with the preview data."
  },
  "data": {
    "asOf": "2026-09-28",
    "sentiment": 0.2,
    "ivRank1y": 45,
    "atmIv": 0.4,
    "expectedMove1d": 0.0373,
    "pcVol": 0.7,
    "pcVolPctl1y": 60,
    "maxPain": 150
  }
}
```

FREE history requested with `window=5y`, for a ticker without stored aggregates (HTTP 200). The served window is `1y`, and an empty series is not a preview.

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "ticker": "EXAMPLE",
    "window": "1y",
    "series": []
  }
}
```

Missing key (HTTP 401, `Content-Type: application/json`, `Cache-Control: no-store`). An invalid or revoked key returns `invalid_api_key` with the message "Invalid or revoked API key".

```json
{
  "error": "api_key_required",
  "message": "API key (header `X-SentiSense-API-Key`) is required for programmatic access. Get one at https://sentisense.ai/docs/api"
}
```

Per-minute limit reached on a 30 requests/minute key (HTTP 429, `Retry-After: 60`, same content and cache headers). Wait at least that many seconds before retrying. The message begins as shown; a plan-specific upgrade sentence or offer text may be appended.

```json
{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded (30 requests/minute). Retry after 60 seconds."
}
```

Monthly request quota exhausted (HTTP 429, no `Retry-After`, same content and cache headers). Stop automatic retries until the monthly reset or a quota change.

```json
{
  "error": "quota_exceeded",
  "message": "Monthly API request quota exceeded for your current plan"
}
```

---

## GET /options/overview

Market-wide Options Radar: two boards plus a few market-pulse aggregates. `rows` is one row per covered stock; `etfRows` is the same shape for covered ETFs. Both arrive ranked by `interestScore` descending. Unscored rows sort last. FREE keys receive the top 25 rows as a preview with `totalCount`; PRO keys receive every row.

**Authentication:** API key required

**Parameters:** None

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/options/overview"
```

The ETF board covers index, sector and commodity funds. Read it from the same response (requires jq):

```bash
curl -s -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/options/overview" \
  | jq '.data.etfRows[0:5] | .[] | {ticker, sector, interestScore, ivRank1y}'
```

For ETF rows the `sector` field carries the fund's asset class (`Equity`, `Bond`, `Commodity`, ...) rather than a GICS sector.

**Response:** the `data` object is `null` before the first nightly build populates it, otherwise:

| Field | Type | Description |
|-------|------|-------------|
| `asOf` | ISO date | Latest session date across all rows |
| `builtAt` | integer | When this board was built, Unix epoch seconds (UTC): the evening refresh or the morning pass that wrote it. Pair it with `asOf` to see how fresh the board is |
| `medianIvRank` | number | Median IV rank (0-100) across covered **stocks** |
| `marketPcVol` | number | Median put/call volume ratio across covered **stocks** |
| `extremeCount` | integer | Number of **stock** rows meeting any available condition: `pcVolPctl1y` or `skewPctl1y` at most 5 or at least 95, or `ivRank1y` at least 90. Each row counts once |
| `coverageCount` | integer | Number of stocks in this build. Counts the **stock** board only; ETFs are never included |
| `rows` | array | The stock board: one row per covered stock, ranked by `interestScore` descending (FREE keys: top 25, with the full size in the envelope's `totalCount`) |
| `etfRows` | array | The ETF board, ranked, same row shape as `rows`. Omitted entirely when a build has no ETF rows |
| `etfMedianIvRank` | number | Median IV rank (0-100) across covered **ETFs** |
| `etfMarketPcVol` | number | Median put/call volume ratio across covered **ETFs** |
| `etfExtremeCount` | integer | The same count for the **ETF** board, counted separately |
| `etfCoverageCount` | integer | Number of ETFs in this build. This is the denominator for `etfExtremeCount`, and it stays the full board size even when `etfRows` is a FREE-key top-25 slice |
| `etfTotalCount` | integer | FREE keys only: the full size of the ETF board behind the truncated `etfRows`. The envelope's `totalCount` reports the stock board |
| `highlights` | array | Session highlights for the stock board: the highlight contract of up to 10 stocks in the latest completed session, most unusual for its own ticker first (FREE keys: top 3). Omitted when the build has none |
| `etfHighlights` | array | The same for the ETF board, ranked among ETFs only (FREE keys: top 3) |
| `highlightPolicy` | string | The rule the stock `highlights` were chosen by: `ex0dte-v1` (see Session highlights below). Omitted when that list was built before the rule existed |
| `etfHighlightPolicy` | string | The same for `etfHighlights` |
| `intradayActiveCount` | integer | Intraday: stocks on today's intraday board with at least one unusual contract so far. Stock board only. Omitted before the day's first board |
| `intradayRanking` | array | Intraday: up to 25 of those tickers as an array of ticker strings, ordered by each ticker's own-year percentile where one exists and otherwise by the size of its largest flagged contract, with no per-entry score. Omitted with `intradayActiveCount` |
| `capabilities` | object | Where the intraday board lives and who can open it (see Intraday session fields) |

The two boards carry two separate sets of aggregates by design. `medianIvRank`, `marketPcVol`, `extremeCount` and `coverageCount` describe the stock board and never blend in funds; the four `etf*` aggregates describe the ETF board. The four ETF fields are omitted entirely when a build has no ETF rows.

Each element of `rows`:

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string | Primary ticker |
| `name` | string | Company name (omitted if unmapped) |
| `sector` | string | Sector (omitted if unmapped) |
| `asOf` | ISO date | Session date of this row's snapshot |
| `sentiment` | number | Options-implied sentiment, -1 to +1 (omitted on cold start) |
| `interestScore` | number | Composite extremeness score, 0-100; omitted when history, liquidity eligibility or usable components are insufficient |
| `pcVol` | number | Put/call volume ratio today |
| `pcVolPctl1y` | number | Percentile (0-100) of `pcVol` in the trailing 1y window |
| `atmIv` | number | ATM implied volatility, as a fraction (0.42 = 42%) |
| `ivRank1y` | number | IV rank (0-100) of `atmIv` in its trailing 1y range |
| `skew25d` | number | 25-delta skew, `iv25p - iv25c`, a fraction on the same scale as IV (0.03 = 3 IV points) |
| `skewPctl1y` | number | Percentile (0-100) of `skew25d` in the trailing 1y window |
| `notionalVol` | number | Estimated premium activity: session volume × mark × 100, summed in USD; this is mark-based valuation, not observed execution spending |
| `ivMove20` | number | Signed change of `atmIv` vs its ~20-session mean; rank a "biggest IV moves" view by its absolute value |
| `expectedMove1d` | number | Calibrated 90% expected move over 1 trading session, as a fraction of price (0.0407 = 4.07%) |
| `expectedMove5d` | number | The same over 5 trading sessions |
| `expectedMove20d` | number | The same over 20 trading sessions |
| `expectedMove1s1d` | number | One-sigma expected move over 1 trading session: the industry convention, uncalibrated |
| `expectedMove1s5d` | number | One-sigma expected move over 5 trading sessions |
| `expectedMove1s20d` | number | One-sigma expected move over 20 trading sessions |
| `observations1y` | integer | Trailing-1y observation count (used by the score and percentile minimums) |
| `unusualCount` | integer | Number of contracts in the capped `unusual` selection, 0-5; excludes a separate highlight outside that selection |
| `maxVolOiRatio` | number | Largest volume/open-interest multiple among them |
| `maxUnusualPremium` | number | Largest premium (USD) among them |
| `wallSide` | string | Side of the single heaviest open-interest wall, `call` or `put` |
| `wallStrike` | number | Strike of that wall |
| `wallShare` | number | That wall's share of its side's open interest (0-1) |

Abbreviated example: one stock row, with the ETF board and highlights omitted.

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "asOf": "2026-07-17",
    "builtAt": 1784338620,
    "medianIvRank": 38.5,
    "marketPcVol": 0.92,
    "extremeCount": 47,
    "coverageCount": 950,
    "rows": [
      {
        "ticker": "NVDA",
        "name": "NVIDIA Corporation",
        "sector": "Technology",
        "asOf": "2026-07-17",
        "sentiment": 0.21,
        "interestScore": 88.0,
        "pcVol": 0.62,
        "pcVolPctl1y": 18.0,
        "atmIv": 0.53,
        "ivRank1y": 74.0,
        "skew25d": 0.041,
        "skewPctl1y": 61.0,
        "notionalVol": 4820000000,
        "ivMove20": 0.06,
        "expectedMove1s1d": 0.0334,
        "expectedMove1s5d": 0.0747,
        "expectedMove1s20d": 0.1493,
        "expectedMove1d": 0.0494,
        "expectedMove5d": 0.1165,
        "expectedMove20d": 0.2329,
        "observations1y": 214,
        "unusualCount": 5,
        "maxVolOiRatio": 6.8,
        "maxUnusualPremium": 3820000,
        "wallSide": "call",
        "wallStrike": 180.0,
        "wallShare": 0.14
      }
    ]
  }
}
```

Rows arrive ranked by `interestScore`. Re-sort client-side for other views: `notionalVol` for "most active" or `maxUnusualPremium` for the biggest unusual premium.

**Session highlights.** Each element of `highlights` and `etfHighlights` is one ticker's highlight contract from the latest completed session, and a ticker appears at most once. Under `ex0dte-v1` the highlight is the ticker's highest-premium qualifying contract (volume above 1,000 and above 3x prior open interest) whose expiry is at least one day after the session; same-day expiries are not eligible. It is chosen from every qualifying contract, so it can be a contract that is not among the ticker's five `unusual` contracts on `/summary`. A ticker whose only qualifying contracts expire the same day has no highlight. Highlights are ranked by `premiumPctl1y`, how that premium compares with the same ticker's `maxUnusualPremiumEx0dte` on each of its previous 252 sessions. While a ticker has fewer than 60 of those sessions, `premiumPctl1y` is omitted and the highlight ranks after every scored one, by premium.

| Field | Type | Description |
|-------|------|-------------|
| `ticker` | string | Underlying ticker |
| `contract` | string | OCC-style option symbol, same as an `unusual` contract on `/summary` |
| `type` | string | `call` or `put` |
| `strike`, `volOiRatio`, `premium` | number | Strike, volume/open-interest multiple, and estimated premium in USD |
| `expiry` | ISO date | Expiry date |
| `dte`, `volume`, `oi` | integer | Days to expiry, session volume, and open interest on the session's first pull |
| `premiumPctl1y` | number | Percentile (0-100) of `premium` among the ticker's `maxUnusualPremiumEx0dte` over its previous 252 sessions (omitted while fewer than 60 are available) |
| `oiPrior`, `oiNext`, `oiChange` | integer | The open-interest follow-up, same as on `/summary` |
| `oiConfirmation` | string | `opened`, `closed`, `mixed`, `pending` or `unmatched`, same as on `/summary` |
| `oiObservedAt` | integer | When the follow-up open interest was observed, UTC epoch seconds |
| `oiVintage` | string | `prior_settle`, `settled` or `next_session`, same as on `/summary` |
| `asOf` | ISO date | The session |
| `publishedAt` | ISO timestamp | When the build published it (UTC) |
| `session` | string | `completed`: an end-of-day session |

`premium` is volume times the closing mark times 100, an estimate at the mark rather than money paid, and end-of-day data does not identify the buyer or the seller of any contract.

---

## GET /stocks/{ticker}/options/summary

The latest options dossier for one stock or ETF: the aggregate for the returned `asOf` session, its percentile context, the open-interest wall structure with max pain, and the session's unusually-active contracts.

**Authentication:** API key required

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ticker` | path | Yes | Stock or ETF ticker (e.g. `NVDA`, `SPY`) |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/NVDA/options/summary"
```

ETFs use the same path. Index and sector ETFs are the usual macro-hedging reads:

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/SPY/options/summary"
```

**Response:** the `data` object is `null` when the ticker is outside the covered universe (see Coverage above; unknown tickers behave the same, and null responses never spend the dossier allowance) or has no snapshot yet, otherwise `{ asOf, sentiment, latest, context, oiWalls, unusual }`, plus the intraday session fields `intradayFlow`, `largePrintCount`, `largestPrintPctl` and `capabilities` when present (see Intraday session fields). FREE accounts receive ten covered full-summary calls per calendar month for listed tickers, then the headline preview described in the Access section; delisted tickers receive full summaries without spending that allowance.

A delisted symbol's summary is frozen at its last trading session; `asOf` shows that date.

| Field | Type | Description |
|-------|------|-------------|
| `data.listingStatus` | string | `"DELISTED"` when the ticker is classified as delisted; omitted otherwise |
| `data.delistedDate` | string | Delisting date (`YYYY-MM-DD`), present only for a ticker classified as delisted with a recorded date |

- **`latest`** (the daily aggregate for the `asOf` session): `date`, `callVol`, `putVol`, `callOi`, `putOi`, `pcVol`, `pcOi`, `vwIv`, `atmIv`, `skew25d`, `atmIv60`, `atmIv90`, `iv25c`, `iv25p`, `netDelta`, `notionalVol`, `contracts`, `maxUnusualPremium` (the session's largest unusual premium, 0 when none qualified), `maxUnusualPremiumEx0dte` (the premium of the session's highlight contract, the largest qualifying premium with an expiry at least one day after the session: 0 when the session was evaluated and none qualified, omitted when the session was not recorded under this rule), `unusualOi` (the open-interest follow-up for the session's unusual contracts and for its highlight contract when it is not one of them: an array with one `{ contract, oiPrior, oiNext, oiChange, oiConfirmation, oiObservedAt, oiVintage }` object per contract, present once it has been read; `oiNext` and `oiChange` are omitted on `unmatched` entries), plus the six expected-move fields `expectedMove1d`, `expectedMove5d`, `expectedMove20d`, `expectedMove1s1d`, `expectedMove1s5d` and `expectedMove1s20d` when `atmIv` is valid (see "How the expected move is computed" below). Ratio and IV fields are omitted when undefined (for example `pcVol` when call volume is 0). History follow-ups are matched by session date and contract ID. `unusualOi` can include a highlight beyond the five summary contracts; its entries contain OI metadata, so original volume, expiry and premium must be retained separately if needed.
- **`context`** (percentiles of `latest`): `pcVolPctl1y`, `pcVolPctl5y`, `pcOiPctl1y`, `ivRank1y`, `skewPctl1y`, `unusualPremiumPctl1y` (percentile of `maxUnusualPremium` in the trailing 1y window), `observations1y`. `pcVolPctl5y` uses the available observations within the trailing five-year window. A percentile requires a current reading and at least 60 usable observations in its window; IV rank is also omitted for a flat range.
- **`oiWalls`** (point-in-time, for the dossier expiry): `expiry`, `maxPain`, `callWalls`, `putWalls`. Each wall list holds up to 3 `{ strike, oi }` levels, descending by open interest.
- **`unusual`** (top 5 by premium): contracts whose volume far exceeds open interest (fresh positioning), each `{ contract, type, strike, expiry, dte, volume, oi, volOiRatio, premium, oiPrior, oiNext, oiChange, oiConfirmation, oiObservedAt, oiVintage }`. `contract` is the OCC-style option symbol. `oi` is the open interest on the session's first pull; the list is chosen from the first read of the session, against that level.
- **Open-interest follow-up** (on each `unusual` contract): `oiPrior` is OI on the session's first pull, `oiNext` is the next session's open interest, and `oiChange` is the difference. `oiConfirmation` is a string enum: `opened` when OI change is at least 50% of flagged-session volume, `closed` when at most -25%, `mixed` otherwise, `pending` before observation, or `unmatched` when the contract cannot be matched. `oiObservedAt` is the observation time in UTC epoch seconds; `oiVintage` is `prior_settle`, `settled` (a revised open-interest observation from the same session), or `next_session`. The labels describe net OI changes; buyer or seller not identified.

Abbreviated example: `maxUnusualPremium`, `maxUnusualPremiumEx0dte`, `unusualOi`, `unusualPremiumPctl1y` and the contract's `oiObservedAt` and `oiVintage` are omitted.

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "asOf": "2026-07-17",
    "sentiment": 0.21,
    "latest": {
      "date": "2026-07-17",
      "callVol": 1840000,
      "putVol": 1140000,
      "callOi": 9200000,
      "putOi": 7600000,
      "pcVol": 0.62,
      "pcOi": 0.83,
      "vwIv": 0.51,
      "atmIv": 0.53,
      "skew25d": 0.04,
      "atmIv60": 0.49,
      "atmIv90": 0.47,
      "iv25c": 0.50,
      "iv25p": 0.54,
      "expectedMove1d": 0.0494,
      "expectedMove5d": 0.1165,
      "expectedMove20d": 0.2329,
      "expectedMove1s1d": 0.0334,
      "expectedMove1s5d": 0.0747,
      "expectedMove1s20d": 0.1493,
      "netDelta": 128000000,
      "notionalVol": 4820000000,
      "contracts": 5120
    },
    "context": {
      "pcVolPctl1y": 18.0,
      "pcVolPctl5y": 22.0,
      "pcOiPctl1y": 44.0,
      "ivRank1y": 74.0,
      "skewPctl1y": 61.0,
      "observations1y": 214
    },
    "oiWalls": {
      "expiry": "2026-08-21",
      "maxPain": 175.0,
      "callWalls": [
        { "strike": 180.0, "oi": 96000 },
        { "strike": 190.0, "oi": 72000 }
      ],
      "putWalls": [
        { "strike": 170.0, "oi": 68000 },
        { "strike": 160.0, "oi": 54000 }
      ]
    },
    "unusual": [
      {
        "contract": "NVDA260821C00200000",
        "type": "call",
        "strike": 200.0,
        "expiry": "2026-08-21",
        "dte": 35,
        "volume": 41000,
        "oi": 6000,
        "volOiRatio": 6.83,
        "premium": 3820000,
        "oiPrior": 6000,
        "oiNext": 42000,
        "oiChange": 36000,
        "oiConfirmation": "opened"
      }
    ]
  }
}
```

Dual-class tickers resolve to their canonical symbol server-side, the same as the other research endpoints.

---

## How the expected move is computed

Six fields ride on the daily aggregate: `data.latest` of the dossier, every row of `/stocks/{ticker}/options/history`, and each row of both boards in `/options/overview`. All six are fractions of price (0.0407 = 4.07%) over 1, 5 and 20 trading sessions, 252 to the year.

`expectedMove1s1d`, `expectedMove1s5d` and `expectedMove1s20d` are the one-sigma expected move, `atmIv * sqrt(h / 252)`: the industry convention, with no calibration applied.

`expectedMove1d`, `expectedMove5d` and `expectedMove20d` are a calibrated 90% range, `k_h * atmIv * sqrt(h / 252)`. Each `k` is the 90th percentile of the realised h-session move divided by sigma_h, fit walk-forward on SentiSense's stored option history and refit yearly.

| Field | Sessions | Scale `k` |
|-------|----------|-----------|
| `expectedMove1d` | 1 | 1.48 |
| `expectedMove5d` | 5 | 1.56 |
| `expectedMove20d` | 20 | 1.56 |

All six expected-move fields are computed from finite, positive `atmIv`; they are omitted when that input is unavailable or invalid.

---

## Intraday session fields

During the trading session the SentiSense app runs an intraday options board, rebuilt every 15 minutes from 15-minute delayed chains, and after the close it publishes the session's large prints. On the API the intraday session is a count and a percentile; the contract-level board is in the app. The fields below carry no contract symbol, strike, price or premium amount, ride on `/stocks/{ticker}/options/summary` and `/options/overview` for every tier, and are omitted when their source is absent.

On `/stocks/{ticker}/options/summary`:

| Field | Type | Description |
|-------|------|-------------|
| `intradayFlow` | object | The ticker's row on today's intraday board (stock or ETF); omitted before the day's first board and when the ticker has no row |
| `largePrintCount` | integer | Number of published large-print events for this ticker (one contract's fills clustered into a single order after the close) in the latest available prints session; `0` when it has none. That session's date is not returned here and need not match the EOD snapshot or intraday timestamp |
| `largestPrintPctl` | number | Highest percentile (0-100) among those prints, each print's premium against the ticker's own trailing prints; omitted when none is scored |
| `capabilities` | object | Where the intraday board lives and who can open it |

Fields of `intradayFlow`:

| Field | Type | Description |
|-------|------|-------------|
| `unusualCount` | integer | Contracts on the ticker's row passing the unusual rule so far this session (the rule behind the end-of-day `unusual` list), up to 5 |
| `firstSeenEt` | string | Earliest board cycle that flagged one of them, `HH:mm ET`; omitted when none |
| `firstSeenAt` | integer | `firstSeenEt` as Unix epoch seconds |
| `flowPctl1y` | number | Percentile (0-100) of the ticker's intraday highlight contract, computed like `premiumPctl1y` on the session highlights; present only for highlighted tickers (up to 10 stocks and 10 ETFs), and omitted while the ticker's `maxUnusualPremiumEx0dte` history holds fewer than 60 sessions |
| `asOfEt` | string | The board's snapshot time, `HH:mm ET` |
| `asOf` | integer | The same instant as Unix epoch seconds (the dossier's own `asOf` is an ISO date) |
| `live` | boolean | `true` while the board updates during the session, `false` once it has stopped for the day |
| `delayMinutes` | integer | How far the board's chain data trails the market, in minutes |

On `/options/overview`, `intradayActiveCount` and `intradayRanking` (listed under GET /options/overview) summarize the same board across stocks. `intradayRanking` orders tickers by `flowPctl1y`, and by the size of each ticker's largest flagged contract where that percentile is absent.

`capabilities.intradayBoard` describes the board, not the data:

| Field | Type | Description |
|-------|------|-------------|
| `available` | boolean | `true` when the app offers the board |
| `access` | string | Who can open the board in the app: `signed_in_pro` (any signed-in PRO account) or `power_user` (an early-access group) |
| `apiData` | boolean | `false`: the board's rows are app-only, and the API serves only the fields in this section |
| `delayMinutes` | integer | The board's data delay, in minutes |
| `url` | string | The Options page in the app |

```json
{
  "intradayFlow": {
    "unusualCount": 3,
    "firstSeenEt": "10:15 ET",
    "firstSeenAt": 1784297700,
    "asOfEt": "14:30 ET",
    "asOf": 1784313000,
    "live": true,
    "delayMinutes": 15
  },
  "largePrintCount": 4,
  "largestPrintPctl": 88.0,
  "capabilities": {
    "intradayBoard": {
      "available": true,
      "access": "signed_in_pro",
      "apiData": false,
      "delayMinutes": 15,
      "url": "https://app.sentisense.ai/options"
    }
  }
}
```

---

## GET /stocks/{ticker}/options/history

The daily options-aggregate time series for one stock or ETF, ascending by date. Use it to chart how a reading has trended over time. Available options history: 2+ years (from July 2024), growing daily. `window=5y` requests the available history within that window; it does not guarantee five years of observations. FREE keys are served the `1y` window regardless of the requested value; PRO keys get the full depth.

**Authentication:** API key required

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `ticker` | path | Yes | - | Stock or ETF ticker |
| `window` | string | No | `1y` | `1y`, `2y`, or `5y`, capped at 252, 504 and 1,260 sessions; `5y` returns the available history within that window. Any other value clamps to `1y`. FREE keys always receive `1y` |

**Example Request:**

```bash
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/NVDA/options/history?window=1y"
```

**Response:** `data` is `{ ticker, window, series }`, where each element of `series` has the same shape as the `latest` aggregate above. An empty `series` means the ticker has no stored aggregates yet. Optional maxima and OI follow-ups appear when recorded for that session; otherwise they are omitted.

Abbreviated example: the second row shows a subset of fields.

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "ticker": "NVDA",
    "window": "1y",
    "series": [
      {
        "date": "2026-07-16",
        "callVol": 1760000,
        "putVol": 1090000,
        "callOi": 9100000,
        "putOi": 7500000,
        "pcVol": 0.62,
        "pcOi": 0.82,
        "vwIv": 0.50,
        "atmIv": 0.52,
        "skew25d": 0.04,
        "atmIv60": 0.48,
        "atmIv90": 0.46,
        "iv25c": 0.49,
        "iv25p": 0.53,
        "expectedMove1s1d": 0.0328,
        "expectedMove1s5d": 0.0732,
        "expectedMove1s20d": 0.1465,
        "expectedMove1d": 0.0485,
        "expectedMove5d": 0.1143,
        "expectedMove20d": 0.2285,
        "netDelta": 121000000,
        "notionalVol": 4510000000,
        "contracts": 5080
      },
      {
        "date": "2026-07-17",
        "callVol": 1840000,
        "putVol": 1140000,
        "pcVol": 0.62,
        "atmIv": 0.53,
        "skew25d": 0.041,
        "expectedMove1s1d": 0.0334,
        "expectedMove1s5d": 0.0747,
        "expectedMove1s20d": 0.1493,
        "expectedMove1d": 0.0494,
        "expectedMove5d": 0.1165,
        "expectedMove20d": 0.2329,
        "notionalVol": 4820000000,
        "contracts": 5120
      }
    ]
  }
}
```

---

## Errors

| Status | Code | Description |
|--------|------|-------------|
| 200 | - | Success. `data` is `null` for `/options/overview` before the first nightly build, and for `/summary` when the ticker is outside the covered universe, is unknown, or has no snapshot yet; `/history` returns an empty `series` when the ticker has no stored aggregates |
| 401 | api_key_required | No API key on the call |
| 401 | invalid_api_key | The key is invalid or revoked |
| 429 | rate_limit_exceeded | Per-minute rate limit exceeded (Free: 30/min, PRO: 300/min); `Retry-After: 60` |
| 429 | quota_exceeded | Monthly request quota exhausted (Free: 1,000/month; PRO has no monthly cap); no `Retry-After` |

Error bodies are `{ error, message }` without the success envelope; see "Access examples" above.

---

## Educational use only

The Options Intelligence API provides derived, end-of-day analytics for research and educational purposes. It is informational only. It is not investment advice, not a personalized recommendation, and not a solicitation to buy or sell any security or option. Expected-move ranges describe what the option chain implies today versus the ticker's own history; they carry no direction or target, and their historical coverage is a measured rate on SentiSense's stored history, not a promise about any future period. Options carry a high level of risk. You are responsible for your own decisions. Use of the API is subject to the [API Terms of Service](https://sentisense.ai/agreement/API-Terms-of-Service.pdf).
