← tanukitrade.com Open WebApp (opens in a new tab) Start 7-day free trial

API

One API. 43 POST routes.

TanukiTrade PRO exposes derived options analytics over HTTP and over MCP. Every market route is POST /api/v1/… with a flat JSON body. The same body is the MCP tool argument. There is one version.

New to the API? Start with the five-minute quickstart. This page lists every route.

How a request reaches derived analytics A client script or MCP host sends a POST request with a Bearer token to TanukiTrade PRO over REST (/api/v1) or MCP (/mcp/v1), and receives a structured JSON envelope of derived options analytics across 8 route groups. ARCHITECTURE · ONE CONTRACT, TWO DOORS 120 req / min · gzip 1. CALLERS Your code & scripts Python, Node, curl, dashboards api.tanukitrade.com AI agents & IDEs Claude, Cursor, custom MCP hosts mcp.tanukitrade.com Bearer id.key POST JSON 2. UNIFIED GATEWAY TanukiTrade PRO 40 market + 3 account routes REST POST /api/v1/… Flat JSON body (max 4 KiB) MCP POST /mcp/v1 Stateless JSON-RPC tools/call Discovery · Gamma · Volatility · Range Chain · Fundamentals · Technical · History 200 JSON 3. DERIVED ANALYTICS Consistent JSON envelope Same fields on HTTP and MCP { "ok": true, "data": { "items": […] }, "meta": { next_cursor, built_at } } ET timestamps · cursor paging Scope boundary: Derived options analytics only — no raw OPRA chains, no per-contract quotes, no order routing, and no trade signals.
One product, two doors. REST is for your own code; MCP is for an assistant or IDE that calls the same tools directly.

Every route

43 POST /api/v1 routes: 40 market and 3 account. Each row is one MCP tool. The path opens that route. The group name opens its page.

RouteMCP toolGroup
/api/v1/discovery/symbolslist_symbolsDiscovery
/api/v1/discovery/expirieslist_expiriesDiscovery
/api/v1/discovery/glossaryget_glossaryDiscovery
/api/v1/fundamentals/profilelist_fundamentals_profileFundamentals
/api/v1/fundamentals/benchmark-correlationlist_fundamentals_benchmark_correlationFundamentals
/api/v1/fundamentals/earningslist_earningsFundamentals
/api/v1/volatility/iv-metricslist_volatility_iv_metricsVolatility
/api/v1/volatility/term-structurelist_volatility_term_structureVolatility
/api/v1/volatility/backwardation-summarylist_volatility_backwardation_summaryVolatility
/api/v1/volatility/call-put-skewlist_volatility_call_put_skewVolatility
/api/v1/range/0dte-boxlist_0dte_expected_move_boxesRange
/api/v1/range/binary-expected-movelist_range_binary_expected_moveRange
/api/v1/range/std1-sigmalist_range_std1_sigmaRange
/api/v1/range/delta-interpolated-ladderlist_range_delta_interpolated_ladderRange
/api/v1/range/tanuki-smart-rangelist_range_tanuki_smart_rangeRange
/api/v1/gamma/profile-stateslist_gamma_profile_statesGamma
/api/v1/gamma/key-levels/primarylist_key_levels_primaryGamma
/api/v1/gamma/key-levels/secondarylist_key_levels_secondaryGamma
/api/v1/gamma/net-exposureslist_gamma_net_exposuresGamma
/api/v1/gamma/strikes/net-gexlist_gamma_strikes_net_gexGamma
/api/v1/gamma/strikes/net-dexlist_gamma_strikes_net_dexGamma
/api/v1/gamma/strikes/net-oilist_gamma_strikes_net_oiGamma
/api/v1/gamma/strikes/abs-oilist_gamma_strikes_abs_oiGamma
/api/v1/gamma/strikes/abs-gexlist_gamma_strikes_abs_gexGamma
/api/v1/gamma/strikes/net-volumelist_gamma_strikes_net_volumeGamma
/api/v1/gamma/strikes/gex-by-volumelist_gamma_strikes_gex_by_volumeGamma
/api/v1/chain/activitylist_chain_activityChain
/api/v1/chain/expiry-distributionlist_chain_expiry_distributionChain
/api/v1/technical/price-changelist_technical_price_changeTechnical
/api/v1/technical/realized-volatilitylist_technical_realized_volatilityTechnical
/api/v1/history/volatility/iv-ranklist_history_volatility_iv_rankHistory
/api/v1/history/volatility/ivxlist_history_volatility_ivxHistory
/api/v1/history/volatility/call-put-skewlist_history_volatility_call_put_skewHistory
/api/v1/history/range/0dte-boxlist_history_0dte_expected_move_boxesHistory
/api/v1/history/gamma/profile-stateslist_history_gamma_profile_statesHistory
/api/v1/history/gamma/key-levels/primarylist_history_key_levels_primaryHistory
/api/v1/history/gamma/key-levels/secondarylist_history_key_levels_secondaryHistory
/api/v1/history/gamma/net-exposureslist_history_gamma_net_exposuresHistory
/api/v1/history/technical/price-changelist_history_technical_price_changeHistory
/api/v1/history/technical/realized-volatilitylist_history_technical_realized_volatilityHistory
/api/v1/account/usageget_account_usageAccount
/api/v1/account/clientsget_account_clientsAccount
/api/v1/account/statusget_account_statusAccount

Authorization

  1. Sign in and open API Access.
  2. Copy the bearer token from that page. REST and MCP use the same token.
  3. Store it in a password manager or secret store. Do not leave it in a chat or a committed file.

Send one header on every call:

http
Authorization: Bearer 12345.YOUR_API_KEY

The token is api_user_id, a dot, then the key. The id is 8 lowercase letters or digits. The key is 16 to 128 letters or digits. The whole token is at most 160 characters. It is not trimmed and it is not lowercased for you. 12345.YOUR_API_KEY is a placeholder, not a working token: it fails the grammar on purpose.

API and MCP access are included with an active TanukiTrade PRO subscription. The chart login is not the bearer token. Copy the token from the signed-in API Access page. REST and MCP share that key and the same per-user rate limit. Keep it in an environment variable, secret store, or MCP host header field. Do not put it in a URL, query string, log line, or tool argument.

shell
export TANUKI_BASE_URL="https://api.tanukitrade.com"
export TANUKI_TOKEN="12345.YOUR_API_KEY"

The call

Start with discovery. This verifies the URL, token, JSON body, and response envelope before you add symbol-specific arguments:

curl
curl --compressed \
  --request POST \
  --url "${TANUKI_BASE_URL}/api/v1/discovery/symbols" \
  --header "Authorization: Bearer ${TANUKI_TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{}'
  • Method is POST. Another method is 405 and Allow: POST.
  • Content-Type: application/json. The body is one flat object, at most 4 KiB. A nested object, a repeated key, unknown keys, and bad JSON are 422. A larger body is 413.
  • An integer outside signed 64-bit is 422.
  • Send Accept-Encoding: gzip. curl --compressed inflates the body.
  • Timestamps are ISO 8601 with an America/New_York offset, to milliseconds. A Z suffix does not come back. Dates are YYYY-MM-DD.
  • A non-integer number is at most 4 decimal places (half-even). A whole number stays an integer. A value that is not finite, or an integer outside the signed 64-bit range, is null.

Envelope

A success is ok, data, and meta. Lists put rows in data.items. meta.request echoes every argument except cursor. A missing optional argument is null in that echo. Defaults you did not send, such as method: cumulative, are filled in.

meta fieldMeaning
countRows in this page.
totalRows that matched the filter, across pages.
page / page_countThis page, and how many pages total splits into at the route’s fixed page size.
next_cursorOpaque string for the next page, or null on the last page.
generationId of the data generation. It changes when the inputs change.
as_ofSource times that apply to this route. Often empty.
built_atWhen this payload was assembled, ET, with an offset.
omittedSymbol strings left out of the rows. Always a list. Empty is [].
notesServer text on the 0DTE list when a futures or pause sentence applies. Other routes do not send the key.

Paging

You do not choose the page size. Pass cursor from the previous meta.next_cursor with the same other arguments. A cursor that does not match the request, or a generation that moved, is 422: Cursor does not match this request or the data changed; start again without cursor.

The benchmark list is the route that actually paged on the captured stack (200 of 261 rows). The live cursor is a long opaque string. The example uses eyJ... because that value is bound to the server domain and is not something to copy out of a doc.

next page
{
  "cursor": "eyJ..."
}

Null, and a missing key

  • A missing earnings move block is null. The key stays.
  • A 0DTE session that was not stored (eod, pre, or open) is null. The key stays.
  • meta.omitted is always a list. Empty is []. When SPY is dropped from relative strength, omitted is ["SPY"].
  • A chain count is 0 when nothing is stored. A call/put ratio is null when the put side is 0. A share percent is null when the symbol total is 0.

Expiries

An alias is 0, 1, w1–w4, M1–M3, or ALL. Letter case folds before the cache key. Weekly aliases keep a lowercase w. Monthly aliases and ALL stay uppercase.

An unknown expiry sent with a symbol is 422 UNKNOWN_EXPIRY. The same unknown expiry sent alone is HTTP 200 and an empty list. The strike header does not include is_monthly. Expiry rows elsewhere do.

Where each question goes

QuestionPage
Which tickers exist, and which expiries does this one have?Discovery
Profile, correlation, earnings.Fundamentals
IV rank, skew, term structure.Volatility
0DTE box and expected-move distance.Range
State, walls, net GEX, strikes.Gamma
Open interest and volume.Chain
Percent change, realized vol, relative strength.Technical
The same readings on a past ET day.History
How many calls did this token make, and from where?Account

A default PRO token can call every group in this contract, and MCP tools/list includes them. HTTP 403 ENDPOINT_NOT_ENABLED happens only when the token is limited to an explicit group list and the route is not on it. See Connect.

Account is 3 more POST routes for this token’s own usage, recent clients, and service status. They are not market data, the group filter does not hide them, and they are also MCP tools: get_account_usage, get_account_clients, and get_account_status. The full list is every route.

What this API does not return

  • No raw OPRA chain, no per-contract quote, and no tick feed.
  • spot_price is returned on every route that lists it in fields, and on the 0DTE box snapshots. On a gamma or strike snapshot it is that snapshot’s underlying price, not a live quote. On the expected-move distance rows it is that expiry’s average opex spot. The current price-change row is the price at snapshot_at. The history price-change row is that day’s close. A 0DTE snapshot’s spot_price is the peg the box is centred on. widest.spot_price is the peg of the latest snapshot that arrived, and a history change carries the peg of the snapshot that triggered it. A missing, zero, or negative spot_price is null. Box edges (em_upper, em_lower, sigma_upper, sigma_lower) are expected-move bounds, not a quote. A state sentence that uses the word “price” says where the underlier sits relative to a level. That sentence is not a price field. A strike row does not repeat spot_price or is_spot; the header carries spot_price. On a strike route meta.as_of.price is the chain snapshot time, ET with an offset, the same instant as snapshot_at.
  • No orders and no trade signals.

Next: errors, rate limits, or the Python client.

TanukiTrade API and MCP. Derived options analytics for your code and for agents. Not investment advice. tanukitrade.com