Unusual options activity radar for US stocks and ETFs: end-of-day IV rank, implied volatility, options sentiment, put/call percentile, 25-delta skew, open-interest walls, and max pain, each ranked against the ticker's own trailing history. Use for unusual options activity, options flow scanner, IV rank, implied volatility, options sentiment, put/call ratio, max pain, open-interest walls, spotting where options positioning is stretched for a ticker. Read-only. No trading, no purchases, no write operations, no wallet access.
---
name: unusual-options-activity
description: "Unusual options activity radar for US stocks and ETFs: end-of-day IV rank, implied volatility, options sentiment, put/call percentile, 25-delta skew, open-interest walls, and max pain, each ranked against the ticker's own trailing history. Use for unusual options activity, options flow scanner, IV rank, implied volatility, options sentiment, put/call ratio, max pain, open-interest walls, spotting where options positioning is stretched for a ticker. Read-only. No trading, no purchases, no write operations, no wallet access."
homepage: https://sentisense.ai
requires:
env:
- SENTISENSE_API_KEY
primaryEnv: SENTISENSE_API_KEY
metadata:
openclaw:
requires:
env:
- SENTISENSE_API_KEY
primaryEnv: SENTISENSE_API_KEY
envVars:
- name: SENTISENSE_API_KEY
required: true
description: "SentiSense API key. Get one free at https://app.sentisense.ai/get-api-key. Used only to authenticate read-only data calls; no write or trading scope."
---
# Options Radar (SentiSense)
Read the options market for US stocks without pulling and cleaning a full option chain yourself. This skill turns each session's chain into a small set of end-of-day analytics through the read-only SentiSense API, and it ranks every reading against that stock's own trailing history rather than against other stocks. You get a market-wide radar of the most interesting names, and a per-stock dossier covering IV rank, put/call percentile, 25-delta skew, open-interest walls, max pain, and the session's unusually active contracts.
Read-only educational data interface. Output is informational context about how a chain looks today versus its own past, never a personalized buy or sell recommendation, and never a forecast.
## When to Use
Reach for this skill when the question is about options positioning or activity on a stock:
- "Any unusual options activity in $NVDA today?" (unusually active contracts, ranked by premium)
- "Where is implied volatility for this name relative to its own range?" (IV rank)
- "Is the options market leaning puts or calls here?" (put/call ratio and its percentile)
- "What is the downside skew on $TSLA?" (25-delta put-minus-call skew and its percentile)
- "Where are the open-interest walls and max pain?" (strike structure for the dossier expiry)
- "Which stocks have the most stretched options readings right now?" (the market-wide radar board)
This skill pairs naturally with `stock-sentiment`, `politicians-stock-tracker`, and `institutional-13f-tracker`: the strongest reads come from convergence. Rich call activity that lines up with climbing sentiment, a congressional buy, and institutional accumulation on the same ticker is a story; any one signal alone is noise.
Do not use it for order entry, portfolio management, greeks-based hedging, or personalized advice. It has no write, trading, or wallet surface; every endpoint is a GET.
## What this data actually is (read before interpreting)
Options data is easy to over-read. Four things to hold onto:
- **It is end-of-day, not real-time.** Each reading is the latest completed session, refreshed the next morning after the session settles. The `asOf` date is the prior trading day. This is not an intraday tape, so it does not classify sweeps or blocks and it does not stream live prints. "Unusually active contracts" means the session's volume ran far above standing open interest, which is a fresh-positioning signal, not a live order-flow feed.
- **Percentiles are the point, not the raw levels.** A put/call ratio of 0.9 or an IV of 45% means little on its own. Every reading is served next to its rank within the ticker's own trailing window (a percentile for put/call and skew, a min-max range position for IV rank), so "put/call volume at the 92nd percentile of its 1y range" is the actual signal: unusual *for this specific name*. Lead with the ranked context, not the raw number.
- **Coverage is two bounded universes.** Stocks: about a thousand of the most actively optioned US names (1,028 on 2026-09-05, and it moves as the universe is rebuilt), reported in the overview's `coverageCount`, which is the number to quote rather than any figure written here. The `rows` of `/options/overview` are the authoritative list. ETFs: the US ETFs SentiSense tracks (`GET /api/v1/etfs`) get the same coverage on the same `/stocks/{ticker}/options/...` paths, and on the radar they are a SEPARATE board, `etfRows`, never mixed into `rows`. `coverageCount` counts stocks only. Rank the two boards independently: every reading is a percentile of that ticker's own history, so an ETF's `interestScore` compares to other ETFs, not to a single stock. A ticker in neither universe returns `200` with `data: null` (summary) or an empty `series` (history). Treat a null as "not covered", not as an error.
- **Building baseline is not zero.** A covered ticker with too little history (roughly under 60 sessions) or below a liquidity floor returns its raw readings with the percentiles and `interestScore` omitted while its baseline accrues. Treat a missing percentile as "not enough history yet", never as a low reading.
## Prerequisites
- A free `SENTISENSE_API_KEY`. Get one at https://app.sentisense.ai/get-api-key. The key is required on every call; anonymous requests return `401 api_key_required`.
- Any HTTP client. Plain `curl` works, or Python 3.8+ using only the standard library (`urllib`, `json`); no third-party packages required. On macOS python.org installs can raise `CERTIFICATE_VERIFY_FAILED` (missing CA certs): run the bundled `Install Certificates.command`, use the system `/usr/bin/python3`, or use `curl`.
- Network access to `https://app.sentisense.ai`.
- Read-only scope. Every endpoint here is a GET. Nothing this skill does can place a trade, move money, or modify account state.
## Permissions
- Network: HTTPS to app.sentisense.ai only.
- Credentials: SENTISENSE_API_KEY from the environment.
- Shell: none required.
- Files: none.
| Tier | Request quota | Rate | Options data |
|------|---------------|------|--------------|
| Free | 1,000 requests/month | 30 requests/min | Radar: top 25 rows plus every market-pulse aggregate. Per-stock dossier: full detail for the first 10 calls each calendar month, then a headline-only preview. History: `1y` window. |
| PRO ($15/mo) | Unlimited | 300 requests/min | Full radar board, unlimited full dossiers, and up to `5y` history. |
The free tier exercises every workflow below on real data. Uncovered tickers that return `data: null` never spend the monthly dossier meter.
## How to Run
Issue HTTP GET requests to `https://app.sentisense.ai` and synthesize the JSON into a concise, sourced answer. Authenticate every request with the `X-SentiSense-API-Key` header; keep the key in the shell environment and never place it in a query string or in user-facing output.
Every endpoint returns the wrapped envelope `{ isPreview, previewReason, data }`. When `isPreview` is `true` (`previewReason: "PRO_REQUIRED"`), say so ("showing the free preview slice"). Null-valued fields are omitted from the JSON entirely, so check for field presence rather than comparing against `null`. Two distinct `429` responses exist: a per-minute `rate_limit_exceeded` includes a `Retry-After: 60` header, so wait that long before retrying; a monthly `quota_exceeded` carries no `Retry-After` header and does not clear until the next calendar month, so stop calling rather than retrying.
```python
import os, json, urllib.parse, urllib.request
API_ORIGIN = "https://app.sentisense.ai"
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def sentisense_api_url(path):
url = urllib.parse.urljoin(API_ORIGIN + "/", path)
parsed = urllib.parse.urlparse(url)
if (parsed.scheme != "https" or parsed.hostname != "app.sentisense.ai"
or parsed.netloc != "app.sentisense.ai"
or parsed.username is not None or parsed.password is not None
or parsed.port is not None):
raise ValueError("API URL must use https://app.sentisense.ai with no credentials or port")
return url
def get(path):
url = sentisense_api_url(path)
req = urllib.request.Request(
url,
headers={"X-SentiSense-API-Key": os.environ["SENTISENSE_API_KEY"]},
)
with urllib.request.build_opener(NoRedirect).open(req) as r:
return json.load(r)
board = get("/api/v1/options/overview")
data = board.get("data") # None before the first nightly build
rows = (data or {}).get("rows", [])
```
The REST recipe in this file is the primary path. A maintained command-line client is available as the separate `sentisense-cli` skill for hosts that prefer one.
**Company and fund names are not tickers.** When the user names the company or the fund ("unusual activity in tesla", "skew on the S&P 500 ETF") instead of typing a symbol, resolve it first: `GET /api/v1/kb/entities/search?q={name}&type=company&limit=5`, or `type=etf` for a fund (`SPY` resolves only under `etf`, never under `company`). The response is a bare array of `{name, urlSlug, type, ticker}`, best match first; take the first match with a non-null `ticker` (a tracked subsidiary can outrank its listed parent: "google" returns Google LLC with `ticker: null` before Alphabet `GOOGL`), ask a one-line clarification when several plausible matches carry tickers, and say so when the array is empty. Never uppercase the name into a symbol: `/stocks/TESLA/options/summary` answers `200` with `data: null`, which reads like an uncovered name when the real failure was the identifier. An exact ticker the user typed skips this step.
## Endpoints
- **`GET /api/v1/options/overview`** : the market-wide radar, one row per covered stock in `rows` plus the ETF board in `etfRows` (same row shape, omitted when a build has none), plus a few market-pulse aggregates (`asOf`, `medianIvRank`, `marketPcVol`, `extremeCount`, `coverageCount`). Rows arrive ranked by `interestScore` descending, so the top of the list is the most interesting names today; building-baseline rows sort last. Free keys receive the top 25 rows plus `totalCount`; PRO keys receive every row. Every row carries `ticker`, `name`, `sector`, `asOf`, `atmIv`, `skew25d`, `notionalVol`, `observations1y`, `unusualCount` and the expected-move set (`expectedMove1d`/`5d`/`20d` and their `1s` variants). **The rest are sparse, and the sparse ones are exactly the fields worth sorting on**, because a row only carries them when that reading exists for the ticker: on a full board of 1,028 rows measured 2026-09-05, `ivMove20` appeared on 1,020, `pcVol` on 950, `ivRank1y` and `skewPctl1y` on 943, `interestScore` on 870, `sentiment` and `pcVolPctl1y` on 865, `maxVolOiRatio` and `maxUnusualPremium` on **189**, and `wallSide` / `wallStrike` / `wallShare` on **31**. Since null-valued fields are omitted from the JSON entirely, a re-sort of the board by premium or by wall is ranking the 18% and the 3% of rows that have one, not the board. Treat an absent field as "no reading for this ticker", never as a zero or a low value: say how many rows carried it when you rank on one, and do not describe a wall board of 31 names as the market's heaviest walls.
- **`GET /api/v1/stocks/{ticker}/options/summary`** : the latest dossier for one stock. `data` is `null` for uncovered or unknown tickers (which never spend the dossier meter), otherwise `{ asOf, sentiment, latest, context, oiWalls, unusual }`. Free keys receive this full dossier for the first 10 calls each calendar month; after that, `data` is a headline-only preview of exactly `{ asOf, sentiment, ivRank1y, atmIv, pcVol, pcVolPctl1y, maxPain }` with `isPreview: true` until the monthly reset. `latest` is today's aggregate (volumes, open interest, `pcVol`/`pcOi`, `vwIv`, `atmIv` plus the `atmIv60`/`atmIv90` term structure, `iv25c`/`iv25p`, `skew25d`, `netDelta`, `notionalVol`, `contracts`). `context` holds `ivRank1y` (a min-max range position, 0-100) plus the percentile readings (`pcVolPctl1y`, `pcVolPctl5y`, `pcOiPctl1y`, `skewPctl1y`) and `observations1y`. `oiWalls` holds `expiry`, `maxPain`, and up to three `callWalls` / `putWalls` `{ strike, oi }` levels. `unusual` is the top 5 contracts by premium, each `{ contract, type, strike, expiry, dte, volume, oi, volOiRatio, premium }`.
- **`GET /api/v1/stocks/{ticker}/options/history`** : the daily aggregate time series, ascending by date, `{ ticker, window, series }`. Each `series` element has the same shape as `latest`. `window` is `1y` (default), `2y`, or `5y`; `5y` returns all stored history (currently about two years, backfilled from mid-2024). Free keys always receive `1y` regardless of the requested value; the response's `window` field reports what was actually served.
## Workflows
**1. Scan the radar for stretched names**
**Identify your client.** Send a `User-Agent` naming your agent runtime and this skill, for
example `OpenClaw/1.4 (unusual-options-activity)` or `ClaudeCode/2.1 (unusual-options-activity)`. Substitute your own runtime and
version if neither matches. You can also volunteer what your agent is called by adding an
`agent/<your-agent-name>` token inside the same parentheses, as in
`OpenClaw/1.4 (unusual-options-activity; agent/research-desk)`. All of it is optional, and it is what tells
us this skill has real integrations behind it, so it gets prioritized and you get notice before it
changes.
```bash
curl -s -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
"https://app.sentisense.ai/api/v1/options/overview"
```
Rows are pre-ranked by `interestScore`. Lead with the top few by that score, then re-sort client-side for a specific lens: `notionalVol` for "most active by premium", `abs(ivMove20)` for "biggest IV moves", or `pcVolPctl1y` for the most put-heavy names. Always report the percentile alongside the raw reading, and skip rows where `interestScore` is omitted (baseline still building).
**2. Read one stock's options dossier**
```bash
curl -s -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
"https://app.sentisense.ai/api/v1/stocks/NVDA/options/summary"
```
Summarize in percentile terms: where `atmIv` sits in its 1y range (`ivRank1y`), whether `pcVol` is high or low for this name (`pcVolPctl1y`), and which way `skew25d` leans (positive means puts bid richer than calls, a downside-demand tilt). Note `maxPain` and the nearest walls as context for the dossier expiry, not as targets. If `context` percentiles are missing, say the baseline is still building. If `isPreview` is `true` instead, the free monthly dossier meter is spent: only the headline fields are present, so summarize those and say the full dossier needs PRO or the next monthly reset, rather than reading the missing sections as a data gap.
**3. Spot unusually active contracts**
```bash
curl -s -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
"https://app.sentisense.ai/api/v1/stocks/TSLA/options/summary" | \
python3 -c "import sys,json; d=json.load(sys.stdin).get('data') or {}; print(json.dumps(d.get('unusual', []), indent=2))"
```
The `unusual` list is contracts whose session volume ran far above open interest (`volOiRatio`), ranked by dollar `premium`. A high ratio on a short-dated contract is often event-driven, so quote the `dte` and let the reader weigh it. This is end-of-day activity, so describe it as "unusually active in the last session", not as a live sweep.
**4. Chart how a reading has trended**
```bash
curl -s -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
"https://app.sentisense.ai/api/v1/stocks/AAPL/options/history?window=1y"
```
Pull `atmIv`, `pcVol`, or `skew25d` out of `series` to show the trend behind today's percentile. A free key always gets `1y`; the `window` field confirms what was served.
**5. Follow the convergence.** When rich call activity or a low put/call percentile lines up with climbing sentiment (`stock-sentiment`), a congressional buy (`politicians-stock-tracker`), or institutional accumulation (`institutional-13f-tracker`) on the same ticker in the same window, that agreement is the read worth surfacing. Say so explicitly, cite each source, and note when the dots disagree (for example, bullish flow against a put-heavy skew) rather than forcing a clean story.
## Answering well
- **Lead with the percentile.** "IV rank 74 (elevated for this name)" carries the signal; the bare 53% IV does not. Do the same for put/call and skew.
- **Say end-of-day.** Frame every reading as the latest completed session. Never imply real-time flow, live sweeps, or intraday order tape.
- **Do not overstate structure.** Max pain and open-interest walls are descriptive magnets and context, not predictions of where the stock will close. `netDelta` is the chain's aggregate net delta exposure (open-interest-weighted), not an inference about dealer books and not a gamma or hedging figure.
- **Respect the baseline.** If percentiles or `interestScore` are omitted, state that history is still accruing rather than reading it as a zero or a bearish signal.
- **Report only what the API returns.** Do not infer greeks, dealer gamma, or intentions the data does not contain, and do not frame any of it as advice. Options carry a high level of risk; this is derived analytics for education.
## Going further
Free covers every workflow above: the top of the radar, ten full dossiers a month, and a year of history. **PRO ($15/mo)** lifts the monthly request cap (no monthly limit, just a 300/min rate), returns the full radar board and unlimited full dossiers, and deepens history, plus sentiment, smart-money flows, insider detail, and AI insights across the rest of the SentiSense API. Apply coupon `AGENTS26` at checkout for a builder launch discount: https://app.sentisense.ai/pricing?coupon=AGENTS26
**ClawHub Skill:** [clawhub.ai/TheSentiTrader/unusual-options-activity](https://clawhub.ai/TheSentiTrader/unusual-options-activity)
---
*SentiSense is a read-only financial intelligence API. Options analytics here are derived, end-of-day, and for informational and educational purposes only, not investment advice. Options carry a high level of risk.*
don't have the plugin yet? install it then click "run inline in claude" again.
scan the options market for US stocks and ETFs without pulling and cleaning a full option chain yourself. this skill reads end-of-day options analytics through the sentisense api and ranks every metric against that specific ticker's own trailing history, not against the broader market. use it when the question is about options positioning, implied volatility, put/call activity, or where open interest is concentrated on a name. read-only educational interface only. no trading, no purchases, no write operations.
required environment variable
SENTISENSE_API_KEY: sentisense api key, free tier at https://app.sentisense.ai/get-api-key. required on every request via the X-SentiSense-API-Key header. never pass it in query strings or expose it in output. unauthenticated requests return 401 api_key_required.external connection
https://app.sentisense.ai. read-only GET endpoints only. coverage includes roughly 950 actively optioned US stocks and the US ETFs sentisense tracks. a ticker in neither universe returns 200 with data: null (summary endpoint) or an empty series (history endpoint).rate limits and quotas
1y.5y.429 responses: per-minute rate_limit_exceeded includes Retry-After: 60 header (wait and retry); monthly quota_exceeded has no Retry-After header and does not clear until next calendar month (stop calling).http client
urllib, json). macos python.org installs may raise CERTIFICATE_VERIFY_FAILED: run bundled Install Certificates.command, use system /usr/bin/python3, or use curl.step 1: authenticate and prepare the request
SENTISENSE_API_KEY from environment.X-SentiSense-API-Key header set to the api key value.example python:
import os, json, urllib.request
def get(path):
req = urllib.request.Request(
f"https://app.sentisense.ai{path}",
headers={"X-SentiSense-API-Key": os.environ["SENTISENSE_API_KEY"]},
)
with urllib.request.urlopen(req) as r:
return json.load(r)
step 2: scan the market-wide radar (workflow: stretched names)
GET /api/v1/options/overview.{ isPreview, data } where data contains:rows: array of covered stocks ranked by interestScore (descending). free tier receives top 25 rows; pro receives all rows.etfRows: same row schema, separate board for etfs, omitted if no etf data in this build.asOf: session date (prior trading day).medianIvRank, marketPcVol, extremeCount, coverageCount: market-pulse aggregates.ticker, name, sector, sentiment (range -1 to +1, options-implied), interestScore (0-100), pcVol / pcVolPctl1y, atmIv / ivRank1y, skew25d / skewPctl1y, notionalVol, ivMove20, observations1y, unusualCount, maxVolOiRatio, maxUnusualPremium, wallSide / wallStrike / wallShare (heaviest open-interest wall).interestScore as primary sort. re-sort client-side by notionalVol (most active by dollar premium), abs(ivMove20) (biggest iv swings), or pcVolPctl1y (put-heavy names). skip rows with missing interestScore (baseline still building). always report the percentile alongside raw readings.step 3: read one stock's options dossier (workflow: full snapshot)
NVDA, TSLA).GET /api/v1/stocks/{ticker}/options/summary.{ isPreview, isPreview, previewReason, data } where data is:null if ticker is uncovered or unknown (does not spend dossier meter).{ asOf, sentiment, latest, context, oiWalls, unusual } on first 10 calls per month (free tier) or always (pro tier).{ asOf, sentiment, ivRank1y, atmIv, pcVol, pcVolPctl1y, maxPain } with isPreview: true after 10 calls per month (free tier). say so: "showing the free preview slice. full dossier needs pro or next monthly reset."latest contains session aggregate: pcVol, pcOi, vwIv, atmIv, atmIv60 / atmIv90 (term structure), iv25c / iv25p, skew25d, netDelta, notionalVol, contracts (count), volume, openInterest.context contains ivRank1y (0-100 range position), percentiles pcVolPctl1y / pcVolPctl5y / pcOiPctl1y / skewPctl1y, and observations1y (session count).oiWalls contains expiry, maxPain, up to three callWalls and putWalls each with strike and oi.unusual is top 5 contracts by premium: each entry has contract, type (call/put), strike, expiry, dte, volume, oi, volOiRatio, premium.context percentiles are missing, say baseline is still building. note maxPain and walls as context for dossier expiry, not as price targets.step 4: spot unusually active contracts (workflow: contract activity)
GET /api/v1/stocks/{ticker}/options/summary (reuse from step 3).unusual array from data.unusual.premium (descending). report contract (e.g., "nvda 130c jan24"), dte, volume, volOiRatio, and premium. describe as "unusually active in the last session" (end-of-day, not live flow). a high volOiRatio on short-dated contract often signals event-driven positioning.step 5: chart the trend (workflow: history)
1y, 2y, 5y).GET /api/v1/stocks/{ticker}/options/history?window=1y (or 2y / 5y).{ ticker, window, series } where series is daily aggregate time series (ascending date). each element has same shape as latest from step 3.1y regardless of requested window. response window field confirms what was served. pull atmIv, pcVol, or skew25d out of series to show trend behind today's percentile reading.step 6: check response envelope and handle errors
{ isPreview, previewReason, data } envelope.data is null: ticker not covered (not an error). say "not covered" and do not read missing fields as gaps.isPreview: true and previewReason: "PRO_REQUIRED": free dossier meter spent. say so and suggest pro or next monthly reset.429 rate_limit_exceeded: check for Retry-After header. if present, wait that many seconds and retry. if absent, monthly quota exhausted; stop calling until next month.401: api key missing, invalid, or not set in environment. check SENTISENSE_API_KEY env var and header.4xx or 5xx other: log the full response and stop.step 7: synthesize the answer
maxPain and walls as descriptive context for the dossier expiry, not as predictions.netDelta as a dealer hedging signal or gamma inference. it is the chain's aggregate net delta exposure, open-interest-weighted, and nothing more.asOf date.if ticker is uncovered or unknown
GET /api/v1/stocks/{ticker}/options/summary returns 200 with data: null.if free tier dossier meter is spent (after 10 calls per calendar month)
GET /api/v1/stocks/{ticker}/options/summary returns 200 with isPreview: true, previewReason: "PRO_REQUIRED", and data contains only headline fields (asOf, sentiment, ivRank1y, atmIv, pcVol, pcVolPctl1y, maxPain).if per-minute rate limit is hit
429 with rate_limit_exceeded and Retry-After: 60 header (or other second count).Retry-After, then retry the request. do not retry immediately.if monthly quota is exhausted
429 with quota_exceeded and no Retry-After header.if baseline is still building (fewer than 60 sessions or below liquidity floor)
interestScore, and outlier flags.if api key is missing or invalid
401 with api_key_required or similar.SENTISENSE_API_KEY environment variable is set and non-empty. confirm the key is valid at https://app.sentisense.ai/get-api-key. do not proceed without valid authentication.if network timeout or connection failure
if history window exceeds tier capability
window=2y or 5y in GET /api/v1/stocks/{ticker}/options/history?window=....1y to free tier. response window field confirms. do not attempt to request longer windows on free; pro tier lifts the cap.if etf board is empty (no etf data in the build)
GET /api/v1/options/overview returns data.etfRows omitted or null.if comparing across two separate boards (stocks vs etfs)
interestScore and etf interestScore are computed within their own universes.interestScore of 45 directly against an etf's 45; each is a percentile of its own peer set. if reporting top movers, separate the two boards in the output.if a raw reading contradicts percentile context (e.g., high iv but low iv rank)
radar overview (step 2)
200.{ isPreview: false, data: { rows: [...], etfRows: [...] (optional), asOf: "YYYY-MM-DD", medianIvRank, marketPcVol, extremeCount, coverageCount } }.rows capped at top 25; pro tier: full list.interestScore. include ticker, sector, pcVolPctl1y, ivRank1y, skewPctl1y, and interestScore for each row. skip rows with missing interestScore. cite asOf date.stock dossier (step 3)
200.{ isPreview: false, data: { asOf, sentiment, latest: {...}, context: {...}, oiWalls: {...}, unusual: [...] } }.{ isPreview: true, previewReason: "PRO_REQUIRED", data: { asOf, sentiment, ivRank1y, atmIv, pcVol, pcVolPctl1y, maxPain } }.{ isPreview: false, data: null }.history time series (step 5)
200.{ data: { ticker, window: "1y" (or "2y" / "5y" served), series: [ { asOf, atmIv, pcVol, skew25d, ... }, ... ] } }.window: "1y".window parameter and whether the free tier capped it.error responses
401: { error: "api_key_required" } or similar. output: "api key missing or invalid. set SENTISENSE_API_KEY environment variable."429 rate_limit_exceeded: includes Retry-After header. output: "rate limited. wait {retry_after} seconds and try again."429 quota_exceeded: no Retry-After header. output: "monthly quota exhausted. no more requests until next calendar month."null data (uncovered ticker): output: "ticker not in sentisense coverage."success indicator
200 ok with a valid json envelope. data key is present and either contains the requested object or is explicitly null. no exceptions raised.the user knows the skill worked when:
interestScore descending. the user sees names, sectors, iv ranks, put/call percentiles, and skew context alongside the raw readings, all cited to a specific asOf date. if the free tier is active, the user sees exactly the top 25