From 55bb2228f4f0b2ad92e7f184b06055067926975e Mon Sep 17 00:00:00 2001 From: Yifan Lu <111810457+yl231@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:46:49 -0500 Subject: [PATCH 1/3] Add a daily price sync with OpenRouter's list prices scripts/pricing/sync_openrouter.py sets each price profile that resolves to an OpenRouter model (through openrouter_id, or an exact id/alias match) to OpenRouter's current list price, updating as_of and source in place and regenerating model_cost.json. Profiles with sync: false keep their price. When a price changes, the run also writes model_cost/price_snapshots/.json. The Price sync workflow runs it daily and proposes the result as one pull request (branch price-sync/auto, updated in place). It changes the price table only; leaderboard costs change when the routers are re-priced. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/price-sync.yml | 59 +++++++ scripts/pricing/sync_openrouter.py | 245 +++++++++++++++++++++++++++++ tests/test_price_sync.py | 148 +++++++++++++++++ 3 files changed, 452 insertions(+) create mode 100644 .github/workflows/price-sync.yml create mode 100644 scripts/pricing/sync_openrouter.py create mode 100644 tests/test_price_sync.py diff --git a/.github/workflows/price-sync.yml b/.github/workflows/price-sync.yml new file mode 100644 index 00000000..4a60cf0f --- /dev/null +++ b/.github/workflows/price-sync.yml @@ -0,0 +1,59 @@ +name: Price sync + +# Daily sync of model_cost/model_profiles.yaml with OpenRouter's list prices. +# Every run records model_cost/price_snapshots/.json; when a price changed, the +# profiles and model_cost.json are updated as well. The result is proposed as a pull +# request (one branch, updated in place) for a maintainer to review. Leaderboard costs +# only change when the routers are re-priced, as a separate step. + +on: + schedule: + - cron: "17 6 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: price-sync + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install dependencies + run: python -m pip install pyyaml + + - name: Sync prices from OpenRouter + id: sync + run: | + set -euo pipefail + today=$(date -u +%F) + python scripts/pricing/sync_openrouter.py --date "$today" --report price-sync-report.md + echo "date=$today" >> "$GITHUB_OUTPUT" + + - name: Open or update the price-sync pull request + uses: peter-evans/create-pull-request@v7 + with: + # A personal token lets CI run on the PR; GITHUB_TOKEN-created PRs skip it. + token: ${{ secrets.PRICE_SYNC_TOKEN || github.token }} + branch: price-sync/auto + delete-branch: true + add-paths: | + model_cost/model_profiles.yaml + model_cost/model_cost.json + model_cost/price_snapshots/*.json + commit-message: "Price sync with OpenRouter (${{ steps.sync.outputs.date }})" + title: "Price sync with OpenRouter (${{ steps.sync.outputs.date }})" + body-path: price-sync-report.md + labels: pricing diff --git a/scripts/pricing/sync_openrouter.py b/scripts/pricing/sync_openrouter.py new file mode 100644 index 00000000..50634982 --- /dev/null +++ b/scripts/pricing/sync_openrouter.py @@ -0,0 +1,245 @@ +# SPDX-FileCopyrightText: Copyright contributors to the RouterArena project +# SPDX-License-Identifier: Apache-2.0 + +"""Sync model prices in ``model_cost/model_profiles.yaml`` with OpenRouter's list prices. + +For every profile that resolves to an OpenRouter model, the profile's price is set to +OpenRouter's current list price (prompt/completion, converted to USD per 1M tokens), and +``as_of`` and ``source`` are updated. ``model_cost.json`` is then regenerated. + +A profile resolves through its ``openrouter_id`` field, or else through an exact match of +its id or one of its aliases against OpenRouter's model ids. Set ``sync: false`` on a +profile to keep its price fixed (a vendor's standard price, a local model, ...). + +When a price changes, the run also writes ``model_cost/price_snapshots/.json`` with +the OpenRouter prices of every resolved model, so each change point is kept as history +(``--always-snapshot`` writes one even when nothing changed). + +Syncing changes the price table only. Leaderboard costs change when a maintainer re-prices +the routers, as a separate step. + +Usage: + python scripts/pricing/sync_openrouter.py # fetch, update, write + python scripts/pricing/sync_openrouter.py --dry-run # report only + python scripts/pricing/sync_openrouter.py --models-json f # use a saved API response + python scripts/pricing/sync_openrouter.py --report out.md # also write a Markdown report +""" + +from __future__ import annotations + +import argparse +import datetime as dt +import json +import re +import sys +import urllib.request +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import build_model_cost # noqa: E402 + +OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models" +SNAPSHOT_DIR = build_model_cost.REPO_ROOT / "model_cost" / "price_snapshots" + +Price = Tuple[float, float] + + +def per_million(per_token: Any) -> Optional[float]: + """OpenRouter prices are USD per token, as strings. Return USD per 1M tokens.""" + try: + value = float(per_token) + except (TypeError, ValueError): + return None + if value < 0: # OpenRouter uses -1 for "varies" (e.g. routers like openrouter/auto) + return None + return round(value * 1_000_000, 6) + + +def load_openrouter_prices(source: Optional[str] = None) -> Dict[str, Price]: + """Map OpenRouter model id -> (input, output) USD per 1M tokens. + + ``source`` is a saved API response (JSON file); by default the live API is fetched.""" + if source: + payload = json.loads(Path(source).read_text(encoding="utf-8")) + else: + request = urllib.request.Request( + OPENROUTER_MODELS_URL, headers={"User-Agent": "routerarena-price-sync"} + ) + with urllib.request.urlopen(request, timeout=60) as response: + payload = json.load(response) + prices: Dict[str, Price] = {} + for model in payload.get("data", []): + pricing = model.get("pricing") or {} + prompt = per_million(pricing.get("prompt")) + completion = per_million(pricing.get("completion")) + if prompt is None or completion is None: + continue + prices[model["id"]] = (prompt, completion) + return prices + + +def resolve_openrouter_id( + model_id: str, profile: Dict[str, Any], openrouter: Dict[str, Price] +) -> Optional[str]: + """The OpenRouter id a profile syncs from, or None. No fuzzy matching.""" + explicit = profile.get("openrouter_id") + if explicit: + return explicit if explicit in openrouter else None + for name in [model_id, *(profile.get("aliases") or [])]: + if name in openrouter: + return name + return None + + +def plan_updates( + profiles: Dict[str, Dict[str, Any]], openrouter: Dict[str, Price] +) -> Tuple[List[Dict[str, Any]], Dict[str, str], List[str]]: + """Return (changes, resolved, unresolved). + + ``changes`` lists profiles whose price differs from OpenRouter's; ``resolved`` maps + profile id -> OpenRouter id for every synced profile; ``unresolved`` lists profiles + that sync but match no OpenRouter model (or whose ``openrouter_id`` is unknown).""" + changes: List[Dict[str, Any]] = [] + resolved: Dict[str, str] = {} + unresolved: List[str] = [] + for model_id, profile in profiles.items(): + if profile.get("sync") is False: + continue + or_id = resolve_openrouter_id(model_id, profile, openrouter) + if or_id is None: + unresolved.append(model_id) + continue + resolved[model_id] = or_id + new = openrouter[or_id] + old = (float(profile["price"]["input"]), float(profile["price"]["output"])) + if abs(old[0] - new[0]) > 1e-9 or abs(old[1] - new[1]) > 1e-9: + changes.append( + {"model": model_id, "openrouter_id": or_id, "old": old, "new": new} + ) + return changes, resolved, unresolved + + +def _fmt(value: float) -> str: + text = f"{value:.6f}".rstrip("0").rstrip(".") + return text if text else "0" + + +def apply_updates(text: str, changes: List[Dict[str, Any]], today: str) -> str: + """Rewrite the price, as_of and source lines of each changed profile in place, + leaving every other line (comments, aliases, notes) untouched.""" + lines = text.split("\n") + for change in changes: + key = change["model"] + header = re.compile(r'^ ("?)' + re.escape(key) + r"\1:\s*$") + start = next((i for i, line in enumerate(lines) if header.match(line)), None) + if start is None: + raise ValueError(f"profile {key!r} not found as a block in the YAML file") + end = start + 1 + while end < len(lines) and lines[end].startswith(" "): + end += 1 + block = lines[start + 1 : end] + new_in, new_out = change["new"] + source = f'"https://openrouter.ai/{change["openrouter_id"]}"' + fields = { + "price": f" price: {{input: {_fmt(new_in)}, output: {_fmt(new_out)}}}", + "as_of": f' as_of: "{today}"', + "source": f" source: {source}", + } + seen = set() + for j, line in enumerate(block): + for name, replacement in fields.items(): + if line.startswith(f" {name}:"): + block[j] = replacement + seen.add(name) + block.extend( + fields[name] for name in ("price", "as_of", "source") if name not in seen + ) + lines[start + 1 : end] = block + return "\n".join(lines) + + +def write_snapshot( + resolved: Dict[str, str], openrouter: Dict[str, Price], today: str +) -> Path: + SNAPSHOT_DIR.mkdir(parents=True, exist_ok=True) + path = SNAPSHOT_DIR / f"{today}.json" + prices = { + or_id: {"input": openrouter[or_id][0], "output": openrouter[or_id][1]} + for or_id in sorted(set(resolved.values())) + } + payload = {"date": today, "source": OPENROUTER_MODELS_URL, "prices": prices} + path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + return path + + +def report(changes: List[Dict[str, Any]], unresolved: List[str], today: str) -> str: + out = [f"## OpenRouter price sync, {today}", ""] + if changes: + out += [ + f"{len(changes)} price(s) changed (USD per 1M tokens, input / output):", + "", + "| Model | OpenRouter id | Old | New |", + "| --- | --- | --- | --- |", + ] + for c in changes: + out.append( + f"| `{c['model']}` | `{c['openrouter_id']}` | " + f"{_fmt(c['old'][0])} / {_fmt(c['old'][1])} | " + f"{_fmt(c['new'][0])} / {_fmt(c['new'][1])} |" + ) + else: + out.append("No price changed.") + if unresolved: + out += [ + "", + f"{len(unresolved)} profile(s) match no OpenRouter model and kept their price " + "(add `openrouter_id`, or `sync: false` to silence): " + + ", ".join(f"`{m}`" for m in unresolved), + ] + return "\n".join(out) + "\n" + + +def main(argv: Optional[List[str]] = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--models-json", help="saved OpenRouter /models response") + parser.add_argument("--date", default=dt.date.today().isoformat()) + parser.add_argument( + "--dry-run", action="store_true", help="report only; write nothing" + ) + parser.add_argument("--report", help="also write the Markdown report to this path") + parser.add_argument( + "--always-snapshot", + action="store_true", + help="write the price snapshot even when no price changed", + ) + args = parser.parse_args(argv) + + profiles = build_model_cost.load_profiles(build_model_cost.PROFILES_PATH) + openrouter = load_openrouter_prices(args.models_json) + changes, resolved, unresolved = plan_updates(profiles, openrouter) + text = report(changes, unresolved, args.date) + print(text) + if args.report: + Path(args.report).write_text(text, encoding="utf-8") + if args.dry_run: + return 0 + + if changes or args.always_snapshot: + write_snapshot(resolved, openrouter, args.date) + if changes: + path = build_model_cost.PROFILES_PATH + path.write_text( + apply_updates(path.read_text(encoding="utf-8"), changes, args.date), + encoding="utf-8", + ) + updated = build_model_cost.load_profiles(path) # validates the edited file + build_model_cost.COST_PATH.write_text( + build_model_cost.render(build_model_cost.build_cost_table(updated)), + encoding="utf-8", + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_price_sync.py b/tests/test_price_sync.py new file mode 100644 index 00000000..b1a986e5 --- /dev/null +++ b/tests/test_price_sync.py @@ -0,0 +1,148 @@ +# SPDX-FileCopyrightText: Copyright contributors to the RouterArena project +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for the OpenRouter price sync (scripts/pricing/sync_openrouter.py).""" + +import json +import sys +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts" / "pricing")) + +import build_model_cost # noqa: E402 +import sync_openrouter # noqa: E402 + +PROFILES = """\ +# A comment that must survive. +models: + "vendor/model-a": + price: {input: 1.0, output: 2.0} + aliases: ["model-a"] + as_of: "2026-09-27" + source: null + "model-b": + price: {input: 0.5, output: 3} + openrouter_id: "google/model-b" + as_of: "2026-09-27" + source: null + note: "keep me" + "pinned": + price: {input: 9.0, output: 9.0} + sync: false + as_of: "2026-09-27" + "local-only": + price: {input: 0.1, output: 0.1} + as_of: "2026-09-27" +""" + +MODELS = { + "data": [ + { + "id": "vendor/model-a", + "pricing": {"prompt": "0.000001", "completion": "0.000002"}, + }, + { + "id": "google/model-b", + "pricing": {"prompt": "0.000000825", "completion": "0.000004125"}, + }, + {"id": "pinned", "pricing": {"prompt": "0.000001", "completion": "0.000001"}}, + {"id": "openrouter/auto", "pricing": {"prompt": "-1", "completion": "-1"}}, + ] +} + + +@pytest.fixture +def setup(tmp_path, monkeypatch): + profiles = tmp_path / "model_profiles.yaml" + profiles.write_text(PROFILES, encoding="utf-8") + models = tmp_path / "models.json" + models.write_text(json.dumps(MODELS), encoding="utf-8") + monkeypatch.setattr(build_model_cost, "PROFILES_PATH", profiles) + monkeypatch.setattr(build_model_cost, "COST_PATH", tmp_path / "model_cost.json") + monkeypatch.setattr(sync_openrouter, "SNAPSHOT_DIR", tmp_path / "snapshots") + return tmp_path, profiles, models + + +def test_prices_convert_to_per_million_and_skip_variable_prices(setup): + _, _, models = setup + prices = sync_openrouter.load_openrouter_prices(str(models)) + assert prices["google/model-b"] == (0.825, 4.125) + assert "openrouter/auto" not in prices + + +def test_plan_resolves_by_explicit_id_and_respects_sync_false(setup): + _, profiles, models = setup + loaded = build_model_cost.load_profiles(profiles) + changes, resolved, unresolved = sync_openrouter.plan_updates( + loaded, sync_openrouter.load_openrouter_prices(str(models)) + ) + assert resolved == {"vendor/model-a": "vendor/model-a", "model-b": "google/model-b"} + assert [c["model"] for c in changes] == ["model-b"] # model-a is unchanged + assert unresolved == ["local-only"] # "pinned" is skipped, not unresolved + + +def test_run_updates_only_changed_lines_and_writes_snapshot(setup): + tmp_path, profiles, models = setup + assert ( + sync_openrouter.main(["--models-json", str(models), "--date", "2026-10-30"]) + == 0 + ) + + text = profiles.read_text(encoding="utf-8") + assert text.startswith("# A comment that must survive.") + assert 'note: "keep me"' in text + data = yaml.safe_load(text)["models"] + assert data["model-b"]["price"] == {"input": 0.825, "output": 4.125} + assert data["model-b"]["as_of"] == "2026-10-30" + assert data["model-b"]["source"] == "https://openrouter.ai/google/model-b" + assert ( + data["vendor/model-a"]["as_of"] == "2026-09-27" + ) # unchanged profile untouched + assert data["pinned"]["price"] == {"input": 9.0, "output": 9.0} + + snapshot = json.loads((tmp_path / "snapshots" / "2026-10-30.json").read_text()) + assert set(snapshot["prices"]) == {"vendor/model-a", "google/model-b"} + + table = json.loads((tmp_path / "model_cost.json").read_text()) + assert table["model-b"]["output_token_price_per_million"] == 4.125 + + +def test_dry_run_writes_nothing(setup): + tmp_path, profiles, models = setup + before = profiles.read_text(encoding="utf-8") + sync_openrouter.main(["--models-json", str(models), "--dry-run"]) + assert profiles.read_text(encoding="utf-8") == before + assert not (tmp_path / "snapshots").exists() + + +def test_missing_source_and_as_of_lines_are_added(setup): + _, profiles, _ = setup + change = { + "model": "local-only", + "openrouter_id": "x/local-only", + "old": (0.1, 0.1), + "new": (0.2, 0.3), + } + text = sync_openrouter.apply_updates( + profiles.read_text(encoding="utf-8"), [change], "2026-10-30" + ) + block = yaml.safe_load(text)["models"]["local-only"] + assert block["price"] == {"input": 0.2, "output": 0.3} + assert block["source"] == "https://openrouter.ai/x/local-only" + + +def test_no_change_writes_no_snapshot_unless_asked(setup): + tmp_path, profiles, models = setup + data = json.loads(models.read_text()) + data["data"][1]["pricing"] = {"prompt": "0.0000005", "completion": "0.000003"} + models.write_text(json.dumps(data)) + sync_openrouter.main(["--models-json", str(models), "--date", "2026-10-30"]) + assert not (tmp_path / "snapshots").exists() + sync_openrouter.main( + ["--models-json", str(models), "--date", "2026-10-30", "--always-snapshot"] + ) + assert (tmp_path / "snapshots" / "2026-10-30.json").exists() From b0e04a4895a312a112474de26063437be88dd7a1 Mon Sep 17 00:00:00 2001 From: Yifan Lu Date: Sat, 31 Oct 2026 17:06:47 -0500 Subject: [PATCH 2/3] Leave the daily price-sync schedule for a later PR The manual path comes first: a maintainer runs sync_openrouter.py and re-prices the leaderboard as separate steps. The scheduled workflow will be added once that path is settled. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/price-sync.yml | 59 -------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 .github/workflows/price-sync.yml diff --git a/.github/workflows/price-sync.yml b/.github/workflows/price-sync.yml deleted file mode 100644 index 4a60cf0f..00000000 --- a/.github/workflows/price-sync.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Price sync - -# Daily sync of model_cost/model_profiles.yaml with OpenRouter's list prices. -# Every run records model_cost/price_snapshots/.json; when a price changed, the -# profiles and model_cost.json are updated as well. The result is proposed as a pull -# request (one branch, updated in place) for a maintainer to review. Leaderboard costs -# only change when the routers are re-priced, as a separate step. - -on: - schedule: - - cron: "17 6 * * *" - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -concurrency: - group: price-sync - cancel-in-progress: false - -jobs: - sync: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v5 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - - name: Install dependencies - run: python -m pip install pyyaml - - - name: Sync prices from OpenRouter - id: sync - run: | - set -euo pipefail - today=$(date -u +%F) - python scripts/pricing/sync_openrouter.py --date "$today" --report price-sync-report.md - echo "date=$today" >> "$GITHUB_OUTPUT" - - - name: Open or update the price-sync pull request - uses: peter-evans/create-pull-request@v7 - with: - # A personal token lets CI run on the PR; GITHUB_TOKEN-created PRs skip it. - token: ${{ secrets.PRICE_SYNC_TOKEN || github.token }} - branch: price-sync/auto - delete-branch: true - add-paths: | - model_cost/model_profiles.yaml - model_cost/model_cost.json - model_cost/price_snapshots/*.json - commit-message: "Price sync with OpenRouter (${{ steps.sync.outputs.date }})" - title: "Price sync with OpenRouter (${{ steps.sync.outputs.date }})" - body-path: price-sync-report.md - labels: pricing From 8d85212cbab7d8306b1b8abafeaf7ac35a937dea Mon Sep 17 00:00:00 2001 From: Yifan Lu Date: Sat, 31 Oct 2026 17:06:47 -0500 Subject: [PATCH 3/3] Sync model prices with OpenRouter's list prices of 2026-10-31 Ran scripts/pricing/sync_openrouter.py against OpenRouter's model list of 2026-10-31. 30 profiles keyed by an OpenRouter id take its list price; the prices used are recorded in model_cost/price_snapshots/2026-10-31.json. Eight profiles are pinned with sync: false and keep their price: models retired from OpenRouter (grok-4-1-fast-reasoning, claude-sonnet-4, claude-3-haiku), DeepSeek's API name deepseek-reasoner, and models never listed there (agnes-2.0-flash, GLM-4-9B-0414, DeepSeek-R1-0528-Qwen3-8B, glm-4-air). 23 older profiles that no leaderboard router uses match no OpenRouter model; the sync lists them and leaves their price. The leaderboard is not re-priced here. Co-Authored-By: Claude Opus 5.5 --- model_cost/model_cost.json | 218 +++++++-------- model_cost/model_profiles.yaml | 201 +++++++------- model_cost/price_snapshots/2026-10-31.json | 306 +++++++++++++++++++++ 3 files changed, 524 insertions(+), 201 deletions(-) create mode 100644 model_cost/price_snapshots/2026-10-31.json diff --git a/model_cost/model_cost.json b/model_cost/model_cost.json index 0ff5af5a..bf12991d 100644 --- a/model_cost/model_cost.json +++ b/model_cost/model_cost.json @@ -52,12 +52,12 @@ "output_token_price_per_million": 25 }, "anthropic/claude-opus-4.7": { - "input_token_price_per_million": 15, - "output_token_price_per_million": 75 + "input_token_price_per_million": 5, + "output_token_price_per_million": 25 }, "claude-opus-4-7": { - "input_token_price_per_million": 15, - "output_token_price_per_million": 75 + "input_token_price_per_million": 5, + "output_token_price_per_million": 25 }, "anthropic/claude-opus-5.5": { "input_token_price_per_million": 4, @@ -96,12 +96,12 @@ "output_token_price_per_million": 4.94 }, "deepseek/deepseek-chat": { - "input_token_price_per_million": 0.27, - "output_token_price_per_million": 1.1 + "input_token_price_per_million": 0.2574, + "output_token_price_per_million": 1.0287 }, "deepseek-chat": { - "input_token_price_per_million": 0.27, - "output_token_price_per_million": 1.1 + "input_token_price_per_million": 0.2574, + "output_token_price_per_million": 1.0287 }, "deepseek/deepseek-reasoner": { "input_token_price_per_million": 0.28, @@ -120,32 +120,32 @@ "output_token_price_per_million": 0.38 }, "deepseek/deepseek-v4-flash": { - "input_token_price_per_million": 0.14, - "output_token_price_per_million": 0.28 + "input_token_price_per_million": 0.091, + "output_token_price_per_million": 0.182 }, "deepseek/deepseek-v4-flash-0731": { - "input_token_price_per_million": 0.14, - "output_token_price_per_million": 0.28 + "input_token_price_per_million": 0.119, + "output_token_price_per_million": 0.238 }, "deepseek-ai/DeepSeek-V4-Flash-0731": { - "input_token_price_per_million": 0.14, - "output_token_price_per_million": 0.28 + "input_token_price_per_million": 0.119, + "output_token_price_per_million": 0.238 }, "deepseek/deepseek-v4-pro": { - "input_token_price_per_million": 0.435, - "output_token_price_per_million": 0.87 + "input_token_price_per_million": 1.6, + "output_token_price_per_million": 3.2 }, "deepseek/deepseek-v4.1-flash": { - "input_token_price_per_million": 0.09, - "output_token_price_per_million": 0.18 + "input_token_price_per_million": 0.045, + "output_token_price_per_million": 0.5 }, "deepseek/deepseek-v4.1-flash@high": { - "input_token_price_per_million": 0.09, - "output_token_price_per_million": 0.18 + "input_token_price_per_million": 0.045, + "output_token_price_per_million": 0.5 }, "deepseek/deepseek-v4.1-flash@low": { - "input_token_price_per_million": 0.09, - "output_token_price_per_million": 0.18 + "input_token_price_per_million": 0.045, + "output_token_price_per_million": 0.5 }, "gemini-2.0-flash-001": { "input_token_price_per_million": 0.1, @@ -200,12 +200,12 @@ "output_token_price_per_million": 1.5 }, "google/gemini-3.1-flash-lite-preview": { - "input_token_price_per_million": 0.1, - "output_token_price_per_million": 0.4 + "input_token_price_per_million": 0.25, + "output_token_price_per_million": 1.5 }, "gemini-3.1-flash-lite-preview": { - "input_token_price_per_million": 0.1, - "output_token_price_per_million": 0.4 + "input_token_price_per_million": 0.25, + "output_token_price_per_million": 1.5 }, "google/gemini-3.1-pro-preview": { "input_token_price_per_million": 2, @@ -224,20 +224,20 @@ "output_token_price_per_million": 3.75 }, "google/gemini-3.8-flash": { - "input_token_price_per_million": 0.75, - "output_token_price_per_million": 3.75 + "input_token_price_per_million": 0.825, + "output_token_price_per_million": 4.125 }, "gemini-3.8-flash": { - "input_token_price_per_million": 0.75, - "output_token_price_per_million": 3.75 + "input_token_price_per_million": 0.825, + "output_token_price_per_million": 4.125 }, "google/gemma-3n-e4b-it": { "input_token_price_per_million": 0.02, "output_token_price_per_million": 0.04 }, "google/gemma-4-31b-it": { - "input_token_price_per_million": 0.08, - "output_token_price_per_million": 0.35 + "input_token_price_per_million": 0.2, + "output_token_price_per_million": 0.4 }, "gpt-5-chat": { "input_token_price_per_million": 1.25, @@ -253,11 +253,11 @@ }, "inclusionai/ling-3.0-flash": { "input_token_price_per_million": 0.021, - "output_token_price_per_million": 0.062 + "output_token_price_per_million": 0.063 }, "lingdt-3.0-flash": { "input_token_price_per_million": 0.021, - "output_token_price_per_million": 0.062 + "output_token_price_per_million": 0.063 }, "llama-4-maverick-17b-128e-instruct-fp8": { "input_token_price_per_million": 0.25, @@ -276,16 +276,16 @@ "output_token_price_per_million": 3.5 }, "minimax/minimax-m3": { - "input_token_price_per_million": 0.6, - "output_token_price_per_million": 2.4 + "input_token_price_per_million": 0.3, + "output_token_price_per_million": 1.2 }, "MiniMax-M3": { - "input_token_price_per_million": 0.6, - "output_token_price_per_million": 2.4 + "input_token_price_per_million": 0.3, + "output_token_price_per_million": 1.2 }, "MiniMaxAI/MiniMax-M3": { - "input_token_price_per_million": 0.6, - "output_token_price_per_million": 2.4 + "input_token_price_per_million": 0.3, + "output_token_price_per_million": 1.2 }, "mistralai/ministral-3-14b-2512": { "input_token_price_per_million": 0.2, @@ -316,19 +316,19 @@ "output_token_price_per_million": 0.22 }, "moonshotai/kimi-k2.5": { - "input_token_price_per_million": 0.6, - "output_token_price_per_million": 3 + "input_token_price_per_million": 0.5, + "output_token_price_per_million": 2.5 }, "moonshotai/kimi-k2.6": { - "input_token_price_per_million": 0.95, - "output_token_price_per_million": 4 + "input_token_price_per_million": 0.65, + "output_token_price_per_million": 3.41 }, "moonshotai/Kimi-K2.6": { - "input_token_price_per_million": 0.95, - "output_token_price_per_million": 4 + "input_token_price_per_million": 0.65, + "output_token_price_per_million": 3.41 }, "moonshotai/kimi-k3": { - "input_token_price_per_million": 3, + "input_token_price_per_million": 0.192, "output_token_price_per_million": 15 }, "nvidia/nemotron-3-nano-30b-a3b": { @@ -452,12 +452,12 @@ "output_token_price_per_million": 15 }, "openai/gpt-5.4-mini": { - "input_token_price_per_million": 0.4, - "output_token_price_per_million": 1.6 + "input_token_price_per_million": 0.75, + "output_token_price_per_million": 4.5 }, "gpt-5.4-mini": { - "input_token_price_per_million": 0.4, - "output_token_price_per_million": 1.6 + "input_token_price_per_million": 0.75, + "output_token_price_per_million": 4.5 }, "openai/gpt-5.4-nano": { "input_token_price_per_million": 0.2, @@ -520,76 +520,76 @@ "output_token_price_per_million": 0.6 }, "openai/gpt-oss-20b": { - "input_token_price_per_million": 0.03, - "output_token_price_per_million": 0.13 + "input_token_price_per_million": 0.018, + "output_token_price_per_million": 0.09 }, "gpt-oss-20b": { - "input_token_price_per_million": 0.03, - "output_token_price_per_million": 0.13 + "input_token_price_per_million": 0.018, + "output_token_price_per_million": 0.09 }, "qwen/qwen3-235b-a22b-2507": { - "input_token_price_per_million": 0.071, - "output_token_price_per_million": 0.1 + "input_token_price_per_million": 0.09, + "output_token_price_per_million": 0.55 }, "alibaba/qwen3-235b-a22b-instruct-2507": { - "input_token_price_per_million": 0.071, - "output_token_price_per_million": 0.1 + "input_token_price_per_million": 0.09, + "output_token_price_per_million": 0.55 }, "qwen3-235b-a22b-instruct-2507": { - "input_token_price_per_million": 0.071, - "output_token_price_per_million": 0.1 + "input_token_price_per_million": 0.09, + "output_token_price_per_million": 0.55 }, "qwen_qwen3-235b-a22b-2507": { - "input_token_price_per_million": 0.071, - "output_token_price_per_million": 0.1 + "input_token_price_per_million": 0.09, + "output_token_price_per_million": 0.55 }, "qwen/qwen3-30b-a3b-instruct-2507": { - "input_token_price_per_million": 0.15, - "output_token_price_per_million": 0.6 + "input_token_price_per_million": 0.1, + "output_token_price_per_million": 0.3 }, "alibaba/qwen3-30b-a3b-instruct-2507": { - "input_token_price_per_million": 0.15, - "output_token_price_per_million": 0.6 + "input_token_price_per_million": 0.1, + "output_token_price_per_million": 0.3 }, "qwen3-30b-a3b-instruct-2507": { - "input_token_price_per_million": 0.15, - "output_token_price_per_million": 0.6 + "input_token_price_per_million": 0.1, + "output_token_price_per_million": 0.3 }, "qwen/qwen3-coder": { - "input_token_price_per_million": 0.22, - "output_token_price_per_million": 0.95 + "input_token_price_per_million": 0.3, + "output_token_price_per_million": 1 }, "qwen_qwen3-coder": { - "input_token_price_per_million": 0.22, - "output_token_price_per_million": 0.95 + "input_token_price_per_million": 0.3, + "output_token_price_per_million": 1 }, "qwen/qwen3-coder-30b-a3b-instruct": { "input_token_price_per_million": 0.07, - "output_token_price_per_million": 0.27 + "output_token_price_per_million": 0.28 }, "qwen/qwen3-coder-next": { - "input_token_price_per_million": 0.07, - "output_token_price_per_million": 0.3 + "input_token_price_per_million": 0.12, + "output_token_price_per_million": 0.8 }, "Qwen/Qwen3-Coder-Next": { - "input_token_price_per_million": 0.07, - "output_token_price_per_million": 0.3 + "input_token_price_per_million": 0.12, + "output_token_price_per_million": 0.8 }, "qwen/qwen3-next-80b-a3b-instruct": { "input_token_price_per_million": 0.09, "output_token_price_per_million": 1.1 }, "qwen/qwen3-vl-235b-a22b-instruct": { - "input_token_price_per_million": 0.2, - "output_token_price_per_million": 1.2 + "input_token_price_per_million": 0.21, + "output_token_price_per_million": 1.9 }, "qwen_qwen3-vl-235b-a22b-instruct": { - "input_token_price_per_million": 0.2, - "output_token_price_per_million": 1.2 + "input_token_price_per_million": 0.21, + "output_token_price_per_million": 1.9 }, "qwen/qwen3.5-9b": { - "input_token_price_per_million": 0.05, - "output_token_price_per_million": 0.15 + "input_token_price_per_million": 0.17, + "output_token_price_per_million": 0.25 }, "qwen/qwen3.5-flash-02-23": { "input_token_price_per_million": 0.065, @@ -624,8 +624,8 @@ "output_token_price_per_million": 0.6 }, "upstage/solar-pro4": { - "input_token_price_per_million": 0.3, - "output_token_price_per_million": 1.2 + "input_token_price_per_million": 0.15, + "output_token_price_per_million": 0.6 }, "x-ai/grok-4.1-fast": { "input_token_price_per_million": 0.2, @@ -652,63 +652,63 @@ "output_token_price_per_million": 1.5 }, "xiaomi/mimo-v2.5": { - "input_token_price_per_million": 0.14, - "output_token_price_per_million": 0.28 + "input_token_price_per_million": 0.4, + "output_token_price_per_million": 2 }, "xiaomi_mimo-v2-flash:free": { "input_token_price_per_million": 0.1, "output_token_price_per_million": 0.3 }, "z-ai/glm-4.5-air": { - "input_token_price_per_million": 0.1, - "output_token_price_per_million": 0.3 + "input_token_price_per_million": 0.13, + "output_token_price_per_million": 0.85 }, "glm-4.5-air": { - "input_token_price_per_million": 0.1, - "output_token_price_per_million": 0.3 + "input_token_price_per_million": 0.13, + "output_token_price_per_million": 0.85 }, "z-ai/glm-4.6": { - "input_token_price_per_million": 0.28, - "output_token_price_per_million": 1.12 + "input_token_price_per_million": 0.5, + "output_token_price_per_million": 2 }, "glm-4.6": { - "input_token_price_per_million": 0.28, - "output_token_price_per_million": 1.12 + "input_token_price_per_million": 0.5, + "output_token_price_per_million": 2 }, "z-ai/glm-4.7": { - "input_token_price_per_million": 0.4, - "output_token_price_per_million": 1.5 + "input_token_price_per_million": 0.6, + "output_token_price_per_million": 2.2 }, "z-ai_glm-4.7": { - "input_token_price_per_million": 0.4, - "output_token_price_per_million": 1.5 + "input_token_price_per_million": 0.6, + "output_token_price_per_million": 2.2 }, "z-ai/glm-5": { "input_token_price_per_million": 1, "output_token_price_per_million": 3.2 }, "z-ai/glm-5.3": { - "input_token_price_per_million": 0.079, + "input_token_price_per_million": 0.0699, "output_token_price_per_million": 1.4 }, "z-ai/glm-5.3@high": { - "input_token_price_per_million": 0.079, + "input_token_price_per_million": 0.0699, "output_token_price_per_million": 1.4 }, "z-ai/glm-5.3@low": { - "input_token_price_per_million": 0.079, + "input_token_price_per_million": 0.0699, "output_token_price_per_million": 1.4 }, "z-ai/glm-5.3-flash": { - "input_token_price_per_million": 0.041, - "output_token_price_per_million": 0.077 + "input_token_price_per_million": 0.0149, + "output_token_price_per_million": 0.1543 }, "glm-5.3-flash": { - "input_token_price_per_million": 0.041, - "output_token_price_per_million": 0.077 + "input_token_price_per_million": 0.0149, + "output_token_price_per_million": 0.1543 }, "z-ai/glm-5.3-flash@default": { - "input_token_price_per_million": 0.041, - "output_token_price_per_million": 0.077 + "input_token_price_per_million": 0.0149, + "output_token_price_per_million": 0.1543 } } diff --git a/model_cost/model_profiles.yaml b/model_cost/model_profiles.yaml index 82e78902..4d280dce 100644 --- a/model_cost/model_profiles.yaml +++ b/model_cost/model_profiles.yaml @@ -13,21 +13,29 @@ # as_of date the price was recorded # source URL of the published price (required for new models) # note free text, e.g. known conflicts to resolve +# openrouter_id OpenRouter id to sync from, when the key isn't one +# sync false keeps the price fixed (retired or never-listed models) # -# Prices are fixed for now; a market sync will update them later. A model name that -# matches no id or alias is charged the highest input and output price in this file. +# Profiles keyed by an OpenRouter id (or with openrouter_id) take OpenRouter's list price +# when a maintainer runs: +# python scripts/pricing/sync_openrouter.py +# which also records the prices used in model_cost/price_snapshots/.json. A model +# name that matches no id or alias is charged the highest price in this file. models: "agnes-2.0-flash": price: {input: 0.03, output: 0.15} as_of: "2026-09-27" source: null + note: "Not listed on OpenRouter." + sync: false "anthropic/claude-3-haiku": price: {input: 0.25, output: 1.25} aliases: ["claude-3-haiku-20240307"] as_of: "2026-09-27" source: null note: "claude-3-haiku-20240307: retired from OpenRouter; kept at its last listed price." + sync: false "anthropic/claude-haiku-4.5": price: {input: 1, output: 5} aliases: ["anthropic/claude-haiku-4-5-20251001", "claude-haiku-4-5", "claude-haiku-4-5-20251001", "claude-haiku-4.5"] @@ -49,10 +57,10 @@ models: as_of: "2026-09-27" source: null "anthropic/claude-opus-4.7": - price: {input: 15, output: 75} + price: {input: 5, output: 25} aliases: ["claude-opus-4-7"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/anthropic/claude-opus-4.7" "anthropic/claude-opus-5.5": price: {input: 4, output: 20} as_of: null @@ -63,6 +71,7 @@ models: as_of: "2026-09-27" source: null note: "anthropic/claude-sonnet-4: retired from OpenRouter; kept at its last listed price." + sync: false "anthropic/claude-sonnet-4.5": price: {input: 3, output: 15} aliases: ["anthropic/claude-sonnet-4-5", "claude-sonnet-4-5"] @@ -76,43 +85,47 @@ models: price: {input: 0.18, output: 0.85} as_of: "2026-09-27" source: null + note: "Not listed on OpenRouter." + sync: false "deepseek-v3.1": price: {input: 1.23, output: 4.94} as_of: "2026-09-27" source: null "deepseek/deepseek-chat": - price: {input: 0.27, output: 1.1} + price: {input: 0.2574, output: 1.0287} aliases: ["deepseek-chat"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/deepseek/deepseek-chat" "deepseek/deepseek-reasoner": price: {input: 0.28, output: 0.42} aliases: ["deepseek-reasoner"] as_of: "2026-09-27" source: null + note: "DeepSeek's API name; the model behind it changed over time, so it is not synced." + sync: false "deepseek/deepseek-v3.2": price: {input: 0.26, output: 0.38} aliases: ["deepseek-v3.2"] as_of: "2026-09-27" source: null "deepseek/deepseek-v4-flash": - price: {input: 0.14, output: 0.28} - as_of: "2026-09-27" - source: null + price: {input: 0.091, output: 0.182} + as_of: "2026-10-31" + source: "https://openrouter.ai/deepseek/deepseek-v4-flash" "deepseek/deepseek-v4-flash-0731": - price: {input: 0.14, output: 0.28} + price: {input: 0.119, output: 0.238} aliases: ["deepseek-ai/DeepSeek-V4-Flash-0731"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/deepseek/deepseek-v4-flash-0731" "deepseek/deepseek-v4-pro": - price: {input: 0.435, output: 0.87} - as_of: "2026-09-27" - source: null + price: {input: 1.6, output: 3.2} + as_of: "2026-10-31" + source: "https://openrouter.ai/deepseek/deepseek-v4-pro" "deepseek/deepseek-v4.1-flash": - price: {input: 0.09, output: 0.18} + price: {input: 0.045, output: 0.5} aliases: ["deepseek/deepseek-v4.1-flash@high", "deepseek/deepseek-v4.1-flash@low"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/deepseek/deepseek-v4.1-flash" "gemini-2.0-flash-001": price: {input: 0.1, output: 0.4} as_of: "2026-09-27" @@ -131,6 +144,7 @@ models: as_of: "2026-09-27" source: null note: "Alias glm-4-air was previously matched by substring; kept at this price." + sync: false "google/gemini-2.5-flash": price: {input: 0.3, output: 2.5} aliases: ["gemini-2.5-flash"] @@ -155,10 +169,10 @@ models: as_of: "2026-09-27" source: null "google/gemini-3.1-flash-lite-preview": - price: {input: 0.1, output: 0.4} + price: {input: 0.25, output: 1.5} aliases: ["gemini-3.1-flash-lite-preview"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/google/gemini-3.1-flash-lite-preview" "google/gemini-3.1-pro-preview": price: {input: 2, output: 12} aliases: ["gemini-3.1-pro-preview"] @@ -173,18 +187,18 @@ models: as_of: null source: null "google/gemini-3.8-flash": - price: {input: 0.75, output: 3.75} + price: {input: 0.825, output: 4.125} aliases: ["gemini-3.8-flash"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/google/gemini-3.8-flash" "google/gemma-3n-e4b-it": price: {input: 0.02, output: 0.04} as_of: "2026-09-27" source: null "google/gemma-4-31b-it": - price: {input: 0.08, output: 0.35} - as_of: "2026-09-27" - source: null + price: {input: 0.2, output: 0.4} + as_of: "2026-10-31" + source: "https://openrouter.ai/google/gemma-4-31b-it" "gpt-5-chat": price: {input: 1.25, output: 10} as_of: "2026-09-27" @@ -198,10 +212,10 @@ models: as_of: "2026-09-27" source: null "inclusionai/ling-3.0-flash": - price: {input: 0.021, output: 0.062} + price: {input: 0.021, output: 0.063} aliases: ["lingdt-3.0-flash"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/inclusionai/ling-3.0-flash" note: "lingdt-3.0-flash: priced as its public base model, per its submitter (#241)." "llama-4-maverick-17b-128e-instruct-fp8": price: {input: 0.25, output: 1} @@ -217,10 +231,10 @@ models: as_of: "2026-09-27" source: null "minimax/minimax-m3": - price: {input: 0.6, output: 2.4} + price: {input: 0.3, output: 1.2} aliases: ["MiniMax-M3", "MiniMaxAI/MiniMax-M3"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/minimax/minimax-m3" "mistralai/ministral-3-14b-2512": price: {input: 0.2, output: 0.2} as_of: "2026-09-27" @@ -247,18 +261,18 @@ models: as_of: "2026-09-27" source: null "moonshotai/kimi-k2.5": - price: {input: 0.6, output: 3} - as_of: "2026-09-27" - source: null + price: {input: 0.5, output: 2.5} + as_of: "2026-10-31" + source: "https://openrouter.ai/moonshotai/kimi-k2.5" "moonshotai/kimi-k2.6": - price: {input: 0.95, output: 4} + price: {input: 0.65, output: 3.41} aliases: ["moonshotai/Kimi-K2.6"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/moonshotai/kimi-k2.6" "moonshotai/kimi-k3": - price: {input: 3, output: 15} - as_of: null - source: null + price: {input: 0.192, output: 15} + as_of: "2026-10-31" + source: "https://openrouter.ai/moonshotai/kimi-k3" "nvidia/nemotron-3-nano-30b-a3b": price: {input: 0.06, output: 0.24} aliases: ["nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B"] @@ -331,10 +345,10 @@ models: as_of: "2026-09-27" source: null "openai/gpt-5.4-mini": - price: {input: 0.4, output: 1.6} + price: {input: 0.75, output: 4.5} aliases: ["gpt-5.4-mini"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/openai/gpt-5.4-mini" "openai/gpt-5.4-nano": price: {input: 0.2, output: 1.25} aliases: ["gpt-5.4-nano"] @@ -376,49 +390,49 @@ models: as_of: "2026-09-27" source: null "openai/gpt-oss-20b": - price: {input: 0.03, output: 0.13} + price: {input: 0.018, output: 0.09} aliases: ["gpt-oss-20b"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/openai/gpt-oss-20b" "qwen/qwen3-235b-a22b-2507": - price: {input: 0.071, output: 0.1} + price: {input: 0.09, output: 0.55} aliases: ["alibaba/qwen3-235b-a22b-instruct-2507", "qwen3-235b-a22b-instruct-2507", "qwen_qwen3-235b-a22b-2507"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-235b-a22b-2507" note: "Unified spellings; previous prices: qwen3-235b-a22b-instruct-2507 0.5/2. alibaba/qwen3-235b-a22b-instruct-2507: same model, Alibaba's spelling." "qwen/qwen3-30b-a3b-instruct-2507": - price: {input: 0.15, output: 0.6} + price: {input: 0.1, output: 0.3} aliases: ["alibaba/qwen3-30b-a3b-instruct-2507", "qwen3-30b-a3b-instruct-2507"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-30b-a3b-instruct-2507" note: "Unified spellings; previous prices: qwen/qwen3-30b-a3b-instruct-2507 0.08/0.33." "qwen/qwen3-coder": - price: {input: 0.22, output: 0.95} + price: {input: 0.3, output: 1} aliases: ["qwen_qwen3-coder"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-coder" "qwen/qwen3-coder-30b-a3b-instruct": - price: {input: 0.07, output: 0.27} - as_of: "2026-09-27" - source: null + price: {input: 0.07, output: 0.28} + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-coder-30b-a3b-instruct" "qwen/qwen3-coder-next": - price: {input: 0.07, output: 0.3} + price: {input: 0.12, output: 0.8} aliases: ["Qwen/Qwen3-Coder-Next"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-coder-next" "qwen/qwen3-next-80b-a3b-instruct": price: {input: 0.09, output: 1.1} as_of: "2026-09-27" source: null "qwen/qwen3-vl-235b-a22b-instruct": - price: {input: 0.2, output: 1.2} + price: {input: 0.21, output: 1.9} aliases: ["qwen_qwen3-vl-235b-a22b-instruct"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3-vl-235b-a22b-instruct" "qwen/qwen3.5-9b": - price: {input: 0.05, output: 0.15} - as_of: "2026-09-27" - source: null + price: {input: 0.17, output: 0.25} + as_of: "2026-10-31" + source: "https://openrouter.ai/qwen/qwen3.5-9b" "qwen/qwen3.5-flash-02-23": price: {input: 0.065, output: 0.26} as_of: "2026-09-27" @@ -439,6 +453,8 @@ models: price: {input: 0.05, output: 0.61} as_of: "2026-09-27" source: null + note: "Not listed on OpenRouter." + sync: false "upstage/solar-mini": price: {input: 0.15, output: 0.15} as_of: null @@ -452,15 +468,16 @@ models: as_of: null source: null "upstage/solar-pro4": - price: {input: 0.3, output: 1.2} - as_of: null - source: null + price: {input: 0.15, output: 0.6} + as_of: "2026-10-31" + source: "https://openrouter.ai/upstage/solar-pro4" "x-ai/grok-4.1-fast": price: {input: 0.2, output: 0.5} aliases: ["grok-4-1-fast-reasoning", "x-ai_grok-4.1-fast"] as_of: "2026-09-27" source: null note: "grok-4-1-fast-reasoning: retired from OpenRouter; kept at its last listed price." + sync: false "x-ai/grok-4.3": price: {input: 1.25, output: 2.5} aliases: ["grok-4.3"] @@ -471,40 +488,40 @@ models: as_of: "2026-09-27" source: null "xiaomi/mimo-v2.5": - price: {input: 0.14, output: 0.28} - as_of: "2026-09-27" - source: null + price: {input: 0.4, output: 2} + as_of: "2026-10-31" + source: "https://openrouter.ai/xiaomi/mimo-v2.5" "xiaomi_mimo-v2-flash:free": price: {input: 0.1, output: 0.3} as_of: "2026-09-27" source: null "z-ai/glm-4.5-air": - price: {input: 0.1, output: 0.3} + price: {input: 0.13, output: 0.85} aliases: ["glm-4.5-air"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/z-ai/glm-4.5-air" "z-ai/glm-4.6": - price: {input: 0.28, output: 1.12} + price: {input: 0.5, output: 2} aliases: ["glm-4.6"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/z-ai/glm-4.6" "z-ai/glm-4.7": - price: {input: 0.4, output: 1.5} + price: {input: 0.6, output: 2.2} aliases: ["z-ai_glm-4.7"] - as_of: "2026-09-27" - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/z-ai/glm-4.7" "z-ai/glm-5": price: {input: 1, output: 3.2} as_of: "2026-09-27" source: null "z-ai/glm-5.3": - price: {input: 0.079, output: 1.4} + price: {input: 0.0699, output: 1.4} aliases: ["z-ai/glm-5.3@high", "z-ai/glm-5.3@low"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/z-ai/glm-5.3" "z-ai/glm-5.3-flash": - price: {input: 0.041, output: 0.077} + price: {input: 0.0149, output: 0.1543} aliases: ["glm-5.3-flash", "z-ai/glm-5.3-flash@default"] - as_of: null - source: null + as_of: "2026-10-31" + source: "https://openrouter.ai/z-ai/glm-5.3-flash" note: "Unified spellings; previous prices: glm-5.3-flash 0.15/0.5; z-ai/glm-5.3-flash 0.15/0.5." diff --git a/model_cost/price_snapshots/2026-10-31.json b/model_cost/price_snapshots/2026-10-31.json new file mode 100644 index 00000000..3a438eed --- /dev/null +++ b/model_cost/price_snapshots/2026-10-31.json @@ -0,0 +1,306 @@ +{ + "date": "2026-10-31", + "source": "https://openrouter.ai/api/v1/models", + "prices": { + "anthropic/claude-haiku-4.5": { + "input": 1.0, + "output": 5.0 + }, + "anthropic/claude-haiku-5.5": { + "input": 0.1, + "output": 0.5 + }, + "anthropic/claude-opus-4.1": { + "input": 15.0, + "output": 75.0 + }, + "anthropic/claude-opus-4.6": { + "input": 5.0, + "output": 25.0 + }, + "anthropic/claude-opus-4.7": { + "input": 5.0, + "output": 25.0 + }, + "anthropic/claude-opus-5.5": { + "input": 4.0, + "output": 20.0 + }, + "anthropic/claude-sonnet-4.5": { + "input": 3.0, + "output": 15.0 + }, + "anthropic/claude-sonnet-5.5": { + "input": 2.0, + "output": 10.0 + }, + "deepseek/deepseek-chat": { + "input": 0.2574, + "output": 1.0287 + }, + "deepseek/deepseek-v3.2": { + "input": 0.26, + "output": 0.38 + }, + "deepseek/deepseek-v4-flash": { + "input": 0.091, + "output": 0.182 + }, + "deepseek/deepseek-v4-flash-0731": { + "input": 0.119, + "output": 0.238 + }, + "deepseek/deepseek-v4-pro": { + "input": 1.6, + "output": 3.2 + }, + "deepseek/deepseek-v4.1-flash": { + "input": 0.045, + "output": 0.5 + }, + "google/gemini-2.5-flash": { + "input": 0.3, + "output": 2.5 + }, + "google/gemini-2.5-flash-lite": { + "input": 0.1, + "output": 0.4 + }, + "google/gemini-2.5-pro": { + "input": 1.25, + "output": 10.0 + }, + "google/gemini-3-flash-preview": { + "input": 0.5, + "output": 3.0 + }, + "google/gemini-3.1-flash-lite": { + "input": 0.25, + "output": 1.5 + }, + "google/gemini-3.1-flash-lite-preview": { + "input": 0.25, + "output": 1.5 + }, + "google/gemini-3.1-pro-preview": { + "input": 2.0, + "output": 12.0 + }, + "google/gemini-3.5-flash-lite": { + "input": 0.3, + "output": 2.5 + }, + "google/gemini-3.7-flash": { + "input": 0.75, + "output": 3.75 + }, + "google/gemini-3.8-flash": { + "input": 0.825, + "output": 4.125 + }, + "google/gemma-4-31b-it": { + "input": 0.2, + "output": 0.4 + }, + "inclusionai/ling-3.0-flash": { + "input": 0.021, + "output": 0.063 + }, + "meta-llama/llama-3.3-70b-instruct": { + "input": 0.1, + "output": 0.32 + }, + "minimax/minimax-m3": { + "input": 0.3, + "output": 1.2 + }, + "mistralai/ministral-3b-2512": { + "input": 0.1, + "output": 0.1 + }, + "mistralai/ministral-8b-2512": { + "input": 0.15, + "output": 0.15 + }, + "moonshotai/kimi-k2.5": { + "input": 0.5, + "output": 2.5 + }, + "moonshotai/kimi-k2.6": { + "input": 0.65, + "output": 3.41 + }, + "moonshotai/kimi-k3": { + "input": 0.192, + "output": 15.0 + }, + "nvidia/nemotron-3-nano-30b-a3b": { + "input": 0.06, + "output": 0.24 + }, + "openai/gpt-4.1": { + "input": 2.0, + "output": 8.0 + }, + "openai/gpt-4.1-mini": { + "input": 0.4, + "output": 1.6 + }, + "openai/gpt-4.1-nano": { + "input": 0.1, + "output": 0.4 + }, + "openai/gpt-4o": { + "input": 2.5, + "output": 10.0 + }, + "openai/gpt-4o-mini": { + "input": 0.15, + "output": 0.6 + }, + "openai/gpt-5": { + "input": 1.25, + "output": 10.0 + }, + "openai/gpt-5-mini": { + "input": 0.25, + "output": 2.0 + }, + "openai/gpt-5-nano": { + "input": 0.05, + "output": 0.4 + }, + "openai/gpt-5.1": { + "input": 1.25, + "output": 10.0 + }, + "openai/gpt-5.2": { + "input": 1.75, + "output": 14.0 + }, + "openai/gpt-5.2-chat": { + "input": 1.75, + "output": 14.0 + }, + "openai/gpt-5.4": { + "input": 2.5, + "output": 15.0 + }, + "openai/gpt-5.4-mini": { + "input": 0.75, + "output": 4.5 + }, + "openai/gpt-5.4-nano": { + "input": 0.2, + "output": 1.25 + }, + "openai/gpt-5.5": { + "input": 5.0, + "output": 30.0 + }, + "openai/gpt-5.6-luna": { + "input": 0.2, + "output": 1.2 + }, + "openai/gpt-5.6-sol": { + "input": 4.0, + "output": 20.0 + }, + "openai/gpt-5.6-terra": { + "input": 2.0, + "output": 12.0 + }, + "openai/gpt-6-luna": { + "input": 0.1, + "output": 0.5 + }, + "openai/gpt-6-sol": { + "input": 2.0, + "output": 10.0 + }, + "openai/gpt-6.1-sol": { + "input": 2.0, + "output": 10.0 + }, + "openai/gpt-oss-120b": { + "input": 0.15, + "output": 0.6 + }, + "openai/gpt-oss-20b": { + "input": 0.018, + "output": 0.09 + }, + "qwen/qwen3-235b-a22b-2507": { + "input": 0.09, + "output": 0.55 + }, + "qwen/qwen3-30b-a3b-instruct-2507": { + "input": 0.1, + "output": 0.3 + }, + "qwen/qwen3-coder": { + "input": 0.3, + "output": 1.0 + }, + "qwen/qwen3-coder-30b-a3b-instruct": { + "input": 0.07, + "output": 0.28 + }, + "qwen/qwen3-coder-next": { + "input": 0.12, + "output": 0.8 + }, + "qwen/qwen3-next-80b-a3b-instruct": { + "input": 0.09, + "output": 1.1 + }, + "qwen/qwen3-vl-235b-a22b-instruct": { + "input": 0.21, + "output": 1.9 + }, + "qwen/qwen3.5-9b": { + "input": 0.17, + "output": 0.25 + }, + "qwen/qwen3.5-flash-02-23": { + "input": 0.065, + "output": 0.26 + }, + "upstage/solar-pro4": { + "input": 0.15, + "output": 0.6 + }, + "x-ai/grok-4.3": { + "input": 1.25, + "output": 2.5 + }, + "xiaomi/mimo-v2.5": { + "input": 0.4, + "output": 2.0 + }, + "z-ai/glm-4.5-air": { + "input": 0.13, + "output": 0.85 + }, + "z-ai/glm-4.6": { + "input": 0.5, + "output": 2.0 + }, + "z-ai/glm-4.7": { + "input": 0.6, + "output": 2.2 + }, + "z-ai/glm-5": { + "input": 1.0, + "output": 3.2 + }, + "z-ai/glm-5.3": { + "input": 0.0699, + "output": 1.4 + }, + "z-ai/glm-5.3-flash": { + "input": 0.0149, + "output": 0.1543 + } + } +}