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.
Source code in src/offpeak/client.py
47 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 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 | |
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
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
Quote
dataclass
¶
A pre-trade quote. No API calls were made to produce this.
Source code in src/offpeak/quote.py
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 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 | |
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
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 | |
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
258 259 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 | |
offpeak.receipt(results)
¶
Settle a run: aggregate per-job receipts into one :class:Settlement.
Source code in src/offpeak/client.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 | |
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 | |
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
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
Result
dataclass
¶
The outcome of one job.
Source code in src/offpeak/job.py
112 113 114 115 116 117 118 119 120 121 122 123 124 | |
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.Result
dataclass
¶
The outcome of one job.
Source code in src/offpeak/job.py
112 113 114 115 116 117 118 119 120 121 122 123 124 | |
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 | |
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
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
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
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 | |
offpeak.Quote
dataclass
¶
A pre-trade quote. No API calls were made to produce this.
Source code in src/offpeak/quote.py
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 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 | |
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
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
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
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
supports(model)
abstractmethod
¶
Whether this venue can run model.
Source code in src/offpeak/venues/base.py
36 37 38 | |
submit(jobs)
abstractmethod
¶
Submit jobs as one batch; return an opaque batch handle.
Source code in src/offpeak/venues/base.py
40 41 42 | |
status(handle)
abstractmethod
¶
Poll a batch's progress.
Source code in src/offpeak/venues/base.py
44 45 46 | |
collect(handle)
abstractmethod
¶
Fetch results for a finished batch, keyed by job id.
Source code in src/offpeak/venues/base.py
48 49 50 | |
cancel(handle)
abstractmethod
¶
Best-effort cancel of an in-flight batch.
Source code in src/offpeak/venues/base.py
52 53 54 | |
run_sync(job)
abstractmethod
¶
Run one job synchronously at list price (the SLA fallback path).
Source code in src/offpeak/venues/base.py
56 57 58 | |
offpeak.BatchState
dataclass
¶
A venue batch's progress.
Source code in src/offpeak/venues/base.py
17 18 19 20 21 22 23 24 25 26 27 28 | |
offpeak.default_venues()
¶
Provider batch tiers, tried in order. SDKs import lazily on first use.
Source code in src/offpeak/client.py
23 24 25 26 27 28 | |
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, and Google 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. 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.
Corrections¶
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
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
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
138 139 140 | |
get_price(model)
¶
Standard (synchronous) list price for model, USD per 1M tokens.
Source code in src/offpeak/prices.py
155 156 157 | |
get_fast_price(model)
¶
Fast-tier price for model, USD per 1M tokens.
None where the venue publishes no fast tier — which is everywhere
except OpenAI's gpt-5.6 family today. 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
160 161 162 163 164 165 166 167 168 | |
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
171 172 173 174 175 176 177 178 | |
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
181 182 183 184 185 186 187 188 189 190 191 | |
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
206 207 208 209 210 211 | |
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.
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
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | |
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
238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |