Reduce OpenAI Batch API Costs by 50% in Production
OpenAI offers an automatic 50% discount on every request routed through its Batch API, yet most engineering teams continue to run asynchronous, non-time-sensitive workloads through expensive real-time endpoints. If your application processes background data enrichment, nightly document summarization, offline embeddings, or bulk classification, paying full price for immediate HTTP responses is a massive allocation of wasted engineering budget.
Moving asynchronous workloads to the OpenAI Batch API is the single most effective lever for reducing AI infrastructure costs. By submitting bulk requests in .jsonl files, OpenAI processes the jobs within a 24-hour turnaround window in exchange for half the token cost across input, output, and cached tokens.
In this guide, we break down how the OpenAI Batch API works under the hood, how to architect your backend to handle asynchronous completion loops, step-by-step implementation code, and advanced techniques to push your total cost savings well beyond 50%.
Understanding the OpenAI Batch API Unit Economics
The financial argument for the Batch API is straightforward: OpenAI charges exactly 50% of the standard pay-as-you-go pricing for all models accessible through the endpoint, including flagship reasoning models like GPT-4o, o1, and smaller utilities like GPT-4o-mini.
When you hit standard endpoints like /v1/chat/completions, you pay a premium for low-latency compute. OpenAI must maintain immediate GPU availability to handle your HTTP request within milliseconds. With the /v1/batches endpoint, OpenAI queues your requests and processes them during low-demand periods across their compute clusters. In exchange for granting OpenAI temporal flexibility, you get an immediate half-price discount.
Comparing Pricing Breakdown Across OpenAI Endpoints
To understand where the savings come from, look at the price structure across standard endpoints, prompt-cached requests, and batch endpoints:
| Model | Real-Time Input (per 1M) | Real-Time Output (per 1M) | Batch Input (per 1M) | Batch Output (per 1M) | Batch + Cached Input (per 1M) |
|---|---|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | $1.25 | $5.00 | $0.625 |
| GPT-4o-mini | $0.15 | $0.60 | $0.075 | $0.30 | $0.0375 |
| o1 | $15.00 | $60.00 | $7.50 | $30.00 | $3.75 |
| text-embedding-3-large | $0.13 | N/A | $0.065 | N/A | N/A |
Notice the cumulative effect in the rightmost column: when you combine the 50% Batch API discount with OpenAI's automatic Prompt Caching (which grants a 50% discount on repeated prompt prefixes over 1,024 tokens), your effective input token cost drops by 75% compared to baseline real-time requests.
Identifying Candidates for Batch vs. Real-Time Routing
Not every LLM call belongs in a batch queue. The key architectural step is separating requests that require human-in-the-loop interactive speed from tasks that are inherently background operations.
Ideal Batch Candidates (24-Hour SLA Acceptable)
- Background Data Enrichment: Ingesting CRM contacts, scoring leads, or extracting structured data from newly uploaded customer invoices.
- Large-Scale Content Generation: Creating SEO metadata, localizing product catalogs into twenty languages, or generating personalized email copy for weekly marketing campaigns.
- Offline RAG & Vector Indexing: Chunking, summarizing, and generating vector embeddings for large technical documentation sets or legal archives.
- Model Evaluation & Synthetic Data: Running continuous evaluation suites (Evals) on thousands of prompt variants, or generating synthetic training datasets for custom fine-tuning.
- Batch Sentiment Analysis & Moderation: Analyzing thousands of app store reviews, customer support tickets, or user-generated forum posts overnight.
Workloads to Keep on Real-Time Endpoints
- User-facing conversational chatbots where latency over 2 seconds causes user drop-off.
- Copilot extensions that autocomplete code or text while a user is actively typing.
- Real-time routing logic that dictates live application flow during an active session.
- Fraud detection systems that must block a transaction in under 500 milliseconds.
Production Batch API Architecture: How the Pipeline Works
Moving from a synchronous REST call to an asynchronous batch system requires shifting your backend architecture from a simple request-response loop to an event-driven file processing pipeline.
The Five Steps of a Batch API Execution Loop
- Extraction & File Formatting: Your application queries your database for pending jobs, constructs individual JSON requests, and serializes them into a single .jsonl (JSON Lines) file stored in local storage or S3.
- File Upload: You upload the .jsonl file to the OpenAI Files API with the purpose set to batch. OpenAI returns a unique File ID (for example, file-xyz123).
- Batch Creation: You call the /v1/batches endpoint, providing the File ID, the target endpoint (such as /v1/chat/completions), and a completion time window (currently standardized to 24h).
- Queue Monitoring: Your backend checks the job status periodically via poll calls or listens for webhook status changes. OpenAI processes requests in parallel across available compute.
- Retrieval & Ingestion: Once the batch reaches the completed state, OpenAI provides an output_file_id containing responses, along with an error_file_id for any failed requests. Your app downloads the output file, matches responses back to database records via custom request IDs, and updates your state.
Step-by-Step Implementation in Python

Let's walk through a production-grade implementation using the official Python OpenAI SDK. We will cover file construction, submission, status polling, and output parsing.
Step 1: Generating the JSONL Batch File
Each line in a .jsonl file must be an independent JSON object containing a custom_id (a unique string you use to map the response back to your records), the HTTP method, the target url, and the standard OpenAI body parameters.
python import json
Sample records pulled from your production database
items_to_process = [ {"id": "usr_101", "text": "Great service, but the shipping was delayed by three days."}, {"id": "usr_102", "text": "The platform is intuitive, reliable, and well documented."}, {"id": "usr_103", "text": "I was double billed for my monthly subscription. Please refund."}, ]
jsonl_filename = "sentiment_batch_input.jsonl"
with open(jsonl_filename, "w", encoding="utf-8") as f: for item in items_to_process: request_data = { "custom_id": f"req_{item['id']}", "method": "POST", "url": "/v1/chat/completions", "body": { "model": "gpt-4o-mini", "messages": [ { "role": "system", "content": "You are an automated sentiment classifier. Respond with JSON containing 'sentiment' (positive, negative, neutral) and 'urgency' (high, medium, low)." }, { "role": "user", "content": item["text"] } ], "response_format": {"type": "json_object"}, "temperature": 0.2, "max_tokens": 150 } } f.write(json.dumps(request_data) + "\n")
print(f"Generated {jsonl_filename} successfully.")
Step 2: Uploading the File and Creating the Batch
Once the .jsonl file is generated, send it to OpenAI's infrastructure using the SDK.
python import os from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
Upload the jsonl file to the Files API
uploaded_file = client.files.create( file=open("sentiment_batch_input.jsonl", "rb"), purpose="batch" )
print(f"Uploaded File ID: {uploaded_file.id}")
Submit the file to the Batch API endpoint
batch_job = client.batches.create( input_file_id=uploaded_file.id, endpoint="/v1/chat/completions", completion_window="24h", metadata={ "job_type": "sentiment_analysis", "environment": "production" } )
print(f"Batch Job Created ID: {batch_job.id}") print(f"Current Status: {batch_job.status}")
Step 3: Polling for Completion and Downloading Results
Batch jobs often complete in 15 to 45 minutes, though OpenAI guarantees completion within 24 hours. In production, you should run this check inside a scheduled worker task like Celery, Temporal, or AWS Lambda rather than a continuous blocking loop.
python import json import os from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def check_and_download_batch(batch_id): batch = client.batches.retrieve(batch_id) print(f"Batch {batch_id} status: {batch.status}")
if batch.status == "completed": print(f"Job completed! Downloading output file: {batch.output_file_id}")
Download output content
file_response = client.files.content(batch.output_file_id) output_data = file_response.text
Parse output JSONL
for line in output_data.strip().split("\n"): result = json.loads(line) custom_id = result["custom_id"]
Check for HTTP 200 equivalent inside the batch wrapper
if result["response"]["status_code"] == 200: content = result["response"]["body"]["choices"][0]["message"]["content"] print(f"Result for {custom_id}: {content}") else: print(f"Error for {custom_id}: {result['response']['body']}")
elif batch.status in ["failed", "canceled", "expired"]: print(f"Batch failed with status: {batch.status}") if batch.error_file_id: error_response = client.files.content(batch.error_file_id) print(f"Error details: {error_response.text}")
else: print("Batch is still processing. Check again later.")
Combining Batch API with Prompt Caching for 75% Savings

Getting a 50% discount is great, but combining strategies yields massive operational savings. The most powerful pairing is the Batch API with OpenAI Prompt Caching.
When you send requests that share a long common prefix (such as system prompts, background context, brand guidelines, or standard database schemas), OpenAI automatically caches those initial tokens across requests.
How Prompt Caching Interacts with Batch Processing
- Minimum Threshold: Prompt Caching automatically activates on prompts longer than 1,024 tokens.
- Cache Match Discount: Cached input tokens receive a 50% discount on standard endpoints.
- The Stacking Effect: On the Batch API, the 50% batch discount applies on top of the cached token rate.
When you structure your batch .jsonl files, order requests so that calls sharing the same long system instructions or source documentation context are grouped together. This guarantees high cache hit ratios, dropping your input token costs by up to 75% relative to real-time non-cached calls.
Four Common Pitfalls and How to Avoid Them
While the Batch API is financially attractive, engineers frequently run into operational friction during initial deployment. Here is how to navigate the edge cases.
1. Treating Batch Separate Limits as Real-Time Limits
OpenAI enforces separate Tier-based Rate Limits for the Batch API. Your batch limits do not consume your real-time Requests Per Minute (RPM) or Tokens Per Minute (TPM) quotas. However, Batch APIs have a Total Enqueued Tokens Limit (for example, Tier 4 accounts might have a limit of 400,000,000 enqueued tokens). If you submit a .jsonl file that exceeds your total enqueued token quota, the entire batch creation call will fail.
Solution: Calculate the total estimated tokens across your batch file before calling client.batches.create(). If your file exceeds your account's enqueued token quota, split your job into two smaller .jsonl files and submit the second one after the first job transitions to in_progress or completed.
2. Missing Error Files for Individual Request Failures
A Batch API job can have an overall status of completed, yet individual requests within that file can still fail (for example, due to content policy flags, malformed schema inputs, or context window overflows).
Solution: Always check the error_file_id parameter alongside output_file_id. When parsing completed batches, check the inner status_code for every entry. Do not assume that batch.status == "completed" means every row produced an HTTP 200 response.
3. File Size Exceeding Limits
OpenAI caps individual batch input files at 200 MB or 50,000 requests, whichever comes first.
Solution: Build a file wrapper in your batch generation job that monitors file size during write time. Once your writer hits 45,000 items or 180 MB, close the file, initiate upload, and open a second file for the remaining records.
4. Poor Request Identifier Mapping
If your application relies on array indexing to match LLM answers back to database rows, your data will eventually get corrupted. OpenAI does not guarantee that responses in the output .jsonl file will appear in the exact same order as your input file.
Solution: Always pass a resilient, unique database key into the custom_id field (such as custom_id: "doc_9842_chunk_3"). Parse your results by indexing on custom_id rather than line numbers.
Production Cost Comparison: Real-World Case Study
Consider a SaaS platform that processes 10,000 PDF documents every night for enterprise customers. Each document requires extracting structured entities using gpt-4o with an average input prompt of 3,000 tokens (including system instructions) and generating an output summary averaging 500 tokens.
Let's calculate the monthly cost difference between running this workload over standard real-time HTTP calls versus optimized Batch API calls with prompt caching.
Cost Breakdown Metrics
- Daily Document Volume: 10,000 documents
- Monthly Document Volume: 300,000 documents
- Input Tokens per Doc: 3,000 (2,000 shared system prompt context + 1,000 unique document text)
- Output Tokens per Doc: 500
Scenario A: Standard Real-Time Endpoints
- Monthly Input Tokens: 300,000 multiplied by 3,000 = 900,000,000 tokens (900M)
- Monthly Output Tokens: 300,000 multiplied by 500 = 150,000,000 tokens (150M)
- Input Cost (at $2.50 / 1M): 900 multiplied by $2.50 = $2,250
- Output Cost (at $10.00 / 1M): 150 multiplied by $10.00 = $1,500
- Total Monthly Spend: $3,750
Scenario B: Batch API + Prompt Caching
- Shared Input Tokens (2,000 cached at $0.625 / 1M): 600M tokens = $375
- Unique Input Tokens (1,000 batch at $1.25 / 1M): 300M tokens = $375
- Output Tokens (500 batch at $5.00 / 1M): 150M tokens = $750
- Total Monthly Spend: $1,500
Total Impact
By migrating this background document processing task to the OpenAI Batch API and structuring prompts to leverage caching, the team saves $2,250 per month ($27,000 per year) on a single pipeline—a 60% overall reduction in AI bill spend with zero model degradation or accuracy loss.
Architectural Decision Framework: When to Use Batch API
Use this comparison matrix when designing new AI features in your application backend:
| Metric / Need | Real-Time Endpoint (/v1/chat/completions) | Batch API (/v1/batches) |
|---|---|---|
| Max Acceptable Latency | Sub-second to 5 seconds | 15 minutes to 24 hours |
| Primary Pricing Driver | User experience & immediate answer | Infrastructure cost optimization |
| Rate Limit Bucket | Standard RPM / TPM limits | Separate Enqueued Token limits |
| Input Format | Single JSON payload over HTTP | .jsonl file via Files API |
| Discount Rate | Baseline pricing (0% discount) | 50% discount on all tokens |
| Failure Handling | Synchronous retries (exponential backoff) | Async error file parsing |
How Saasbonus Helps You Optimize SaaS Infrastructure Costs
Managing cloud overhead and LLM infrastructure costs is an ongoing engineering challenge. At Saasbonus, we help growing engineering teams and SaaS founders make smart, data-backed software choices. From analyzing backend developer tools and observability suites to evaluating specialized AI model providers and infrastructure platforms, our independent, hands-on reviews ensure you build a performant tech stack without burning through capital.
If you are scaling an AI-native SaaS application, optimizing your provider choice is just as critical as optimizing your code. Check out our in-depth architecture guides and software comparisons on Saasbonus to streamline your cloud tool stack today.