Half Price Until You Pay Twice: 6 Ways an OpenAI Batch Job in .NET Quietly Costs You More A .NET 10 batch pipeline built on the official OpenAI 2.14.0 package and SQLite for state can lose the Batch API's 50% discount through six implementation mistakes, including re-uploading a shard after a crash before the batch id is saved, which runs duplicate batches at full price. The author recommends tagging each batch with a shard_id in its metadata and listing recent batches on restart to adopt an existing match rather than resending, plus saving state before every provider call. Other fixes include retrying only 408, 409, 429, 5xx, batch_expired and missing lines, doubling max_completion_tokens on finish_reason: length up to a cap, capping items at three attempts before dead-lettering, and looping over submitted items rather than the output file so cancelled or expired batches don't silently drop rows. Half Price Until You Pay Twice: 6 Ways an OpenAI Batch Job in .NET Quietly Costs You More The pitch for the OpenAI Batch API is simple. You have a pile of work nobody needs answered in two seconds, say 40,000 rows to classify overnight. You put the requests in a JSONL file, upload it, and the provider finishes within 24 hours at half the price of the normal API. The discount is real. Wha t eats it is the code around the batch. A batch job lives for hours, and anything that lives for hours meets crashes, retries and deadlines. Each of those can quietly turn "half price" into "full price, twice", or into results that are silently missing. Here are six mistakes I made or nearly made while building a batch pipeline in .NET 10 official OpenAI 2.14.0 package, SQLite for state . The steps are general. The code is trimmed to the shape, and the trimmed parts are marked. Tech Skill Builder is $5/month for a very limited time only normally $10 . Grab it now before it goes back to $10. The complete, working, tested version of this batch project is inside, with full source and an in-depth PDF guide. Join Tech Skill Builder This is the expensive one. Your worker uploads the file, calls "create batch", and dies before it saves the batch id. On restart, your database says this shard was never sent, so you send it again. Now two batches run the same requests, and you pay for both. The fix doesn't need extra infrastructure. Batches accept metadata, so tag each one with your own shard id when you create it. On restart, before re-sending a shard that has no batch id, list the recent batches and look for that tag: var metadata = new Dictionary