DevelopmentFramework18 min readPublished August 1, 2026

Batch APIs at 50% off · zero native idempotency keys · QA by statistical sampling

How to Run a 1M-Row LLM Job Without Losing Money

Cheap inference made million-row LLM jobs affordable. It did not make them safe. Batch endpoints halve the bill, but no documented idempotency key dedupes a resubmitted job, result ordering is explicitly not guaranteed, and none of them produce byte-identical output twice — so the discipline has to live in your pipeline, not in the vendor.

DA
Digital Applied Team
Senior engineers · Published Aug 1, 2026
PublishedAug 1, 2026
Read time18 min
Sources11 vendor & standards primaries
Batch discount
50%
off standard rates
3 of 4 vendors
Native idempotency keys
0
on OpenAI’s or Anthropic’s bulk APIs
build your own
QA sample at 1M rows
385
95% confidence, ±5% margin
computed, not vendor
Largest published batch cap
100k
requests or 256 MB, Anthropic

A million-row LLM job is not a big version of a small one. Batch APIs change the cost per row by half, and a cheap model tier whose standard-list input rates sit under $0.30 per million tokens changed it again. What did not change is the failure surface: a crashed worker that resubmits, a result file that comes back out of order, and output that is not reproducible even at temperature zero.

Every one of those failure modes costs money rather than throwing an exception. A duplicate submission bills twice and produces two plausible answers. An out-of-order result file silently misaligns row 412,000 with row 411,000 if you map by position. A nondeterministic model turns a green regression suite into a coin flip. None of this shows up in a proof-of-concept over a thousand rows, which is exactly why it lands in production.

This guide covers the six things that are specific to bulk LLM work: which vendors actually publish a batch API and what each one caps, the submission and result contract, the idempotency ledger you have to build yourself because nobody ships one, partial-failure recovery, statistical QA for nondeterministic output with the sample-size math shown, and cost guards that hold when the platform limit is only approximate. Each vendor claim below is attributed to that vendor’s own documentation, with the primary sources linked at first mention; the QA sample-size table is computed from a formula printed on the page rather than sourced from anyone.

Key takeaways
  1. 01
    Three vendors publish a batch API. One does not.OpenAI, Anthropic and Alibaba Cloud Model Studio all run first-party batch endpoints at 50% of their standard rate. DeepSeek's own API pricing documentation publishes no batch product at all — check before you architect around one.
  2. 02
    custom_id is not a dedup key.Both OpenAI and Anthropic scope custom_id to matching results inside one already-submitted batch. Nothing in either doc says a resubmitted batch carrying the same IDs is detected, deduplicated or discounted. A crashed worker that resubmits pays twice.
  3. 03
    Neither OpenAI nor Anthropic ships an idempotency key.OpenAI's only documented Idempotency-Key header lives in its Agentic Commerce API, not Chat Completions, Responses or Batch. Anthropic publishes none. Borrow Stripe's pattern and keep the ledger in your own database.
  4. 04
    Output is nondeterministic even at temperature 0.Thinking Machines Lab traced production nondeterminism to GPU kernels that are not batch-invariant: the same input gives different numerics depending on the server-side batch size it lands in. Diff-testing against one golden answer cannot work.
  5. 05
    QA is acceptance sampling, and the sample size plateaus.At 95% confidence and a ±5% margin, a 10,000-row batch needs about 370 sampled rows and a 1,000,000-row batch needs about 385. Reviewing a fixed percentage of a batch is the wrong instinct; the required sample barely moves above roughly 10,000 rows.

01Batch SurfacesWho actually has a batch API — and who doesn’t.

The first architectural decision is not which model to use. It is whether the model you want is reachable through a batch endpoint at all, because the 50% discount is the single largest cost lever available on a bulk job and it is not universal. Most published coverage of this topic is single-vendor: an OpenAI page about OpenAI’s batch API, an Anthropic doc about Anthropic’s. Put the four providers most bulk teams actually shortlist into one table and the gaps become the finding.

Bulk LLM batch-API comparison matrix across OpenAI, Anthropic, Alibaba Cloud Model Studio and DeepSeek, covering whether a first-party batch API exists, the discount versus standard rates, the completion window, the maximum batch size and — for OpenAI and Anthropic — whether a native request-level idempotency key is documented. Compiled from each vendor’s own documentation at the time of writing.
ProviderFirst-party batch APIDiscount vs standardCompletion windowMax per batchNative idempotency key
Vendors publishing a first-party batch product
OpenAIYes — Batch API, JSONL submission50%24 hours, fixed50,000 requests per file · 200 MB input file · up to 2,000 batches created per hourNo — the documented Idempotency-Key header is scoped to the Agentic Commerce API only
AnthropicYes — Message Batches API50%24 hours; most batches finish in under 1 hour100,000 requests or 256 MB, whichever is hit firstNo — none published for Messages or Batches
Alibaba Cloud Model Studio (Qwen)Yes — OpenAI-compatible batch interface50%Caller-specified, 24–336 hours50,000 requests per file · 500 MB total · 6 MB per requestNot assessed — its docs were not audited for this column
Vendor with no first-party batch product
DeepSeekNone published on the official pricing documentationn/an/an/an/a

Two cells in that table are worth more than the rest. The first is Alibaba’s completion window. OpenAI and Anthropic both fix theirs at 24 hours; Model Studio lets the caller name a window anywhere from 24 to 336 hours. For a job that genuinely does not need same-day results — a backfill, a corpus re-classification, a one-off enrichment pass — a 14-day ceiling removes the expiry-risk problem that forces everyone else to shard aggressively. Its supported model list also includes DeepSeek R1 and V3, which means a batch layer for DeepSeek weights exists even though DeepSeek itself does not run one.

The second is the idempotency column, and it is the reason this post exists. Neither OpenAI nor Anthropic documents a request-level idempotency key on its batch product — a finding that only emerges from reading two vendor documentation sets against a third surface, OpenAI’s payments API, which does publish one. Model Studio’s batch reference was not audited for the same header in this pass, so treat that cell as unchecked rather than as a confirmed negative.

Be precise about DeepSeek
DeepSeek has no first-party batch API — its own pricing documentation covers standard per-token rates and an announced peak/off-peak multiplier, and mentions no batch product. That is not the same as “DeepSeek cannot run bulk workloads.” Third-party inference providers run their own batch layers on top of DeepSeek’s open weights, and Alibaba Cloud Model Studio hosts DeepSeek models inside its own batch interface. Any discount you see quoted on those routes is the reseller’s pricing on the reseller’s infrastructure, not DeepSeek vendor pricing — do not present the two as the same number. For what the cheap tier is genuinely good at, see our DeepSeek V4 Flash bulk-workloads playbook.

On price, label the surface every single time. Anthropic’s batch rate is 50% of standard across all supported models — Claude Opus 5 is $5.00 in / $25.00 out per million tokens on standard, and $2.50 / $12.50 on batch. OpenAI’s own pricing documentation shows the same halving on its batch and flex surfaces: GPT-5.6 Luna lists at $0.20 in / $1.20 out per million tokens on standard and $0.10 / $0.60 on batch. Aggregator listings frequently show the batch number in a column labelled as list price, and the mistake is invisible unless you check against the vendor’s own pricing page. A 2× error in the input rate of a ten-million-row job is not a rounding difference. Our breakdown of the August 2026 cheap-tier repricing walks the same discipline across the whole tier.

02Job ShapeThree files, and the one that most teams forget.

Every batch API that exists has converged on the same contract, which is unusual enough to be worth naming: JSONL in, a caller-supplied identifier on every line, a success file out, and a separate error file out. OpenAI requires custom_id, method, url and body on each input line and supports chat completions, responses, embeddings, moderations, image generation and video endpoints. Anthropic requires a custom_id matching ^[a-zA-Z0-9_-]{1,64}$. Alibaba’s interface is deliberately OpenAI-compatible and takes the same shape. If you build your submission layer around that contract, it ports.

Input
The JSONL request file
one line per row · custom_id required

Your row identifier travels as custom_id and is the only thing that survives the round trip. Make it your own primary key, not a loop counter — Anthropic's guidance is explicitly to use meaningful custom_id values.

OpenAI · Anthropic · Model Studio
Output
The success file
output_file_id · order not guaranteed

Retrieved through the Files API once the batch reaches completed. OpenAI's documentation instructs mapping by custom_id rather than position; Anthropic states results can be returned in any order. Position-based joins are a silent corruption bug.

Never map by line number
Overlooked
The error file
error_file_id · separate artifact

Failed requests are written to their own file rather than mixed into the success output. A pipeline that only reads output_file_id will report a clean run while quietly dropping every row that failed — including rows that expired at the window boundary.

Read it or lose rows
The ordering trap, in the vendor’s words
Anthropic’s batch documentation is blunt about it: “Batch results can be returned in any order, and may not match the ordering of requests when the batch was created… To correctly match results with their corresponding requests, always use the custom_id field.” The same instruction appears in OpenAI’s batch guide. This is the cheapest bug in the whole pipeline to avoid and the most expensive one to discover three weeks after a job shipped.

03IdempotencyThe ledger nobody ships, and you have to build.

Here is the assumption that quietly costs teams real money: custom_id looks like an idempotency key, so people treat it as one. It is not. In both vendors’ documentation its scope is result-matching inside a single already-submitted batch. Nothing in either doc states that resubmitting the same batch with the same identifiers — after a crash, a timeout, an ambiguous 500, or a deploy that replayed a queue — is detected, deduplicated or discounted. Submit the same 400,000 rows twice and you are billed for 800,000 rows and left holding two sets of plausible answers with no principled way to choose between them.

It is not that idempotency is an unsolved problem. It is that the LLM vendors have not implemented it at the model-call layer. OpenAI does publish a first-class Idempotency-Key header where the same key with the same parameters replays the cached result and the same key with different parameters returns a 409 conflict — but only in its Agentic Commerce API, the payments surface. Chat Completions, the Responses API and the Batch API have no equivalent. Anthropic publishes none at all. The OpenAI SDKs do attach an internal idempotency header on their own automatic retries for connection errors, 408, 409, 429 and 5xx responses, which is useful and entirely insufficient: it protects one call’s internal retry loop, not a worker process that died and came back.

Native keys available
On OpenAI's and Anthropic's bulk APIs
0

OpenAI's documented Idempotency-Key header covers the Agentic Commerce API only. Anthropic publishes no idempotency header for Messages or Batches. Neither gives you cross-submission dedup, so the ledger has to be yours.

Per both vendors' docs
Stripe key retention
The reference implementation
24h+

Stripe caches an idempotency key for at least 24 hours: same key plus same parameters replays the original response without re-running the operation, same key plus different parameters errors, and a v4 UUID is the recommended entropy source.

Borrow the semantics
IETF status
An emerging web-wide convention
Draft

A generic Idempotency-Key HTTP header is in active IETF standardisation through the httpapi working group. The pattern is becoming a web convention; LLM vendors simply have not adopted it at the completions or batch layer yet.

draft-ietf-httpapi

The practical build is unglamorous. Give every row a stable, caller-generated identifier derived from the source record and the job version — not an autoincrement, because an autoincrement changes when the input is re-read. Write it to a ledger table before submission with a status column. Check the ledger for already-processed rows before building the JSONL, not after the results come back. Store the provider’s batch identifier against the row so a partially completed batch can be resumed rather than re-run. Then make the whole thing enforceable at the database level with a unique constraint on the row key, so a duplicate submission fails loudly at insert time instead of succeeding expensively at the API.

Read the direction of travel here as a market signal rather than an oversight. Batch APIs were built for throughput on jobs assumed to be re-runnable and cheap; the pricing and the semantics both reflect that. As bulk inference moves from experiments into scheduled production pipelines — nightly enrichment, continuous re-classification, standing moderation passes — the absence of a dedup primitive stops being a rough edge and starts being a billing hazard. Payments APIs solved this a decade ago and the IETF is standardising the header now. It would be reasonable to expect one of the three batch vendors to ship a documented key in the next cycle or two. Until one does, the ledger is not optional infrastructure.

04Partial FailureFour terminal states, four different recovery paths.

A batch does not succeed or fail. Individual requests inside it reach one of several terminal states, and the recovery logic differs by state. Anthropic documents four — succeeded, errored, canceled and expired — and states explicitly that requests which error, are canceled, or expire are not billed. It also states per-row isolation as a design property rather than an implementation detail. OpenAI writes failures into its own error file with messages including the expiry case, phrased as a request that could not be executed before the completion window expired.

succeeded
Result present, still unverified

The row returned a response. That is a delivery guarantee, not a quality one — a syntactically perfect answer can be wrong. Mark it processed in the ledger, then hand it to the sampling QA stage in section 06 before anything downstream consumes it.

Ledger: processed
errored
Safe to retry by design

Per-row isolation means one failed request does not disturb the others, and Anthropic states errored requests are not billed. Collect these from the error file, keep the failure reason, and resubmit them as their own smaller batch rather than re-running the parent.

Retry the row, not the batch
canceled
Partial results are real results

A submitted batch cannot be edited — changing anything means cancel and resubmit. Cancellation is not instantaneous, so a canceled batch can still contain results for whatever had already been processed. Harvest them through the ledger before resubmitting, or you pay for that work twice.

Harvest before resubmit
expired
The window beat you

Anything unfinished when the completion window closes expires. Anthropic does not bill expired requests. This is the state that argues for sharding a very large job into sub-batches: the smaller the unit, the less work sits at risk when a window closes.

Shard smaller, resubmit

Anthropic’s own written guidance points the same way — monitor batch status regularly, implement retry logic for failed requests, use meaningful custom_id values, and consider breaking very large datasets into multiple batches for manageability. Sharding is the single highest-leverage decision here, and it is worth being deliberate about the size. Anthropic caps a batch at 100,000 requests or 256 MB; OpenAI at 50,000 requests or 200 MB per file with up to 2,000 batches created per hour. Those are ceilings, not targets. Sub-batches well below the cap give you finer-grained resume points, smaller blast radius on an expiry, and a per-shard cost checkpoint you can actually act on.

One retention detail catches people. Anthropic keeps batch requests and responses for up to 29 days measured from batch creation, not completion, after which the metadata remains visible but the results can no longer be downloaded. If your pipeline treats the provider as durable storage for a long-running reconciliation, that clock is shorter than it looks. Pull results into your own store as soon as a batch completes.

Per-row isolation, stated by the vendor
Anthropic’s documentation says it plainly: “Note that the failure of one request in a batch does not affect the processing of other requests.” That is what makes row-level retry the correct unit of recovery. If you re-run a whole 50,000-row batch because 60 rows errored, you have paid for 49,940 rows of work that already succeeded — and, with no idempotency key in play, produced a second set of answers for all of them.

05NondeterminismWhy you cannot diff-test a bulk LLM job.

Set temperature to zero, use greedy sampling, send the identical prompt twice to a production endpoint, and you can still get two different answers. Most engineers who notice this reach for the standard explanation — floating-point non-associativity plus GPU concurrency — and stop there. Thinking Machines Lab published a piece in September 2025 showing that explanation is wrong, because GPUs do reproduce bitwise-identical matrix multiplication results across runs despite both of those conditions being present.

The actual cause they identify is that most GPU kernels — RMSNorm, matmul, attention — are not batch-invariant. The same input element produces slightly different numeric results depending on the size of the batch it happens to be processed alongside, and on a shared production endpoint the batch size is a function of how much other traffic arrived in the same millisecond. Your request’s numerics depend on other people’s requests. That is a structural property of production serving, not a temperature setting you forgot to turn down.

"The primary reason nearly all LLM inference endpoints are nondeterministic is that the load (and thus batch-size) nondeterministically varies!"— Horace He, Thinking Machines Lab, September 2025

The team demonstrated the effect by sampling completions at temperature zero from a large open model on their own inference stack, finding a meaningful spread of distinct outputs that began diverging partway through generation, and then eliminated it by building batch-invariant kernels — at a real latency cost. Two caveats matter for anyone reading this as a benchmark. The specific divergence rate they measured belongs to that model on that stack and should not be treated as a constant that transfers to every vendor’s production API. And the fix is available to people who control their own serving layer, which is not you if you are calling a hosted batch endpoint.

The consequence for pipeline design is direct and non-negotiable: you cannot validate a bulk LLM job by diffing its output against a fixed golden file. Re-running the same prompts is not guaranteed to reproduce the same bytes, so a byte-level regression suite will throw false failures forever and everyone will start ignoring it. What you can validate is the distribution of quality across the output, which is a statistical question with a statistical answer.

06Sampling QAAcceptance sampling, with the arithmetic shown.

Manufacturing solved this problem before software existed. Acceptance sampling takes a random sample from a production lot, measures the defect rate in the sample, and accepts or rejects the whole lot on that evidence rather than inspecting every unit. Substitute “completed sub-batch” for “lot” and it maps onto bulk LLM output cleanly: define an acceptable row-level error rate before the job runs, pull a properly sized random sample from each finished shard, and decide release-or-re-review on the sample’s measured defect rate with a confidence interval attached — not on someone eyeballing the first twenty rows.

The sample size comes from the standard formula for estimating a proportion, with a finite-population correction because your batch is a known finite size: n₀ = z² · p · (1 − p) / e², then n = n₀ / (1 + (n₀ − 1) / N). Using the conservative p = 0.5 assumption (which maximises the required sample when you have no prior estimate of your error rate), z = 1.96 for 95% confidence and z = 2.576 for 99%, and rounding every result up to a whole row, the table below is what falls out. Every cell is computed from that formula — no vendor publishes a recommended QA sample size, and you can reproduce all of these in a spreadsheet.

QA sample-size reference for bulk LLM jobs, computed from the standard sample-size formula for a proportion with finite population correction at p equals 0.5, showing required random sample sizes at 95 percent confidence with a 5 percent margin and at 99 percent confidence with a 3 percent margin, for batches of 1,000 to 1,000,000 rows.
Batch size (N)Sample n — 95% / ±5%Share of batch sampledSample n — 99% / ±3%Review hours at 2 min/row (95% column)
Computed — n₀ = z²·p·(1−p)/e², n = n₀ / (1 + (n₀−1)/N), p = 0.5, rounded up
1,00027827.8%6499.3
10,0003703.7%1,55712.3
100,0003830.38%1,81012.8
1,000,0003850.04%1,84012.8

Required QA sample size barely moves as the batch grows

Computed from the standard sample-size formula for a proportion (p = 0.5, 95% confidence, ±5% margin, finite-population corrected)
1,000-row batch278 rows sampled · 27.8% of the batch
278
10,000-row batch370 rows sampled · 3.7% of the batch
370
100,000-row batch383 rows sampled · 0.38% of the batch
383
1,000,000-row batch385 rows sampled · 0.04% of the batch
385

That flat line is the whole point. The instinct on a large job is to review a fixed percentage — “we’ll spot-check 1%” — which produces 10,000 rows of human review on a million-row batch and 10 rows on a thousand-row batch. Both numbers are wrong in opposite directions. The statistically required sample climbs from 278 to 385 across three orders of magnitude and then stops. At roughly two minutes of reviewer time per row, a million-row job needs a little under thirteen hours of structured review to make a defensible release decision, and a thousand-row job a little over nine. The two-minute figure is an illustrative planning assumption, not a measured constant — substitute your own and the shape of the conclusion does not change.

Two honest caveats on the method. First, this sizing estimates an overall defect proportion; if you need per-category confidence — error rates by document type, by language, by source system — you need a stratified sample, which means this n per stratum, not in total. Second, some practitioner writeups describe layering a statistical-process-control gate on top, flagging a batch when a monitored quality metric drifts outside roughly a mean plus or minus 1.5 standard deviations band across sampled outputs. That is one documented community pattern rather than an established standard, and it is worth treating as a starting hypothesis you calibrate against your own data. Where it does help is in reframing the question from per-row pass/fail to variance monitoring, which pairs naturally with tracing and cost observability for LLM pipelines.

07Schema GuaranteesA hard shape guarantee that says nothing about correctness.

Sampling QA is the second layer. The first is making malformed output structurally impossible. OpenAI’s Structured Outputs feature — response_format with strict: true, or strict function calling — constrains token sampling itself, so the model cannot emit a token that would violate your schema. That is a sampling-time guarantee rather than a validate-after-the-fact check, and it is available on the batch path too, because the Batch API’s supported endpoints include chat completions and responses, which carry the same parameter. Our complete guide to OpenAI Structured Outputs covers the schema constraints and the subset of JSON Schema it accepts.

On a million rows, that changes the economics of parsing. Without it, some fraction of rows come back with a stray preamble, a trailing comma, or a hallucinated extra field, and you build a repair stage that re-prompts the failures — a second pass that costs money and introduces its own nondeterminism. With it, the parse stage becomes deterministic and the repair stage disappears. For a bulk job, that is usually a larger practical win than the model choice.

The limit of a schema
A schema guarantees shape, never truth. A perfectly-formed JSON object carrying the label “refund_request” on a row that was actually a delivery complaint validates cleanly, passes every type check, and is wrong. This is exactly why structured outputs and sampling QA are additive layers rather than substitutes: the first eliminates parse failures, the second is the only thing that catches valid-but-wrong values. Teams that ship one and skip the other tend to ship the one that is easier to verify.

A useful stress case is bulk synthetic-data generation, where both layers get exercised hard and a valid-but-wrong row propagates straight into a training set — the failure mode is covered in our decision guide for LLM synthetic-data generation. The general rule for any bulk job: schema-constrain everything you can express as a schema, and sample-verify the semantics you cannot. Getting that split right for a specific dataset is most of the work in the pipeline builds we run through our AI and digital transformation engagements, and it is the part that decides whether a bulk job is repeatable or a one-off.

08Cost GuardsPlatform limits are approximate at batch concurrency.

The last layer is the one people set up on day one and never test. OpenAI lets an organisation or project set a monthly spend limit and toggle enforcement of a hard limit; once enforced, requests over the limit return HTTP 429 with insufficient_quota and traffic actually stops. A spend alert is a different mechanism entirely: it emails you and shows a dashboard banner while the key keeps working and the charges keep accruing. For an unattended overnight job, a soft alert is not a cost guard. It is a notification that you already spent the money.

Even the hard limit deserves less trust than its name implies at batch scale. Anthropic’s own documentation warns that because of high batch throughput, batches may go slightly over a Workspace’s configured spend limit. That is the vendor telling you its hard cap is approximate under exactly the conditions a bulk job creates. The design conclusion is not to abandon platform limits — keep them, hard-enforced — but to layer your own checkpointed counter underneath them.

Hard limit
Enforced, and it stops traffic
429

OpenAI's hard monthly spend limit returns HTTP 429 with insufficient_quota once the ceiling is reached. This is the only platform-level control that actually halts requests, and it belongs on every project that runs unattended bulk jobs.

Set it per project
Soft alert
Requests stopped by a spend alert
0

A soft spend alert triggers an email and a dashboard banner. The key keeps working, the job keeps running, and the bill keeps growing. Useful as an early-warning signal; useless as a guard on an overnight million-row run.

Notification, not control
Overshoot risk
What the vendor concedes
limit

Anthropic states that batches may go slightly over a Workspace's configured spend limit because of batch throughput. Your own per-shard cost checkpoint — halting submission of the next sub-batch once a running total is hit — is the backstop that closes that gap.

Layer your own counter

The implementation follows directly from the sharding decision in section 04. If the job is submitted as sub-batches rather than one enormous file, you have a natural checkpoint between them: after each shard completes, add its actual token usage to a running total, and refuse to submit the next shard if the total exceeds the budget authorised for the job. That is a control you own, it is exact rather than approximate, and it degrades gracefully — a job that stops at 60% with 60% of the rows written and a resumable ledger is a recoverable situation. A job that runs to completion at four times the estimated cost is a conversation with finance. The same per-workload budgeting logic applies to agent runs, which we work through in our agent token-budget and cost-control framework.

One more lever, with an honest caveat attached. Prompt caching stacks with the batch discount, which matters enormously on bulk jobs where a long shared system prompt and few-shot block precede every short row-specific payload. But Anthropic is explicit that cache hits are best-effort inside a batch, because requests process concurrently and in no guaranteed order — it reports observed hit rates ranging from 30% to 98% depending on traffic patterns. Model your cost projections on the pessimistic end of that range and treat a high hit rate as upside, not as the plan.

09ConclusionThe discipline lives in your pipeline, not the vendor.

Bulk LLM engineering, August 2026

Cheap inference removed the cost excuse. It did not remove the engineering.

The batch layer is genuinely good and genuinely cheap. Three of the four providers most teams shortlist run first-party batch endpoints at half their standard rate, all three converged on the same JSONL contract, and one of them will hold a job open for up to fourteen days. Against a model tier whose standard-list input rates sit under $0.30 per million tokens, that stacks into bulk workloads that used to be shelved on cost being routine.

What did not arrive alongside the discount is the safety layer. There is no request-level idempotency key documented on OpenAI’s or Anthropic’s bulk endpoints, custom_id is not the dedup mechanism it looks like, result ordering is explicitly not guaranteed, failed rows are written somewhere you have to remember to read, output is not reproducible even at temperature zero, and the platform spend cap is approximate at exactly the concurrency a batch job creates. Every one of those is documented by the vendors themselves. None of them are hidden. They are just not in the quickstart.

So the checklist is short and it is yours to build: an idempotency ledger keyed on a stable row identifier and enforced by a unique constraint, sub-batches well under the vendor cap so a shard is your resume point, a reader for the error file as well as the success file, schema-constrained sampling to kill parse failures, an acceptance-sampling QA gate sized from the formula rather than from instinct, and your own checkpointed cost counter sitting underneath the platform’s hard limit. Build those six once and they carry across every model and every provider you swap in afterwards — which, at the rate this tier is repricing, will be more often than you expect.

Run bulk inference without the surprises

A million-row job is an engineering problem long before it is a model choice.

We design and operate bulk LLM pipelines — batch submission, idempotency ledgers, schema-constrained extraction, statistical QA gates and cost guards — for classification, enrichment and content operations at production scale.

Free consultationExpert guidanceTailored solutions
What we build

Bulk LLM pipeline engagements

  • Batch-API selection and cost modelling per workload
  • Idempotency ledgers and resumable sharded jobs
  • Structured-output schemas for deterministic parsing
  • Acceptance-sampling QA gates with defined error budgets
  • Checkpointed spend guards under platform hard limits
FAQ · Bulk LLM job engineering

The questions that come up on every bulk job.

At the time of writing, three of the four providers most bulk teams shortlist do. OpenAI publishes a Batch API taking JSONL input with custom_id, method, url and body on each line, at 50% of synchronous pricing with a fixed 24-hour completion window. Anthropic publishes the Message Batches API, also at 50% of standard pricing across all supported models, with a 24-hour window and most batches finishing in under an hour. Alibaba Cloud Model Studio publishes an OpenAI-compatible batch interface at 50% of real-time cost with a caller-specified window of 24 to 336 hours. DeepSeek's official API pricing documentation publishes no batch product at all — it covers standard per-token rates and an announced peak/off-peak multiplier only. Third-party providers do run their own batch layers on DeepSeek's open weights, and Model Studio hosts DeepSeek models inside its batch interface, but neither is a DeepSeek vendor offering.
Related dispatches

Continue exploring production AI engineering.