Stop writing boilerplate for batch APIs.
Get the 50–80% cost savings of provider batch endpoints without rewriting your application logic, managing .jsonl files, or waiting 24 hours for agent loops to finish.
Sure, you could build it yourself. But should you?
Writing a script to upload a .jsonl file takes an afternoon. But building a resilient system that handles partial batch failures, routes mixed request types, and manages state across multi-step agent workflows is an ongoing infrastructure tax. Doubleword handles the plumbing so your team can focus on building the actual product.
Why teams choose Doubleword
The Autobatcher: Zero-Refactor Savings
Don't rip out your standard async calls. Our autobatcher acts as a drop-in replacement that transparently collects and batches requests over a configurable window.
Async Inference: Built for Agentic Workflows
24-hour turnaround times are fine for nightly data dumps, but they completely break multi-stage AI pipelines. Synchronous calls are too expensive for bulk recursive tasks.
Mixed Request Routing: Clean Architecture at Scale
Don't force your architecture to conform to rigid provider batch rules. Doubleword lets you fire off chat completions, embeddings, and JSON-mode requests in the same batch window.
Drop-in Ecosystem Compatibility
Vendor lock-in to a specific provider's batch API creates technical debt. Doubleword offers seamless, unified compatibility.
See the difference
import json, time, openai
# 1. Build the .jsonl file
requests = []
for item in dataset:
requests.append({
"custom_id": item["id"],
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "gpt-4o",
"messages": [{"role":"user",
"content": item["prompt"]}]
}
})
with open("batch.jsonl","w") as f:
for r in requests:
f.write(json.dumps(r) + "\n")
# 2. Upload, create batch, poll…
file = openai.files.create(
file=open("batch.jsonl","rb"),
purpose="batch"
)
batch = openai.batches.create(
input_file_id=file.id,
endpoint="/v1/chat/completions",
completion_window="24h"
)
while batch.status != "completed":
time.sleep(60)
batch = openai.batches.retrieve(batch.id)
# 3. Download & parse results
content = openai.files.content(
batch.output_file_id)
results = [json.loads(l)
for l in content.text.split("\n") if l]
from doubleword import Autobatcher
client = Autobatcher(window="5m")
# That's it. Use it exactly like
# your existing async client.
result = await client.chat.completions.create(
model="qwen3.5-397b-a17b",
messages=[
{"role": "user",
"content": item["prompt"]}
]
)
# Requests are transparently batched,
# routed, and returned — with an
# async SLA guarantee.
