{
  "markdown": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/logo-dark.png\">\n    <img src=\"assets/logo.png\" alt=\"PromptSpend\" width=\"417\">\n  </picture>\n</p>\n\n<p align=\"center\">\n  <b>Know the tab before you build.</b><br>\n  Estimate, compare and understand what an AI feature will cost &mdash; from a catalog that re-checks itself every morning, so the numbers are never a year out of date.\n</p>\n\n<p align=\"center\">\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-green.svg\" alt=\"MIT license\"></a>\n  <img src=\"https://img.shields.io/badge/models-78-2456E6.svg\" alt=\"78 models tracked\">\n  <img src=\"https://img.shields.io/badge/providers-12-2456E6.svg\" alt=\"12 providers\">\n  <img src=\"https://img.shields.io/badge/tests-1097-blue.svg\" alt=\"1097 tests\">\n  <img src=\"https://img.shields.io/badge/initial%20payload-97%20KB%20gzip-blue.svg\" alt=\"96 KB gzip initial payload\">\n  <a href=\"https://github.com/AndrewAvery7/promptspend/actions/workflows/ci.yml\"><img src=\"https://github.com/AndrewAvery7/promptspend/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"></a>\n  <a href=\"https://github.com/AndrewAvery7/promptspend/actions/workflows/sync-pricing.yml\"><img src=\"https://github.com/AndrewAvery7/promptspend/actions/workflows/sync-pricing.yml/badge.svg\" alt=\"Sync pricing\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://promptspend.com\"><b>&rarr; Open PromptSpend</b></a>\n  &nbsp;·&nbsp; free &nbsp;·&nbsp; open source &nbsp;·&nbsp; no accounts, no tracking\n  &nbsp;·&nbsp; <a href=\"https://promptspend.com/writing/what-llm-cost-calculators-get-wrong/\">Writing: What LLM Cost Calculators Get Wrong</a>\n  &nbsp;·&nbsp; <a href=\"https://promptspend.com/writing/2026-08-price-movement-report/\">August 2026 Price Movement Report</a>\n</p>\n\n<!-- PROMO-VIDEO. A bare user-attachments URL on its own line: GitHub turns that\n     into an inline player. Markdown image or link syntax does NOT work, and a\n     release-asset URL downloads instead of playing, so do not \"tidy\" this into\n     a relative path or a link.\n\n     The URL only exists after the file is dragged into a comment box in the web\n     UI AND that comment is submitted; an abandoned draft uploads the file but\n     does not retain it, and the URL then 404s. Verify any replacement before\n     committing - a live attachment answers an unsigned request with 302 or 403,\n     never 404. See docs/PROMO.md. -->\n\nhttps://github.com/user-attachments/assets/8ddf3e53-2a97-4d86-ac93-d09507c387de\n\n<p align=\"center\">\n  <i>2 minutes 8 &mdash; press play, and hit &#128266; to unmute (GitHub starts videos silent).</i>\n  &nbsp;·&nbsp;\n  <a href=\"https://github.com/AndrewAvery7/promptspend/releases/latest/download/promptspend-promo.mp4\">Download the MP4</a>\n</p>\n\n---\n\n## Why this exists\n\nEvery LLM cost calculator on the web has the same failure mode: it is a snapshot. Someone builds it,\nhard-codes a dozen model prices, and within a few months the entire premise is wrong — the models it\ncompares have been superseded and the prices it quotes no longer exist.\n\nPromptSpend is built the other way round. **The pricing pipeline is the product**; the calculator is what\nsits on top of it. Every morning a GitHub Action re-fetches the catalog from independent sources, merges\nthem under an explicit trust order, runs sanity checks, and either commits the result or opens a pull\nrequest for a human. Capture patterns are family-level, so a brand-new model version is picked up\nautomatically without anyone touching code.\n\n## What it does\n\n- **Receipt** — copy a fully visible, one-response cost-audit object into an existing AI conversation.\n  It estimates the visible workload, retrieves current PromptSpend prices, names the two largest cost\n  drivers and suggests cheaper models worth testing without pretending price proves equal quality.\n- **Estimate** — describe one interaction (paste your real prompt or move the sliders), set your scale,\n  and see the monthly, yearly, per-user and margin numbers for up to four models side by side.\n- **Compare** — every tracked model on a price-versus-capability value map, plus a sortable catalog you\n  can select from directly, with the source and verification date for every row.\n- **Learn** — seven short interactive lessons on tokens, why output costs more, how wide the price spread\n  is, compounding chat history, hidden reasoning tokens, caching and batching, and how the data pipeline\n  works.\n- **Data & Alerts** — pipeline health, full provenance for every number with a link to the vendor page it\n  came from, what is currently flagged, and four ways to hear about a price change: an Atom feed, the\n  repository's own pull requests, browser push, and an email digest.\n\n### Price alerts\n\nOpt-in, and off unless a deployment is configured for them. Browser push stores nothing personal — a push\nsubscription is an opaque URL the browser issues. Email is double opt-in with one-click unsubscribe, and\nstores your address, the models you follow, and the date you asked; nothing else. Either channel can watch\nthe whole catalog or just the models you pick.\n\nThe delivery service is a Cloudflare Worker in [`worker/`](worker), with the push payload encryption\nwritten out against RFC 8291 and checked byte for byte against the RFC's own worked example.\n[docs/ALERTS.md](docs/ALERTS.md) has the architecture, the cost model and the domain cutover.\n\n## Things it does that most calculators get wrong\n\n|                                    |                                                                                                                                                                                                |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Output is priced separately**    | Output typically costs 3–5× input. Averaging the two, as many calculators do, understates most real workloads.                                                                                 |\n| **Chat history compounds**         | Turn _N_ re-sends turns 1…*N*−1 as input, so conversation cost grows with the square of the turn count.                                                                                        |\n| **Tokenizers differ per family**   | The same pasted text is counted with each model's own tokenizer — exactly (js-tiktoken, run in your browser) for OpenAI-family models and with a clearly labelled calibrated ratio elsewhere.  |\n| **Caching is not free**            | Cache _writes_ cost 1.25× input at both OpenAI and Anthropic. Counting only the cheaper reads reports a saving your invoice will not have, so writes are billed and caching is off by default. |\n| **Long context costs more**        | Above 272K input tokens OpenAI bills the whole request at 2× input and 1.5× output. Tiers apply per request, so a conversation can cross over partway through.                                 |\n| **Reasoning tokens are billable**  | A multiplier for hidden thinking tokens, because the visible answer is not what you pay for.                                                                                                   |\n| **Promotional pricing expires**    | Introductory rates apply only inside their window, and the engine takes a date.                                                                                                                |\n| **Assumptions are visible**        | Every non-published number used in a calculation is listed under the results, not buried — and so is what the prices do not cover.                                                             |\n| **Impossible scenarios are named** | A request that will not fit the context window, or a response past the output ceiling, is flagged rather than priced as if it would work.                                                      |\n\n## How the data stays current\n\n```\n                    ┌─────────────────────────┐\n  LiteLLM catalog ──┤                         │\n                    │   allowlist → merge →   │──→ public/data/pricing.json ──→ the app\n  OpenRouter API ───┤   validate → diff       │\n                    └───────────┬─────────────┘\n  data/pricing-overrides.json ──┘            └──→ docs/pricing-changelog.md\n       (hand-verified, wins)\n```\n\n**The trust ladder**, in order:\n\n1. **Hand-verified vendor rates** (`data/pricing-overrides.json`) — win every conflict.\n2. **The LiteLLM community catalog** — the automated daily feed; thousands of models, updated within days\n   of a release.\n3. **The OpenRouter API** — an independent cross-check, never a source of record. Reseller pricing differs\n   from first-party list pricing, so a disagreement of more than 20% _flags_ the model rather than\n   changing it.\n4. **Sanity rules** — schema validation, non-negative rates, implausibility checks, a hold on any price\n   that moved more than 50% in a single day, a floor on how many rows a source may return, and a cap on\n   how much the catalog may shrink in one run.\n\nA clean diff is committed and deployed automatically. A **newly** raised flag becomes a pull request — a\nlong-standing disagreement does not re-open one every morning. A run that loses a source or trips a size\nguard is **degraded**: it publishes nothing, records why in `public/data/sync-status.json`, and fails\nloudly. Either way the change lands in [`docs/pricing-changelog.md`](docs/pricing-changelog.md).\n\n**Nothing disappears on one bad morning.** A model missing from the feed is kept and marked `stale`, not\ndeleted; retiring one for good is a deliberate edit to `data/models-allowlist.json`.\n\n**Rung 1 is worked by hand, and the split is published rather than blurred.** Every row says which rung it\ncame from, and rows read against a vendor's own page carry the URL that was read and the date it was read.\nMost of the catalog is now vendor-sourced — but not all of it, because some vendors do not publish a\npricing page that lists their own models, and the honest answer there is to leave the row labelled\n`litellm` rather than launder an aggregator's number into a first-party claim. The models this currently\naffects are named in [docs/DEFERRED.md](docs/DEFERRED.md). A number is only worth what its worst source is,\nso the site shows you the source per row instead of an average you cannot inspect.\n\n### Something watches the promise\n\nA daily job reads **the live site** — not the copy in this repository — and raises an issue if the\npublished catalog is more than two days old. That distinction is the whole point. Between a price moving\nupstream and you seeing it, four things can fail: the sync errors, the sync opens a review pull request\nnobody merges, the deploy fails after a green sync, or the CDN serves a stale artifact. Checking the file\nin git catches the first of those. Fetching what the site actually serves catches all four.\n\nIt exists because the sync once failed at the pull-request step and nothing said so; the site quietly\nserved a day-old catalog until someone happened to look. A project that claims its numbers are never a\nyear out of date should be the first to know when they are.\n\n### Two dates, not one\n\nThe site shows **prices last changed** and **sources last checked** separately, because they answer\ndifferent questions. A quiet week in the market makes the first date age while everything works\nperfectly; a broken scheduler looks exactly the same if you only publish one date. The second comes from\na health manifest written on every run, successful or not:\n\n```\nhttps://promptspend.com/data/sync-status.json\n```\n\nRight now the first of those reads **not yet recorded**, and that is correct rather than broken. No\nvendor has moved a price since this catalog's history began on 2026-08-01, so there is no date to show —\nand the alternative, quietly displaying the last build date, would announce a price change every morning\nnothing happened. An empty field you can trust beats a populated one you cannot.\n\nThe second is now a state and not just a date: the chip on the results panel is green while the sources\nwere checked within a day, blue at two days, amber past the window the freshness monitor tolerates or\nafter a degraded run, and outlined-grey when the manifest could not be loaded at all. That last state\nexists on purpose. A status light that reads \"fine\" when its own evidence is missing is precisely the\nfailure the monitor was built to catch.\n\n### Three things a number can do\n\nKeeping \"prices last changed\" honest means the pipeline has to tell apart three events that all look\nlike a diff:\n\n|                  | What happened                                                                             | `lastChanged` |\n| ---------------- | ----------------------------------------------------------------------------------------- | ------------- |\n| **Price change** | Same source, different number. The vendor repriced.                                       | stamped       |\n| **Coverage**     | A field went from absent to present. We started recording a rate that was always offered. | untouched     |\n| **Correction**   | The number moved in the same run its source URL did. We had been reading the wrong page.  | untouched     |\n\nOnly the first is news. Conflating them is not hypothetical: reading x.ai's real pricing page instead of\nits model list once gained a long-context tier and restated a cached-input rate, and the catalog stamped\nboth as price changes — putting `PRICES CHANGED 2026-08-06` on the busiest panel of the site with no\nvendor having touched a rate. Suppressing corrections can hide a genuine same-day move, and that trade is\ndeliberate: on a catalog whose whole claim is provenance, announcing a change nobody made costs more than\nmissing one by a day.\n\n## Use the data yourself\n\nThere is a free, keyless, CORS-open API at **[promptspend.dev](https://promptspend.dev)** — no account, no\nrate limit, no logging of who calls it:\n\n```bash\ncurl https://promptspend.dev/v1/prices          # flat rows: the numbers only\ncurl https://promptspend.dev/v1/models/gpt-5    # one model, in full\ncurl https://promptspend.dev/v1/prices.csv      # the same rows, for a spreadsheet\n```\n\nFilters: `?provider=`, `?status=`, `?aliases=include`. Every response carries\n`X-PromptSpend-Generated-At`, and OpenAPI 3.1 lives at\n[`/openapi.json`](https://promptspend.dev/openapi.json). See [docs/API.md](docs/API.md).\n\n### Or inside your coding agent\n\n```bash\nclaude mcp add promptspend -- npx -y @promptspend/mcp\n```\n\nOther pricing MCP servers exist and index more models than this one. What none of them do is\ntell you **where a number came from and when it was last confirmed** — the competing server's own\ndocumentation says only that pricing \"is updated regularly\", with no verification date per price.\nThat gap is the reason this exists.\n\nIt matters more to a model than to a person. Someone reading a web page sees the interface around a\nfigure and calibrates. A model handed a bare number repeats it with whatever confidence the sentence\nimplies. Given the source and the date it can say _\"as of 1 August, per OpenAI's pricing page\"_ —\nand when two sources disagree it is told so, rather than being handed a number somebody picked.\n\nThree tools, deliberately. `estimate_cost` is the one a price lookup cannot provide: it runs **this\nrepository's cost engine**, imported rather than reimplemented, so it accounts for compounding\nconversation history, cache writes, long-context tiers and reasoning tokens — and cannot report a\nfigure that disagrees with the calculator, because it is the same code. A test asserts that.\n\nAnd because MCP tool definitions load into your context on _every_ turn, the server's own overhead is\nmeasured, budgeted at 900, checked in CI and published: **~700 tokens**. Nothing else in the\necosystem appears to publish its own footprint. See [mcp/README.md](mcp/README.md).\n\n### Or in your editor\n\nThe same catalog, on the line of code that chooses the model.\n\n```python\nresponse = client.messages.create(\n    model=\"claude-sonnet-4-6\", # $3 / $15 per M · max out $0.061\n    max_tokens=4096,\n)\n```\n\nA **linter for model choice** rather than a calculator in a sidebar — a calculator in an editor is\nonly this website with worse ergonomics. What an editor can do that a web page cannot is notice\nthings: a nearby `max_tokens` becomes the output ceiling in money, a cap above what the model can\nemit is named rather than priced as though it would work, deprecated and disputed rows reach the\nProblems panel, and an Explorer view lists every model the repository calls, dearest first.\n\nEverything it says is _Information_ severity, never a warning. None of these are mistakes and the\nextension has no idea what the code needs. The cheaper-model suggestion is off by default for the\nsame reason the value map's capability axis is labelled illustrative: it is an opinion, where the\nrest are facts.\n\n```bash\ncode --install-extension promptspend.promptspend\n```\n\nOr search **PromptSpend** in the Extensions pane. It is on\n[Open VSX](https://open-vsx.org/extension/promptspend/promptspend) too, which is where Cursor, Windsurf\nand VSCodium look.\n\nSame rule as everywhere else — no bundled prices. If the catalog cannot be reached it says so and\nshows nothing, and the status bar carries the generation date at all times. See\n[vscode/README.md](vscode/README.md).\n\nOr read the file the API reads. The catalog is plain, versioned JSON with a stable shape:\n\n```\nhttps://promptspend.com/data/pricing.json\n```\n\n```jsonc\n{\n  \"schemaVersion\": 2,\n  \"generatedAt\": \"2026-08-02T02:27:22.781Z\",\n  \"providers\": [{ \"id\": \"openai\", \"name\": \"OpenAI\", \"country\": \"US\", \"pricingUrl\": \"https://…\" }],\n  \"models\": [\n    {\n      \"id\": \"gpt-5.6-terra\",\n      \"providerId\": \"openai\",\n      \"displayName\": \"GPT-5.6 Terra\",\n      \"status\": \"current\", // current | legacy | deprecated\n      \"contextWindow\": 1050000,\n      \"pricing\": {\n        \"input\": 2, // USD per 1M tokens\n        \"output\": 12,\n        \"cachedInput\": 0.2,\n        \"cacheWrite\": 2.5, // writing costs *more* than sending\n        \"batchDiscount\": 0.5,\n        \"longContext\": { \"thresholdTokens\": 272000, \"input\": 4, \"output\": 18 },\n      },\n      \"tokenizer\": { \"kind\": \"tiktoken\", \"encoding\": \"o200k_base\" },\n      \"provenance\": {\n        \"source\": \"vendor\", // vendor | litellm | openrouter\n        \"lastVerified\": \"2026-08-01\", // when it was checked — always present\n        \"lastChanged\": \"2026-08-01\", // optional: when the number last moved\n        \"verifiedUrl\": \"https://developers.openai.com/api/docs/pricing\",\n      },\n    },\n  ],\n}\n```\n\nOptional fields worth knowing: `aliasOf` marks an id that routes to another model (so it is not counted\ntwice), `provenance.stale` marks a row upstream has stopped listing, and `provenance.needsReview` plus\n`reviewNote` carry an unresolved disagreement and both numbers involved.\n\n`provenance.lastChanged` is optional too, and **absent on every model today** — this catalog's recorded\nhistory starts on 2026-08-01 and no vendor has moved a price since, so there is no date to report. Read\nits absence as \"no change on record\", never as \"unknown freshness\": `lastVerified` answers that, and it\nis always there. A field that reported the last sync instead would read \"changed today\" every morning,\nwhich is the one failure a provenance catalog cannot afford — so it stays empty until a rate actually\nmoves.\n\n## Running it locally\n\n```bash\nnpm ci                 # `ci`, not `install` — this repo has a lockfile and CI honours it\nnpm run dev            # http://localhost:5173\n```\n\n```bash\nnpm run verify             # exactly what CI runs, and what the deploy gate runs\nnpm run sync:pricing:dry   # see what today's sync would change, without writing\n```\n\n`verify` is typecheck, lint, format check, an encoding check, tests with coverage thresholds, the\npublished test counts, the published page counts, a production build, catalog schema validation, the\nbundle budget, the Content Security Policy and the SEO checks. The deploy workflow calls the same reusable workflow CI does and\npublishes the artifact it produced, so a commit that fails any of them cannot reach the live site.\n\nIt does **not** run the other four packages' suites — CI has a job each for `api/`, `mcp/`, `vscode/` and\n`worker/`. See [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Project layout\n\n```\nsrc/lib/engine/     the cost engine — pure functions, no React, heavily tested\nsrc/receipt/        the transparent Receipt object, page and versioned instruction contract\nreceipt/            the second Vite HTML entry, published as /receipt/\nsrc/lib/tokenize/   exact tokenizer (lazy-loaded) + calibrated ratios\nsrc/lib/pricing/    catalog schema, validation, lookups\nsrc/lib/alerts/     browser-side push and alerts API client\nsrc/components/     the four views\nsrc/lib/seo/        the generated pages: slugs, page model, HTML renderer\nscripts/            the daily sync pipeline (scripts/lib is unit-tested)\ndata/               capture patterns and hand-verified overrides\npublic/data/        the published catalog the app reads\npublic/sw.js        service worker — push display only, no offline cache\nworker/             the alerts API (Cloudflare Worker, own package and tests)\napi/                the public pricing API on promptspend.dev (own package and tests)\nsrc/state/          the scenario hook, and the URL it mirrors itself into\ntests/              the browser suite: Playwright at four viewports, plus axe\ntools/              the promo pipeline — capture, render, stitch\nmcp/                the MCP server — imports the engine above, so it cannot disagree with it\nvscode/             the VS Code extension — imports it too, for the same reason\n```\n\nBeyond the calculator and Receipt, the build writes 163 crawlable pages — one per model, one per provider, and a\ncurated set of head-to-heads — from the same catalog and the same cost engine. See\n[docs/PAGES.md](docs/PAGES.md).\n\n## Design and accessibility\n\nCobalt on a cool-paper canvas, with a distinct set of _money_ colours that never change with branding:\ngreen means savings, red means this option costs more. The input/output chart pair is validated for\ncolour-vision deficiency **in CI** — `src/lib/palette.test.ts` simulates protanopia and deuteranopia and\nfails the build if the two marks stop being distinguishable.\n\n`src/lib/contrast.test.ts` reads `tokens.css` directly and fails the build if any accent, on any theme and\nany canvas, drops below 4.5:1 against a surface it can appear on. Every interactive target is at least\n24×24 (44×44 on touch), no text renders below 12px, and there is no horizontal page scroll from 320px up.\nKeyboard navigation throughout — including every point on the value map, which is a real button — focus is\nreturned when a dialog closes, `prefers-reduced-motion` is respected in JavaScript as well as CSS, and\nthere is a `Ctrl`/`Cmd`+`K` command palette.\n\n## Documentation\n\n| Document                                               | What is in it                                                                                             |\n| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |\n| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)           | How the pipeline, the engine and the state layer work, and **why** each is shaped that way                |\n| [docs/TESTING.md](docs/TESTING.md)                     | What the 1097 tests cover, the uneven coverage thresholds, and what the suite deliberately does not cover |\n| [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)     | \"The estimate does not match my bill\", flagged prices, missing models, running it locally                 |\n| [docs/PAGES.md](docs/PAGES.md)                         | The 163 generated pages: what is built, why the comparison set is curated, and the IndexNow pipeline      |\n| [docs/API.md](docs/API.md)                             | The public pricing API on `promptspend.dev` — endpoints, why it fetches rather than bundles, going live   |\n| [docs/DOMAINS.md](docs/DOMAINS.md)                     | What each hostname serves and why, plus the cutover runbook and rollback                                  |\n| [docs/ALERTS.md](docs/ALERTS.md)                       | The price-alerts Worker — push and email architecture, the cost model, the domain cutover                 |\n| [docs/pricing-changelog.md](docs/pricing-changelog.md) | Every price change the daily sync has published, written by the pipeline itself                           |\n| [CHANGELOG.md](CHANGELOG.md)                           | Changes to the application, as opposed to the data                                                        |\n| [docs/DEFERRED.md](docs/DEFERRED.md)                   | Work proposed and deliberately not done yet, with the reason — a decision, not a gap                      |\n| [docs/PROMO.md](docs/PROMO.md)                         | How the promo video is built from real screenshots, and how to rebuild it                                 |\n| [mcp/README.md](mcp/README.md)                         | The MCP server — pricing for coding agents, with the source and date on every number                      |\n| [vscode/README.md](vscode/README.md)                   | The VS Code extension — prices on the line of code that chooses the model                                 |\n| [CONTRIBUTING.md](CONTRIBUTING.md)                     | Adding a model, the house style, and the rules that are not negotiable                                    |\n| [SECURITY.md](SECURITY.md)                             | What is in scope — including a wrong price, which is treated as the most serious class of bug             |\n\n## Contributing\n\nAdding a model is usually a one-line change. See [CONTRIBUTING.md](CONTRIBUTING.md), or open a\n[model request](../../issues/new?template=model-request.yml) and someone will pick it up.\n\nNot a code change? **info@promptspend.com**. Security reports go to\n**security@promptspend.com** or GitHub's private vulnerability reporting — see [SECURITY.md](SECURITY.md).\n\nThe three rules that are not up for negotiation, because breaking any of them turns an estimator into a\nguess with good typography:\n\n1. **Never invent a rate.** If a provider does not publish a number, charge full price and say so.\n2. **A claim on screen must be true of the code.** When behaviour and copy disagree, fixing the copy is a\n   legitimate fix; leaving both is not.\n3. **No enabled control that does nothing.** Describe a planned feature — do not simulate it.\n\n## Honest limitations\n\nThe point of this section is that it is longer than it needs to be. An estimator that hides its edges is\njust a confident guess.\n\n- **Scope of the prices.** Standard-tier, global-endpoint list prices in USD. Not modelled: regional and\n  data-residency premiums (OpenAI and Anthropic both charge 1.1×), fast/priority tiers, server-side tool\n  call fees, fine-tuning, and negotiated or committed-use discounts. The site says this under every\n  estimate, not only here.\n- **\"Exact\" means exact raw text.** The tokenizer counts the string you give it. A real request also bills\n  message framing, tool definitions and any images. Treat an exact count as a floor.\n- **Token counts for non-OpenAI families are estimates** from calibrated characters-per-token ratios,\n  labelled as such everywhere they appear. Those providers do not ship a browser-runnable tokenizer.\n- **The capability axis on the value map is illustrative**, not a benchmark. Models without an estimate\n  are not plotted at all rather than being given a default, and the chart says how many that is.\n- **Caching is off by default** and the estimate charges cache writes where a provider publishes a rate.\n  Where one does not, cached tokens are billed at the full input rate rather than at an invented discount.\n  Providers that bill cache residency separately are labelled with that per-hour rate; it is excluded from\n  the estimate until a retention duration is known, and enabling caching raises an explicit warning.\n- **Long-context tiers are modelled where they are published** (per request, not per conversation). Where\n  a provider has a tier we have not recorded, the estimate says so instead of quietly using the flat rate.\n- **The Receipt sees a transcript, not an invoice.** It cannot see hidden system prompts, provider-side\n  tools or media, cache usage, hidden reasoning, or an exact billable model unless the conversation exposes\n  them. It reports ranges and exclusions, and refuses to invent a dollar amount when current pricing is\n  unavailable.\n- **Exact counting downloads a ~3 MB tokenizer chunk**, and only when you paste text for an OpenAI-family\n  model. The initial page is under 100 KB gzipped, and CI fails if that stops being true.\n\n## Privacy, precisely\n\n**The estimator itself sends nothing anywhere.** No accounts, no analytics, no cookies. Fonts are\nself-hosted. Pasted prompt text is tokenised in your browser, deliberately excluded from the shareable\nURL, held in a bounded in-memory cache, and gone when you close the tab. `localStorage` holds two things:\nwhether you dismissed the welcome banner, and your theme choice.\n\n**Price alerts are the one exception, and only if you opt in.** They are a feature you have to switch on,\nand they are the only reason this project has a server at all\n(a Cloudflare Worker — [docs/ALERTS.md](docs/ALERTS.md)). Precisely what changes:\n\n- The Content Security Policy opens `connect-src` for that one API origin, and — only where Turnstile is\n  configured — `script-src` and `frame-src` for `challenges.cloudflare.com`. Nothing else, ever. That\n  `connect-src` line is what stops a compromised dependency exfiltrating a pasted prompt, so it is\n  generated from one configured value rather than hand-maintained.\n- **Browser push stores nothing personal.** A push subscription is an opaque URL the browser issues. No\n  address, no name, nothing that identifies you.\n- **Email stores your address**, the models you follow, and the date you asked. That is the whole record.\n  No name, no raw IP (consent is recorded as a salted hash), no opens, no clicks, no third-party\n  processor. Double opt-in, one-click unsubscribe, and unconfirmed addresses are deleted within a week.\n- The alerts form never sees anything you paste into the estimator. Those are different parts of the page\n  and the prompt text never leaves the browser.\n\nA deployment with no API configured — which is what this repository builds by default — keeps a strictly\nself-only policy and says on screen that alerts are not switched on, rather than rendering a form that\ncannot work.\n\n## Security\n\nSee [SECURITY.md](SECURITY.md). Wrong prices are treated as the most serious class of bug this project\ncan have, and are explicitly in scope for a report.\n\n## Licence\n\nMIT — see [LICENSE](LICENSE). The self-hosted typefaces (Space Grotesk, IBM Plex Sans, JetBrains Mono)\nare SIL Open Font License 1.1.\n",
  "bytes": 31182,
  "sha": "fa05d52ac92d71bf7a645ced0a642ab296c8b418b36823036fde52de99507de1",
  "repo_slug": "andrewavery7/promptspend",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_andrewavery7_promptspend_7832e9fa/readme"
}