This is the abridged developer documentation for Tapetide API # Indian market data for code and agents > Quotes, financial statements, price history, shareholding, options analytics, indices and market flows for about 8,200 NSE and BSE stocks. Call it over REST, or connect an AI assistant over MCP with the same token. ## What you get [Section titled “What you get”](#what-you-get) * **One contract.** Every success is `{ data, meta }`, every error is `{ error: { code, message } }`. Collections add `links.next` and an opaque cursor. * **Complete documents.** Nothing is trimmed to fit. Routes that page say so in `meta`. * **Honest values.** A missing figure is `null`, never `0`. Dates are IST trading dates. * **Point-in-time fields.** Financial periods carry the date they became public, so a backtest can avoid look-ahead. * **Agent ready.** An MCP server, `llms.txt`, and a Markdown twin of every page on this site. [Quickstart](/quickstart/)Create a token and call a real endpoint with curl, Python or JavaScript. [Authentication](/authentication/)Personal tokens, access tokens and the shared MCP quota. [Pagination](/pagination/)Cursor paging on collections and end_date paging on price history. [Use Tapetide from an agent](/guides/agents/)MCP setup for Claude, ChatGPT, Cursor and more, or REST from your own agent. Machine-readable contract: [`openapi.json`](https://api.tapetide.com/v1/openapi.json) (OpenAPI 3.1). Data is for information only and is not investment advice. Tapetide is not a SEBI-registered investment adviser or research analyst. # Quickstart > Create a token and make your first Tapetide API request with curl, Python or JavaScript. 1. **Create a token.** Sign in at [tapetide.com/settings](https://tapetide.com/settings) and create a personal token. It starts with `tpt_rt_`. Keep it out of source control. ```bash export TAPETIDE_TOKEN=tpt_rt_... ``` 2. **Check your quota.** `/v1/me/usage` is free, so it is a safe first call. ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" https://api.tapetide.com/v1/me/usage ``` 3. **Fetch a quote.** This costs one request unit. * curl ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" \ https://api.tapetide.com/v1/stocks/TCS/quote ``` * Python ```python import os import requests resp = requests.get( "https://api.tapetide.com/v1/stocks/TCS/quote", headers={"Authorization": f"Bearer {os.environ['TAPETIDE_TOKEN']}"}, timeout=30, ) resp.raise_for_status() quote = resp.json()["data"] print(quote["symbol"], quote["price"], quote["change_pct"]) ``` * JavaScript ```js const res = await fetch("https://api.tapetide.com/v1/stocks/TCS/quote", { headers: { Authorization: `Bearer ${process.env.TAPETIDE_TOKEN}` }, }); if (!res.ok) { const { error } = await res.json(); throw new Error(`${res.status} ${error.code}: ${error.message}`); } const { data, meta } = await res.json(); console.log(data.symbol, data.price, meta.as_of); ``` 4. **Read the response.** Every success has the same envelope (abridged here). ```json { "data": { "symbol": "TCS", "name": "Tata Consultancy Services Ltd", "price": 2427, "change": 77, "change_pct": 3.28, "prev_close": 2350, "volume": 5532201, "market_cap": 878109.84, "pe_ttm": 17.63, "high_52w": 3350, "low_52w": 1976.8, "updated_at": "2026-10-16T05:57:16+00:00" }, "meta": { "as_of": "2026-10-16T06:01:12.418Z", "route": "getStockQuote" } } ``` `market_cap` is in INR crore. `meta.as_of` is when the response was assembled; `data.updated_at` is when the price was observed. ## Next requests to try [Section titled “Next requests to try”](#next-requests-to-try) ```bash # Find a symbol by name, BSE code or ISIN curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" "https://api.tapetide.com/v1/stocks?q=hdfc%20bank" # Quarterly P&L with the date each quarter became public curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" "https://api.tapetide.com/v1/stocks/TCS/financials?section=quarterly" # 30 days of daily OHLCV curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" "https://api.tapetide.com/v1/stocks/RELIANCE/price?days=30" # Up to 20 quotes in one call curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" "https://api.tapetide.com/v1/quotes?symbols=RELIANCE,TCS,HDFCBANK" ``` Every operation, with parameters and response schemas, is in the [API reference](/api/). To try requests in the browser, open the [playground](/playground/). # Authentication > Bearer tokens for the Tapetide API, how to create them, and how they relate to the MCP server. Send a token on every request: ```http GET /v1/stocks/TCS/quote HTTP/1.1 Host: api.tapetide.com Authorization: Bearer tpt_rt_... ``` ## Token types [Section titled “Token types”](#token-types) | Token | Prefix | Where it comes from | Use it for | | -------------- | --------- | ------------------------------------------------------------------------ | ------------------------------------------ | | Personal token | `tpt_rt_` | Created by you at [tapetide.com/settings](https://tapetide.com/settings) | Scripts, servers, notebooks, agents | | Access token | `tpt.` | Issued by Tapetide sign-in (OAuth) to an MCP client | Short-lived; you rarely handle it directly | Both are accepted by the REST API and the MCP server. A personal token works until you revoke it in settings. ## One account, one quota [Section titled “One account, one quota”](#one-account-one-quota) REST and MCP share the same account and the same quota. A REST request costs exactly what an MCP tool call costs, and a token created for one works on the other. See [Rate limits and quota](/rate-limits/). ## Failures [Section titled “Failures”](#failures) | Status | `error.code` | Meaning | | ------ | ------------------------- | ----------------------------------------------------------------------- | | 401 | `authentication_required` | No `Authorization: Bearer` header was sent. | | 401 | `invalid_token` | The token is unknown, expired or revoked. Create a new one in settings. | 401 responses carry `WWW-Authenticate: Bearer realm="tapetide"` and are never charged. ## Keeping tokens safe [Section titled “Keeping tokens safe”](#keeping-tokens-safe) * Store tokens in environment variables or a secret manager, not in code or notebooks you share. * Use one token per application so you can revoke one without breaking the others. * Browser code exposes its token to anyone who opens dev tools. Call the API from a server you control, or use the [playground](/playground/) for experiments. # Pagination > How Tapetide collections page with an opaque cursor, and how price history pages with end_date. Tapetide never truncates a response to fit. When a result is larger than one page, the response says so and gives you the exact value to ask for the next page. ## Cursor pagination (collections) [Section titled “Cursor pagination (collections)”](#cursor-pagination-collections) Collection endpoints return an array in `data`, paging fields in `meta`, and a ready-made next URL in `links`: ```json { "data": [ { "date": "2026-10-20", "name": "Diwali Laxmi Pujan", "exchange": "NSE", "type": "FULL" } ], "meta": { "as_of": "2026-10-16T09:30:00.000Z", "route": "listMarketHolidays", "count": 1, "limit": 100, "has_more": true, "next_cursor": "eyJkIjoiMjAyNi0xMC0yMCJ9" }, "links": { "next": "/v1/market/holidays?from=2026-01-01&cursor=eyJkIjoiMjAyNi0xMC0yMCJ9" } } ``` | Field | Meaning | | ------------------ | -------------------------------------------------------------------------------------------------------------- | | `meta.count` | Items in this page. | | `meta.limit` | The page size that was applied. | | `meta.has_more` | `true` when another page exists. | | `meta.next_cursor` | Opaque string for the next page, or `null` on the last page. | | `links.next` | Path and query for the next page with your filters kept, or `null`. Prefix it with `https://api.tapetide.com`. | ### The rules [Section titled “The rules”](#the-rules) * **`limit`** defaults per endpoint (shown in the reference) and has a per-endpoint maximum. A value above the maximum is rejected with `400 invalid_request`. It is never silently clamped. * **`cursor`** is opaque. Pass `meta.next_cursor` back unchanged, with the same filters. A cursor the API did not issue returns `400 invalid_request` with `details.param = "cursor"`. * Paging is keyset, not offset, so pages stay stable while new rows arrive at the other end of the range. * Each page is one request and costs one unit. * Unknown query parameters are rejected with a `400` that lists the accepted ones, so a typo never silently returns unfiltered data. ### Follow every page [Section titled “Follow every page”](#follow-every-page) ```python import os, requests BASE = "https://api.tapetide.com" s = requests.Session() s.headers["Authorization"] = f"Bearer {os.environ['TAPETIDE_TOKEN']}" url = "/v1/market/holidays?from=2020-01-01&limit=500" rows = [] while url: body = s.get(BASE + url, timeout=30).json() rows.extend(body["data"]) url = body["links"]["next"] print(len(rows)) ``` ```js let url = "/v1/market/holidays?from=2020-01-01&limit=500"; const rows = []; while (url) { const res = await fetch(`https://api.tapetide.com${url}`, { headers: { Authorization: `Bearer ${process.env.TAPETIDE_TOKEN}` }, }); const body = await res.json(); rows.push(...body.data); url = body.links.next; } ``` ## Window pagination (price history) [Section titled “Window pagination (price history)”](#window-pagination-price-history) `/v1/stocks/{symbol}/price` serves at most 2,000 calendar days per call. It reports the window it actually served in `meta.window`: ```json "window": { "requested_days": 2000, "served_days": 2000, "max_days": 2000, "truncated": false, "first_date": "2021-04-26", "last_date": "2026-10-14", "has_more": true, "next_cursor": "2021-04-25" } ``` To page further back, pass `meta.window.next_cursor` as `end_date` on the next call and repeat until `has_more` is `false`. Always read `truncated`: when it is `true` you asked for more days than one call serves. ## Bounded lists without a cursor [Section titled “Bounded lists without a cursor”](#bounded-lists-without-a-cursor) Some older routes (for example `/v1/stocks/{symbol}/events`, `/deals`, `/v1/market/news`) return the most recent N items with a `limit` parameter and no cursor. The reference states the default and maximum for each. # Errors > The Tapetide API error envelope, every error code, and which errors are charged. Every error has the same shape and an HTTP status that matches it: ```json { "error": { "code": "invalid_request", "message": "\"limit\" number must be less than or equal to 500.", "details": { "param": "limit" } } } ``` * `code` is stable. Branch on it, not on `message`. * `message` is for people and may change wording. * `details` is optional. For parameter errors it names the offending `param`. ## Error codes [Section titled “Error codes”](#error-codes) | Status | `code` | When | What to do | | ------ | ------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | 400 | `invalid_request` | A parameter is unknown, malformed, out of range, or a cursor was not issued by the API. | Fix the parameter named in `details.param`. | | 401 | `authentication_required` | No bearer token was sent. | Send `Authorization: Bearer `. | | 401 | `invalid_token` | The token is invalid, expired or revoked. | Create a new token at [tapetide.com/settings](https://tapetide.com/settings). | | 404 | `not_found` | The symbol, index or document does not exist. | Look the symbol up with `GET /v1/stocks?q=`. | | 404 | `route_not_found` | No such path. | Check the path against the [reference](/api/). | | 405 | `method_not_allowed` | A method other than `GET` (or `OPTIONS`). | The API is read-only. | | 429 | `rate_limited` | Per-minute burst cap reached. | Wait `Retry-After` seconds. | | 429 | `quota_exhausted` | Your plan’s daily or monthly quota is used. | Wait until `X-RateLimit-Reset`, or upgrade. | | 500 | `tool_error` | The request reached a dataset that could not answer it. | Retry once; report it if it persists. | | 500 | `internal_error` | An unexpected failure. | Retry with backoff. | | 502 | `upstream_unavailable` | A data dependency was briefly unavailable. | Retry with backoff. | | 503 | `service_unavailable` | Tapetide is briefly unavailable. | Wait `Retry-After` seconds. | ## Charging [Section titled “Charging”](#charging) Only a `200` costs a request unit. Every error above is free, including `429`. ## Retrying [Section titled “Retrying”](#retrying) Retry `429`, `500`, `502` and `503`. Do not retry `400`, `401`, `404` or `405` without changing the request. ```python import time, requests def get(session, url, attempts=4): for i in range(attempts): r = session.get(url, timeout=30) if r.status_code not in (429, 500, 502, 503): return r wait = int(r.headers.get("Retry-After", 2 ** i)) if r.status_code == 429 and r.json()["error"]["code"] == "quota_exhausted": return r # waiting will not help within this run time.sleep(min(wait, 60)) return r ``` # Data conventions > Dates, units, nulls and point-in-time fields in Tapetide API responses. These rules hold across every endpoint unless the reference for a field says otherwise. ## Dates and times [Section titled “Dates and times”](#dates-and-times) | Kind | Format | Example | | ---------------------- | -------------------------------------------------- | ---------------------- | | Trading date | `YYYY-MM-DD`, the IST calendar date of the session | `2026-10-14` | | Timestamp | ISO-8601 in UTC | `2026-10-16T05:57:16Z` | | Month | `YYYY-MM`, IST | `2026-10` | | Financial period label | Month and year the period ends | `Jun 2026` | `meta.as_of` is when the response was assembled. A field like `updated_at` or `snapshot_ts` is when the underlying value was observed. Quote both when freshness matters. ## Units [Section titled “Units”](#units) | Quantity | Unit | | ------------------------------------------- | ----------------------------------------------------------------------- | | Prices, values, P\&L | INR, as numbers | | Market capitalisation and statement figures | INR crore unless a field says otherwise | | Percentages | Numbers where `1.5` means 1.5% (`change_pct`, `iv_pct`, `delivery_pct`) | | Volume, open interest | Shares or contracts, as integers | ## Null is not zero [Section titled “Null is not zero”](#null-is-not-zero) A missing value is `null`. It means the figure is not published or not available for that row. It is never a stand-in for `0`. Treat `null` as unknown: exclude it from averages, do not plot it as zero, and do not compute a return across it. ## Symbols [Section titled “Symbols”](#symbols) * Paths take NSE symbols such as `RELIANCE`, `TCS`, `HDFCBANK`. Search by name, BSE code or ISIN with `GET /v1/stocks?q=`. * Percent-encode `&`: `M&M` is `/v1/stocks/M%26M/quote`. * Responses carry `symbol` and, where relevant, `isin`. Internal ids are not part of the contract. ## Point-in-time fields [Section titled “Point-in-time fields”](#point-in-time-fields) Historical research must use only what was known on each date. Tapetide exposes the fields that make that possible: * **Financial statements** carry an `availability` array per section: each period’s `available_from` date and a `basis` of `reported` (the actual result-declaration date) or `estimated` (a regulatory-lag estimate when the declaration date is unknown). The values are the latest reported figures and can include later restatements. * **Price history** `close` is adjusted for splits and bonuses, not for dividends. `raw_close` is the unadjusted print where available, and `null` where it is not. Never substitute `close` for a missing `raw_close`: the two are on different scales. * **Option chains** are end-of-day snapshots stamped with `snapshot_ts`. See [Build a backtest with point-in-time data](/guides/backtest-point-in-time/). ## Field names [Section titled “Field names”](#field-names) `snake_case`, plural kebab-case paths, and no abbreviations beyond the market’s own (`pe_ttm`, `oi`, `iv`). # Agents: use Tapetide via MCP or REST > Connect Claude, ChatGPT, Cursor and other AI clients to Tapetide over MCP, or call the REST API from your own agent. Tapetide has two front doors to the same data, account and quota. | | MCP server | REST API | | -------- | ------------------------------------------------ | --------------------------------------------- | | Endpoint | `https://mcp.tapetide.com/mcp` (Streamable HTTP) | `https://api.tapetide.com/v1` | | Best for | Chat apps and coding assistants that speak MCP | Your own code, pipelines and custom agents | | Auth | Google sign-in (OAuth) or a personal token | Personal token | | Shape | 55 tools with LLM-oriented descriptions | Typed JSON documents described by OpenAPI 3.1 | Calls on either side count against one quota: 50 successful calls a day on the free plan. ## Connect an MCP client [Section titled “Connect an MCP client”](#connect-an-mcp-client) Most clients only need the server URL, then a Google sign-in when the browser opens. **Claude Code** ```bash claude mcp add --transport http tapetide https://mcp.tapetide.com/mcp ``` Then open `/mcp` in Claude Code, select Tapetide and sign in. **Cursor** (`.cursor/mcp.json`) ```json { "mcpServers": { "tapetide": { "url": "https://mcp.tapetide.com/mcp" } } } ``` **Codex** ```bash codex mcp add tapetide --url https://mcp.tapetide.com/mcp codex mcp login tapetide ``` **Claude.ai and ChatGPT**: add a custom connector named Tapetide with the URL `https://mcp.tapetide.com/mcp` and sign in with Google. **Clients that cannot open a browser** send a personal token instead: `Authorization: Bearer tpt_rt_...`. Claude Desktop uses the local helper: ```json { "mcpServers": { "tapetide": { "command": "bunx", "args": ["tapetide-mcp"], "env": { "TAPETIDE_TOKEN": "YOUR_TOKEN" } } } } ``` Step-by-step pages for each client are canonical on tapetide.com: * Chat apps: [Claude.ai](https://tapetide.com/mcp/claude-ai), [ChatGPT](https://tapetide.com/mcp/chatgpt), [Grok](https://tapetide.com/mcp/grok), [Claude Desktop](https://tapetide.com/mcp/claude-desktop) * Editors: [Cursor](https://tapetide.com/mcp/cursor), [VS Code](https://tapetide.com/mcp/vscode), [Windsurf](https://tapetide.com/mcp/windsurf), [Zed](https://tapetide.com/mcp/zed), [Kiro](https://tapetide.com/mcp/kiro), [Antigravity](https://tapetide.com/mcp/antigravity) * Agents and CLIs: [Claude Code](https://tapetide.com/mcp/claude-code), [Codex](https://tapetide.com/mcp/codex), [Gemini CLI](https://tapetide.com/mcp/gemini-cli), [Cline](https://tapetide.com/mcp/cline), [OpenCode](https://tapetide.com/mcp/opencode), [Command Code](https://tapetide.com/mcp/commandcode) Confirm the connection with a prompt such as “Use Tapetide to show Reliance’s latest price and when it was updated.” The answer should include a timestamp. Full MCP tool reference: [tapetide.com/mcp/llms-full.txt](https://tapetide.com/mcp/llms-full.txt). For when to pick which, see [MCP vs API for Indian stock data](https://tapetide.com/mcp/guides/mcp-vs-api-indian-stock-data). ## Give your own agent the REST API [Section titled “Give your own agent the REST API”](#give-your-own-agent-the-rest-api) If you are building the agent, REST is usually simpler to control and test: 1. Load the OpenAPI document from `https://api.tapetide.com/v1/openapi.json` and turn operations into tools. Most agent frameworks accept OpenAPI directly. 2. Point the model at [`/llms.txt`](/llms.txt) or [`/llms-full.txt`](/llms-full.txt) for the conventions: units, nulls, paging. 3. Put the token in the HTTP client, never in the prompt. 4. Have the agent quote `meta.as_of` and any observation timestamp (`updated_at`, `snapshot_ts`) with numbers it reports. A minimal tool for an agent loop: ```python def tapetide_get(path: str, **params) -> dict: """GET a Tapetide API path such as /v1/stocks/TCS/quote. Returns the JSON body.""" r = session.get(f"https://api.tapetide.com{path}", params=params, timeout=30) return r.json() # errors come back as {"error": {"code", "message"}} for the model to read ``` Returning error bodies to the model works well: messages name the bad parameter and list accepted values. # Build a backtest with point-in-time data > Assemble prices and fundamentals for a backtest without look-ahead bias, using Tapetide's availability dates and adjusted price series. A backtest is only as honest as its inputs. The common failure is look-ahead: using a quarterly result on the day the quarter ended, weeks before the company published it. This guide builds a small daily panel of prices and trailing earnings for one stock where every value was public on its date. ## 1. Pull the full price history [Section titled “1. Pull the full price history”](#1-pull-the-full-price-history) Price history pages backwards with `end_date`. Each call serves up to 2,000 calendar days. ```python import os, requests, pandas as pd BASE = "https://api.tapetide.com/v1" s = requests.Session() s.headers["Authorization"] = f"Bearer {os.environ['TAPETIDE_TOKEN']}" def price_history(symbol): rows, end = [], None while True: params = {"days": 2000, **({"end_date": end} if end else {})} body = s.get(f"{BASE}/stocks/{symbol}/price", params=params, timeout=60).json() rows.extend(body["data"]) win = body["meta"]["window"] if not win["has_more"]: break end = win["next_cursor"] df = pd.DataFrame(rows).drop_duplicates("date") df["date"] = pd.to_datetime(df["date"]) return df.sort_values("date").set_index("date") px = price_history("TCS") ``` `close` is adjusted for splits and bonuses, so returns computed from it are continuous across corporate actions. It is not dividend-adjusted: add dividends separately if you need total return. `raw_close` is the unadjusted print, `null` where unavailable. ## 2. Pull fundamentals with their availability dates [Section titled “2. Pull fundamentals with their availability dates”](#2-pull-fundamentals-with-their-availability-dates) ```python fin = s.get(f"{BASE}/stocks/TCS/financials", params={"section": "quarterly"}, timeout=60).json() q = fin["data"][0] if isinstance(fin["data"], list) else fin["data"] eps = pd.Series(q["data"]["EPS in Rs"]) # {"Jun 2026": 36.9, ...} avail = {a["period"]: a["available_from"] for a in q["availability"]} eps_pit = pd.DataFrame({ "eps": eps, "available_from": pd.to_datetime(pd.Series(avail)), }).dropna().sort_values("available_from") ``` Each period’s `available_from` is the result-declaration date when `basis` is `reported`, or a regulatory-lag estimate when it is `estimated`. Treat estimated rows with care, or drop them, in sensitive tests. ## 3. Join on the date the data became public [Section titled “3. Join on the date the data became public”](#3-join-on-the-date-the-data-became-public) ```python eps_pit["ttm_eps"] = eps_pit["eps"].rolling(4).sum() panel = pd.merge_asof( px[["close"]].reset_index(), eps_pit[["available_from", "ttm_eps"]].rename(columns={"available_from": "date"}), on="date", direction="backward", # only rows already published on each date ) panel["pe"] = panel["close"] / panel["ttm_eps"] ``` `merge_asof(..., direction="backward")` gives each trading day the latest quarter that had been published by then, never one from the future. ## 4. Respect the trading calendar [Section titled “4. Respect the trading calendar”](#4-respect-the-trading-calendar) Use the holiday list rather than a weekday calendar. Exchanges sometimes open on weekends for special sessions and close on weekdays. ```python holidays, url = [], "/v1/market/holidays?from=2015-01-01&limit=500" while url: body = s.get("https://api.tapetide.com" + url, timeout=30).json() holidays += [h["date"] for h in body["data"]] url = body["links"]["next"] ``` A trading date with no bar is not a zero return. Mask it, do not fill it with `0`. ## Caveats to state in your results [Section titled “Caveats to state in your results”](#caveats-to-state-in-your-results) * Statement values are the latest reported figures and can include restatements made after `available_from`. * Today’s index constituents are not yesterday’s. Building a universe from a current index list adds survivorship bias. * Prices are end-of-day. Intraday bars are not served by these routes. Each call above costs one request unit. A full price history is a handful of calls; cache it locally rather than refetching per run. # LLM-friendly docs > llms.txt, llms-full.txt, Markdown twins and the raw OpenAPI document for feeding these docs to a model. Everything on this site is available in plain text for models and tools. | URL | What it is | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`/llms.txt`](/llms.txt) | Index of every page with a one-line description, per [llmstxt.org](https://llmstxt.org/). | | [`/llms-full.txt`](/llms-full.txt) | Every page concatenated as Markdown. Paste it into a context window. | | [`/llms-small.txt`](/llms-small.txt) | A compact version with notes and examples trimmed. | | `/.md` | A Markdown twin of any page: [`/quickstart.md`](/quickstart.md), [`/errors.md`](/errors.md), [`/api/operations/getstockquote.md`](/api/operations/getstockquote.md). | | [`/openapi.json`](/openapi.json) | The OpenAPI 3.1 document this site was built from. The live copy is `https://api.tapetide.com/v1/openapi.json`. | ## Suggested use [Section titled “Suggested use”](#suggested-use) * **Coding assistant writing a client:** give it `/llms-full.txt` plus `/openapi.json`. * **Agent at runtime:** keep `/llms.txt` in the system prompt and let it fetch the `.md` twin of the page it needs. * **MCP users:** the MCP server’s own tool guide is at [tapetide.com/mcp/llms.txt](https://tapetide.com/mcp/llms.txt). The site is rebuilt whenever the API’s OpenAPI document changes and once a day, so these files track production. # Pull option chains > Fetch index option chains, IV history and per-expiry analytics from the Tapetide API, and read the fields correctly. Three routes cover options: | Route | What it returns | Coverage | | ------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------- | | `GET /v1/options/{symbol}/chain` | Strike ladder with IV, Greeks, bid/ask, OI and volume | Index underlyings: NIFTY, BANKNIFTY, FINNIFTY, MIDCPNIFTY | | `GET /v1/options/{symbol}/iv-history` | ATM IV, IV rank and percentile, realised vol, PCR, max pain, skew over time | About 556 underlyings including stocks | | `GET /v1/stocks/{symbol}/options` | The same analytics per expiry for one underlying, latest snapshot | Stocks and indices with listed options | ## Fetch a chain [Section titled “Fetch a chain”](#fetch-a-chain) ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" \ "https://api.tapetide.com/v1/options/NIFTY/chain?strikes=10" ``` Omit `expiry` for the nearest one. `strikes` is the number of strikes each side of ATM. The response lists every expiry in `available_expiries`, so a second call can target a later one: ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" \ "https://api.tapetide.com/v1/options/NIFTY/chain?expiry=2026-10-27&strikes=10" ``` Abridged response: ```json { "data": { "underlying": "NIFTY", "expiry_date": "2026-10-19", "snapshot_ts": "2026-10-15 10:00:00+00", "grain": "eod", "spot": 22997.35, "atm_strike": 23000, "atm_iv_pct": 11.71, "pcr": 1.65, "max_pain_strike": 22900, "lot_size": 65, "ladder": [ { "strike": 23000, "atm_offset": 0, "call": { "ltp": 91.85, "bid": 91.45, "ask": 91.85, "iv_pct": 8.05, "delta": 0.5496, "oi": 8564465, "iv_unsolved": false, "illiquid": false }, "put": { "ltp": 140, "bid": 139.3, "ask": 140, "iv_pct": 15.38, "delta": -0.4716, "oi": 5971355, "iv_unsolved": false, "illiquid": false } } ] } } ``` ## Read it correctly [Section titled “Read it correctly”](#read-it-correctly) * **It is a snapshot, not a stream.** Quote `snapshot_ts` with any number you take from the chain. * **IV fields are percents.** `iv_pct: 8.05` means 8.05%. Do not multiply by 100. * **Nulls mean unpublished.** `iv_unsolved: true` means IV and Greeks are `null` for that leg (common deep in the money). `illiquid: true` means no OI or volume, so the premium is indicative. Render these as missing, never as `0`. * **`atm_offset`** is the signed distance in strikes from ATM, handy for selecting “ATM±2” without arithmetic on strike steps. ## IV history [Section titled “IV history”](#iv-history) ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" \ "https://api.tapetide.com/v1/options/RELIANCE/iv-history?range=1y" ``` `range` of `2y` and longer returns weekly bars: read `bar_interval` before counting points as sessions. `iv_rank_pct` (position in the trailing high-low band) and `iv_percentile_pct` (share of sessions below today) are different measures, both in percent. Data is for information only and is not investment advice. # Rate limits and quota > Tapetide plan quotas, the per-minute burst cap, the headers that report them, and /v1/me/usage. Two limits apply to every token. Both are shared between the REST API and the MCP server. | Limit | Free plan | Paid plans | Error when reached | | ---------- | -------------------------------------------------- | -------------------------------------------------------------------------- | --------------------- | | Plan quota | 50 successful requests per IST day, no monthly cap | Higher daily and monthly caps, see [pricing](https://tapetide.com/pricing) | `429 quota_exhausted` | | Burst cap | 60 requests per minute | 60 requests per minute | `429 rate_limited` | * Only a `200` counts against the plan quota. Errors are refunded. * Days and months reset on IST (Asia/Kolkata) boundaries: midnight IST and the first of the month IST. * `/v1/me/usage` and `/v1/openapi.json` are free. ## Headers [Section titled “Headers”](#headers) | Header | On | Meaning | | ------------------- | --------------------- | -------------------------------------------- | | `X-RateLimit-Reset` | Every `200` and `429` | Unix seconds when the binding window resets. | | `Retry-After` | Every `429` and `503` | Seconds to wait before retrying. | ## Check your position [Section titled “Check your position”](#check-your-position) ```bash curl -s -H "Authorization: Bearer $TAPETIDE_TOKEN" https://api.tapetide.com/v1/me/usage ``` ```json { "data": { "plan": "free", "day": { "period": "2026-10-16", "used": 12, "limit": 50, "remaining": 38, "resets_at": "2026-10-16T18:30:00.000Z" }, "month": { "period": "2026-10", "used": 212, "limit": null, "remaining": null, "resets_at": "2026-10-31T18:30:00.000Z" } }, "meta": { "as_of": "2026-10-16T06:01:12.418Z", "route": "getMyUsage" } } ``` A `limit` of `null` means unlimited for that window. ## Spending fewer units [Section titled “Spending fewer units”](#spending-fewer-units) * Use `/v1/quotes?symbols=A,B,C` for up to 20 quotes in one unit. * Ask for the widest page you need: `limit` up to the endpoint maximum, `days=2000` on price history. * Cache reference data such as holidays and financial statements; they change on a schedule, not by the minute. # Changelog > Changes to the Tapetide API contract. The API reference on this site is rebuilt from the live OpenAPI document, so it always matches production. This page records changes that affect clients. ## 2026-10 [Section titled “2026-10”](#2026-10) * Developer docs launched at developers.tapetide.com, with a generated API reference, playground, `llms.txt` and Markdown twins. * In preview: typed data endpoints with cursor pagination (`meta.next_cursor`, `links.next`), starting with `GET /v1/market/holidays`. They appear in the reference as they go live. ## 2026-09 [Section titled “2026-09”](#2026-09) * REST API at `https://api.tapetide.com/v1`: stocks, market, indices, options and account routes, sharing the MCP token and quota. * Parsed filings: `/v1/stocks/{symbol}/documents`, `/documents/summary`, `/documents/read`.