# Message Batches

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.

## Overview

Message Batches enable:
- Bulk processing of multiple independent message requests
- Asynchronous result retrieval
- 50% cost reduction compared to standard API
- Processing of up to 10,000 requests per batch
- 24-hour processing window with automatic expiration

## API Reference

```typescript { .api }
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:

```typescript
client.messages.batches.*  // Standard API
client.beta.messages.batches.*  // Beta API
```

## Creating a Batch

```typescript { .api }
client.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:**

```typescript
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);
```

### Custom IDs

Each request requires a unique `custom_id`:

```typescript
// ✅ 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.

## Checking Batch Status

```typescript { .api }
client.messages.batches.retrieve(
  messageBatchID: string
): APIPromise<MessageBatch>;
```

**Example:**

```typescript
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);
}
```

### Processing Status

```typescript
type ProcessingStatus =
  | 'in_progress'  // Batch is being processed
  | 'canceling'    // Cancellation in progress
  | 'ended'        // Processing complete (success/error/expired)
  ;
```

### Polling for Completion

```typescript
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);
```

## Retrieving Results

```typescript { .api }
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:**

```typescript
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');
  }
}
```

### Collecting Results

```typescript
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}`);
```

## Listing Batches

```typescript { .api }
client.messages.batches.list(
  params?: MessageBatchListParams
): MessageBatchesPage;

interface MessageBatchListParams {
  before_id?: string;
  after_id?: string;
  limit?: number;  // Default: 20, max: 100
}
```

**Example:**

```typescript
// 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);
}
```

## Canceling a Batch

```typescript { .api }
client.messages.batches.cancel(
  messageBatchID: string
): APIPromise<MessageBatch>;
```

**Example:**

```typescript
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.

## Deleting a Batch

```typescript { .api }
client.messages.batches.delete(
  messageBatchID: string
): APIPromise<DeletedMessageBatch>;

interface DeletedMessageBatch {
  id: string;
  type: 'message_batch';
  deleted: boolean;
}
```

**Example:**

```typescript
// Delete a batch (must be ended first)
const deleted = await client.messages.batches.delete('batch_123');
console.log('Deleted:', deleted.deleted); // true
```

**Important**: You can only delete batches that have `processing_status === 'ended'`. Archived batches are automatically deleted.

## Complete Workflow Example

```typescript
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);
```

## Use Cases

### Bulk Document Processing

```typescript
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.',
            },
          ],
        },
      ],
    },
  })),
});
```

### Data Analysis Pipeline

```typescript
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);
  }
}
```

### Translation Service

```typescript
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}`,
        },
      ],
    },
  })),
});
```

## Limitations

- **Batch Size**: Maximum 10,000 requests per batch
- **Expiration**: Batches expire after 24 hours
- **Processing Time**: No guaranteed completion time (typically minutes to hours)
- **No Streaming**: Individual requests cannot stream results
- **Rate Limits**: Subject to account-level rate limits

## Best Practices

### Error Handling

```typescript
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);
  }
}
```

### Request Chunking

```typescript
// ✅ 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
});
```

### Result Processing

```typescript
// ✅ 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
}
```

### Monitoring

```typescript
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
  }
}
```

## Cost Optimization

Batches provide 50% cost savings:

```typescript
// 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:
- Non-urgent bulk processing
- Offline analysis
- Data pipelines
- Scheduled tasks

Use standard API for:
- Real-time responses
- User-facing applications
- Time-sensitive requests

## See Also

- [Messages API](./messages.md) - Individual message creation
- [Tools](./tools.md) - Using tools in batch requests
- [Types](./types.md) - Batch type definitions
