Skip to content

Commit efe9c87

Browse files
author
skullcmd
committed
fix(api/pricing): align special model pricing and billing with produced output
- Add explicit per-second/per-minute/per-million-character special pricing units. - Publish duration-billed video metadata for Sora and all Veo video models, including default produced durations (Sora 4s, Veo 8s) and per-second rates. - Update pricing source/generator data for stale Sora, Veo, TTS, Whisper, and Gemini image-preview prices. - Overlay dynamic pricing at /v1/models and usage-cache read time so stale stored Redis model pricing cannot override current pricing code. - Bill video requests by canonical model id and effective produced duration. - Make Gemini/Veo duration explicit upstream when omitted. - Bill TTS/STT using model-specific metrics where available and actual STT usage when upstream returns duration/tokens. - Bill image generation/edit by produced image count when response data is available. Verified: typecheck, production build, service restart, live /v1/models pricing, video validation, TTS, and Whisper STT smoke tests.
1 parent 7f7df51 commit efe9c87

9 files changed

Lines changed: 488 additions & 143 deletions

File tree

apps/api/dev/fetchPricing.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ interface ModelPricing {
2424
audio_output?: number; // $ per million audio output tokens
2525
per_image?: number; // $ per generated image
2626
per_request?: number; // $ per request (for special models)
27+
per_second?: number; // $ per generated video second
28+
per_minute?: number; // $ per audio minute
29+
per_million_characters?: number; // $ per million input/output characters
2730
source: 'official' | 'openrouter' | 'estimated';
2831
updated_at: string;
2932
}
@@ -36,6 +39,9 @@ interface PricingFile {
3639
type PricingSeed = Partial<ModelPricing> & {
3740
image?: number;
3841
request?: number;
42+
second?: number;
43+
minute?: number;
44+
million_characters?: number;
3945
};
4046

4147
type PricingMap = Record<string, PricingSeed>;
@@ -206,6 +212,12 @@ function buildPricingEntry(price: PricingSeed, source: ModelPricing['source'], n
206212
else if (typeof price.image === 'number') normalized.per_image = price.image;
207213
if (typeof price.per_request === 'number') normalized.per_request = price.per_request;
208214
else if (typeof price.request === 'number') normalized.per_request = price.request;
215+
if (typeof price.per_second === 'number') normalized.per_second = price.per_second;
216+
else if (typeof price.second === 'number') normalized.per_second = price.second;
217+
if (typeof price.per_minute === 'number') normalized.per_minute = price.per_minute;
218+
else if (typeof price.minute === 'number') normalized.per_minute = price.minute;
219+
if (typeof price.per_million_characters === 'number') normalized.per_million_characters = price.per_million_characters;
220+
else if (typeof price.million_characters === 'number') normalized.per_million_characters = price.million_characters;
209221

210222
return normalized;
211223
}
@@ -329,11 +341,11 @@ const OFFICIAL_PRICES: Record<string, PricingSeed> = {
329341
'gpt-realtime-mini-2025-10-06': { input: 0.60, output: 2.40, audio_input: 10.00, audio_output: 20.00, source: 'official' },
330342
'gpt-realtime-mini-2025-12-15': { input: 0.60, output: 2.40, audio_input: 10.00, audio_output: 20.00, source: 'official' },
331343
'gpt-realtime-1.5': { input: 4.00, output: 16.00, audio_input: 30.00, audio_output: 60.00, source: 'official' },
332-
'tts-1': { input: 15.00, source: 'official' },
333-
'tts-1-1106': { input: 15.00, source: 'official' },
334-
'tts-1-hd': { input: 30.00, source: 'official' },
335-
'tts-1-hd-1106': { input: 30.00, source: 'official' },
336-
'whisper-1': { audio_input: 0.006, source: 'official' },
344+
'tts-1': { input: 0, output: 0, per_million_characters: 15.00, source: 'official' },
345+
'tts-1-1106': { input: 0, output: 0, per_million_characters: 15.00, source: 'official' },
346+
'tts-1-hd': { input: 0, output: 0, per_million_characters: 30.00, source: 'official' },
347+
'tts-1-hd-1106': { input: 0, output: 0, per_million_characters: 30.00, source: 'official' },
348+
'whisper-1': { input: 0, output: 0, per_minute: 0.006, source: 'official' },
337349
// Image generation
338350
'gpt-image-1': { input: 0, output: 0, per_image: 0.04, source: 'official' },
339351
'gpt-image-1-mini': { input: 0, output: 0, per_image: 0.02, source: 'official' },
@@ -343,8 +355,8 @@ const OFFICIAL_PRICES: Record<string, PricingSeed> = {
343355
'chatgpt-image-latest': { input: 0, output: 0, per_image: 0.04, source: 'official' },
344356
'dall-e-3': { input: 0, output: 0, per_image: 0.04, source: 'official' },
345357
'dall-e-2': { input: 0, output: 0, per_image: 0.02, source: 'official' },
346-
'sora-2': { input: 0, output: 0, per_image: 0.10, source: 'official' },
347-
'sora-2-pro': { input: 0, output: 0, per_image: 0.20, source: 'official' },
358+
'sora-2': { input: 0, output: 0, per_second: 0.10, source: 'official' },
359+
'sora-2-pro': { input: 0, output: 0, per_second: 0.20, source: 'official' },
348360
// Embeddings
349361
'text-embedding-3-small': { input: 0.02, output: 0, source: 'official' },
350362
'text-embedding-3-large': { input: 0.13, output: 0, source: 'official' },
@@ -379,6 +391,8 @@ const OFFICIAL_PRICES: Record<string, PricingSeed> = {
379391
'gemini-3-flash-preview': { input: 0.10, output: 0.40, source: 'official' },
380392
'gemini-3-pro-preview': { input: 1.00, output: 8.00, source: 'official' },
381393
'gemini-3-pro-image-preview': { input: 1.00, output: 8.00, per_image: 0.04, source: 'official' },
394+
'gemini-3.1-flash-image-preview': { input: 0.10, output: 0.40, per_image: 0.04, source: 'official' },
395+
'google/gemini-3.1-flash-image-preview': { input: 0.10, output: 0.40, per_image: 0.04, source: 'official' },
382396
'gemini-3.1-pro-preview': { input: 1.00, output: 8.00, source: 'official' },
383397
'gemini-3.1-pro-preview-customtools': { input: 1.00, output: 8.00, source: 'official' },
384398
'gemini-robotics-er-1.5-preview': { input: 1.25, output: 5.00, source: 'official' },
@@ -395,11 +409,12 @@ const OFFICIAL_PRICES: Record<string, PricingSeed> = {
395409
'imagen-4.0-generate-001': { input: 0, output: 0, per_image: 0.04, source: 'official' },
396410
'imagen-4.0-fast-generate-001': { input: 0, output: 0, per_image: 0.02, source: 'official' },
397411
'imagen-4.0-ultra-generate-001': { input: 0, output: 0, per_image: 0.08, source: 'official' },
398-
'veo-2.0-generate-001': { input: 0, output: 0, per_request: 0.35, source: 'official' },
399-
'veo-3.0-generate-001': { input: 0, output: 0, per_image: 0.40, source: 'official' },
400-
'veo-3.0-fast-generate-001': { input: 0, output: 0, per_image: 0.10, source: 'official' },
401-
'veo-3.1-generate-preview': { input: 0, output: 0, per_image: 0.40, source: 'official' },
402-
'veo-3.1-fast-generate-preview': { input: 0, output: 0, per_image: 0.10, source: 'official' },
412+
'veo-2.0-generate-001': { input: 0, output: 0, per_second: 0.35, source: 'official' },
413+
'veo-3.0-generate-001': { input: 0, output: 0, per_second: 0.40, source: 'official' },
414+
'veo-3.0-fast-generate-001': { input: 0, output: 0, per_second: 0.10, source: 'official' },
415+
'veo-3.1-generate-preview': { input: 0, output: 0, per_second: 0.40, source: 'official' },
416+
'veo-3.1-fast-generate-preview': { input: 0, output: 0, per_second: 0.10, source: 'official' },
417+
'veo-3.1-lite-generate-preview': { input: 0, output: 0, per_second: 0.10, source: 'official' },
403418

404419
// ============================================================
405420
// Deepseek — https://api-docs.deepseek.com/quick_start/pricing

0 commit comments

Comments
 (0)