API reference¶
Everything below is exported from the package root: offpeak.run,
offpeak.Job, and so on.
Running work¶
offpeak.run(jobs, deadline, *, venues=None, fallback='sync', poll_interval=None, risk_buffer=None)
¶
Run jobs against deadline on the cheapest supporting venue.
Submits each job to its venue's batch tier, polls until everything lands,
and — if the batch has not completed by the time the remaining window
shrinks to risk_buffer seconds — cancels and re-runs the stragglers
synchronously at list price so the deadline is met (fallback="sync",
the default; fallback="none" reports them failed instead).
Returns one :class:Result per job, in input order, each with a
:class:Receipt.
Provider failures never escape: if a venue raises while submitting, polling
or running the sync fallback, the affected jobs are rescued through the
fallback where the deadline still allows it and otherwise come back as
failed :class:Result objects carrying the provider's message. Exceptions
out of run() are reserved for programming errors — a bad deadline, or a
model no configured venue supports.
run() blocks for as long as the batch takes. When the calling process
cannot stay alive that long — a laptop, a CI step, a serverless function —
use :func:submit and :func:collect and keep the :class:Ticket between
them; run() is exactly collect(submit(...)).
Source code in src/offpeak/client.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
offpeak.submit(jobs, deadline, *, venues=None, risk_buffer=None)
¶
Submit jobs to their venues' batch tiers and return immediately.
The returned :class:Ticket is the run's whole state; keep it (save())
and finish with :func:collect — in this process or another. Raises only
for programming errors (a bad or past deadline, a model no venue supports);
a venue that fails at submit is recorded on the ticket and its jobs are
rescued by the fallback at collect time.
Source code in src/offpeak/ticket.py
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | |
offpeak.collect(ticket, *, venues=None, fallback='sync', wait=True, poll_interval=None)
¶
Finish a submitted run.
With wait=True (default) this is the back half of :func:run: poll
until every batch lands or the remaining window shrinks to the ticket's
risk buffer, then cancel stragglers and rescue them synchronously
(fallback="sync") or report them failed (fallback="none"). Returns
one :class:Result per job, in input order.
With wait=False it does one sweep and returns the results only if the
run can be settled now — everything landed, or the deadline is close
enough that the buffer rule fires. Otherwise it returns None with the
ticket updated (anything that landed is kept on it); call again later.
Pass the same venues you submitted with. Venues are matched by name;
a venue missing from this process fails its jobs with a clear message.
Source code in src/offpeak/ticket.py
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | |
offpeak.status(ticket, *, venues=None)
¶
One look at every open batch on ticket, keyed by venue name.
Read-only: nothing is collected, cancelled or rescued. A venue that
errors while being polled reports a failed state carrying the message.
Source code in src/offpeak/ticket.py
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
offpeak.quote
¶
The free quote — what a deadline is worth, before you spend anything.
quote() prices a job list against the bundled price sheet and returns what
each venue's batch tier would save versus running the same tokens synchronously
at list. It makes no API calls: no submission, no token-counting round
trip, no key required. It is arithmetic against published numbers, which is the
same thing a receipt is — just before the trade instead of after.
Token counts come from the job where the job knows them and are estimated where
it does not. Every quote says which, per figure, in :attr:Quote.basis: a
number you cannot trace back to its source is not a quote.
Output size is the one figure a pre-trade quote cannot know. Left alone, an
unknown output is priced at zero and the whole quote is marked a FLOOR —
understated on purpose, and saying so. A caller who does know roughly what the
model will write can say so and get a usable number instead, per job with
metadata={"expected_output_tokens": n} or across the run with
quote(..., assumed_output_ratio=r). Those quotes are marked EST. The
assumption is always the caller's, never the library's: nothing here invents an
output size on your behalf.
VenueQuote
dataclass
¶
What one venue's batch tier is worth for the jobs routed to it.
Source code in src/offpeak/quote.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
Quote
dataclass
¶
A pre-trade quote. No API calls were made to produce this.
Source code in src/offpeak/quote.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
is_floor
property
¶
True when some job's output tokens were unknown and priced at zero.
Output is the expensive side on every model on the sheet, so a quote that silently omits it reads far cheaper than the bill. Such a quote is a floor, and says so.
is_estimated
property
¶
True when some job's output size was assumed rather than known.
Distinct from :attr:is_floor. A floor is understated by construction —
output priced at zero. An estimate is priced on an assumption the caller
supplied, so it can land either side of the bill. Both are marked on the
card; neither is silent.
within_batch_window
property
¶
Whether the deadline clears the venues' published completion window.
estimate_tokens(j, *, assumed_output_ratio=None)
¶
(input, output, input_basis, output_basis) for one job.
Input: an explicit count on job.metadata wins, else a chars/4 estimate.
Output, in order — a count, then the caller's own expectation, then a ceiling, then the run-wide ratio if one was opted into, then nothing:
metadata["output_tokens"]— a count someone measured.metadata["expected_output_tokens"]— what the caller expects this job to write. More specific than a ceiling set for safety, so it outranks one, and labeled an assumption either way.params["max_tokens"]— an upper bound, priced as one.- assumed_output_ratio × the input tokens, when the caller passed one.
- Nothing: zero, labeled unknown, which is what makes a quote a floor.
Each figure reports its own provenance so a quote never launders an estimate into a fact.
Source code in src/offpeak/quote.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
quote(jobs, deadline, *, venues=None, assumed_output_ratio=None)
¶
Price jobs against deadline without calling any provider.
Routes each job to the venue that would run it, then settles list versus batch cost from the bundled price sheet.
assumed_output_ratio is an explicit opt-in: for jobs that carry no output
signal at all, assume they write ratio x their input tokens. 0.25
suits summarization; a long-form generator writes more than it reads and
wants a ratio above 1. Without it, such jobs price at zero output and the
quote is a FLOOR — the library does not guess on your behalf. With it,
the quote is marked EST and :attr:Quote.is_estimated is true. Per-job
expectations (metadata={"expected_output_tokens": n}) take precedence
and are marked the same way.
Raises ValueError for a deadline in the past, a model no venue supports,
or a non-positive ratio — the same programming errors :func:offpeak.run
reserves exceptions for.
Source code in src/offpeak/quote.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | |
offpeak.receipt(results)
¶
Settle a run: aggregate per-job receipts into one :class:Settlement.
Source code in src/offpeak/client.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
offpeak.job
¶
Job, Result, and Receipt — the unit of deferred work and its settlement.
Job
dataclass
¶
A venue-agnostic chat-completion job.
Source code in src/offpeak/job.py
23 24 25 26 27 28 29 30 31 32 | |
Receipt
dataclass
¶
Per-job settlement: what ran where, when, and what the hour was worth.
Source code in src/offpeak/job.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
list_usd
property
¶
What the job would have cost run synchronously at list price.
paid_usd
property
¶
What the job cost on the venue it actually ran on.
spread_usd
property
¶
Captured spread: list minus paid.
__str__()
¶
One line, in money you can actually read.
The float properties above stay floats — this is the rendering, so a sub-cent job reports what it cost instead of $0.00.
Source code in src/offpeak/job.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
Result
dataclass
¶
The outcome of one job.
Source code in src/offpeak/job.py
125 126 127 128 129 130 131 132 133 134 135 136 137 | |
job(model, input=None, *, system=None, metadata=None, **params)
¶
Build a :class:Job.
input may be a plain prompt string or a full messages list.
Extra keyword arguments (temperature, max_tokens, ...) are passed
through to the venue.
Source code in src/offpeak/job.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
Types¶
offpeak.Job
dataclass
¶
A venue-agnostic chat-completion job.
Source code in src/offpeak/job.py
23 24 25 26 27 28 29 30 31 32 | |
offpeak.Ticket
dataclass
¶
Everything needed to finish a run that was started somewhere else.
Produced by :func:submit, consumed by :func:collect. Serialisable with
:meth:to_dict / :meth:from_dict (plain JSON types only), and
:meth:save / :meth:load for the one-file case.
Source code in src/offpeak/ticket.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | |
pending
property
¶
True while any batch is still open at a venue.
remaining
property
¶
Seconds until the deadline (negative once it has passed).
save(path)
¶
Write the ticket as JSON. Overwrites.
Source code in src/offpeak/ticket.py
190 191 192 193 194 | |
offpeak.Result
dataclass
¶
The outcome of one job.
Source code in src/offpeak/job.py
125 126 127 128 129 130 131 132 133 134 135 136 137 | |
offpeak.Receipt
dataclass
¶
Per-job settlement: what ran where, when, and what the hour was worth.
Source code in src/offpeak/job.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
list_usd
property
¶
What the job would have cost run synchronously at list price.
paid_usd
property
¶
What the job cost on the venue it actually ran on.
spread_usd
property
¶
Captured spread: list minus paid.
__str__()
¶
One line, in money you can actually read.
The float properties above stay floats — this is the rendering, so a sub-cent job reports what it cost instead of $0.00.
Source code in src/offpeak/job.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
offpeak.Status
¶
Bases: str, Enum
Source code in src/offpeak/job.py
15 16 17 18 19 20 | |
offpeak.Settlement
dataclass
¶
Aggregate receipt across a run.
Source code in src/offpeak/client.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
offpeak.Quote
dataclass
¶
A pre-trade quote. No API calls were made to produce this.
Source code in src/offpeak/quote.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
is_floor
property
¶
True when some job's output tokens were unknown and priced at zero.
Output is the expensive side on every model on the sheet, so a quote that silently omits it reads far cheaper than the bill. Such a quote is a floor, and says so.
is_estimated
property
¶
True when some job's output size was assumed rather than known.
Distinct from :attr:is_floor. A floor is understated by construction —
output priced at zero. An estimate is priced on an assumption the caller
supplied, so it can land either side of the bill. Both are marked on the
card; neither is silent.
within_batch_window
property
¶
Whether the deadline clears the venues' published completion window.
offpeak.VenueQuote
dataclass
¶
What one venue's batch tier is worth for the jobs routed to it.
Source code in src/offpeak/quote.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
Deadlines¶
offpeak.parse_deadline(value, *, now=None)
¶
Resolve value to an aware datetime.
Raises ValueError if the form is unrecognized or the resolved deadline
is not in the future, and TypeError for unsupported types.
Source code in src/offpeak/deadline.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
offpeak.seconds_until(deadline, *, now=None)
¶
Seconds remaining until deadline (negative if it has passed).
Source code in src/offpeak/deadline.py
55 56 57 58 59 | |
Venues¶
offpeak.Venue
¶
Bases: ABC
A place deferred work can execute, plus a synchronous escape hatch.
Source code in src/offpeak/venues/base.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
supports(model)
abstractmethod
¶
Whether this venue can run model.
Source code in src/offpeak/venues/base.py
78 79 80 | |
submit(jobs)
abstractmethod
¶
Submit jobs as one batch; return an opaque batch handle.
Source code in src/offpeak/venues/base.py
82 83 84 | |
status(handle)
abstractmethod
¶
Poll a batch's progress.
Source code in src/offpeak/venues/base.py
86 87 88 | |
collect(handle)
abstractmethod
¶
Fetch results for a finished batch, keyed by job id.
Source code in src/offpeak/venues/base.py
90 91 92 | |
cancel(handle)
abstractmethod
¶
Best-effort cancel of an in-flight batch.
Source code in src/offpeak/venues/base.py
94 95 96 | |
run_sync(job)
abstractmethod
¶
Run one job synchronously at list price (the SLA fallback path).
Source code in src/offpeak/venues/base.py
98 99 100 | |
offpeak.BatchState
dataclass
¶
A venue batch's progress.
Source code in src/offpeak/venues/base.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
offpeak.default_venues()
¶
Provider batch tiers, tried in order. SDKs import lazily on first use.
Anthropic and OpenAI only. Every other venue in the tree — Groq, Mistral, Gemini, DeepSeek, Qwen — is opt-in: it wants its own key and its own extra, and a model name should not start costing money at a venue nobody asked for. Pass them explicitly::
from offpeak.venues import DeepSeekClock, QwenBatch
offpeak.run(jobs, "06:00", venues=[DeepSeekClock(), QwenBatch()])
Source code in src/offpeak/client.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
Prices¶
offpeak.prices
¶
List-price sheet and batch discounts, for receipts.
Receipts are arithmetic against public price sheets — no estimates. The prices
below are a bundled snapshot (see PRICE_SHEET_DATE); providers change
prices, so verify against their published sheets and override at runtime with
:func:register_price where they have moved. Costs for unknown models resolve
to None rather than a guess.
Batch tiers at OpenAI, Anthropic, Google, Groq, Mistral and Alibaba are publicly
priced at 50% of list, which is what :data:BATCH_DISCOUNT encodes. OpenAI's flex tier prices
identically to its batch tier on the gpt-5.6 family, and its fast tier at
twice list — the same model, priced for urgency. Anthropic publishes a fast
tier too, on Claude Opus 5 and Opus 4.8, also at twice list. Fast is stored
rather than derived (:func:get_fast_price), because unlike batch it is not a
discount rule but its own published row; :func:urgency_spread divides the two
so the price of an hour is a computed number and not a claim in prose.
Some list prices are promotional and will step up on a published date. Those
carry a :class:PromoNote in :data:PROMO_NOTES — the date and the post-promo
list — so a quote or a docs page can flag the decay instead of reading a
temporary number as permanent.
Two venues price the same 2.0x spread on a different axis. DeepSeek publishes
no batch tier; it publishes a clock, with peak hours on weekdays and half
price everywhere else. The sheet stores its peak rate as the standard row, so
BATCH_DISCOUNT reproduces the off-peak rate exactly — but the lane is a
clock and not a queue, and :func:lane_for says which. See
:mod:offpeak.venues.deepseek_clock.
Corrections¶
2026-08-30 — two venues added; nothing already on the sheet moved.
DeepSeek, from the rendered page at api-docs.deepseek.com/quick_start/pricing
(read 2026-08-28, re-read 2026-08-30 for the per-model columns). The standard
row is the peak rate — $0.44 / $1.32 on deepseek-v4-flash, $1.32 / $3.96
on deepseek-v4-pro — and the page's own words are "off-peak rates are half of
the peak rates", so the batch rule gives $0.22 / $0.66 and $0.66 / $1.98,
which is what the page prints. Cache-hit input ($0.007 / $0.022 off-peak) is
on the page and not on the sheet: there is no cache dimension here, every
input token settles at the miss rate, and a cache-heavy run overstates. The
lane is "clock" in :data:_LANES, since DeepSeek has no batch API and the
discount is decided per request by the wall clock.
Qwen, from alibabacloud.com/help/en/model-studio/model-pricing, international
(Singapore) region: qwen3.7-max at $2.50 / $7.50 (first read 2026-08-21,
confirmed 2026-08-30) and qwen3.8-max at $2.00 / $6.00 (read 2026-08-30).
Batch is 50% on both, per the same page and the batch-interface docs, so the
rule covers it. The page marks qwen3.7-max's rate "Limited-time 50% off"
without a date it runs through, and a :class:PromoNote needs one — so there
is no note, and a reader of this sheet should know the number may step up
unannounced. The Beijing region is priced separately and in its own currency;
it is not on the sheet.
2026-08-28 — two rows that had been wrong since before the sheet watch took
its first reading, so no hash diff could have found them; tools/sheet_reconcile.py
did, by reading the committed page text against this table.
Fast mode is no longer an OpenAI-only tier. Anthropic publishes one on Claude
Opus 5 and Opus 4.8 at $10 / $50 per 1M — research preview, first-party Claude
API only, and explicitly not available with the Batch API. The rows are in
:data:_FAST_PRICES; :func:urgency_spread therefore answers 4.0 for those two
models where it previously answered None. Nothing that was priced before
this date changes: a fast row is an addition, and no standard or batch number
moved.
Claude Sonnet 5's $2 / $10 is now the standard price. It shipped as
introductory pricing through 2026-08-31, with a scheduled step up to $3 / $15 on
2026-09-01; Anthropic has cancelled that increase. The numbers here were
already right — Sonnet 5 never carried a :class:PromoNote, so no quote ever
promised the step-up — but prose elsewhere that called the rate introductory was
describing a decay that will not happen, and has been removed.
2026-08-23 — the Mistral and Google blocks were read off mistral.ai/pricing/api and ai.google.dev/pricing on this date, and the snapshot date moved with them. The Anthropic, OpenAI and Groq blocks are carried forward unchanged from the 2026-08-21 reading; the date on the sheet is when it was last touched, not a claim that every row was re-verified.
2026-08-21 — the OpenAI block through 0.2.0 held that provider's batch sheet in the standard-price table (gpt-5.6-sol 2.50/15.00, terra 1.00/6.00, luna 0.10/0.60). The published short-context standard rates are 4.00/20.00, 2.00/12.00 and 0.20/1.20; the batch rows are 2.00/10.00, 1.00/6.00 and 0.10/0.60. Receipts for OpenAI models in 0.1.1–0.2.0 therefore understated both the list cost they compared against and the batch price actually billed — the wrong sheet derived $1.25/$7.50 for a batched sol job against a true $2.00 / $10.00. Anthropic's block was unaffected.
PromoNote
dataclass
¶
A list price that is promotional, and what it decays to.
A promotional rate is a real price today and a wrong one later. Carrying the step-up here keeps the sheet honest in both directions: receipts settle at the price actually charged, while a quote or a docs page can say — from data rather than prose — that the number has an expiry and what replaces it.
Source code in src/offpeak/prices.py
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
SheetLoad
dataclass
¶
What :func:load_sheet did — reported, never assumed.
Source code in src/offpeak/prices.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
sheet_date()
¶
The date of the sheet currently in force.
:data:PRICE_SHEET_DATE is the sheet this release bundles and never
moves. This is what is actually pricing jobs right now, which is the figure
a quote or a receipt should print.
Source code in src/offpeak/prices.py
393 394 395 396 397 398 399 400 | |
export_sheet()
¶
The sheet in force, as the published wire format.
This is the whole publishing story: the sheet is data, so it serializes.
No service, no database — a dated JSON file that anyone can fetch, diff,
pin, or check against the provider pages named in sources.
Source code in src/offpeak/prices.py
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | |
load_sheet(source, *, replace=False)
¶
Load a published price sheet over the bundled one. Opt in, always.
source is an https:// URL, a filesystem path, or an already-parsed
dict. Nothing in the library calls this for you: the default sheet is the
one this release shipped with, so offpeak keeps working offline and a
receipt settled today can still be checked next year against the numbers
that settled it.
replace=True clears the table first, so the loaded sheet is the whole
truth and a model it omits resolves to None. The default merges, which
keeps any :func:register_price overrides and older models you still run.
A sheet declaring a different batch_discount than this release is
refused rather than applied. The discount is a rule the venues publish
identically, not a row — and client and quote bound their copy of it
at import, so honouring it here would price some arithmetic at the new rate
and some at the old. That is a release, not a download.
Source code in src/offpeak/prices.py
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 | |
reset_sheet()
¶
Put the release's own bundled sheet back. Returns its date.
Source code in src/offpeak/prices.py
564 565 566 567 568 569 570 571 572 573 574 575 576 577 | |
register_price(model, input_per_m, output_per_m)
¶
Set or override the list price for model (USD per 1M tokens).
Source code in src/offpeak/prices.py
580 581 582 | |
get_price(model)
¶
Standard (synchronous) list price for model, USD per 1M tokens.
Source code in src/offpeak/prices.py
597 598 599 | |
get_fast_price(model)
¶
Fast-tier price for model, USD per 1M tokens.
None where the venue publishes no fast tier — which today is everywhere
except OpenAI's gpt-5.6 family and Anthropic's Opus 5 / Opus 4.8. Unlike
batch, fast is not a discount rule applied to list: it is its own published
row, so it is stored, not derived.
Source code in src/offpeak/prices.py
602 603 604 605 606 607 608 609 610 | |
get_promo_note(model)
¶
The :class:PromoNote for model, if its list price is promotional.
None means "no published promotion", which is also what a model
registered at runtime with :func:register_price returns — an override is
a price we were told, not a price we can date.
Source code in src/offpeak/prices.py
613 614 615 616 617 618 619 620 | |
lane_for(model)
¶
How model's venue sells its discount: "batch" or "clock".
"batch" for every priced model that has no lane row — the default,
since it is what every venue but one publishes. "clock" for a venue
whose half price is a function of when the request is made rather than
how long it may wait. None for a model that is not on the sheet: a
lane for a rate nobody published is not information.
Source code in src/offpeak/prices.py
623 624 625 626 627 628 629 630 631 632 633 634 | |
promo_decay(model)
¶
Multiple the (input, output) price steps up by when the promo lapses.
(1.25, 1.5) on gpt-5.6-sol: $4/$20 today, $5/$30 after. None where
the price is not promotional or the model is off the sheet.
Source code in src/offpeak/prices.py
637 638 639 640 641 642 643 644 645 646 647 | |
fast_cost_usd(model, input_tokens, output_tokens)
¶
What the same tokens cost on the venue's fast tier, where it has one.
Source code in src/offpeak/prices.py
662 663 664 665 666 667 | |
urgency_spread(model)
¶
How much the same model costs at its most urgent published tier over its most patient one: fast ÷ batch.
This is the intra-venue price of an hour with the model held constant — one provider, one model, two deadlines. On gpt-5.6-sol that is $8/$40 per 1M against $2/$10, a 4x spread; on claude-opus-5, $10/$50 against $2.50/$12.50, the same 4x at a different venue.
Both legs are checked and the lower is returned, so the figure can never
overstate what a venue publishes. None where the venue prices no fast
tier for the model, or the model is off the sheet.
Source code in src/offpeak/prices.py
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 | |
format_usd(amount)
¶
Money for humans: 2dp once there are cents to show, more significant digits below that so a sub-cent job does not settle as a column of $0.00.
None (an unpriced model) renders as an em dash, never as zero — a price
we do not know is not a price of nothing.
Source code in src/offpeak/prices.py
695 696 697 698 699 700 701 702 703 704 705 706 707 708 | |