diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd4e225..dcaa68e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,11 @@ jobs: node site/playground.test.js node site/playground.dom.test.js + # The pricing page embeds the schedule its strip reads; this pins + # the period arithmetic and the schedule against the ground truth. + - name: pricing page + run: node site/pricing.test.js + # The rate card exists in two modules that cannot import each other. - name: rate cards have not drifted run: make price-check diff --git a/AGENTS.md b/AGENTS.md index 6550f35..ff4db72 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,8 +102,14 @@ deepseek models --json deepseek balance --json # exits 3 if exhausted deepseek usage --since 7d --json # local ledger, not billing deepseek usage --entries --json # individual calls +deepseek pricing --json # the schedule and the billing period right now ``` +Pricing is time-of-day from 2026-08-16 16:00 UTC: peak hours 01:00–04:00 +and 06:00–10:00 UTC bill at twice the off-peak rate. `pricing` computes +the current period locally — no network, nothing spent — from the same +schedule the cost estimates use. + ### Documentation, offline The binary carries every page of api-docs.deepseek.com plus the FAQ. @@ -198,6 +204,7 @@ These are computed locally, not from the API: ``` usage --json {"since","total":{...},"by_model":{...},"by_api":{...}} +pricing --json {"now_utc","now_local","now_beijing","period","multiplier","next_change","reprice_at","peak_windows_utc":[...],"peak_multiplier","current":{...},"off_peak":{...},"peak":{...},"source"} check --json {"base_url","key_set","ok","probes":[{"name","path","ok","detail","error","ms"}]} session ls --json [{"name","model","turns","updated","bytes"}] status --json {"base_url","ok","models":[...],"latency_ms","balance","status_page"} diff --git a/Makefile b/Makefile index 22392f5..5e5137a 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ site-check: node site/md.test.js node site/playground.test.js node site/playground.dom.test.js + node site/pricing.test.js node site/waves.test.js node site/waves.dom.test.js ./site/bans.sh diff --git a/README.md b/README.md index 52d71b0..c113392 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ One command per endpoint, named for what it does. | `tokens` | `POST /beta/completions` | Exact token counts, from the model's own tokenizer. | | `docs` | *(local)* | DeepSeek's own API docs, in the binary. Search, read, and ask. | | `usage` | *(local)* | What this CLI has spent, from its own ledger. | +| `pricing` | *(local)* | The rate card, the time-of-day schedule, and the billing period right now. | | `session` | *(local)* | The conversations `chat --continue` replays. | | `status` | `GET /models`, `/user/balance` | Is it up, for this key, from here. Costs nothing. | | `check` | *(all six)* | Preflight. | @@ -382,10 +383,13 @@ Honest limits: - Costs are **estimates** from the published USD rate card, not billed amounts. Token counts are exact, and they are what the ledger stores, so old rows can be repriced when the card changes. -- DeepSeek has announced peak/off-peak pricing (2× during 09:00–12:00 - and 14:00–18:00 Beijing time) with **no effective date**. It is - deliberately not applied — guessing that a call was billed double - would be inventing data. +- DeepSeek's repricing **is dated and encoded**: at 16:00 UTC on + 2026-08-16 billing moves to peak/off-peak on a new, higher card (peak + 01:00–04:00 and 06:00–10:00 UTC, 2× the off-peak rate). Estimates use + the flat card until that instant and switch on it automatically — + never before, because applying a price before its effective date + would be inventing data. `deepseek pricing` prints the schedule and + the period in effect right now. - `--no-ledger` skips the write; `--no-stats` hides the line. ## For agents diff --git a/TASTE.md b/TASTE.md index f24d91f..7e6d1f8 100644 --- a/TASTE.md +++ b/TASTE.md @@ -152,6 +152,14 @@ the multiplier goes when it lands. **Expires.** When DeepSeek announces the effective date — then the multiplier goes in, gated on that date. +**Expired 2026-08-13.** DeepSeek dated it: peak/off-peak billing on a new +card from 2026-08-16 16:00 UTC, windows defined in UTC (01:00–04:00 and +06:00–10:00). The multiplier went in exactly as this entry prescribed — +gated on the effective instant, never applied before it. The schedule +lives in `internal/deepseek/pricing.go`, mirrored by the gateway meter and +the site's pricing page; `deepseek pricing` prints it. The principle +stands for the next undated announcement. + --- ## 2026-08-05 rejected: executing the tool calls the model asks for diff --git a/gateway/internal/meter/meter.go b/gateway/internal/meter/meter.go index 8cd7393..3bcdf49 100644 --- a/gateway/internal/meter/meter.go +++ b/gateway/internal/meter/meter.go @@ -12,6 +12,7 @@ import ( "bytes" "encoding/json" "strings" + "time" ) // Usage is token accounting normalised across the four wire formats. @@ -39,32 +40,98 @@ type Price struct { Output float64 } -// Rates as published on 2026-08-02. -var rates = map[string]Price{ +// RepriceAt is when DeepSeek's dated repricing takes effect: 16:00 UTC +// on 2026-08-16, announced 2026-08-13. From that instant billing is +// peak/off-peak on a new, higher card. Under-charging our own budget +// after the flip would drain the credit pool at yesterday's prices, so +// the switch is encoded here and gated on the date, exactly as the CLI's +// copy does. +var RepriceAt = time.Date(2026, time.August, 16, 16, 0, 0, 0, time.UTC) + +// ratesFlat is the card published 2026-08-02, in force before RepriceAt. +var ratesFlat = map[string]Price{ "deepseek-v4-flash": {CacheHitInput: 0.0028, CacheMissInput: 0.14, Output: 0.28}, "deepseek-v4-pro": {CacheHitInput: 0.003625, CacheMissInput: 0.435, Output: 0.87}, } -// PriceFor returns the rate card for a model, defaulting to the more -// expensive one. Charging an unknown model at pro rates is deliberate: -// if DeepSeek ships a third model and we have not updated this table, we -// want to over-charge our own budget, not under-charge it. +// ratesOffPeak is the base card from RepriceAt on; during peakWindows +// every billing item costs peakMultiplier times these numbers. +var ratesOffPeak = map[string]Price{ + "deepseek-v4-flash": {CacheHitInput: 0.007, CacheMissInput: 0.22, Output: 0.66}, + "deepseek-v4-pro": {CacheHitInput: 0.022, CacheMissInput: 0.66, Output: 1.98}, +} + +const peakMultiplier = 2.0 + +// peakWindows are the daily peak hours from RepriceAt on, in minutes of +// the UTC day, end exclusive: 01:00-04:00 and 06:00-10:00 UTC. +var peakWindows = [][2]int{{1 * 60, 4 * 60}, {6 * 60, 10 * 60}} + +func inPeak(t time.Time) bool { + u := t.UTC() + m := u.Hour()*60 + u.Minute() + for _, w := range peakWindows { + if m >= w[0] && m < w[1] { + return true + } + } + return false +} + +// PriceFor returns the rate card in effect right now, defaulting to the +// more expensive model. Charging an unknown model at pro rates is +// deliberate: if DeepSeek ships a third model and we have not updated +// this table, we want to over-charge our own budget, not under-charge it. func PriceFor(model string) Price { - if p, ok := rates[model]; ok { - return p + return PriceAt(model, time.Now()) +} + +// PriceAt is PriceFor at a chosen instant: that era's base card, doubled +// inside a peak window. +func PriceAt(model string, t time.Time) Price { + if t.Before(RepriceAt) { + return cardFor(ratesFlat, model) } - if strings.Contains(model, "pro") || model == "" { - return rates["deepseek-v4-pro"] + p := cardFor(ratesOffPeak, model) + if inPeak(t) { + p = scale(p, peakMultiplier) } - if strings.Contains(model, "flash") { - return rates["deepseek-v4-flash"] + return p +} + +func cardFor(cards map[string]Price, model string) Price { + if p, ok := cards[model]; ok { + return p + } + switch { + case strings.Contains(model, "pro") || model == "": + return cards["deepseek-v4-pro"] + case strings.Contains(model, "flash"): + return cards["deepseek-v4-flash"] + default: + return cards["deepseek-v4-pro"] } - return rates["deepseek-v4-pro"] } -// Cost prices a usage record. +func scale(p Price, mult float64) Price { + p.CacheHitInput *= mult + p.CacheMissInput *= mult + p.Output *= mult + return p +} + +// Cost prices a usage record at the card in force right now — the +// response being settled just arrived. func Cost(model string, u Usage) float64 { - p := PriceFor(model) + return CostAt(model, u, time.Now()) +} + +// CostAt prices a usage record under the card in force at one instant. +func CostAt(model string, u Usage, t time.Time) float64 { + return costWith(PriceAt(model, t), u) +} + +func costWith(p Price, u Usage) float64 { const perMillion = 1_000_000.0 miss := u.InputTokens - u.CacheHitTokens if miss < 0 { @@ -93,18 +160,67 @@ func Cost(model string, u Usage) float64 { // A search request breaks the first rule: the pages DeepSeek reads on the // caller's behalf arrive as input tokens the body never contained, so // searchInputAllowance is added to the input bound instead. +// A third rule joined them with the dated repricing: the reservation is +// priced at the dearest card the request could settle under, not the +// card of the admission instant. A request admitted just before a peak +// window (or just before the repricing flip) can settle inside it, and +// an estimate the clock can outrun is not a ceiling. func Estimate(model string, requestBytes, maxTokens int, search bool) float64 { + return EstimateAt(model, requestBytes, maxTokens, search, time.Now()) +} + +// EstimateAt is Estimate at a chosen instant. +func EstimateAt(model string, requestBytes, maxTokens int, search bool, t time.Time) float64 { input := requestBytes + 1 if search { input += searchInputAllowance } - return Cost(model, Usage{ + return costWith(ceilingAt(model, t), Usage{ InputTokens: input, OutputTokens: maxTokens + reasoningAllowance, Found: false, }) } +// ceilingAt is the dearest card a request admitted at t could settle +// under. Upstream holds a connection up to ten minutes before inference +// begins, so a request is given an hour of in-flight allowance: if that +// hour crosses the repricing flip or touches a peak window, the +// reservation is priced at the dearer side. Off-peak admissions far from +// any boundary still reserve at the off-peak card — a ceiling should be +// unbeatable, not double. +func ceilingAt(model string, t time.Time) Price { + const inFlight = time.Hour + if t.Add(inFlight).Before(RepriceAt) { + return cardFor(ratesFlat, model) + } + if !t.Before(RepriceAt) && !peakTouches(t, inFlight) { + return cardFor(ratesOffPeak, model) + } + return scale(cardFor(ratesOffPeak, model), peakMultiplier) +} + +// peakTouches reports whether any instant of [t, t+d] falls in a peak +// window. The endpoint checks cover every span shorter than the gaps +// between windows; the start-of-window check keeps this correct even if +// a future card ships a window shorter than the span. +func peakTouches(t time.Time, d time.Duration) bool { + if inPeak(t) || inPeak(t.Add(d)) { + return true + } + u := t.UTC() + m := u.Hour()*60 + u.Minute() + span := int(d / time.Minute) + for _, w := range peakWindows { + for _, start := range []int{w[0], w[0] + 24*60} { + if start > m && start < m+span { + return true + } + } + } + return false +} + // reasoningAllowance is the output headroom reserved for chain-of-thought // tokens on top of the caller's visible max_tokens. 32k covers the // longest thinking runs measured live; at flash rates it prices at under diff --git a/gateway/internal/meter/meter_test.go b/gateway/internal/meter/meter_test.go index f778425..77cfb1a 100644 --- a/gateway/internal/meter/meter_test.go +++ b/gateway/internal/meter/meter_test.go @@ -4,6 +4,7 @@ import ( "math" "strings" "testing" + "time" ) // The payloads below are verbatim from the live API on 2026-08-05, one @@ -183,6 +184,54 @@ func TestEstimateExceedsATypicalRealCharge(t *testing.T) { } } +// The dated repricing: flat until 16:00 UTC on 2026-08-16, then a new +// base card off-peak with peak windows at exactly double. Under-charging +// after the flip would drain the credit pool at yesterday's prices. +func TestRepricingSwitchesOnItsEffectiveInstant(t *testing.T) { + u := Usage{InputTokens: 1_000_000, OutputTokens: 1_000_000, Found: true} + + before := CostAt("deepseek-v4-flash", u, RepriceAt.Add(-time.Second)) + if math.Abs(before-0.42) > 1e-9 { + t.Errorf("before the flip: got %v, want the flat 0.42", before) + } + // 16:00 UTC is outside both peak windows: the off-peak card. + at := CostAt("deepseek-v4-flash", u, RepriceAt) + if math.Abs(at-0.88) > 1e-9 { + t.Errorf("at the flip: got %v, want the off-peak 0.88", at) + } + // 02:00 UTC is inside 01:00-04:00: double. + peak := CostAt("deepseek-v4-flash", u, time.Date(2026, 8, 17, 2, 0, 0, 0, time.UTC)) + if math.Abs(peak-1.76) > 1e-9 { + t.Errorf("in a peak window: got %v, want 1.76", peak) + } +} + +// The reservation must be a true upper bound across period boundaries: a +// request admitted minutes before a peak window (or before the flip) can +// settle inside it, so its ceiling is priced at the dearer side. +func TestEstimateCeilingCoversTheNextPeriod(t *testing.T) { + const model = "deepseek-v4-flash" + justBeforePeak := time.Date(2026, 8, 17, 5, 30, 0, 0, time.UTC) + insidePeak := time.Date(2026, 8, 17, 6, 5, 0, 0, time.UTC) + if est, peak := EstimateAt(model, 400, 1000, false, justBeforePeak), EstimateAt(model, 400, 1000, false, insidePeak); est < peak { + t.Errorf("admitted at 05:30 UTC the reservation %v is under the peak-priced %v it could settle at", est, peak) + } + + justBeforeFlip := RepriceAt.Add(-10 * time.Minute) + afterFlip := EstimateAt(model, 400, 1000, false, RepriceAt) + if est := EstimateAt(model, 400, 1000, false, justBeforeFlip); est < afterFlip { + t.Errorf("admitted before the flip the reservation %v is under the post-flip %v", est, afterFlip) + } + + // And far from any boundary, off-peak reserves at the off-peak card, + // not at a permanent doubling. + quiet := time.Date(2026, 8, 17, 12, 0, 0, 0, time.UTC) + offPeak := costWith(cardFor(ratesOffPeak, model), Usage{InputTokens: 401, OutputTokens: 1000 + reasoningAllowance}) + if est := EstimateAt(model, 400, 1000, false, quiet); math.Abs(est-offPeak) > 1e-12 { + t.Errorf("quiet off-peak reservation %v, want the off-peak card's %v", est, offPeak) + } +} + // If DeepSeek ships a model we have not priced, the unknown must be // charged at the higher rate. Guessing low would let a new model become // a way to spend the budget faster than it is counted. diff --git a/internal/cli/pricing.go b/internal/cli/pricing.go new file mode 100644 index 0000000..805dfc5 --- /dev/null +++ b/internal/cli/pricing.go @@ -0,0 +1,189 @@ +package cli + +import ( + "fmt" + "strings" + "text/tabwriter" + "time" + + "github.com/spf13/cobra" + "github.com/thevibeworks/deepseek-cli/internal/deepseek" +) + +// beijing is fixed arithmetic, not a timezone database: Asia/Shanghai is +// UTC+8 and has not observed daylight saving since 1991. Loading the +// IANA zone could fail on a machine without tzdata; a constant cannot. +var beijing = time.FixedZone("UTC+8", 8*60*60) + +// pricingPrice is one rate card row in USD per 1M tokens. +type pricingPrice struct { + CacheHitInput float64 `json:"cache_hit_input"` + CacheMissInput float64 `json:"cache_miss_input"` + Output float64 `json:"output"` +} + +// pricingResult is the JSON shape of `deepseek pricing`. Computed +// locally from the same schedule the cost estimates use, so the two can +// never disagree. +type pricingResult struct { + NowUTC string `json:"now_utc"` + NowLocal string `json:"now_local"` + NowBeijing string `json:"now_beijing"` + Period string `json:"period"` + Multiplier float64 `json:"multiplier"` + NextChange string `json:"next_change"` + + RepriceAt string `json:"reprice_at"` + PeakWindowsUTC []string `json:"peak_windows_utc"` + PeakMultiplier float64 `json:"peak_multiplier"` + + // Current is the card in effect at now_utc; OffPeak and Peak are the + // dated cards that apply from reprice_at. + Current map[string]pricingPrice `json:"current"` + OffPeak map[string]pricingPrice `json:"off_peak"` + Peak map[string]pricingPrice `json:"peak"` + + Source string `json:"source"` +} + +func newPricingCmd(o *Options) *cobra.Command { + return &cobra.Command{ + Use: "pricing", + Short: "The rate card, the time-of-day schedule, and the period right now", + Long: strings.TrimSpace(` +Answer what a token costs at this instant, and when that changes. + +DeepSeek's repricing of 2026-08-13 is dated: until 16:00 UTC on +2026-08-16 every hour bills at the flat card of 2026-08-02, and from +that instant billing is peak/off-peak on a new, higher card — peak hours +01:00-04:00 and 06:00-10:00 UTC daily at twice the off-peak rate. + +This command reads no network and spends nothing: the schedule is the +same one the cost estimates use, so what it prints is what the usage +line will charge. The upstream page it encodes is +` + "`deepseek docs show quick_start/pricing`" + `, offline.`), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + now := time.Now() + return o.emitValue(pricingAt(now), formatPricing(now)) + }, + } +} + +func pricingAt(now time.Time) *pricingResult { + period := deepseek.PeriodAt(now) + res := &pricingResult{ + NowUTC: now.UTC().Format(time.RFC3339), + NowLocal: now.Format(time.RFC3339), + NowBeijing: now.In(beijing).Format(time.RFC3339), + Period: period.Label, + Multiplier: period.Multiplier, + NextChange: deepseek.NextChange(now).Format(time.RFC3339), + RepriceAt: deepseek.RepriceAt.Format(time.RFC3339), + PeakMultiplier: deepseek.PeakMultiplier, + Current: map[string]pricingPrice{}, + OffPeak: map[string]pricingPrice{}, + Peak: map[string]pricingPrice{}, + Source: "https://api-docs.deepseek.com/quick_start/pricing", + } + for _, w := range deepseek.PeakWindows { + res.PeakWindowsUTC = append(res.PeakWindowsUTC, + fmt.Sprintf("%s-%s", fmtMinutes(w.Start), fmtMinutes(w.End))) + } + for _, m := range []string{deepseek.ModelFlash, deepseek.ModelPro} { + if p, ok := deepseek.PriceAt(m, now); ok { + res.Current[m] = pricingPrice{p.CacheHitInput, p.CacheMissInput, p.Output} + } + if p, ok := deepseek.PriceAt(m, deepseek.RepriceAt); ok { + res.OffPeak[m] = pricingPrice{p.CacheHitInput, p.CacheMissInput, p.Output} + res.Peak[m] = pricingPrice{ + p.CacheHitInput * deepseek.PeakMultiplier, + p.CacheMissInput * deepseek.PeakMultiplier, + p.Output * deepseek.PeakMultiplier, + } + } + } + return res +} + +func formatPricing(now time.Time) string { + period := deepseek.PeriodAt(now) + next := deepseek.NextChange(now) + + var b strings.Builder + fmt.Fprintf(&b, "period: %s", period.Label) + if period.Multiplier != 1 { + fmt.Fprintf(&b, " (%gx the off-peak card)", period.Multiplier) + } + fmt.Fprintf(&b, "\nlocal %s · utc %s · beijing %s\n", + now.Format("15:04 (UTC-07:00)"), + now.UTC().Format("15:04"), + now.In(beijing).Format("15:04")) + + switch { + case now.Before(deepseek.RepriceAt): + fmt.Fprintf(&b, "next change: %s (%s) — peak/off-peak billing begins on a new card\n", + deepseek.RepriceAt.Format("2006-01-02 15:04 UTC"), humanUntil(deepseek.RepriceAt.Sub(now))) + default: + nextPeriod := deepseek.PeriodAt(next) + fmt.Fprintf(&b, "next change: %s UTC (%s) — %s\n", + next.UTC().Format("15:04"), humanUntil(next.Sub(now)), nextPeriod.Label) + } + + fmt.Fprintf(&b, "\nUSD per 1M tokens, in effect now (%s):\n", period.Label) + w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "MODEL\tIN (CACHED)\tIN (MISS)\tOUT") + for _, m := range []string{deepseek.ModelFlash, deepseek.ModelPro} { + if p, ok := deepseek.PriceAt(m, now); ok { + fmt.Fprintf(w, "%s\t$%g\t$%g\t$%g\n", m, p.CacheHitInput, p.CacheMissInput, p.Output) + } + } + w.Flush() + + windows := make([]string, 0, len(deepseek.PeakWindows)) + for _, win := range deepseek.PeakWindows { + windows = append(windows, fmtMinutes(win.Start)+"-"+fmtMinutes(win.End)) + } + if now.Before(deepseek.RepriceAt) { + fmt.Fprintf(&b, "\nfrom %s — peak hours %s UTC daily, all other hours off-peak at half of peak:\n", + deepseek.RepriceAt.Format("2006-01-02 15:04 UTC"), strings.Join(windows, " and ")) + } else { + fmt.Fprintf(&b, "\nthe full card — peak hours %s UTC daily, all other hours off-peak at half of peak:\n", + strings.Join(windows, " and ")) + } + w = tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "MODEL\tPERIOD\tIN (CACHED)\tIN (MISS)\tOUT") + for _, m := range []string{deepseek.ModelFlash, deepseek.ModelPro} { + p, ok := deepseek.PriceAt(m, deepseek.RepriceAt) + if !ok { + continue + } + fmt.Fprintf(w, "%s\toff-peak\t$%g\t$%g\t$%g\n", m, p.CacheHitInput, p.CacheMissInput, p.Output) + fmt.Fprintf(w, "%s\tpeak\t$%g\t$%g\t$%g\n", m, + p.CacheHitInput*deepseek.PeakMultiplier, p.CacheMissInput*deepseek.PeakMultiplier, p.Output*deepseek.PeakMultiplier) + } + w.Flush() + + fmt.Fprint(&b, "\ncost estimates switch cards on the effective instant automatically.\nupstream copy, offline: deepseek docs show quick_start/pricing") + return b.String() +} + +func fmtMinutes(m int) string { + return fmt.Sprintf("%02d:%02d", m/60, m%60) +} + +// humanUntil renders a duration the way a person waits through it: +// days and hours far out, minutes when it is close. +func humanUntil(d time.Duration) string { + if d < 0 { + d = 0 + } + switch { + case d >= 48*time.Hour: + return fmt.Sprintf("in %dd%dh", int(d.Hours())/24, int(d.Hours())%24) + case d >= time.Hour: + return fmt.Sprintf("in %dh%02dm", int(d.Hours()), int(d.Minutes())%60) + default: + return fmt.Sprintf("in %dm", int(d.Minutes())) + } +} diff --git a/internal/cli/pricing_test.go b/internal/cli/pricing_test.go new file mode 100644 index 0000000..a6a44a0 --- /dev/null +++ b/internal/cli/pricing_test.go @@ -0,0 +1,82 @@ +package cli + +import ( + "strings" + "testing" + "time" + + "github.com/thevibeworks/deepseek-cli/internal/deepseek" +) + +// `deepseek pricing` is the CLI face of the same schedule the cost +// estimates use. A wrong answer here names a price, so the shape is +// pinned at fixed instants on both sides of the repricing flip. + +func TestPricingBeforeTheFlip(t *testing.T) { + now := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC) + res := pricingAt(now) + + if res.Period != "flat" || res.Multiplier != 1 { + t.Errorf("period = %s at %gx, want flat at 1x", res.Period, res.Multiplier) + } + if res.NextChange != deepseek.RepriceAt.Format(time.RFC3339) { + t.Errorf("next change = %s, want the repricing instant", res.NextChange) + } + if got := res.Current["deepseek-v4-flash"]; got.CacheMissInput != 0.14 { + t.Errorf("current flash miss = %v, want the flat 0.14", got.CacheMissInput) + } + // The dated card rides along so a script can see the future without + // a second source. + if got := res.OffPeak["deepseek-v4-pro"]; got.Output != 1.98 { + t.Errorf("off-peak pro output = %v, want 1.98", got.Output) + } + if got, want := res.Peak["deepseek-v4-flash"].CacheMissInput, 0.44; got != want { + t.Errorf("peak flash miss = %v, want %v (2x off-peak)", got, want) + } + + text := formatPricing(now) + for _, want := range []string{"period: flat", "2026-08-16 16:00 UTC", "$0.14", "$0.22", "off-peak", "peak"} { + if !strings.Contains(text, want) { + t.Errorf("text output is missing %q:\n%s", want, text) + } + } +} + +func TestPricingInsideAPeakWindow(t *testing.T) { + now := time.Date(2026, 8, 17, 2, 0, 0, 0, time.UTC) // 01:00-04:00 UTC window + res := pricingAt(now) + + if res.Period != "peak" || res.Multiplier != deepseek.PeakMultiplier { + t.Errorf("period = %s at %gx, want peak at %gx", res.Period, res.Multiplier, deepseek.PeakMultiplier) + } + if got := res.Current["deepseek-v4-flash"]; got.CacheMissInput != 0.44 { + t.Errorf("current flash miss = %v, want the peak 0.44", got.CacheMissInput) + } + if res.NextChange != time.Date(2026, 8, 17, 4, 0, 0, 0, time.UTC).Format(time.RFC3339) { + t.Errorf("next change = %s, want the window end 04:00 UTC", res.NextChange) + } + + text := formatPricing(now) + if !strings.Contains(text, "period: peak") { + t.Errorf("text output does not name the peak period:\n%s", text) + } +} + +func TestPricingJSONAgreesWithTheCostEstimator(t *testing.T) { + // The command and Cost() must read the same table: the card `pricing` + // prints for an instant is the card a call at that instant is priced + // under. If these ever diverge, the tool argues with itself. + for _, now := range []time.Time{ + time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC), + time.Date(2026, 8, 17, 2, 0, 0, 0, time.UTC), + time.Date(2026, 8, 17, 12, 0, 0, 0, time.UTC), + } { + res := pricingAt(now) + for _, m := range []string{deepseek.ModelFlash, deepseek.ModelPro} { + cost, _ := deepseek.CostAt(m, deepseek.Usage{OutputTokens: 1_000_000}, now) + if got := res.Current[m].Output; got != cost { + t.Errorf("%s at %s: pricing prints $%v/M output, Cost charges $%v", m, now, got, cost) + } + } + } +} diff --git a/internal/cli/root.go b/internal/cli/root.go index c33c8d4..2518843 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -297,6 +297,7 @@ stdout is the answer; reasoning, usage and errors go to stderr.`), newTokensCmd(opts), newModelsCmd(opts), newBalanceCmd(opts), + newPricingCmd(opts), newUsageCmd(opts), newSessionCmd(opts), newDocsCmd(opts), diff --git a/internal/deepseek/pricing.go b/internal/deepseek/pricing.go index cf18668..42057c3 100644 --- a/internal/deepseek/pricing.go +++ b/internal/deepseek/pricing.go @@ -1,5 +1,7 @@ package deepseek +import "time" + // Usage is token accounting normalized across all four wire formats, so // one pricing function and one ledger row serve chat, FIM, Responses and // Anthropic alike. @@ -35,25 +37,118 @@ type Price struct { Output float64 } -// prices as published on 2026-08-02. DeepSeek adjusts these; `deepseek -// usage` labels every figure an estimate for that reason, and the raw -// token counts are kept in the ledger so any row can be repriced later. +// RepriceAt is when DeepSeek's dated repricing takes effect: 16:00 UTC +// on 2026-08-16 (midnight, Beijing), announced 2026-08-13 with the V4 GA +// release. From that instant the API bills peak/off-peak on a new, +// higher card, with off-peak at half the peak rate. // -// A peak/off-peak policy has been announced (2x on all billing items -// during 09:00-12:00 and 14:00-18:00 Beijing time) but has no effective -// date yet, so it is deliberately NOT applied: guessing that a request -// was billed double would be inventing data. When it lands, multiply here. -var prices = map[string]Price{ +// TASTE.md's rule against applying announced-but-undated numbers does +// not apply here — these numbers carry their date, so the switch is +// encoded and gated on it, exactly as that scar's expiry clause says. +// Source: https://api-docs.deepseek.com/quick_start/pricing (2026-08-13). +var RepriceAt = time.Date(2026, time.August, 16, 16, 0, 0, 0, time.UTC) + +// pricesFlat is the card published 2026-08-02, in force before RepriceAt. +var pricesFlat = map[string]Price{ ModelFlash: {CacheHitInput: 0.0028, CacheMissInput: 0.14, Output: 0.28}, ModelPro: {CacheHitInput: 0.003625, CacheMissInput: 0.435, Output: 0.87}, } -// PriceFor returns the rate card for a model. Unknown models — including -// the Claude names the Anthropic endpoint remaps server-side — resolve -// through ResolveModel first. +// pricesOffPeak is the base card from RepriceAt on. During PeakWindows +// every billing item costs PeakMultiplier times these numbers; DeepSeek +// publishes the peak figures rather than the rule, and they are exactly +// double, so the multiplier is data, not interpretation. +var pricesOffPeak = map[string]Price{ + ModelFlash: {CacheHitInput: 0.007, CacheMissInput: 0.22, Output: 0.66}, + ModelPro: {CacheHitInput: 0.022, CacheMissInput: 0.66, Output: 1.98}, +} + +// PeakMultiplier scales the off-peak card during PeakWindows. +const PeakMultiplier = 2.0 + +// Window is a daily time-of-day window in minutes of the UTC day, end +// exclusive. Upstream defines the boundaries in UTC, not Beijing. +type Window struct{ Start, End int } + +// PeakWindows are the daily peak hours from RepriceAt on: 01:00-04:00 +// and 06:00-10:00 UTC (09:00-12:00 and 14:00-18:00 Beijing). +var PeakWindows = []Window{{Start: 1 * 60, End: 4 * 60}, {Start: 6 * 60, End: 10 * 60}} + +// Period names the pricing period one instant falls in. +type Period struct { + // Label is "flat" before RepriceAt, then "peak" or "off-peak". + Label string + // Multiplier scales that era's base card. 1 except during peak. + Multiplier float64 +} + +// PeriodAt reports the pricing period in force at one instant. +func PeriodAt(t time.Time) Period { + if t.Before(RepriceAt) { + return Period{Label: "flat", Multiplier: 1} + } + if inPeak(t) { + return Period{Label: "peak", Multiplier: PeakMultiplier} + } + return Period{Label: "off-peak", Multiplier: 1} +} + +func inPeak(t time.Time) bool { + u := t.UTC() + m := u.Hour()*60 + u.Minute() + for _, w := range PeakWindows { + if m >= w.Start && m < w.End { + return true + } + } + return false +} + +// NextChange is the next instant after t at which the price of a call +// changes: the repricing instant while the flat card is in force, then +// the nearest peak-window boundary of the UTC day. +func NextChange(t time.Time) time.Time { + if t.Before(RepriceAt) { + return RepriceAt + } + u := t.UTC() + m := u.Hour()*60 + u.Minute() + day := time.Date(u.Year(), u.Month(), u.Day(), 0, 0, 0, 0, time.UTC) + for _, w := range PeakWindows { + if m < w.Start { + return day.Add(time.Duration(w.Start) * time.Minute) + } + if m < w.End { + return day.Add(time.Duration(w.End) * time.Minute) + } + } + return day.Add(24*time.Hour + time.Duration(PeakWindows[0].Start)*time.Minute) +} + +// PriceAt returns the effective rate card for a model at one instant: +// that era's base card, scaled by the period's multiplier. +func PriceAt(model string, t time.Time) (Price, bool) { + cards := pricesFlat + if !t.Before(RepriceAt) { + cards = pricesOffPeak + } + p, ok := cards[ResolveModel(model)] + if !ok { + return Price{}, false + } + if mult := PeriodAt(t).Multiplier; mult != 1 { + p.CacheHitInput *= mult + p.CacheMissInput *= mult + p.Output *= mult + } + return p, true +} + +// PriceFor returns the rate card in effect right now. Unknown models — +// including the Claude names the Anthropic endpoint remaps server-side — +// resolve through ResolveModel first. func PriceFor(model string) (Price, bool) { - p, ok := prices[ResolveModel(model)] - return p, ok + return PriceAt(model, time.Now()) } // ResolveModel maps whatever the caller asked for onto the model that @@ -79,11 +174,19 @@ func hasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } -// Cost estimates what a request cost, in USD, from its token counts. -// ok is false for a model with no published price, in which case callers -// should report tokens without a figure rather than print a zero. +// Cost estimates what a request cost, in USD, from its token counts, +// priced at the card in force right now — the call being priced just +// happened. ok is false for a model with no published price, in which +// case callers should report tokens without a figure rather than print +// a zero. func Cost(model string, u Usage) (usd float64, ok bool) { - p, ok := PriceFor(model) + return CostAt(model, u, time.Now()) +} + +// CostAt prices token counts under the card in force at one instant, +// which is what makes ledger rows repriceable under any era. +func CostAt(model string, u Usage, t time.Time) (usd float64, ok bool) { + p, ok := PriceAt(model, t) if !ok { return 0, false } @@ -95,9 +198,9 @@ func Cost(model string, u Usage) (usd float64, ok bool) { } // CacheSavings is what the cached part of the prompt would have cost at -// the cache-miss rate, minus what it did cost. This is the number that -// justifies structuring prompts for cache reuse, and no other DeepSeek -// tool surfaces it. +// the cache-miss rate, minus what it did cost, under the card in force +// right now. This is the number that justifies structuring prompts for +// cache reuse, and no other DeepSeek tool surfaces it. func CacheSavings(model string, u Usage) (usd float64, ok bool) { p, ok := PriceFor(model) if !ok { diff --git a/internal/deepseek/usage_test.go b/internal/deepseek/usage_test.go index 9a22a74..c1f2c19 100644 --- a/internal/deepseek/usage_test.go +++ b/internal/deepseek/usage_test.go @@ -4,6 +4,15 @@ import ( "encoding/json" "math" "testing" + "time" +) + +// Fixed instants, one per pricing period, so these tests do not change +// meaning as the wall clock crosses RepriceAt. +var ( + atFlat = time.Date(2026, 8, 10, 12, 0, 0, 0, time.UTC) // before the flip + atOffPeak = time.Date(2026, 8, 17, 12, 0, 0, 0, time.UTC) // after, outside the windows + atPeak = time.Date(2026, 8, 17, 2, 0, 0, 0, time.UTC) // after, inside 01:00-04:00 UTC ) // The three wire formats report token usage with different conventions. @@ -120,8 +129,8 @@ func TestCacheHitRate(t *testing.T) { } func TestCost(t *testing.T) { - // 1M cache-miss input + 1M output on flash: 0.14 + 0.28. - got, ok := Cost(ModelFlash, Usage{InputTokens: 1_000_000, CacheMissTokens: 1_000_000, OutputTokens: 1_000_000}) + // 1M cache-miss input + 1M output on flash, flat card: 0.14 + 0.28. + got, ok := CostAt(ModelFlash, Usage{InputTokens: 1_000_000, CacheMissTokens: 1_000_000, OutputTokens: 1_000_000}, atFlat) if !ok { t.Fatal("flash should be priced") } @@ -130,21 +139,97 @@ func TestCost(t *testing.T) { } // The same prompt served entirely from cache costs 50x less on input. - cached, _ := Cost(ModelFlash, Usage{InputTokens: 1_000_000, CacheHitTokens: 1_000_000}) + cached, _ := CostAt(ModelFlash, Usage{InputTokens: 1_000_000, CacheHitTokens: 1_000_000}, atFlat) if math.Abs(cached-0.0028) > 1e-9 { t.Errorf("cached input = %v, want 0.0028", cached) } } +// The dated repricing: flat until 16:00 UTC on 2026-08-16, then a new +// base card off-peak and exactly double during the two UTC peak windows. +// Wrong period arithmetic here misprices every estimate by 2x, which is +// precisely what the TASTE scar about undated multipliers was guarding +// against — these numbers are dated, so they are encoded and pinned. +func TestRepricingSwitchesOnItsEffectiveInstant(t *testing.T) { + u := Usage{InputTokens: 1_000_000, CacheMissTokens: 1_000_000, OutputTokens: 1_000_000} + + before, _ := CostAt(ModelFlash, u, RepriceAt.Add(-time.Nanosecond)) + if math.Abs(before-0.42) > 1e-9 { + t.Errorf("one instant before the flip: got %v, want the flat 0.42", before) + } + + // 16:00 UTC is outside both peak windows, so the flip lands on the + // off-peak card: 0.22 + 0.66. + at, _ := CostAt(ModelFlash, u, RepriceAt) + if math.Abs(at-0.88) > 1e-9 { + t.Errorf("at the flip: got %v, want the off-peak 0.88", at) + } + + peak, _ := CostAt(ModelFlash, u, atPeak) + if math.Abs(peak-1.76) > 1e-9 { + t.Errorf("in a peak window: got %v, want 1.76 (2x off-peak)", peak) + } + + pro, _ := CostAt(ModelPro, u, atPeak) + if math.Abs(pro-(1.32+3.96)) > 1e-9 { + t.Errorf("pro in a peak window: got %v, want 5.28", pro) + } +} + +func TestPeriodBoundariesAreUTCAndEndExclusive(t *testing.T) { + day := time.Date(2026, 8, 17, 0, 0, 0, 0, time.UTC) + cases := map[time.Duration]string{ + 0 * time.Hour: "off-peak", // midnight UTC + 1 * time.Hour: "peak", // window start is inclusive + 4*time.Hour - time.Minute: "peak", + 4 * time.Hour: "off-peak", // window end is exclusive + 6 * time.Hour: "peak", + 10*time.Hour - time.Nanosecond: "peak", + 10 * time.Hour: "off-peak", + 12 * time.Hour: "off-peak", + } + for d, want := range cases { + if got := PeriodAt(day.Add(d)); got.Label != want { + t.Errorf("PeriodAt(+%v) = %q, want %q", d, got.Label, want) + } + } + if got := PeriodAt(atFlat); got.Label != "flat" || got.Multiplier != 1 { + t.Errorf("before the flip: got %+v, want flat at 1x", got) + } +} + +func TestNextChange(t *testing.T) { + if got := NextChange(atFlat); !got.Equal(RepriceAt) { + t.Errorf("before the flip the next change is the flip, got %v", got) + } + day := time.Date(2026, 8, 17, 0, 0, 0, 0, time.UTC) + cases := map[time.Duration]time.Duration{ + 30 * time.Minute: 1 * time.Hour, // off-peak, next: peak starts + 2 * time.Hour: 4 * time.Hour, // peak, next: peak ends + 5 * time.Hour: 6 * time.Hour, // gap between windows + 7 * time.Hour: 10 * time.Hour, // second window + 12 * time.Hour: 25 * time.Hour, // rest of day: tomorrow's first window + } + for at, want := range cases { + if got := NextChange(day.Add(at)); !got.Equal(day.Add(want)) { + t.Errorf("NextChange(+%v) = %v, want +%v", at, got, want) + } + } +} + func TestCacheSavings(t *testing.T) { // What the cached tokens would have cost at the miss rate, minus what - // they did cost: 1M * (0.14 - 0.0028). - got, ok := CacheSavings(ModelFlash, Usage{InputTokens: 1_000_000, CacheHitTokens: 1_000_000}) + // they did cost, under the card in force now. The exact figure moves + // with the era, so pin the identity against PriceFor rather than a + // constant that would silently change meaning at the flip. + u := Usage{InputTokens: 1_000_000, CacheHitTokens: 1_000_000} + got, ok := CacheSavings(ModelFlash, u) if !ok { t.Fatal("flash should be priced") } - if math.Abs(got-0.1372) > 1e-9 { - t.Errorf("got %v, want 0.1372", got) + p, _ := PriceFor(ModelFlash) + if want := p.CacheMissInput - p.CacheHitInput; math.Abs(got-want) > 1e-9 { + t.Errorf("got %v, want %v", got, want) } if zero, _ := CacheSavings(ModelFlash, Usage{InputTokens: 100, CacheMissTokens: 100}); zero != 0 { t.Errorf("no cache hits should mean no savings, got %v", zero) @@ -173,8 +258,8 @@ func TestResolveModel(t *testing.T) { func TestCostFollowsTheRemappedModel(t *testing.T) { // A Claude name billed at pro rates, not flash rates. u := Usage{InputTokens: 1_000_000, CacheMissTokens: 1_000_000} - opus, _ := Cost("claude-opus-4-1", u) - pro, _ := Cost(ModelPro, u) + opus, _ := CostAt("claude-opus-4-1", u, atOffPeak) + pro, _ := CostAt(ModelPro, u, atOffPeak) if opus != pro { t.Errorf("claude-opus cost %v, deepseek-v4-pro cost %v — should match", opus, pro) } diff --git a/internal/docs/corpus.tar.gz b/internal/docs/corpus.tar.gz index d665799..f205e73 100644 Binary files a/internal/docs/corpus.tar.gz and b/internal/docs/corpus.tar.gz differ diff --git a/site/404.html b/site/404.html index dd96079..4fc7b93 100644 --- a/site/404.html +++ b/site/404.html @@ -73,6 +73,7 @@ commands formats cost + pricing bench news agents @@ -97,7 +98,8 @@
DeepSeek prices are the published USD rate card of 2026-08-02, unchanged at GA; they are a conversion of the RMB card (¥3 / ¥0.025 / ¥6 per 1M for pro) at one consistent rate. GPT-5.6 -Sol prices are from OpenAI's own listing. A broad -DeepSeek repricing is announced but has no date, so it is not applied here.
+Sol prices are from OpenAI's own listing. From 2026-08-16 16:00 UTC DeepSeek +bills peak/off-peak on a higher card – the +pricing page has the dated schedule. Even at +the new peak rate, pro stays ~3.8x cheaper than GPT-5.6 Sol on cache-miss +input and ~7.6x on output.The sober version matters as much as the slogan. The kill line is real for the middle of the market: a model that costs more than V4-Pro and scores below it on the table above is hard to justify, and that is most of the @@ -230,7 +234,7 @@
tokensPOST /beta/completionsdocsusagepricingsessionchat --continue replays.statusGET /models, /user/balancecheckds pricing
+The rate card, the time-of-day schedule, and the billing period in effect +right now – local, UTC and Beijing time, plus when the period next +changes. Computed locally from the same schedule the cost estimates use, so +nothing is spent asking. See the pricing +page for the same table in the browser.
+ds usage # today
ds usage --since 7d
@@ -1057,7 +1166,10 @@ def jstr(s):
invisible unless something is counting – so this counts.
The rate card
-USD per 1M tokens, as published on 2026-08-02:
+USD per 1M tokens, as published on 2026-08-02 and in force before
+2026-08-16 16:00 UTC – from that instant DeepSeek bills peak/off-peak
+on a new card, and the pricing page carries
+the dated schedule:
Model Input (cached) Input (miss) Output
@@ -1067,8 +1179,9 @@ def jstr(s):
-ds models prints this next to the live model list, so the price
-is on screen when you pick.
+ds models prints the card in force next to the live model
+list, so the price is on screen when you pick, and ds pricing
+prints the full schedule with the period you are in right now.
What the cache is worth
On flash, a cache hit costs 1/50th of a miss. The same
@@ -1166,15 +1279,18 @@ def jstr(s):
Estimates, not invoices. Computed from the published USD
rate card. Your account may bill in another currency –
ds balance shows which.
-Peak pricing is not applied. DeepSeek has announced a 2×
-multiplier for 09:00–12:00 and 14:00–18:00 Beijing time, with no
-effective date. Applying it now would double every estimate on a guess, so it
-is deliberately left out until the date is announced.
-A broader repricing is coming. On 2026-08-06 DeepSeek
-gave notice in the platform console that all API services will be repriced
-soon, with a substantial rise expected and no numbers yet. Until there is a
-new published card, estimates stay on the card above – details on the
-news page.
+The repricing is dated, and the switch is encoded.
+From 2026-08-16 16:00 UTC DeepSeek bills peak/off-peak on a new, higher
+card (peak hours 01:00–04:00 and 06:00–10:00 UTC at twice the
+off-peak rate). Estimates use the flat card above until that instant and
+switch automatically on it – never before, because applying a price
+before its effective date would be inventing data. The
+pricing page and ds pricing
+carry the schedule and the new numbers.
+The cache discount narrows at the flip. On the card
+above a cached input token costs 1/50th of a miss on flash and 1/120th
+on pro; on the card of 2026-08-16 both settle at about 1/30th. Still
+the biggest lever on a bill, just a smaller one.
Local only. The ledger records calls made by this CLI on
this machine. It knows nothing about your other clients.
@@ -1185,6 +1301,116 @@ def jstr(s):
""",
))
+PAGES.append(dict(
+ slug="pricing/",
+ crumb="pricing",
+ title="DeepSeek API pricing: the schedule, the peak hours, and the period right now",
+ description="DeepSeek moves to peak/off-peak API billing at 16:00 UTC on 2026-08-16: peak hours 01:00-04:00 and 06:00-10:00 UTC at twice the off-peak rate. The full dated schedule, the current flat card, the new numbers per model, and a strip that reads your clock and names the billing period you are in right now.",
+ keywords="deepseek pricing, deepseek api pricing, deepseek price increase 2026, deepseek repricing, deepseek peak hours, deepseek off-peak pricing, deepseek peak off-peak billing, deepseek api cost, deepseek v4 flash price, deepseek v4 pro price, deepseek pricing 2026-08-16, deepseek new rate card, deepseek token price",
+ jsonld=faq([
+ ("When does DeepSeek's peak/off-peak pricing start?",
+ "At 16:00 UTC on August 16, 2026. Until that instant, every hour bills at the flat card published 2026-08-02. From it, DeepSeek bills peak/off-peak on a new card: peak hours are 01:00-04:00 and 06:00-10:00 UTC daily at twice the off-peak rate, and all other hours are off-peak."),
+ ("What are DeepSeek's peak hours?",
+ "01:00-04:00 and 06:00-10:00 UTC, daily. The boundaries are defined in UTC; in Beijing time (UTC+8) they read 09:00-12:00 and 14:00-18:00. Every other hour is off-peak, at half the peak rate."),
+ ("What will the DeepSeek API cost after August 16, 2026?",
+ "Per 1M tokens (cache hit / cache miss / output): deepseek-v4-flash off-peak $0.007 / $0.22 / $0.66 and peak $0.014 / $0.44 / $1.32; deepseek-v4-pro off-peak $0.022 / $0.66 / $1.98 and peak $0.044 / $1.32 / $3.96."),
+ ("What does the DeepSeek API cost right now?",
+ "Before 16:00 UTC on 2026-08-16, the flat card of 2026-08-02 applies at every hour. Per 1M tokens (cache hit / cache miss / output): deepseek-v4-flash $0.0028 / $0.14 / $0.28; deepseek-v4-pro $0.003625 / $0.435 / $0.87."),
+ ("Is DeepSeek's off-peak price the same as the current price?",
+ "No. Off-peak is half of peak, but on a new, higher base card: a flash cache-miss input token goes from $0.14 to $0.22 per 1M off-peak and $0.44 peak. Even the cheapest hour after the switch costs more than any hour before it."),
+ ("How do I check the current DeepSeek billing period from the terminal?",
+ "Run `deepseek pricing`. It prints the period in effect (flat, off-peak or peak) with your local, UTC and Beijing time, when the period next changes, and both rate cards, all computed locally from the same schedule the CLI's cost estimates use. `deepseek pricing --json` emits the same as JSON."),
+ ]),
+ body="""
+Pricing
+From 16:00 UTC on 2026-08-16, what a DeepSeek token costs
+depends on the hour. This page carries the dated schedule, both rate cards,
+and a strip that reads your clock – the same data the CLI's estimates
+switch on, so the page and ds pricing can never disagree.
+
+Right now
+
+your clock decides
+""" + price_now_verdict() + """
+
+
+The schedule
+One table drives everything on this page: each row is a billing period,
+its daily window in UTC minutes, the multiplier on that era's base card,
+and the instant the row takes effect. The strip above, the CLI's
+estimates and ds pricing all read the same rows.
+
+
+Period Daily window Multiplier Effective from
+
+""" + price_schedule_rows() + """
+
+
+
+The windows are defined in UTC, not Beijing – in Beijing time
+(UTC+8) the peak hours read 09:00–12:00 and 14:00–18:00, which
+is the Chinese working day. The off-peak window covers the whole European
+and American working day, so batch work that can move west should.
+
+Until the switch: the flat card
+USD per 1M tokens, published 2026-08-02, billed at every hour of the day
+until 2026-08-16 16:00 UTC:
+
+
+Model Input (cached) Input (miss) Output
+
+deepseek-v4-flash$0.0028 $0.14 $0.28
+deepseek-v4-pro$0.003625 $0.435 $0.87
+
+
+
+
+From 2026-08-16 16:00 UTC: the peak/off-peak card
+USD per 1M tokens. Off-peak is half of peak by construction, but on a
+new, higher base – even the cheapest hour after the switch costs more
+than any hour before it. A flash cache-miss input token goes from $0.14 to
+$0.22 per 1M off-peak, and to $0.44 in peak hours:
+
+
+Model Period Input (cached) Input (miss) Output
+
+deepseek-v4-flashoff-peak $0.007 $0.22 $0.66
+peak $0.014 $0.44 $1.32
+deepseek-v4-prooff-peak $0.022 $0.66 $1.98
+peak $0.044 $1.32 $3.96
+
+
+
+The context cache stays the biggest
+lever: a cached input token costs about 1/30th of a miss on the new card,
+against 1/50th (flash) and 1/120th (pro) today. Prompt structure will
+still dominate a bill; the hour of the day comes second.
+
+The same answer in the terminal
+ds pricing # period right now, local + UTC + Beijing, both cards
+ds pricing --json # the same, as JSON for scripts
+Computed locally from the same schedule – no network call, nothing
+spent. The cost estimates price each call with
+the card in force at the moment it is made: the flat card until the
+effective instant, peak/off-peak after, never early – applying a
+price before its date would be inventing data. The
+ledger stores exact token counts rather
+than dollar amounts, so historical calls can always be repriced under
+whatever card was, or becomes, real.
+
+Source
+The numbers are DeepSeek's own, from the official
+Models & Pricing page as
+updated on 2026-08-13 alongside the V4-Pro GA release. The CLI carries the
+same page offline – ds docs show quick_start/pricing
+– and ds docs sync refreshes it, so the terminal copy,
+this page and the estimates trace to one upstream.
+
+
+""",
+ scripts='\n',
+))
+
PAGES.append(dict(
slug="bench/",
crumb="bench",
@@ -1313,8 +1539,11 @@ def jstr(s):
DeepSeek prices are the published USD rate card of
2026-08-02, unchanged at GA; they are a conversion of the RMB card
(¥3 / ¥0.025 / ¥6 per 1M for pro) at one consistent rate. GPT-5.6
-Sol prices are from OpenAI's own listing. A broad
-DeepSeek repricing is announced but has no date, so it is not applied here.
+Sol prices are from OpenAI's own listing. From 2026-08-16 16:00 UTC DeepSeek
+bills peak/off-peak on a higher card – the
+pricing page has the dated schedule. Even at
+the new peak rate, pro stays ~3.8x cheaper than GPT-5.6 Sol on cache-miss
+input and ~7.6x on output.
The sober version matters as much as the slogan. The kill line is real for
the middle of the market: a model that costs more than V4-Pro and
scores below it on the table above is hard to justify, and that is most of the
@@ -1362,9 +1591,9 @@ def jstr(s):
PAGES.append(dict(
slug="news/",
crumb="news",
- title="DeepSeek API news: dsh ships, V4-Pro GA, the announced price rise",
- description="What is changing around the DeepSeek API: DeepSeek ships dsh (DeepSeek Harness), its official open-source agent harness, V4-Pro's official release (DeepSeek-V4-Pro-0813), an across-the-board price increase announced with no date yet, the 2x peak-hour pricing policy, V4-Flash's official release, and what each one means for a call.",
- keywords="deepseek harness, dsh, @deepseek-ai/dsh, install dsh, dsh plugins, dsh skills, dsh-plugin, dsh vs claude code, deepseek cli vs dsh, deepseek v4 pro release, deepseek v4 pro ga, deepseek-v4-pro-0813, deepseek api price increase, deepseek price rise 2026, deepseek peak hour pricing, deepseek api news, deepseek api changelog, deepseek v4 flash release, deepseek pricing change",
+ title="DeepSeek API news: the dated repricing, dsh ships, V4-Pro GA",
+ description="What is changing around the DeepSeek API: the price rise now has a date and numbers (peak/off-peak billing from 2026-08-16 16:00 UTC), DeepSeek ships dsh (DeepSeek Harness), its official open-source agent harness, V4-Pro's official release (DeepSeek-V4-Pro-0813), V4-Flash's official release, and what each one means for a call.",
+ keywords="deepseek harness, dsh, @deepseek-ai/dsh, install dsh, dsh plugins, dsh skills, dsh-plugin, dsh vs claude code, deepseek cli vs dsh, deepseek v4 pro release, deepseek v4 pro ga, deepseek-v4-pro-0813, deepseek api price increase, deepseek price rise 2026, deepseek peak hour pricing, deepseek peak off-peak billing, deepseek repricing 2026-08-16, deepseek api news, deepseek api changelog, deepseek v4 flash release, deepseek pricing change",
jsonld=faq([
("Is DeepSeek V4-Pro officially released?",
"Yes. On 2026-08-12 the model version on DeepSeek's Models & Pricing page changed to DeepSeek-V4-Pro-0813, ending the preview that had run since 2026-04-24. The model ID is unchanged (deepseek-v4-pro), the rate card is unchanged, and the release focuses on agentic post-training rather than new pretraining."),
@@ -1373,11 +1602,11 @@ def jstr(s):
("Is dsh the same as deepseek-cli?",
"No. dsh (DeepSeek Harness) is DeepSeek's official agent harness: it runs an agent loop, executes tools, and manages plugins, skills and sessions. deepseek-cli is an unofficial single-binary API client: it sends one request in any of DeepSeek's four wire formats, prints the response and its estimated cost, and carries DeepSeek's API documentation offline. They are complementary, not competing: run agents with dsh, and use deepseek-cli to check a key, price a call, debug the wire formats, or query the docs."),
("Is DeepSeek raising its API prices?",
- "Yes, a rise is announced but not yet in effect. On 2026-08-06 (Beijing time) DeepSeek posted a notice in the platform console and emailed API account holders saying all API services will be repriced in the near term and that the increase is expected to be substantial, advising developers to plan call volume and top-ups accordingly. No new rate card and no effective date have been published. Separately, a 2x peak-hour pricing policy has been announced since June 2026, also without an effective date."),
+ "Yes, and the rise now has a date and numbers. At 16:00 UTC on 2026-08-16 DeepSeek moves to peak/off-peak billing on a new, higher card: peak hours are 01:00-04:00 and 06:00-10:00 UTC daily at twice the off-peak rate, and all other hours are off-peak. Per 1M tokens (cache hit / miss / output), deepseek-v4-flash goes to $0.007/$0.22/$0.66 off-peak and $0.014/$0.44/$1.32 peak; deepseek-v4-pro to $0.022/$0.66/$1.98 off-peak and $0.044/$1.32/$3.96 peak. This resolves both the undated broad price rise announced on 2026-08-06 and the undated peak-hour policy announced in June 2026."),
("When does DeepSeek's peak-hour pricing start?",
- "No effective date has been announced. The published policy: during peak hours, 09:00-12:00 and 14:00-18:00 Beijing time (01:00-04:00 and 06:00-10:00 UTC) daily, all billing items cost 2x the regular price. Until DeepSeek announces the date, the policy is not active and estimates should not apply it."),
+ "At 16:00 UTC on August 16, 2026. Peak hours are 01:00-04:00 and 06:00-10:00 UTC (09:00-12:00 and 14:00-18:00 Beijing time) daily, at twice the off-peak rate; every other hour is off-peak. Before that instant the flat card of 2026-08-02 applies at every hour and no multiplier should be applied."),
("What are DeepSeek's current API prices?",
- "Per 1M tokens, from the rate card published 2026-08-02: deepseek-v4-flash is $0.14 input on a cache miss, $0.0028 on a cache hit, and $0.28 output; deepseek-v4-pro is $0.435 input on a miss, $0.003625 on a hit, and $0.87 output."),
+ "Per 1M tokens, from the rate card published 2026-08-02 and in force until 16:00 UTC on 2026-08-16: deepseek-v4-flash is $0.14 input on a cache miss, $0.0028 on a cache hit, and $0.28 output; deepseek-v4-pro is $0.435 input on a miss, $0.003625 on a hit, and $0.87 output. From that instant peak/off-peak billing applies on a new card."),
("Where can I follow DeepSeek API changes?",
"DeepSeek's own change log lives at api-docs.deepseek.com/updates. The deepseek CLI carries the same documentation inside the binary: `deepseek docs changelog` prints it offline, and `deepseek docs sync` refreshes the snapshot."),
]),
@@ -1388,6 +1617,30 @@ def jstr(s):
live API where that is possible; the in-terminal feed is
ds docs changelog.
+2026-08-13 · the price rise has its date and its numberseffective 2026-08-16
+The other shoe drops. Alongside the V4-Pro GA release, DeepSeek's
+Models & Pricing page now
+carries the repricing that the August 6 notice
+promised and the June peak-hour policy
+sketched, and this time it is dated: at 16:00 UTC on 2026-08-16
+the API moves to peak/off-peak billing. Peak hours are
+01:00–04:00 and 06:00–10:00 UTC daily – the boundaries
+are defined in UTC – at twice the off-peak rate; every other hour is
+off-peak.
+The multiplier is the June policy's, but the base card is new and
+higher. Off-peak is not a discount on today's prices: a flash cache-miss
+input token goes from $0.14 to $0.22 per 1M in the cheapest hour,
+and to $0.44 in peak. The full schedule and both cards are on the
+pricing page, which also reads your clock
+and names the period you are in.
+What it changes here: the switch is encoded, not guessed.
+Estimates price every call with the card in force at the moment it is made
+– the flat card of 2026-08-02 until the effective instant, peak/off-peak
+after, never early. ds pricing prints the period in effect
+right now with local, UTC and Beijing time, and the
+ledger stores token counts, not dollars,
+so history can be repriced under any card.
+
2026-08-13 · DeepSeek ships dsh, its own agent harness
DeepSeek released DeepSeek Harness:
deepseek-ai/deepseek-harness
@@ -1598,7 +1851,11 @@ def jstr(s):
checkpoint since the cell changed. Same price, stronger model; the
estimates already price it correctly.
-2026-08-06 · a broad price rise is comingdate tba
+2026-08-06 · a broad price rise is comingresolved 2026-08-13
+Update: this is no longer a rumour with a direction.
+On 2026-08-13 DeepSeek published the new card and the
+date: peak/off-peak billing from 2026-08-16 16:00 UTC. The entry below
+stands as it was written, as the record of the announcement.
DeepSeek posted a notice in the
platform console: all API
services will be repriced “in the near term”, and the increase is
@@ -1631,7 +1888,13 @@ def jstr(s):
than prices, deliberately – when the card changes, every historical
call can be repriced under it.
-announced 2026-06-29 · 2× during peak hoursdate tba
+announced 2026-06-29 · 2× during peak hoursdated 2026-08-13
+Update: the date exists now, and one detail below did
+not survive it. The 2026-08-13 announcement keeps
+the 2× multiplier and the same windows but puts them on a new, higher
+base card – so “off-peak, the current card stands” is no
+longer true. Effective 2026-08-16 16:00 UTC; the
+pricing page has the numbers.
The pricing page has carried this since late June: the API will move to
peak/off-peak pricing, with every billing item – input, cached input,
output – costing 2× the regular price during peak
@@ -1679,7 +1942,8 @@ def jstr(s):
what does not. The complete feed:
ds docs changelog # DeepSeek's own change log, in the terminal, offline
ds docs sync # refresh the snapshot the binary carries
-ds models # the rate card the estimates use, next to the live model list
+ds models # the rate card the estimates use, next to the live model list
+ds pricing # the time-of-day schedule and the billing period right now
Upstream: the official change log and pricing page, and status.deepseek.com for incidents.
@@ -2022,7 +2286,8 @@ def build(check_only=False):ds pricing
+The rate card, the time-of-day schedule, and the billing period in effect +right now – local, UTC and Beijing time, plus when the period next +changes. Computed locally from the same schedule the cost estimates use, so +nothing is spent asking. See the pricing +page for the same table in the browser.
+ds usage # today
ds usage --since 7d
diff --git a/site/cost/index.html b/site/cost/index.html
index 547abe6..6ee7095 100644
--- a/site/cost/index.html
+++ b/site/cost/index.html
@@ -68,6 +68,7 @@
commands
formats
cost
+ pricing
bench
news
agents
@@ -90,7 +91,10 @@ Cost
invisible unless something is counting – so this counts.
The rate card
-USD per 1M tokens, as published on 2026-08-02:
+USD per 1M tokens, as published on 2026-08-02 and in force before
+2026-08-16 16:00 UTC – from that instant DeepSeek bills peak/off-peak
+on a new card, and the pricing page carries
+the dated schedule:
Model Input (cached) Input (miss) Output
@@ -100,8 +104,9 @@ The rate card
-ds models prints this next to the live model list, so the price
-is on screen when you pick.
+ds models prints the card in force next to the live model
+list, so the price is on screen when you pick, and ds pricing
+prints the full schedule with the period you are in right now.
What the cache is worth
On flash, a cache hit costs 1/50th of a miss. The same
@@ -199,15 +204,18 @@
What these numbers are not
Estimates, not invoices. Computed from the published USD
rate card. Your account may bill in another currency –
ds balance shows which.
-Peak pricing is not applied. DeepSeek has announced a 2×
-multiplier for 09:00–12:00 and 14:00–18:00 Beijing time, with no
-effective date. Applying it now would double every estimate on a guess, so it
-is deliberately left out until the date is announced.
-A broader repricing is coming. On 2026-08-06 DeepSeek
-gave notice in the platform console that all API services will be repriced
-soon, with a substantial rise expected and no numbers yet. Until there is a
-new published card, estimates stay on the card above – details on the
-news page.
+The repricing is dated, and the switch is encoded.
+From 2026-08-16 16:00 UTC DeepSeek bills peak/off-peak on a new, higher
+card (peak hours 01:00–04:00 and 06:00–10:00 UTC at twice the
+off-peak rate). Estimates use the flat card above until that instant and
+switch automatically on it – never before, because applying a price
+before its effective date would be inventing data. The
+pricing page and ds pricing
+carry the schedule and the new numbers.
+The cache discount narrows at the flip. On the card
+above a cached input token costs 1/50th of a miss on flash and 1/120th
+on pro; on the card of 2026-08-16 both settle at about 1/30th. Still
+the biggest lever on a bill, just a smaller one.
Local only. The ledger records calls made by this CLI on
this machine. It knows nothing about your other clients.
@@ -217,7 +225,7 @@ What these numbers are not
a completion, not for bookkeeping.