# indextkn: full reference > indextkn tracks the published list price of every model each major AI provider sells, 900+ models across 17 providers, and serves them as one fast, versioned REST API. Prices are read straight from each provider's own pricing page or public API, re-polled every five minutes, and every price carries an accuracy status so you can tell how much to trust it. This file is the complete reference: the data model, all seven endpoints with parameters and response fields, the two agent integrations that expose the same data as tools, the price-change webhooks an account can subscribe to, errors, limits, and worked use cases. - Website: https://indextkn.com - API base URL: https://api.indextkn.com/api/v1 - Short index for agents: https://indextkn.com/llms.txt - HTML docs and live playground: https://indextkn.com/docs - Agent skill: https://github.com/indextkn/skills (see *Agent skill* below) - MCP server for agents: https://mcp.indextkn.com/mcp (see *MCP server* below) - Contact: contact@indextkn.com ## How to read this file Everything below describes the public, authenticated API at `/api/v1` and the public pages of indextkn.com, plus the price-alert webhooks the dashboard can send you. Nothing else is part of the contract: internal pipeline endpoints on the API host are not documented, not supported, and may change or disappear without notice. If an answer is not in this file, the honest answer is that indextkn does not expose it yet. See *Not supported* near the end for the list of things people commonly assume exist. In every example response, the list an endpoint returns (`data`, `results`, `offers`) is trimmed to a single entry to keep the file readable. `count` and `provider_count` are the real number of matches, so they will not equal the length of the list shown. ## Vocabulary Three words are used throughout, and they are not interchangeable. - **lab**: who *built* a model (`anthropic`, `openai`, `meta`, `mistral`, …). - **provider**: who *sells* access to it (`anthropic`, `aws`, `openrouter`, `groq`, …). The same model is usually sold by several providers at different prices; that spread is the reason this API exists. - **offer**: one provider's price for one model. An offer is what carries a price, a context window, a max output, a source URL and a status. `/prices` returns offers; `/models` returns models with the providers that sell them. A model id is written `lab/model` (`anthropic/claude-opus-4.5`). An offer id is written `provider:provider-model-id` (`anthropic:claude-opus-4-5`). ## Where the numbers come from - Each provider has its own scraper reading that provider's public pricing page or pricing API. There are no estimates, no averages and no hand-typed prices. - Every source is re-polled on a five-minute cycle, staggered so the sources do not all fire at once. - Prices are **published list prices** for the standard tier, in USD. They are not your negotiated rate, not batch/flex pricing, and not what your invoice will say. Enterprise discounts, committed-use discounts, free tiers and promotional credits are not modelled. - A new price is validated before it is published, and compared against the same offer's history and against what other providers charge for the same model. A price that moves sharply, or that sits far away from everyone else, is flagged rather than silently trusted. - `updated_at` on an offer is the time that offer's price last *changed*, not the time it was last checked. A price that has not moved in a month keeps a month-old `updated_at` and is still current. ## Authentication Every data endpoint takes a Bearer API key: ``` Authorization: Bearer itk_live_your_key_here ``` - Keys are created on https://indextkn.com/dashboard after signing in with GitHub or a one-time email link. Accounts and keys are free. - A key is shown **once**, when it is created. Only a prefix and a hash are stored, so a lost key cannot be recovered. Revoke it and create another. - Keys are server-side credentials. Anyone holding one spends your quota, so keep them out of browsers, mobile apps and repositories. - Two endpoints need no key: `GET /api/v1` (the endpoint index) and `GET /api/v1/prices/history` (which answers `501 NOT_IMPLEMENTED`). Missing header → `401 MISSING_API_KEY`. Unknown or revoked key → `401 INVALID_API_KEY`. ## Request and response conventions - **Method**: `GET` only. Any other method on a v1 path returns `404`. - **Format**: every response is JSON, including errors. Prices are JSON numbers (not strings), and a price that a provider does not publish is `null`, never `0`. `0` means free. - **Currency**: always `USD`, on every endpoint. There is no currency parameter. - **Units**: an offer's `unit` says what its prices are per. `1M_tokens` is by far the most common; `1M_chars`, `image`, `second`, `hour`, `video` and `request` also occur. Only `1M_tokens` offers are comparable as money-per-token, so `/compare` and `/calculate` consider those and ignore the rest. A non-token offer has a single rate rather than an input/output pair, and that rate is always reported in `input_price`, with `output_price` `null` - one rule for every non-token unit, so sellers of the same per-hour or per-image model line up against each other. Where a provider genuinely publishes two rates for a non-token offer, both legs are reported as sent. - **Filters are exact, lowercase slugs** (`provider=openai`, `lab=anthropic`), except model lookups, which are case-insensitive and accept several forms. - **CORS**: `access-control-allow-origin: *`, with the rate-limit headers exposed, so a browser client can read them. - **Caching**: responses are sent `cache-control: no-store`. Cache on your side if you want to; polling more often than every five minutes will not surface new numbers. - **Rate-limit headers** ride on every authenticated response. See *Limits*. Quick start (returns every price OpenAI publishes): ```bash curl "https://api.indextkn.com/api/v1/prices?provider=openai" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```javascript const res = await fetch( "https://api.indextkn.com/api/v1/prices?provider=openai", { headers: { Authorization: `Bearer ${process.env.INDEXTKN_API_KEY}` } }, ); if (!res.ok) throw new Error((await res.json()).error.message); const { data } = await res.json(); console.log(data[0]); // { model, provider, input_price, output_price, ... } ``` ```python import os, requests res = requests.get( "https://api.indextkn.com/api/v1/prices", params={"provider": "openai"}, headers={"Authorization": f"Bearer {os.environ['INDEXTKN_API_KEY']}"}, ) res.raise_for_status() print(res.json()["data"][0]) ``` ## Endpoints at a glance | Endpoint | Returns | | --- | --- | | `GET /api/v1` | Version, endpoint list, auth scheme. No key needed. | | `GET /api/v1/models` | Models tracked, and which providers price each. | | `GET /api/v1/models/:id` | One model in full, with every provider offer. | | `GET /api/v1/prices` | Current price per offer. At least one filter required. | | `GET /api/v1/compare` | The provider price spread for one model. | | `GET /api/v1/calculate` | What a token workload costs, on every provider. | | `GET /api/v1/providers` | The seller catalogue, with live counts. | | `GET /api/v1/labs` | The lab catalogue, with live counts. | Pick the endpoint by the question being asked: - "Which models exist / who sells this one?" → `/models`, `/models/:id` - "What does provider X charge?" → `/prices` - "Who is cheapest for model Y?" → `/compare` - "What will this workload cost?" → `/calculate` - "What slugs can I filter by?" → `/providers`, `/labs` If the caller is an AI agent rather than code, the same seven endpoints reach it two other ways: as an installable skill, or as MCP tools. See *Agent skill* and *MCP server* below. --- ## GET /api/v1 The endpoint index. No key, no metering. ```bash curl "https://api.indextkn.com/api/v1" ``` ```json { "version": "v1", "documentation": "/docs", "endpoints": [ "/api/v1/models", "/api/v1/models/:id", "/api/v1/prices", "/api/v1/compare", "/api/v1/calculate", "/api/v1/providers", "/api/v1/labs" ], "coming_soon": ["/api/v1/prices/history"], "authentication": "Authorization: Bearer " } ``` --- ## GET /api/v1/models The models indextkn tracks and which providers sell each one. This is the discovery endpoint: use it to find the `model`, `provider` and `lab` values you pass everywhere else. **Query parameters** (all optional, all exact-match): | Parameter | Example | Meaning | | --- | --- | --- | | `lab` | `anthropic` | Only models built by this lab. | | `provider` | `groq` | Only models this provider sells. | | `modality` | `text` | `text`, `image`, `video`, `audio`, `speech`, `transcription`, `embedding` or `rerank`. | ```bash curl "https://api.indextkn.com/api/v1/models?lab=anthropic" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "count": 38, "data": [ { "id": "anthropic/claude-opus-4.5", "model": "claude-opus-4.5", "lab": "anthropic", "name": "Claude Opus 4.5", "modality": "text", "context": 200000, "max_output": 64000, "tags": ["tools", "vision", "reasoning", "cache"], "providers": ["anthropic", "openrouter", "aws"], "offer_count": 3 } ] } ``` **Model fields** | Field | Type | Example | Description | | --- | --- | --- | --- | | `id` | string | `anthropic/claude-opus-4.5` | Canonical model id. | | `model` | string | `claude-opus-4.5` | Short id, without the lab prefix. | | `name` | string | `Claude Opus 4.5` | Human display name. | | `lab` | string | `anthropic` | Who built the model. | | `modality` | string | `text` | One of `text`, `image`, `video`, `audio`, `speech`, `transcription`, `embedding`, `rerank`. | | `context` | number \| null | `200000` | Widest context window any provider lists. | | `max_output` | number \| null | `64000` | Max output tokens across providers. | | `tags` | string[] | `["tools","vision"]` | Capability tags, where known. | | `providers` | string[] | `["anthropic","aws"]` | Providers that price this model. | | `offer_count` | number | `3` | How many provider offers exist. | Note that `context` and `max_output` here are the **best across providers**. The same numbers on an offer are what *that* provider actually serves, which is often smaller, and are `null` when that provider publishes neither. Size a request against the offer, not the model. --- ## GET /api/v1/models/:id One model in full, with every provider offer attached. `:id` resolves case-insensitively against three forms, so whichever id you are already holding will work: - the canonical id: `anthropic/claude-opus-4.5` - the short id: `claude-opus-4.5` - the display name: `Claude Opus 4.5` The canonical form contains a slash, and it is passed through unencoded: `/api/v1/models/anthropic/claude-opus-4.5`. Everything after `models/` is treated as the id. ```bash curl "https://api.indextkn.com/api/v1/models/anthropic/claude-opus-4.5" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "id": "anthropic/claude-opus-4.5", "model": "claude-opus-4.5", "lab": "anthropic", "name": "Claude Opus 4.5", "modality": "text", "context": 200000, "max_output": 64000, "tags": ["tools", "vision", "reasoning", "cache"], "providers": ["anthropic", "openrouter", "aws"], "offer_count": 3, "offers": [ { "id": "anthropic:claude-opus-4-5", "provider": "anthropic", "input_price": 5.0, "output_price": 25.0, "cache_read_price": 0.5, "cache_write_price": 6.25, "reasoning_price": null, "currency": "USD", "unit": "1M_tokens", "context": 200000, "max_output": 64000, "status": "accurate", "source_url": "https://docs.claude.com/...", "updated_at": "2026-08-28T14:12:00Z" } ] } ``` Top-level fields are the model fields above. Each entry in `offers` uses the offer fields documented under `/prices`. An unknown id returns `404 NOT_FOUND`. --- ## GET /api/v1/prices The current price of every matching offer, one entry per provider x model. **At least one of `model`, `provider` or `lab` is required.** Without one you get `400 FILTER_REQUIRED`: the full catalogue is deliberately not fetchable in a single call. | Parameter | Example | Meaning | | --- | --- | --- | | `model` | `openai/gpt-5` | Canonical id, short id or display name, case-insensitive. | | `provider` | `openai` | Exact provider slug. | | `lab` | `anthropic` | Exact lab slug. | | `status` | `accurate` | `accurate`, `checking`, `suspicious`, `partial` or `failed`. | Filters combine with AND. `?lab=openai&provider=azure` is every OpenAI-built model that Azure sells. `model` resolves to one model first and then returns every provider selling it, so `?model=gpt-5` and `?model=openai/gpt-5` answer identically. A `model` nobody sells is `404 NOT_FOUND`; a `provider` or `lab` slug that does not exist is `400 INVALID_PARAMETER` naming `/api/v1/providers` or `/api/v1/labs`. Neither is answered with an empty list, so a typo never reads as an empty shelf. A real provider with nothing matching does return zero rows. ```bash curl "https://api.indextkn.com/api/v1/prices?provider=openai" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "timestamp": "2026-08-28T14:30:00Z", "count": 72, "data": [ { "id": "openai:gpt-5", "model": "openai/gpt-5", "model_name": "gpt-5", "provider": "openai", "lab": "openai", "input_price": 1.25, "output_price": 10.0, "cache_read_price": 0.125, "cache_write_price": null, "reasoning_price": null, "currency": "USD", "unit": "1M_tokens", "context": 400000, "max_output": 128000, "status": "accurate", "source_url": "https://openai.com/...", "updated_at": "2026-08-28T14:12:00Z" } ] } ``` **Offer fields** | Field | Type | Example | Description | | --- | --- | --- | --- | | `id` | string | `openai:gpt-5` | Stable offer id, `provider:model`. | | `model` | string | `openai/gpt-5` | Canonical model id. | | `model_name` | string | `gpt-5` | Human display name. | | `provider` | string | `openai` | Who sells this offer. | | `lab` | string | `openai` | Who built the model. | | `input_price` | number \| null | `1.25` | Price per unit for input tokens. | | `output_price` | number \| null | `10.0` | Price per unit for output tokens. | | `cache_read_price` | number \| null | `0.125` | Cached-input read price, where published. | | `cache_write_price` | number \| null | `null` | Cache-write price, where published. | | `reasoning_price` | number \| null | `null` | Reasoning-token price when billed apart from output. | | `currency` | string | `USD` | Currency of every price. Always USD. | | `unit` | string | `1M_tokens` | Price unit. Also `1M_chars`, `image`, `second`, `hour`, `video`, `request`. | | `context` | number \| null | `400000` | Context window this provider serves. `null` when it does not publish one. | | `max_output` | number \| null | `128000` | Max output tokens for this offer. `null` when unpublished. | | `status` | string | `accurate` | Accuracy: `accurate`, `checking`, `suspicious`, `partial` or `failed`. | | `source_url` | string | `https://openai.com/...` | Where the price was read from. | | `updated_at` | string | `2026-08-28T14:12:00Z` | ISO time of the last price change. | | `price_tiers` | object[] | *absent* | Step-up rates past a prompt size. Only on offers that have them. | | `off_peak_prices` | object | *absent* | Off-peak rates beside the headline (peak) ones. Only on offers that have them. | `timestamp` on the envelope is when the response was built; `updated_at` on each offer is when that price last moved. `context` and `max_output` are this provider's own numbers and are never filled in from the model rollup: a `null` means that seller does not publish one, not that the model has none. The model-wide figures live on `/models`. **`price_tiers`** appears only where a provider charges a different rate past a prompt length. Google's Pro models cost double above 200,000 input tokens, OpenAI's flagship models step up past 272,000, and xAI's from 200,000. The headline `input_price` and `output_price` are the base tier; each entry holds `above_input_tokens` and the `input_price`, `output_price`, `cache_read_price` and `cache_write_price` that apply beyond it, any of which may be `null` when only some legs step up. Crossing a threshold bills the **whole prompt** at the tier rate, which is how these tiers are published. Most start strictly past the threshold; an entry with `"inclusive": true` (xAI's "≥ 200k prompt tokens") starts at it. An offer with no such tiers omits the key entirely. ```json "price_tiers": [ { "above_input_tokens": 200000, "input_price": 4.0, "output_price": 18.0, "cache_read_price": 0.4, "cache_write_price": 5.0 } ] ``` **`off_peak_prices`** appears only where a provider publishes time-of-day rates beside its standard ones, currently DeepSeek, whose off-peak hours are half the peak rate. The headline prices stay the peak rates (the ceiling of a day's cost); this object holds the off-peak `input_price`, `output_price` and `cache_read_price`. ```json "off_peak_prices": { "input_price": 0.66, "output_price": 1.98, "cache_read_price": 0.022 } ``` --- ## GET /api/v1/compare The arbitrage view: the price spread for one model across every provider that sells it. Answers "who is cheapest, who is dearest, and how much does the choice actually matter". | Parameter | Required | Meaning | | --- | --- | --- | | `model` | yes | Canonical id, short id or display name. | Only offers priced per 1M tokens with a real input price take part. ```bash curl "https://api.indextkn.com/api/v1/compare?model=openai/gpt-oss-120b" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "model": "openai/gpt-oss-120b", "model_name": "gpt-oss-120b", "lab": "openai", "currency": "USD", "unit": "1M_tokens", "provider_count": 6, "input": { "min": 0.037, "min_provider": "openrouter", "max": 0.15, "max_provider": "groq", "median": 0.15, "spread_ratio": 4.05 }, "output": { "min": 0.15, "min_provider": "deepinfra", "max": 0.6, "max_provider": "groq", "median": 0.45, "spread_ratio": 4.0 }, "first_party": null, "offers": [ { "provider": "openrouter", "input_price": 0.037, "output_price": 0.15, "cache_read_price": null, "status": "accurate" } ] } ``` **Response fields** | Field | Type | Description | | --- | --- | --- | | `model` | string | Canonical model id. | | `model_name` | string | Human display name. | | `lab` | string | Who built the model. | | `currency` | string | Always `USD`. | | `unit` | string | `1M_tokens`. | | `provider_count` | number | Providers priced per token for this model. | | `unverified_count` | number | How many of those were held out of the spread as unconfirmed. | | `input` | object \| null | Input-price spread. `null` when nothing comparable. | | `output` | object \| null | Output-price spread. | | `first_party` | object \| null | The lab's own price, when it sells direct. | | `offers` | object[] | Every offer, cheapest input first. | The spread is built from prices we have confirmed. An offer marked `suspicious` or `failed` is left out of the min, median and max and counted in `unverified_count`: the spread is the part a caller acts on, and a wrong price wins it by being wrongly low. Every offer still appears in `offers` with its status attached. `input` and `output` each hold `min`, `min_provider`, `max`, `max_provider`, `median` and `spread_ratio` (max ÷ min, so `4.05` means the dearest provider charges four times the cheapest). `first_party`, when present, holds `provider`, `input_price` and `output_price`, useful for "is going direct cheaper than an aggregator?". Each entry in `offers` holds `provider`, `input_price`, `output_price`, `cache_read_price` and `status`. An unknown model returns `404 NOT_FOUND`; a missing `model` parameter returns `400 INVALID_PARAMETER`. --- ## GET /api/v1/calculate What a token workload would cost, priced on every provider that sells the model, sorted cheapest first. This is the endpoint to reach for whenever the question involves an amount of money. | Parameter | Default | Meaning | | --- | --- | --- | | `model` | required | **Required.** Canonical id, short id or display name. | | `prompt_tokens` | none | The whole prompt per request, cached tokens included; the fresh share is derived. Mutually exclusive with `input_tokens`. | | `input_tokens` | `0` | Fresh (uncached) prompt tokens per request. | | `output_tokens` | `0` | Completion tokens per request. | | `cached_tokens` | `0` | Tokens read from the provider's prompt cache. | | `reasoning_tokens` | `0` | Reasoning tokens per request. | | `requests` | `1` | Multiplies the per-request cost by call volume. | | `provider` | none | Price a single provider instead of all of them. | Rules: - At least one token count must be greater than zero, otherwise `400 INVALID_PARAMETER`. - Token counts and `requests` must be non-negative numbers; anything else is `400 INVALID_PARAMETER`. Absurd values are clamped (1e12 per token component, 1e9 requests). - Token counts are **per request**. `input_tokens=1000&requests=500` prices 500 requests of 1,000 input tokens each, not 1,000 tokens in total. - `cached_tokens` are billed at `cache_read_price` and are *instead of*, not on top of, the same tokens in `input_tokens`, so count each token once. The unambiguous form is `prompt_tokens=4000&cached_tokens=3000` (the whole prompt with the cached share named), from which the fresh 1,000 is derived; passing both `prompt_tokens` and `input_tokens` returns `400 INVALID_PARAMETER`, and `cached_tokens` larger than `prompt_tokens` clamps the fresh share at zero rather than erroring. - `reasoning_tokens` bill at `reasoning_price` where a provider publishes one, and at the output rate otherwise, which is how these providers actually bill. ```bash curl "https://api.indextkn.com/api/v1/calculate?model=openai/gpt-5&input_tokens=1000000&output_tokens=500000" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "model": "openai/gpt-5", "model_name": "gpt-5", "lab": "openai", "currency": "USD", "request": { "input_tokens": 1000000, "output_tokens": 500000, "cached_tokens": 0, "reasoning_tokens": 0, "requests": 1 }, "count": 3, "results": [ { "provider": "openai", "total_cost": 6.25, "breakdown": { "input": 1.25, "cached": 0, "output": 5.0, "reasoning": 0 }, "status": "accurate" } ], "cheapest": { "provider": "openai", "total_cost": 6.25 } } ``` **Response fields** | Field | Type | Description | | --- | --- | --- | | `model` | string | Canonical model id. | | `model_name` | string | Human display name. | | `lab` | string | Who built the model. | | `currency` | string | Always `USD`. | | `request` | object | The token counts you sent, echoed back after clamping. | | `count` | number | How many providers were costed. | | `results` | object[] | Per-provider cost, cheapest first. | | `cheapest` | object \| null | `{ provider, total_cost }` for the cheapest result that is both complete and not in doubt. `null` when none qualifies. | Each entry in `results` holds `provider`, `total_cost`, a `breakdown` (`input`, `cached`, `output`, `reasoning`) and `status`. When the provider does not publish a price for a component you asked about, such as a cache rate, it also carries `partial: true` plus `missing_prices: ["cached"]`. When the prompt is larger than the context window that provider serves it also carries `over_context: { "context": 272000, "prompt_tokens": 300000 }`: the total is still the arithmetic you asked for, but it applies a rate the provider never published for a prompt that size, and the provider would refuse the request. OpenAI, for one, prints some prices as applying below a 272k context length and publishes nothing above it. When the prompt is long enough to cross a provider's step-up threshold, that provider is costed at the higher tier, billing the whole prompt at the tier rate, and the result carries `price_tier_applied: { "above_input_tokens": 200000 }` so a total that does not match the headline rates explains itself. Prompt size for this purpose is the whole prompt (`prompt_tokens`, or `input_tokens + cached_tokens`), however much of it was cached. Tiers flagged `inclusive` start at the threshold, not past it. A partial total is a **lower bound**, not the real cost. `cheapest` is the one number a caller reads on its own, so it has to be a price you could actually pay: it skips every partial result, and every result resting on a `suspicious` or `failed` price. When nothing qualifies, `cheapest` is `null` rather than the cheapest of a bad set. That matters because the cheapest partial result is often `$0`, for a model the provider publishes no token price for at all. Read the rows in `results` for the reason, and treat their totals as floors rather than quotes. Costs are rounded to six decimal places, so sub-cent workloads stay meaningful. --- ## GET /api/v1/providers The seller catalogue, meaning who takes your money, each with live counts. No parameters. ```bash curl "https://api.indextkn.com/api/v1/providers" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "count": 17, "data": [ { "id": "openai", "name": "OpenAI", "kind": "first_party", "homepage": "https://openai.com", "model_count": 70, "offer_count": 72 } ] } ``` **Provider fields** | Field | Type | Example | Description | | --- | --- | --- | --- | | `id` | string | `openai` | Provider slug used in every filter. | | `name` | string | `OpenAI` | Human display name. | | `kind` | string | `first_party` | `first_party`, `cloud`, `aggregator` or `inference_host`. | | `homepage` | string | `https://openai.com` | The provider's homepage. | | `model_count` | number | `70` | Distinct models this provider prices. | | `offer_count` | number | `72` | Total offers from this provider. | **The provider slugs**, so an agent can filter without a discovery call first: | Slug | Provider | Kind | | --- | --- | --- | | `anthropic` | Anthropic | first_party | | `openai` | OpenAI | first_party | | `google` | Google AI Studio | first_party | | `xai` | xAI | first_party | | `deepseek` | DeepSeek | first_party | | `moonshot` | Moonshot AI | first_party | | `zai` | Z.ai | first_party | | `perplexity` | Perplexity | first_party | | `mistral` | Mistral | first_party | | `fireworks` | Fireworks AI | inference_host | | `together` | Together AI | inference_host | | `deepinfra` | DeepInfra | inference_host | | `groq` | Groq | inference_host | | `openrouter` | OpenRouter | aggregator | | `aws` | AWS Bedrock | cloud | | `azure` | Azure AI Foundry | cloud | | `vertex` | Google Vertex AI | cloud | `kind` is worth filtering on: `first_party` is the lab selling direct, `cloud` is a hyperscaler reselling inside its own platform, `inference_host` runs open models on its own hardware, and `aggregator` routes across others. --- ## GET /api/v1/labs The lab catalogue, meaning who built the models, ordered by how many models are tracked for each. No parameters. ```bash curl "https://api.indextkn.com/api/v1/labs" \ -H "Authorization: Bearer $INDEXTKN_API_KEY" ``` ```json { "count": 70, "data": [ { "id": "openai", "name": "OpenAI", "first_party_provider": "openai", "model_count": 210, "provider_count": 9 } ] } ``` The `moonshot` (Kimi) and `z-ai` (GLM) labs sell direct, so their models carry a `first_party_provider` and `/compare` reports a `first_party` price for them. **Lab fields** | Field | Type | Example | Description | | --- | --- | --- | --- | | `id` | string | `openai` | Lab slug used in every filter. | | `name` | string | `OpenAI` | Human display name. | | `first_party_provider` | string \| null | `openai` | Provider slug when the lab sells direct; `null` when it does not. | | `model_count` | number | `210` | Models tracked from this lab. | | `provider_count` | number | `9` | Providers selling this lab's models. | --- ## Agent skill An installable skill that teaches a coding agent to call this API directly, rather than connecting it to a server. Source at https://github.com/indextkn/skills, MIT licensed. ```sh npx skills add indextkn/skills ``` It works with Claude Code, Cursor, Copilot, Windsurf, Gemini CLI, Cline and Codex. As a Claude Code plugin instead: ``` /plugin marketplace add indextkn/skills /plugin install indextkn@indextkn ``` The key comes from `INDEXTKN_API_KEY` in the environment, or from a gitignored `.env` or `.env.local` in the project root. The skill checks the environment first, then both files, and sets the key up before its first request. What it carries beyond the raw endpoints: which endpoint answers which question, the accuracy rules in this file (list prices are not an invoice, `null` is not `0`, a `suspicious` status is surfaced rather than dropped, a `partial` total is a lower bound, a provider's context window is often smaller than the model's), task-shaped playbooks, and a helper script that does one GET with readable errors. The skill and the MCP server below are two routes to the same data. Pick one. Running both leaves an agent two ways to do the same lookup and no reason to prefer either. ## MCP server The same data, exposed as tools an AI agent can call, at `https://mcp.indextkn.com/mcp` over the Streamable HTTP transport. It is a stateless facade over this API: the same key, the same monthly limit, the same numbers. Use the REST endpoints above when writing code, and the MCP server when an agent is doing the asking. Authentication is the same Bearer key, sent as an HTTP header on every request: `Authorization: Bearer itk_live_...`. The server stores nothing. It forwards the caller's key to this API for one request and discards it. A request with no key gets `401` with a `WWW-Authenticate: Bearer realm="indextkn"` challenge. Add it to a client that speaks MCP over HTTP: ```json { "mcpServers": { "indextkn": { "type": "http", "url": "https://mcp.indextkn.com/mcp", "headers": { "Authorization": "Bearer itk_live_YOUR_KEY" } } } } ``` Or, in Claude Code: ```sh claude mcp add --transport http indextkn https://mcp.indextkn.com/mcp \ --header "Authorization: Bearer itk_live_YOUR_KEY" ``` ### Tools Seven, all read-only and annotated `readOnlyHint: true`, one per REST endpoint. Each returns both a readable text block and a `structuredContent` object matching an advertised output schema. | Tool | Endpoint behind it | Arguments | | --- | --- | --- | | `list_models` | `/models` | `search`, `lab`, `provider`, `modality`, `limit`, `offset` | | `get_model` | `/models/:id` | `model` | | `get_prices` | `/prices` | `model`, `provider`, `lab`, `status`, `limit`, `offset` | | `compare_providers` | `/compare` | `model` | | `calculate_cost` | `/calculate` | `model`, `prompt_tokens`, `input_tokens`, `output_tokens`, `cached_tokens`, `reasoning_tokens`, `requests`, `provider` | | `list_providers` | `/providers` | none | | `list_labs` | `/labs` | `limit`, `offset` | Two differences from the REST endpoints, both additions rather than changes: - **Paging.** `list_models`, `get_prices` and `list_labs` take `limit` (default 50, max 200) and `offset`, and report `total`, `returned` and `has_more`. The catalogue is roughly 900 models, so an unpaged list would fill an agent's context with rows it will not read. - **Local search.** `list_models` takes `search`, a case-insensitive substring matched against the model id and display name after the server-side filters have been applied. The REST endpoint has no equivalent. Everything else is identical: the same field names, the same `null` versus `0` meaning, the same `status` values, the same step-up tier handling. ### Prompts Two, for clients that surface MCP prompts: - `cheapest-provider` (argument: `model`) compares every provider selling one model and recommends one, flagging any offer whose status is not `accurate`. - `estimate-monthly-cost` (arguments: `model`, `requests_per_month`, `input_tokens`, `output_tokens`) prices a month of usage per provider, then shows what prompt caching would change. ### Errors An upstream error becomes an ordinary tool result with `isError: true`, so the model reads the message and adapts rather than the connection dropping. The error code is preserved and the message is rewritten as a next step: an exhausted quota names the reset date and says to stop, and any reference to a REST path is rewritten to the tool that reaches it. ### Running it locally The server is open source at https://github.com/indextkn/indextkn-mcp and can run as a local child process over the stdio transport instead, taking the key from `INDEXTKN_API_KEY` in the environment rather than from a header. The repository documents the security posture in full. ## Accuracy status Every offer carries a `status`. It is a claim about the *price*, not about the provider. - **accurate**: passed validation and the cross-provider checks. Safe to use. - **checking**: looked unusual and is being verified against the source. The value shown is the one read from the provider; treat it as provisional. - **suspicious**: moved sharply, or sits far from what every other provider charges for the same model. Confirm it at `source_url` before acting on it. - **partial**: no doubt about the number itself, but the seller publishes only one price leg (input without output, or the reverse). The published number may be fine; any total costed from the offer comes out short. It is a coverage fact, not a suspect price. - **failed**: an automated web check ran and could not confirm the price: either the source showed a different figure, or it could not determine one at all. Do not trust this price; confirm it at `source_url` before acting on it. Filter them out when a wrong number would be expensive: `/prices?provider=groq&status=accurate`. Surface them when a human is reading: the site shows the same signal next to each price. Whatever the status, these are published list prices. Confirm anything you are about to spend real money on against the provider itself. `source_url` on every offer is the page it was read from. ## Limits - Limits are **per account**, not per key: 3,000 requests per month across every key you hold. - The window resets on the 1st of each month at 00:00 UTC. - Every response for a recognised key carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (an ISO-8601 UTC timestamp of the reset instant). - Over the limit, requests return `429 RATE_LIMIT_EXCEEDED` with a `Retry-After` header in seconds. - A `401` carries no rate-limit headers, because no key was identified, so there is no budget to report. Budget advice for agents: one `/compare` or `/calculate` call answers a pricing question completely. Do not walk `/models` and then call `/prices` per model: `/prices?lab=…` or `?provider=…` returns the same rows in one request. Cache for five minutes or more; the underlying poll cycle is five minutes, so faster polling spends quota on identical bytes. ## Webhooks A price alert watches **one provider's listing of one model** and POSTs a signed body to an endpoint you own when that listing's price changes. It is the push side of this service; every endpoint above is the pull side. Alerts are created on https://indextkn.com/dashboard: choose a provider, choose a model, enter an `https://` URL. **There is no alerts endpoint.** Nothing in `/api/v1` creates, lists or deletes one, and the API remains read-only. Each account may hold **10 alerts**. **When one fires.** When a published price for that exact provider-and-model listing changes: `input`, `output`, `cache_read`, `cache_write`, `reasoning`, `context` or `unit`. Sources are re-read every five minutes, and a move large enough to look like a parsing error is held until a second reading confirms it, so a webhook arrives once, after the price is published. A rename, a new tag, or a re-read that returns the same number sends nothing. Two other kinds arrive on the same webhook: `kind: "created"` when a provider starts listing that model (with `old: null`), and `kind: "restored"` when it lists it again after dropping it. **The body.** `old` is every value held before the change, `new` is every value held after it, and `changed` names the fields that moved. There is no history in the payload: the two states either side of this change, nothing older. ```json { "id": "0f2b6c48-1f7e-4a02-9a1e-8b0c0f0f0f0f", "type": "price.changed", "kind": "changed", "created_at": "2026-08-31T12:04:07.000Z", "alert": { "id": "3a9f2c10-7d61-4f5e-9a2b-1c2d3e4f5a6b", "provider": "anthropic", "model": "anthropic/claude-opus-4.5" }, "model": { "id": "anthropic/claude-opus-4.5", "lab": "anthropic", "name": "Claude Opus 4.5", "modality": "text" }, "provider": "anthropic", "provider_model_id": "claude-opus-4-5", "changed": ["input", "cache_read"], "old": { "name": "Claude Opus 4.5", "input": 5, "output": 25, "cache_read": 0.5, "cache_write": 6.25, "reasoning": null, "unit": "1M_tokens", "context": 200000, "max_output": 64000, "status": "accurate" }, "new": { "name": "Claude Opus 4.5", "input": 4.5, "output": 25, "cache_read": 0.45, "cache_write": 6.25, "reasoning": null, "unit": "1M_tokens", "context": 200000, "max_output": 64000, "status": "checking" }, "source_url": "https://www.anthropic.com/pricing", "observed_at": "2026-08-31T12:04:07.000Z" } ``` **Confidence.** `old.status` and `new.status` carry the accuracy status each price held when it was published, the same values an offer carries in *Accuracy status* above: `accurate`, `checking`, `suspicious`, `partial` or `failed`. The old price keeps the confidence it was published with, so a delivery says both how far the number you were using was trusted and how far its replacement is. Status is not one of the watched fields, so a status moving on its own sends nothing. Read `new.status` before acting on a price automatically: `suspicious` and `failed` mean the number has not been confirmed. **Headers.** - `X-Indextkn-Event: price.changed` - `X-Indextkn-Delivery`: the delivery id, the same value as `id` in the body. Stable across retries, so a handler can key its idempotency on it. - `X-Indextkn-Timestamp`: unix seconds, the moment the request was signed. - `X-Indextkn-Signature`: `t=,v1=`, where `` is HMAC-SHA256 of `.` under that alert's own secret. The secret is shown when the alert is created and readable on its row afterwards. **Verifying.** Compute the HMAC over the **raw request bytes**. Re-serializing parsed JSON changes the bytes and will not match. Reject a timestamp older than five minutes, and compare in constant time. **Delivery rules.** - The endpoint must be public `https` on port 443, with no credentials in the URL. A URL that resolves to a private, loopback or link-local address is refused, and the check is repeated immediately before every send. - Answer `2xx` within 10 seconds. A `429`, a `5xx` or a timeout is retried up to 3 attempts in total, 0.5s then 1s apart; any other `4xx` is not retried. - A `3xx` is not followed and counts as a failure. Point the alert at the final URL. - After 10 consecutive failed deliveries the alert is disabled, and stays disabled until it is resumed from the dashboard. - The same change is never delivered twice to the same alert. **The log.** Every attempt is recorded against the account: the time, the listing, the fields that changed, the URL it went to, the exact body sent, and the status the endpoint returned. The dashboard shows the last 30 days. ## Errors Every error, on every endpoint, has the same shape: ```json { "error": { "code": "FILTER_REQUIRED", "message": "Narrow the request with at least one of model, provider or lab." } } ``` | Code | HTTP | Meaning | | --- | --- | --- | | `MISSING_API_KEY` | 401 | No `Authorization` header. | | `INVALID_API_KEY` | 401 | Key not found or revoked. | | `FILTER_REQUIRED` | 400 | `/prices` called without `model`, `provider` or `lab`. | | `INVALID_PARAMETER` | 400 | A missing or malformed parameter, e.g. `/compare` or `/calculate` without a valid model or token count. | | `RATE_LIMIT_EXCEEDED` | 429 | Monthly limit reached. | | `NOT_FOUND` | 404 | Unknown endpoint, unknown model id, or a non-GET method. | | `NOT_IMPLEMENTED` | 501 | Announced but not built yet (history). | | `DATA_UNAVAILABLE` | 503 | Pricing data temporarily unavailable. | | `SERVER_ERROR` | 500 | Unexpected error. | Branch on `error.code`, never on `error.message`. Messages are written for humans and may be reworded. ## Possible use cases Each of these is a real thing the current endpoints do, with the call that does it. **1. Route each request to the cheapest provider for a model.** `/compare?model=openai/gpt-oss-120b` returns every provider's price cheapest first, plus `spread_ratio` so you can see whether switching is worth the work. Refresh it on a schedule and keep the winner in your router's config. **2. Estimate a feature's cost before you build it.** `/calculate?model=anthropic/claude-opus-4.5&input_tokens=4000&output_tokens=800&requests=50000` prices 50,000 calls of that shape on every provider that sells the model, with a per-component breakdown. **3. Turn a month of token usage into a bill you can forecast.** Feed your logged input/output/cached token totals into `/calculate` with `requests=1`. Do it per model, sum the totals, and you have next month's projection at today's list prices, and, from `results`, what the same workload would cost if you moved providers. **4. Decide whether prompt caching pays off.** Call `/calculate` twice for the same workload, once with all tokens as `input_tokens`, once with the reused prefix moved to `cached_tokens`. The difference in `total_cost` is the saving, per provider, with no arithmetic on your side. **5. Show live pricing inside your own product.** `/prices?lab=anthropic` (or `?provider=`) gives every current price with `context`, `max_output`, `status` and `source_url` for attribution. Cache it, render it, and it stays right without anyone maintaining a table by hand. **6. Alert when a price moves.** Poll `/prices?provider=openai` on a schedule and diff `updated_at` per offer id: it only changes when the price does. That is a price-change alert in about ten lines, without a history endpoint. **7. Pick a model that fits the request you actually send.** `/models?modality=text` gives `context` and `max_output` per model, and `/models/:id` gives the same per provider offer. A provider often serves a smaller window than the model supports. Filter by fit first, then price the survivors with `/calculate`. **8. Give a coding agent or chat assistant a factual pricing tool.** Wrap `/compare` and `/calculate` as two tools. It turns "which model is cheapest for this job, and what would a million requests cost?" into a lookup instead of a guess from training data that went stale months ago. Prices with `status: "suspicious"` should be passed through as caveated, not dropped silently. **9. Sanity-check a vendor quote or a cloud bill.** `/compare?model=…` gives the median and the first-party price for the same model, so a quoted rate can be placed against what everyone else charges. `kind` on `/providers` separates the lab selling direct from a cloud reselling it. **10. Track open-model economics across inference hosts.** Open-weight models are sold by many hosts at very different rates. `/compare?model=…` over a list of open models, run daily, is a clean series of who is winning on price. **11. Build a cost-aware fallback chain.** `/calculate?model=…` returns every provider's total for the workload; keep the ordered list as your failover chain so a retry after an outage lands on the next-cheapest option rather than the most expensive one. **12. Answer procurement and finance questions with a citation.** Every offer carries `source_url` and `updated_at`, so a spreadsheet built from `/prices` can point at the provider page each number came from and the day it last moved. ## Not supported Being explicit, so nothing here gets invented: - **No historical prices yet.** `/api/v1/prices/history` is announced and returns `501 NOT_IMPLEMENTED` today. The site shows per-provider price history when you expand a provider row; the API does not serve it yet. - **No write endpoints.** The API is read-only. There is no way to submit or correct a price through it. Use the report button on the site or email contact@indextkn.com. - **No webhook API.** Price-change webhooks exist, but they are set up in the dashboard, not through this API: there is no endpoint that creates, lists or deletes an alert. See *Webhooks* above. There is no streaming; for anything else, poll (see use case 6). - **No pagination parameters.** Filtered responses are returned whole; narrow with `model`, `provider` or `lab`. - **No bulk dump.** `/prices` requires a filter by design. The full catalogue cannot be pulled in one call, and the Terms do not permit recreating the dataset. - **No benchmarks, quality scores or latency data.** indextkn is about price. - **No batch, flex or priority pricing**, and no per-region rates. Prompt-size tiers *are* modelled, via `price_tiers`; the other service tiers are separate products and are not tracked. - **No account, invoice or usage data from providers.** These are published list prices, not your bill. Negotiated rates, committed-use discounts, free tiers and batch pricing are not modelled. - **No currency other than USD**, and no tax or VAT handling. ## The website - **https://indextkn.com/**: the price table, with every tracked model with each provider's input, output, cache-read and cache-write prices, context window and last change. Filter by lab and modality, sort any column, search ids and tags, expand a model for its per-provider offers and each offer's price history. Statically rendered and revalidated once a minute. - **https://indextkn.com/docs**: the API reference as a page, with a playground that runs real requests against the reader's own key and shows the rate-limit headers that come back. It also carries the MCP setup page. - **https://mcp.indextkn.com/mcp**: the MCP server, for AI agents rather than code. Same key, same limit, same numbers. See *MCP server* above. - **https://github.com/indextkn/indextkn-mcp**: the MCP server's source, MIT licensed. - **https://github.com/indextkn/skills**: the agent skill's source, MIT licensed. See *Agent skill* above. - **https://indextkn.com/dashboard**: create and revoke keys, see this month's usage against the limit plus a 30-day request chart, and manage webhook alerts and their delivery log. Requires sign-in. See *Webhooks* above. - **https://indextkn.com/login**: GitHub or a one-time email link. - **https://indextkn.com/changelog**: what shipped, newest first. - **https://indextkn.com/terms**, **https://indextkn.com/privacy**: the legal pages. - **https://indextkn.com/llms.txt**: the short index this file expands on. ## Terms in brief The binding text is at https://indextkn.com/terms; this is the shape of it. Each account allows 3,000 requests per month and up to 10 price alerts, and an alert may only point at a URL you control. The data is for use inside your own products and workflows. You may not resell or redistribute the dataset as your own, use it to build or run a service that competes with indextkn, or scrape the site or API to recreate the underlying database. Prices are list prices collected automatically and provided "as is". They can be delayed, incomplete or wrong, and even a high-confidence price can be out of date. Confirm a price with the provider before relying on it. Spotted a wrong price? Use the report button on the site, or email contact@indextkn.com.