Claude API Batch Processing Requests: A How-To Guide
When you need to run hundreds or thousands of prompts through Claude — summarizing documents, classifying support tickets, generating product descriptions — sending requests one at a time is slow and often hits rate limits. Claude API batch processing means grouping, queuing, and controlling the concurrency of many requests so they complete reliably without errors or runaway costs.
This guide covers the practical mechanics: how to structure batch jobs, manage concurrency and retries, handle partial failures, and keep token usage visible so a bulk job doesn't silently blow your budget.
Why naive loops fail at scale
The simplest approach — a for loop calling the API sequentially — works for a dozen requests but breaks down fast:
- Rate limits. Every account has requests-per-minute and tokens-per-minute ceilings. Sequential loops often stay under them, but they're painfully slow for 10,000 items.
- No concurrency control. Firing all requests at once (
Promise.allon an unbounded array) triggers 429s and wastes retries. - No failure isolation. One malformed input or timeout can crash the whole job if you don't catch errors per-item.
- No cost visibility. Without tracking tokens per request, you find out the batch was expensive after the invoice, not during the run.
A real batch pipeline needs a queue, a concurrency cap, retry logic with backoff, and per-item logging.
A basic concurrency-limited batch runner
Here's a pattern that works well for Claude batch jobs in Node.js — a worker pool processing a queue with a fixed number of concurrent requests:
async function runBatch(items, worker, concurrency = 5) {
const queue = [...items];
const results = [];
const errors = [];
async function runWorker() {
while (queue.length > 0) {
const item = queue.shift();
try {
const result = await worker(item);
results.push({ item, result });
} catch (err) {
errors.push({ item, error: err.message });
}
}
}
const workers = Array.from({ length: concurrency }, runWorker);
await Promise.all(workers);
return { results, errors };
}
Each "worker" function makes one API call and returns the response. Tune concurrency based on your rate limits — start at 3–5 and increase gradually while watching for 429 responses.
Adding retries with backoff
Transient errors (timeouts, rate limits, 5xx responses) are normal at scale. Wrap each call in a retry function instead of failing the whole item immediately:
async function callWithRetry(fn, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await fn();
} catch (err) {
attempt++;
const isRateLimit = err.status === 429;
const delay = isRateLimit ? 2 ** attempt * 1000 : 500 * attempt;
if (attempt >= maxRetries) throw err;
await new Promise((r) => setTimeout(r, delay));
}
}
}
Combine this with the worker pool above: each worker(item) call becomes callWithRetry(() => makeRequest(item)).
Structuring the actual requests
For batch jobs, keep each request self-contained and stateless — don't carry conversation history between items unless the task actually requires it, since that inflates token usage per call unnecessarily.
async function makeRequest(item) {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 500,
messages: [{ role: "user", content: item.prompt }]
})
});
if (!response.ok) {
const err = new Error(`Request failed: ${response.status}`);
err.status = response.status;
throw err;
}
return response.json();
}
If you're routing these calls through SubToAPI, you get a single application API key (sub_live_...) shared across your batch workers instead of juggling raw provider credentials, plus usage metadata on every response so you can log token counts per item without extra API calls. See the quickstart and Messages API reference for request and response shapes.
Tracking cost and token usage mid-batch
A batch job that silently uses 10x the expected tokens is a common and expensive mistake. Log usage per request as you go:
async function makeRequestWithUsage(item) {
const data = await makeRequest(item);
return {
output: data.content,
tokensUsed: data.usage?.input_tokens + data.usage?.output_tokens
};
}
Sum these as the batch runs and set a hard stop — abort the queue if cumulative token usage crosses a threshold you define upfront. This is far cheaper than discovering the overage after the fact.
Checkpointing long-running batches
For jobs that take hours or run across multiple sessions, persist progress so a crash doesn't mean starting over:
- Write completed item IDs to a file or database table as you finish them.
- On restart, filter the input list to exclude already-completed IDs.
- Store the raw response alongside the input so you can re-process failures without re-querying.
This matters more than it sounds — a batch of 50,000 items that fails at item 48,000 without checkpointing is a genuinely bad day.
Streaming vs. batch for bulk jobs
Streaming (/docs/streaming) is built for responsive single-request UX, not bulk throughput — don't stream thousands of batch items, since the overhead of keeping connections open per item adds latency without benefit. Use standard non-streaming calls for batch processing and reserve streaming for interactive features.
If your batch tasks involve structured outputs — extracting fields, calling functions per item — look at tool use rather than parsing free-text responses; it's more reliable at scale across thousands of varied inputs.
Getting started
Running Claude batch jobs reliably comes down to four things: concurrency limits that respect rate limits, retries with backoff for transient failures, per-item error isolation so one bad input doesn't kill the run, and usage tracking so costs stay visible. SubToAPI gives you a single API key, built-in usage metadata, and team seats if multiple people or services need to run batch jobs under shared billing — check the docs for full request/response details.
questions
What's the ideal concurrency level for Claude API batch jobs? Start at 3–5 concurrent requests and increase gradually while monitoring for 429 (rate limit) responses. The right number depends on your plan's rate limits and average response time per request.
Does Claude have a dedicated batch API endpoint? Batch processing here refers to the pattern of running many requests through the standard Messages endpoint with concurrency control, not a separate bulk-upload endpoint — see the Messages docs for the request format you'll loop over.
How do I avoid overspending on a large batch job? Log token usage per request as the batch runs and set a hard stop threshold. Checkpointing progress also prevents re-running completed items after a failure, which doubles cost unnecessarily.