# 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_...`.
<!--
  KEEP IN SYNC: the frontmatter `endpoints:` list above must stay aligned with the
  `## GET /...` (or POST/PUT/DELETE) section headings in this file:
    - SAME ORDER: lib/docs.js stamps each ep.id onto the next method-prefixed heading
      in document order. If the orders drift, sidebar clicks scroll to the wrong section.
    - SAME COUNT: bump `endpointCount` and add/remove entries in `endpoints:` whenever
      you add or remove a section here.
    - When you change paths, update the skill source at `skills/src/sentisense/body.md` and run
      `cd skills && npm run deploy:website` to regenerate `public/skill.md`.

  Note: this page spans two base paths. The market radar lives at /api/v1/options,
  the per-stock endpoints at /api/v1/stocks/{ticker}/options, so baseUrl is /api/v1
  and each heading carries the remainder of the path.
-->

## Overview

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.

**The product thesis is percentile-first.** A raw implied-volatility level or put/call ratio means little on its own. Every reading is served alongside its percentile within the ticker's trailing window, so "put/call volume at the 92nd percentile of its 1y range" tells you the chain is unusual for this specific name. Readings are descriptive of what the chain looks like today 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) |
| 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` (premium traded), `contracts` (rows in the chain) |
| Structure (snapshot only) | `oiWalls` (open-interest walls + `maxPain`), `unusual` (unusually-active contracts) |

**Freshness:** this is end-of-day data, latest session, not real-time. The `asOf` date is the prior trading day, and the analytics refresh each morning after the session closes.

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

- **Stocks:** a bounded universe of the most actively optioned US stocks, roughly 950 names in the latest build, expanding over time. The `rows` of `/options/overview` are the authoritative list, and the overview's `coverageCount` reports its exact size.
- **ETFs:** the US ETFs SentiSense tracks (enumerate them with `GET /etfs`) get the same coverage, 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 in neither universe returns `200` with `data: null` from `/summary` and an empty `series` from `/history`, and an unknown ticker behaves the same way, so treat a null as "not covered", not as an error.

**Building baseline:** separately, a covered ticker that has not yet accumulated enough sessions (roughly 60) or cleared a liquidity floor returns its raw readings with omitted percentiles and no `interestScore` while its baseline builds. Treat a missing percentile as "not enough history yet", not as a zero reading.

**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. On top of that, the options data itself is tiered by key:

- **PRO keys** always get the full response from every endpoint.
- **FREE keys** get a working taster of each endpoint:
  - `/options/overview` returns the **top 25 ranked rows 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` returns the **full dossier for the first 10 calls each calendar month** (the "Options Dossier Views" quota, monthly reset). After that it returns a headline-only preview (`asOf`, `sentiment`, `ivRank1y`, `atmIv`, `pcVol`, `pcVolPctl1y`, `maxPain`) until the reset. Calls that return `data: null` (uncovered tickers) never spend the meter.
  - `/stocks/{ticker}/options/history` always serves `window=1y` (the response's `window` field reports what was served).
- Tiered responses use the standard wrapper: a previewed body carries `isPreview: true` and `previewReason: "PRO_REQUIRED"`; 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.)

Null-valued fields are omitted from the JSON entirely, so check for field presence rather than comparing against `null`.

---

## 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 building-baseline rows last), so the top of the list is the most interesting names today. 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 is the macro tape: index, sector and commodity funds. Read it from the same response:

```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 |
| `medianIvRank` | number | Median IV rank (0-100) across covered **stocks** |
| `marketPcVol` | number | Median put/call volume ratio across covered **stocks** |
| `extremeCount` | integer | How many covered **stocks** show at least one extreme reading |
| `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 | How many covered **ETFs** show at least one extreme reading |
| `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 |

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 0-100 blend of how extreme the row's readings are (omitted while the baseline builds) |
| `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 | Premium traded today (sum of volume times mark times 100) |
| `ivMove20` | number | Signed change of `atmIv` vs its ~20-session mean; rank a "biggest IV moves" view by its absolute value |
| `observations1y` | integer | Trailing-1y observation count (drives the building-baseline state) |
| `unusualCount` | integer | Unusually-active contracts this session |
| `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) |

```json
{
  "isPreview": false,
  "previewReason": null,
  "data": {
    "asOf": "2026-07-17",
    "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,
        "observations1y": 214,
        "unusualCount": 12,
        "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.

---

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

The latest options dossier for one stock or ETF: today's aggregate, 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, and they are only reachable this way since the Radar board is stocks-only:

```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 meter) or has no snapshot yet, otherwise `{ asOf, sentiment, latest, context, oiWalls, unusual }`. FREE keys receive this full dossier for the first 10 calls each month, then the headline-only preview described in the Access section.

- **`latest`** (today's daily aggregate): `date`, `callVol`, `putVol`, `callOi`, `putOi`, `pcVol`, `pcOi`, `vwIv`, `atmIv`, `skew25d`, `atmIv60`, `atmIv90`, `iv25c`, `iv25p`, `netDelta`, `notionalVol`, `contracts`. Ratio and IV fields are omitted when undefined (for example `pcVol` when call volume is 0).
- **`context`** (percentiles of `latest`): `pcVolPctl1y`, `pcVolPctl5y`, `pcOiPctl1y`, `ivRank1y`, `skewPctl1y`, `observations1y`. Any percentile whose trailing window has too few observations is omitted (building baseline).
- **`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 }`. `contract` is the OCC-style option symbol.

```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,
      "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
      }
    ]
  }
}
```

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

---

## 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. History is backfilled from mid-2024, so `5y` currently returns roughly two years of sessions and deepens as more history accrues. 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` (`5y` returns all stored history, currently about two years). 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.

```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,
        "netDelta": 121000000,
        "notionalVol": 4510000000,
        "contracts": 5080
      },
      {
        "date": "2026-07-17",
        "callVol": 1840000,
        "putVol": 1140000,
        "pcVol": 0.62,
        "atmIv": 0.53,
        "skew25d": 0.041,
        "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 |
| 429 | rate_limit_exceeded | Per-minute rate limit exceeded (Free: 30/min, PRO: 300/min) |
| 429 | quota_exceeded | Monthly request quota exhausted (Free: 1,000/month; PRO has no monthly cap) |

---

## 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. 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).
