Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/pricing-history.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Dated OpenAI pricing

Research window: **2025-08-07 to 2026-09-05**, covering more than twelve months.
Catalog: `public/pricing-catalog.js`, version `2026-09-05.1`.
Last source review: 2026-09-05 (Europe/Paris). Next routine review: 2026-10-05.
Research window: **2025-08-07 to 2026-09-23**, covering more than twelve months.
Catalog: `public/pricing-catalog.js`, version `2026-09-23.1`.
Last source review: 2026-09-23 (Europe/Paris). Next routine review: 2026-10-23.

## What the dashboard measures

Expand Down Expand Up @@ -35,6 +35,7 @@ Standard text-token amounts below are USD per million **input / cached input / o
| 2026-08-05 | GPT-5.6 Fast long context | Requests above 272K input tokens become supported in Fast | [Dated API changelog](https://developers.openai.com/api/docs/changelog) |
| 2026-08-21 | **Sol promotional price cut** | 4 / 0.40 / 20; credits become 100 / 10 / 500 | [Dated changelog](https://developers.openai.com/api/docs/changelog), [API and credit reduction notice](https://openai.com/index/gpt-5-6/), [credit card](https://learn.chatgpt.com/docs/pricing) |
| 2026-09-03 | GPT-6 Astra | 10 / 1 / 50; API Fast 2x. Credits: 250 / 25 / 1,250; Codex Fast 2.5x | [Launch date](https://developers.openai.com/api/docs/changelog), [Astra rules](https://developers.openai.com/api/docs/models/gpt-6-astra), [API prices](https://developers.openai.com/api/docs/pricing), [credits](https://learn.chatgpt.com/docs/pricing) |
| 2026-09-22 | GPT-6 Sol and Luna | API input / cached / output: Sol $2 / $0.20 / $10; Luna $0.10 / $0.01 / $0.50. Codex credits: Sol 50 / 5 / 250; Luna 2.5 / 0.25 / 12.5. API Fast is 2x; Codex Fast is 2.5x. API cache writes cost 1.25x input; Codex has no separate cache-write charge. | [Launch and API price change](https://openai.com/index/introducing-gpt-6-sol-and-luna/), [API rate card](https://developers.openai.com/api/docs/pricing), [Sol model card](https://developers.openai.com/api/docs/models/gpt-6-sol), [Luna model card](https://developers.openai.com/api/docs/models/gpt-6-luna), [Codex credit rates](https://learn.chatgpt.com/docs/pricing), [Codex Fast multiplier](https://learn.chatgpt.com/docs/agent-configuration/speed) |

The changelog also covers image, audio, video, tools and storage products. Examples in this window include the December 2025 image-model release, April 2026 image-model release and June 2026 container billing granularity change. Their counters/units are not present in this dashboard's token observations, so they are not silently mixed into text-token estimates. This is a sourced history for supported text/Codex models, not a claim to reconstruct every OpenAI product's invoices or every account-specific contract.

Expand All @@ -49,9 +50,9 @@ The changelog also covers image, audio, video, tools and storage products. Examp
- Historical Fast availability is intentionally conservative. Where only the current API Fast card was verified, the catalog does not invent an earlier start date. GPT-5.5 Priority is supported from its API launch announcement; GPT-5.6 Fast and its long-context eligibility have separate dated gates. Fast long context for GPT-5.4/5.5 has no published applicable price in the reviewed current table.
- Sol's promotion is guaranteed **at least through November 21**, not necessarily ending then. `promotionMinimumUntil` / `reviewAfter` are review reminders, not price-expiration dates. No automatic restoration to the old rate occurs.

## Astra and accounting boundaries
## GPT-6 and accounting boundaries

For API Astra, prompts **strictly above 272,000 input tokens** apply 2x to input/cache and 1.5x to output for the whole request. Fast applies another 2x. These API factors are not copied into the credit calculator; its independent published Fast factor is 2.5x.
For GPT-6 Astra, Sol, and Luna API calls, prompts **strictly above 272,000 input tokens** apply 2x to input/cache and 1.5x to output for the whole request. API Fast applies another 2x. These API factors are not copied into the credit calculator; Codex credits use their own published 2.5x Fast factor.

Input includes cached reads and cache writes, so ordinary fresh input is `inputTokens - cachedInputTokens - cacheWriteInputTokens`. The [official cache cost example](https://developers.openai.com/api/docs/guides/prompt-caching) confirms this accounting. Reasoning output is already part of output tokens and is not charged twice. Invalid/nonfinite counters and cache exceeding input are reported rather than coerced into a plausible amount. A missing published cached rate is `null`, not a free cache price.

Expand Down
18 changes: 15 additions & 3 deletions public/pricing-catalog.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Public, offline rate history. Evidence and maintenance: docs/pricing-history.md.
// Day-only announcements use 00:00 UTC; their boundary day remains estimated.
export const PRICING_CATALOG_VERSION = "2026-09-05.1";
export const PRICING_VERIFIED_AT = "2026-09-05";
export const PRICING_CATALOG_VERSION = "2026-09-23.1";
export const PRICING_VERIFIED_AT = "2026-09-23";
export const PRICING_RESEARCHED_FROM = "2025-08-07";
export const PRICING_REVIEW_AFTER = "2026-10-05";
export const PRICING_REVIEW_AFTER = "2026-10-23";
export const PRICING_SOURCES = Object.freeze({
api: "https://developers.openai.com/api/docs/pricing",
credits: "https://learn.chatgpt.com/docs/pricing",
codexChangelog: "https://learn.chatgpt.com/docs/changelog",
changelog: "https://developers.openai.com/api/docs/changelog",
speed: "https://learn.chatgpt.com/docs/agent-configuration/speed",
gpt5: "https://openai.com/index/introducing-gpt-5-for-developers/",
Expand All @@ -17,6 +18,9 @@ export const PRICING_SOURCES = Object.freeze({
gpt56: "https://openai.com/index/gpt-5-6/",
julyCut: "https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/",
astra: "https://developers.openai.com/api/docs/models/gpt-6-astra",
gpt6Launch: "https://openai.com/index/introducing-gpt-6-sol-and-luna/",
gpt6Sol: "https://developers.openai.com/api/docs/models/gpt-6-sol",
gpt6Luna: "https://developers.openai.com/api/docs/models/gpt-6-luna",
});

const entries = [];
Expand Down Expand Up @@ -71,6 +75,12 @@ api("gpt-6-astra", "2026-09-03", [10, 1, 50], {
...long, fastMultiplier: 2, fastFrom: "2026-09-03", fastLongContextFrom: "2026-09-03",
cacheWriteMultiplier: 1.25, sources: ["astra", "api", "changelog"],
});
const gpt6Api = {
...long, fastMultiplier: 2, fastFrom: "2026-09-22", fastLongContextFrom: "2026-09-22",
cacheWriteMultiplier: 1.25,
};
api("gpt-6-sol", "2026-09-22", [2, 0.2, 10], { ...gpt6Api, sources: ["gpt6Launch", "gpt6Sol", "api", "changelog"] });
api("gpt-6-luna", "2026-09-22", [0.1, 0.01, 0.5], { ...gpt6Api, sources: ["gpt6Launch", "gpt6Luna", "api", "changelog"] });

// Codex credits are independent of API USD. Before our dated observation on
// August 11 the older token rate cards are not recoverable from these sources.
Expand All @@ -87,6 +97,8 @@ for (const [model, values, multiplier] of [
credits("gpt-5.6-sol", "2026-08-11", [125, 12.5, 750], creditOptions(2.5));
credits("gpt-5.6-sol", "2026-08-21", [100, 10, 500], { ...creditOptions(2.5), evidence: "documented", sources: ["credits", "gpt56", "speed"], promotionMinimumUntil: "2026-11-21", reviewAfter: "2026-11-21" });
credits("gpt-6-astra", "2026-09-03", [250, 25, 1250], { fastMultiplier: 2.5, fastFrom: "2026-09-03", sources: ["credits", "speed", "changelog"] });
credits("gpt-6-sol", "2026-09-22", [50, 5, 250], { fastMultiplier: 2.5, fastFrom: "2026-09-22", sources: ["credits", "speed", "codexChangelog"] });
credits("gpt-6-luna", "2026-09-22", [2.5, 0.25, 12.5], { fastMultiplier: 2.5, fastFrom: "2026-09-22", sources: ["credits", "speed", "codexChangelog"] });

for (const entry of entries) {
const next = entries.filter((candidate) => candidate.billing === entry.billing && candidate.model === entry.model && candidate.effectiveFrom > entry.effectiveFrom).sort((a, b) => a.effectiveFrom.localeCompare(b.effectiveFrom))[0];
Expand Down
17 changes: 17 additions & 0 deletions test/api-pricing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ test("recognizes current GPT-5.6 Sol pricing without a fallback", () => {
});
});

test("recognizes GPT-6 Sol and Luna API rates and Fast pricing", () => {
const pricing = mergeApiPricing();
for (const [model, input, cached, output, expectedFastCost] of [
["gpt-6-sol", 2, 0.2, 10, 0.8],
["gpt-6-luna", 0.1, 0.01, 0.5, 0.04],
]) {
assert.deepEqual(apiPriceFor(pricing, model), { input, cached, output, exact: true, key: model });
assert.equal(apiFastMultiplierFor(pricing, model, "fast"), 2);
const result = apiCostOfCalls([{
timestamp: "2026-09-23T12:00:00Z", model, serviceTier: "fast",
usage: usage(200_000, 0, 0),
}], pricing);
assert.equal(result.cost, expectedFastCost);
assert.equal(result.unratedCalls, 0);
}
});

test("separates fresh, cached, and output cost", () => {
const result = apiCostOfCalls([{
timestamp: "2026-08-14T12:00:00Z", model: "gpt-5.6-sol",
Expand Down
62 changes: 62 additions & 0 deletions test/dated-pricing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@ test("Astra long context starts strictly above 272000 and stacks with API Fast",
near(apiCostOfCalls(calls).cost, 9.9);
});

test("GPT-6 Sol and Luna use separate dated API and Codex rates", () => {
const usage = { inputTokens: 1_000_000, cachedInputTokens: 400_000, cacheWriteInputTokens: 100_000, outputTokens: 200_000 };
const cases = [
{ model: "gpt-6-sol", apiStandard: 5.66, apiFast: 11.32, cacheWriteCost: 1, creditsStandard: 82, creditsFast: 205 },
{ model: "gpt-6-luna", apiStandard: 0.283, apiFast: 0.566, cacheWriteCost: 0.05, creditsStandard: 4.1, creditsFast: 10.25 },
];
for (const entry of cases) {
const callAtRelease = call(entry.model, "2026-09-22T12:00:00Z", usage, "fast");
const api = apiCostOfCalls([callAtRelease]);
const credits = codexCreditsOfCalls([callAtRelease]);
near(api.standardCost, entry.apiStandard);
near(api.cost, entry.apiFast);
near(api.cacheWriteCost, entry.cacheWriteCost);
near(credits.standardCredits, entry.creditsStandard);
near(credits.credits, entry.creditsFast);
assert.equal(api.unratedCalls, 0);
assert.equal(credits.unratedCalls, 0);
assert.equal(resolveRate("api", `${entry.model}-2026-09-22`, "2026-09-23").rate.model, entry.model);
assert.equal(apiCostOfCalls([call(entry.model, "2026-09-21", usage)]).unratedCalls, 1);
assert.equal(codexCreditsOfCalls([call(entry.model, "2026-09-21", usage)]).unratedCalls, 1);
}
});

test("GPT-6 Sol and Luna API long-context prices start strictly above 272000", () => {
for (const [model, atBoundary, aboveBoundary] of [
["gpt-6-sol", 0.544, 1.088004],
["gpt-6-luna", 0.0272, 0.0544002],
]) {
near(apiCostOfCalls([call(model, "2026-09-23", { inputTokens: 272_000, cachedInputTokens: 0, outputTokens: 0 })]).cost, atBoundary);
const result = apiCostOfCalls([call(model, "2026-09-23", { inputTokens: 272_001, cachedInputTokens: 0, outputTokens: 0 })]);
near(result.cost, aboveBoundary);
assert.equal(result.longContextCalls, 1);
}
});

test("Fast long-context availability is dated independently from the base price", () => {
const usage = { inputTokens: 300_000, cachedInputTokens: 0, outputTokens: 10_000 };
assert.equal(apiCostOfCalls([call("gpt-5.6-sol", "2026-08-04", usage, "fast")]).unratedCalls, 1);
Expand Down Expand Up @@ -177,6 +212,33 @@ test("the existing collector preserves Astra model, timestamp, counters and serv
} finally { await rm(dir, { recursive: true, force: true }); }
});

test("the collector and Mesh preserve GPT-6 Sol and Luna for both price calculators", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "gpt6-pricing-"));
try {
const file = path.join(dir, "rollout.jsonl");
const rows = [
{ type: "session_meta", timestamp: "2026-09-23T10:00:00Z", payload: { id: "gpt6-fixture", cwd: "C:/private/project", source: "cli" } },
{ type: "event_msg", timestamp: "2026-09-23T10:00:01Z", payload: { type: "task_started", turn_id: "sol-turn" } },
{ type: "turn_context", timestamp: "2026-09-23T10:00:02Z", payload: { turn_id: "sol-turn", model: "gpt-6-sol", effort: "medium", service_tier: "fast" } },
{ type: "event_msg", timestamp: "2026-09-23T10:00:03Z", payload: { type: "token_count", info: { last_token_usage: { input_tokens: 1000, cached_input_tokens: 500, cache_write_input_tokens: 200, output_tokens: 100, total_tokens: 1100 } } } },
{ type: "event_msg", timestamp: "2026-09-23T10:00:04Z", payload: { type: "task_started", turn_id: "luna-turn" } },
{ type: "turn_context", timestamp: "2026-09-23T10:00:05Z", payload: { turn_id: "luna-turn", model: "gpt-6-luna", effort: "low", service_tier: "default" } },
{ type: "event_msg", timestamp: "2026-09-23T10:00:06Z", payload: { type: "token_count", info: { last_token_usage: { input_tokens: 1000, cached_input_tokens: 500, cache_write_input_tokens: 0, output_tokens: 100, total_tokens: 1100 } } } },
];
await writeFile(file, rows.map((row) => JSON.stringify(row)).join("\n"));
const session = await parseSessionFile(file);
const mesh = sanitizeUsageForMesh({ sessions: [session], generatedAt: "2026-09-23T10:01:00Z" }, { projectSalt: "test" });
const captured = mesh.sessions[0].calls;
assert.deepEqual(captured.map((item) => item.model), ["gpt-6-sol", "gpt-6-luna"]);
assert.deepEqual(captured.map((item) => item.serviceTier), ["fast", "default"]);
assert.equal(captured[0].usage.cacheWriteInputTokens, 200);
assert.equal(apiCostOfCalls(captured).unratedCalls, 0);
assert.equal(codexCreditsOfCalls(captured).unratedCalls, 0);
validateSyncPayload({ kind: "sync", snapshotVersion: 1, analyzerVersion: mesh.analyzerVersion, generatedAt: mesh.generatedAt, privacy: mesh.privacy, upserts: mesh.sessions, removals: [] });
assert.ok(!JSON.stringify(mesh).includes("C:/private/project"));
} finally { await rm(dir, { recursive: true, force: true }); }
});

test("Astra cache writes replace ordinary input and stack with long context and Fast", () => {
const usage = { inputTokens: 300000, cachedInputTokens: 100000, cacheWriteInputTokens: 100000, outputTokens: 10000 };
const result = apiCostOfCalls([call("gpt-6-astra", "2026-09-04", usage, "fast")]);
Expand Down
17 changes: 17 additions & 0 deletions test/usage-pricing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ test("keeps standard calls at the base Codex credit rate", () => {
assert.equal(result.fastPremiumCredits, 0);
});

test("applies published GPT-6 Sol and Luna Codex rates and Fast multipliers", () => {
const input = { inputTokens: 1_000_000, cachedInputTokens: 0, outputTokens: 0 };
for (const [model, standardCredits, fastCredits] of [
["gpt-6-sol", 50, 125],
["gpt-6-luna", 2.5, 6.25],
]) {
const standard = codexCreditsOfCalls([{ timestamp: "2026-09-23T12:00:00Z", model, usage: input }]);
const fast = codexCreditsOfCalls([{ timestamp: "2026-09-23T12:00:00Z", model, serviceTier: "fast", usage: input }]);
assert.equal(creditRateFor(model).input, standardCredits);
assert.equal(fastMultiplierFor(model, "fast"), 2.5);
assert.equal(standard.credits, standardCredits);
assert.equal(fast.credits, fastCredits);
assert.equal(standard.unratedCalls, 0);
assert.equal(fast.unratedCalls, 0);
}
});

test("supports documented tiers and does not invent rates for unknown models", () => {
assert.equal(isFastServiceTier("fast"), true);
assert.equal(isFastServiceTier("priority"), true);
Expand Down
Loading