The Message Batches API allows you to send multiple message requests in a single batch, processing them asynchronously. Batches are cost-efficient for bulk operations and provide significant cost savings (50% off) compared to individual API calls.
Message Batches enable:
class Batches extends APIResource {
create(params: MessageBatchCreateParams): APIPromise<MessageBatch>;
retrieve(messageBatchID: string): APIPromise<MessageBatch>;
list(params?: MessageBatchListParams): MessageBatchesPage;
delete(messageBatchID: string): APIPromise<DeletedMessageBatch>;
cancel(messageBatchID: string): APIPromise<MessageBatch>;
results(messageBatchID: string): AsyncIterable<MessageBatchIndividualResponse>;
}Access via:
client.messages.batches.* // Standard API
client.beta.messages.batches.* // Beta APIclient.messages.batches.create(
params: MessageBatchCreateParams
): APIPromise<MessageBatch>;
interface MessageBatchCreateParams {
requests: Array<{
custom_id: string;
params: MessageCreateParams;
}>;
}
interface MessageBatch {
id: string;
type: 'message_batch';
processing_status: 'in_progress' | 'canceling' | 'ended';
request_counts: MessageBatchRequestCounts;
ended_at: string | null;
created_at: string;
expires_at: string;
archived_at: string | null;
cancel_initiated_at: string | null;
results_url: string | null;
}
interface MessageBatchRequestCounts {
processing: number;
succeeded: number;
errored: number;
canceled: number;
expired: number;
}Example:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const batch = await client.messages.batches.create({
requests: [
{
custom_id: 'request-1',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is the capital of France?',
},
],
},
},
{
custom_id: 'request-2',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is 2 + 2?',
},
],
},
},
{
custom_id: 'request-3',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Explain quantum computing in simple terms.',
},
],
},
},
],
});
console.log('Batch ID:', batch.id);
console.log('Status:', batch.processing_status);
console.log('Created at:', batch.created_at);
console.log('Expires at:', batch.expires_at);Each request requires a unique custom_id:
// ✅ Good: Meaningful IDs
{
custom_id: 'user-123-question-1',
params: { /* ... */ }
}
// ✅ Good: Sequential IDs
{
custom_id: 'batch-001-item-001',
params: { /* ... */ }
}
// ❌ Bad: Duplicate IDs
{
custom_id: 'request', // Used multiple times
params: { /* ... */ }
}Custom IDs are used to match requests with results and must be unique within a batch.
client.messages.batches.retrieve(
messageBatchID: string
): APIPromise<MessageBatch>;Example:
const batch = await client.messages.batches.retrieve('batch_123');
console.log('Processing status:', batch.processing_status);
console.log('Request counts:', batch.request_counts);
// {
// processing: 5,
// succeeded: 10,
// errored: 2,
// canceled: 0,
// expired: 0
// }
if (batch.processing_status === 'ended') {
console.log('Batch processing complete!');
console.log('Results URL:', batch.results_url);
}type ProcessingStatus =
| 'in_progress' // Batch is being processed
| 'canceling' // Cancellation in progress
| 'ended' // Processing complete (success/error/expired)
;async function waitForBatch(batchId: string): Promise<MessageBatch> {
while (true) {
const batch = await client.messages.batches.retrieve(batchId);
if (batch.processing_status === 'ended') {
return batch;
}
// Wait before polling again
await new Promise(resolve => setTimeout(resolve, 60000)); // 1 minute
}
}
const batch = await client.messages.batches.create({ /* ... */ });
console.log('Waiting for batch to complete...');
const completed = await waitForBatch(batch.id);
console.log('Batch completed!', completed.request_counts);client.messages.batches.results(
messageBatchID: string
): AsyncIterable<MessageBatchIndividualResponse>;
interface MessageBatchIndividualResponse {
custom_id: string;
result: MessageBatchResult;
}
type MessageBatchResult =
| MessageBatchSucceededResult
| MessageBatchErroredResult
| MessageBatchCanceledResult
| MessageBatchExpiredResult;
interface MessageBatchSucceededResult {
type: 'succeeded';
message: Message;
}
interface MessageBatchErroredResult {
type: 'errored';
error: {
type: string;
message: string;
};
}
interface MessageBatchCanceledResult {
type: 'canceled';
}
interface MessageBatchExpiredResult {
type: 'expired';
}Example:
const batch = await client.messages.batches.create({ /* ... */ });
// Wait for completion
await waitForBatch(batch.id);
// Retrieve results
const results = await client.messages.batches.results(batch.id);
for await (const result of results) {
console.log('Custom ID:', result.custom_id);
if (result.result.type === 'succeeded') {
const message = result.result.message;
console.log('Success:', message.content[0].text);
} else if (result.result.type === 'errored') {
console.error('Error:', result.result.error.message);
} else if (result.result.type === 'canceled') {
console.log('Request was canceled');
} else if (result.result.type === 'expired') {
console.log('Request expired');
}
}const batch = await client.messages.batches.create({ /* ... */ });
await waitForBatch(batch.id);
const results = await client.messages.batches.results(batch.id);
const allResults: MessageBatchIndividualResponse[] = [];
for await (const result of results) {
allResults.push(result);
}
console.log(`Processed ${allResults.length} results`);
// Group by result type
const succeeded = allResults.filter(r => r.result.type === 'succeeded');
const errored = allResults.filter(r => r.result.type === 'errored');
console.log(`Succeeded: ${succeeded.length}, Errored: ${errored.length}`);client.messages.batches.list(
params?: MessageBatchListParams
): MessageBatchesPage;
interface MessageBatchListParams {
before_id?: string;
after_id?: string;
limit?: number; // Default: 20, max: 100
}Example:
// List recent batches
const batches = await client.messages.batches.list({
limit: 10,
});
for (const batch of batches.data) {
console.log('Batch:', batch.id);
console.log('Status:', batch.processing_status);
console.log('Counts:', batch.request_counts);
}
// Auto-pagination
for await (const batch of client.messages.batches.list({ limit: 20 })) {
console.log(batch.id);
}client.messages.batches.cancel(
messageBatchID: string
): APIPromise<MessageBatch>;Example:
const batch = await client.messages.batches.create({ /* ... */ });
// Cancel the batch
const canceled = await client.messages.batches.cancel(batch.id);
console.log('Status:', canceled.processing_status); // 'canceling'
// Wait for cancellation to complete
await waitForBatch(canceled.id);
// Check final status
const final = await client.messages.batches.retrieve(canceled.id);
console.log('Canceled requests:', final.request_counts.canceled);Note: Cancellation may take time. In-flight requests will complete, but pending requests will be canceled.
client.messages.batches.delete(
messageBatchID: string
): APIPromise<DeletedMessageBatch>;
interface DeletedMessageBatch {
id: string;
type: 'message_batch';
deleted: boolean;
}Example:
// Delete a batch (must be ended first)
const deleted = await client.messages.batches.delete('batch_123');
console.log('Deleted:', deleted.deleted); // trueImportant: You can only delete batches that have processing_status === 'ended'. Archived batches are automatically deleted.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// 1. Create batch
const batch = await client.messages.batches.create({
requests: [
{
custom_id: 'analysis-1',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Analyze this data: [1, 2, 3, 4, 5]',
},
],
},
},
{
custom_id: 'analysis-2',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Summarize: The quick brown fox jumps over the lazy dog.',
},
],
},
},
],
});
console.log('Created batch:', batch.id);
// 2. Poll for completion
console.log('Waiting for completion...');
let currentBatch = batch;
while (currentBatch.processing_status !== 'ended') {
await new Promise(resolve => setTimeout(resolve, 30000)); // 30 seconds
currentBatch = await client.messages.batches.retrieve(batch.id);
console.log('Status:', currentBatch.request_counts);
}
// 3. Retrieve results
console.log('Retrieving results...');
const results = await client.messages.batches.results(batch.id);
const resultMap = new Map<string, MessageBatchIndividualResponse>();
for await (const result of results) {
resultMap.set(result.custom_id, result);
}
// 4. Process results
for (const [customId, result] of resultMap) {
console.log(`\nResult for ${customId}:`);
if (result.result.type === 'succeeded') {
console.log('Response:', result.result.message.content[0].text);
} else {
console.error('Failed:', result.result);
}
}
// 5. Cleanup (optional)
// await client.messages.batches.delete(batch.id);const documents = await loadDocuments(); // Load from database
const batch = await client.messages.batches.create({
requests: documents.map((doc, index) => ({
custom_id: `doc-${doc.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'base64',
media_type: 'application/pdf',
data: doc.content,
},
},
{
type: 'text',
text: 'Summarize this document.',
},
],
},
],
},
})),
});const dataPoints = await fetchDataPoints();
const batch = await client.messages.batches.create({
requests: dataPoints.map((data) => ({
custom_id: `analysis-${data.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Analyze this data and provide insights: ${JSON.stringify(data)}`,
},
],
},
})),
});
await waitForBatch(batch.id);
const results = await client.messages.batches.results(batch.id);
for await (const result of results) {
if (result.result.type === 'succeeded') {
await saveAnalysis(result.custom_id, result.result.message);
}
}const textsToTranslate = [
{ id: '1', text: 'Hello, world!', targetLang: 'Spanish' },
{ id: '2', text: 'Good morning!', targetLang: 'French' },
{ id: '3', text: 'Thank you!', targetLang: 'German' },
];
const batch = await client.messages.batches.create({
requests: textsToTranslate.map((item) => ({
custom_id: `translation-${item.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Translate "${item.text}" to ${item.targetLang}`,
},
],
},
})),
});const results = await client.messages.batches.results(batchId);
for await (const result of results) {
if (result.result.type === 'succeeded') {
await processSuccess(result);
} else if (result.result.type === 'errored') {
await logError(result.custom_id, result.result.error);
await retryRequest(result.custom_id);
} else if (result.result.type === 'expired') {
await handleExpiredRequest(result.custom_id);
}
}// ✅ Good: Process in chunks
async function processBatches(requests: Request[]) {
const CHUNK_SIZE = 5000;
for (let i = 0; i < requests.length; i += CHUNK_SIZE) {
const chunk = requests.slice(i, i + CHUNK_SIZE);
const batch = await client.messages.batches.create({
requests: chunk.map((req, index) => ({
custom_id: `chunk-${i / CHUNK_SIZE}-item-${index}`,
params: req.params,
})),
});
await waitForBatch(batch.id);
await processResults(batch.id);
}
}
// ❌ Bad: Exceeding limits
const batch = await client.messages.batches.create({
requests: allRequests.map(/* ... */), // Could be > 10,000
});// ✅ Good: Stream and process results
const results = await client.messages.batches.results(batchId);
for await (const result of results) {
// Process immediately, don't accumulate
await handleResult(result);
}
// ❌ Bad: Loading all into memory
const results = await client.messages.batches.results(batchId);
const allResults = [];
for await (const result of results) {
allResults.push(result); // Can be large
}async function monitorBatch(batchId: string) {
const startTime = Date.now();
while (true) {
const batch = await client.messages.batches.retrieve(batchId);
const elapsed = (Date.now() - startTime) / 1000 / 60; // minutes
console.log(`[${elapsed.toFixed(1)}m] Status:`, batch.request_counts);
if (batch.processing_status === 'ended') {
console.log('Batch complete!');
return batch;
}
await new Promise(resolve => setTimeout(resolve, 60000)); // 1 min
}
}Batches provide 50% cost savings:
// Standard API cost: $0.003 per 1K input tokens (example)
// Batch API cost: $0.0015 per 1K input tokens (50% off)
// For 1000 requests with 500 input tokens each:
// Standard: 1000 * 0.5K * $0.003 = $1.50
// Batch: 1000 * 0.5K * $0.0015 = $0.75
// Savings: $0.75 (50%)Use batches for:
Use standard API for: