Batch Processing
Submit multiple requests for asynchronous processing and retrieve their results later. Batch processing is suitable for offline workloads such as classification, dataset generation and scheduled reports. Pricing and completion windows depend on the provider.
How It Works#
- Create a batch (submit your requests)
- Poll the batch status until it reaches
Completed - Retrieve the results
use Prism\Prism\Facades\Prism; use Prism\Prism\Enums\Provider; use Prism\Prism\Batch\BatchStatus; // 1. Create $job = Prism::batch() ->using(Provider::Anthropic) ->create(items: $items); // 2. Poll from a worker with an application-chosen deadline. $deadline = time() + 3600; $terminal = [BatchStatus::Completed, BatchStatus::Failed, BatchStatus::Expired, BatchStatus::Cancelled]; while (! in_array($job->status, $terminal, true)) { if (time() >= $deadline) { throw new RuntimeException('Batch is still pending; retain its ID and poll later.'); } sleep(30); $job = Prism::batch() ->using(Provider::Anthropic) ->retrieve($job->id); } if ($job->status !== BatchStatus::Completed) { throw new RuntimeException('Handle the unsuccessful batch and its errors before reading results.'); } // 3. Get results $results = Prism::batch() ->using(Provider::Anthropic) ->getResults($job->id); foreach ($results as $result) { echo "{$result->customId}: {$result->text}\n"; }
Creating a Batch#
Anthropic#
Anthropic takes a list of BatchRequestItem objects directly. Each item wraps a normal TextRequest and a unique customId you'll use to match results back to the original input.
use Prism\Prism\Facades\Prism; use Prism\Prism\Enums\Provider; use Prism\Prism\Batch\BatchRequestItem; use Prism\Prism\Text\Request as TextRequest; use Prism\Prism\ValueObjects\Messages\UserMessage; $items = collect($yourData)->map(fn ($row) => new BatchRequestItem( customId: "row-{$row->id}", request: new TextRequest( model: 'claude-sonnet-4-20250514', messages: [new UserMessage("Summarise: {$row->content}")], ), ))->all(); $job = Prism::batch() ->using(Provider::Anthropic) ->create(items: $items); echo $job->id; // "msgbatch_01..." echo $job->status->name; // Validating
OpenAI#
OpenAI accepts the same items array as Anthropic. Prism automatically builds the JSONL payload and uploads it via the Files API before creating the batch — you don't need to handle the file upload yourself.
use Prism\Prism\Facades\Prism; use Prism\Prism\Enums\Provider; use Prism\Prism\Batch\BatchRequestItem; use Prism\Prism\Text\Request as TextRequest; use Prism\Prism\ValueObjects\Messages\UserMessage; $items = collect($yourData)->map(fn ($row) => new BatchRequestItem( customId: "row-{$row->id}", request: new TextRequest( model: 'gpt-4o', messages: [new UserMessage("Summarise: {$row->content}")], ), ))->all(); $job = Prism::batch() ->using(Provider::OpenAI) ->create(items: $items); echo $job->id;
Bringing your own file
If you've already uploaded a JSONL file via the Files API, pass its ID directly via inputFileId instead:
$file = Prism::files() ->using(Provider::OpenAI) ->withProviderOptions(['purpose' => 'batch']) ->upload(content: $jsonlContent, filename: 'batch-input.jsonl'); $job = Prism::batch() ->using(Provider::OpenAI) ->create(inputFileId: $file->id);
Note
Providing both items and inputFileId at the same time throws a PrismException. Use one or the other.
Polling for Completion#
The batch doesn't complete immediately. Poll the status until it reaches a terminal state:
use Prism\Prism\Batch\BatchStatus; $deadline = time() + 3600; do { if (time() >= $deadline) { throw new RuntimeException('Retain the batch ID and schedule another status check.'); } sleep(30); $job = Prism::batch() ->using(Provider::Anthropic) ->retrieve($job->id); } while (! in_array($job->status, [ BatchStatus::Completed, BatchStatus::Failed, BatchStatus::Expired, BatchStatus::Cancelled, ]));
BatchStatus values#
| Status | Meaning |
|---|---|
Validating |
Requests are being validated |
InProgress |
Processing is underway |
Finalizing |
Results are being prepared |
Completed |
All requests have finished |
Failed |
The batch failed to process |
Cancelling |
Cancellation is in progress |
Cancelled |
The batch was cancelled |
Expired |
The batch expired before completing |
Request counts#
The BatchJob object includes a requestCounts property that breaks down progress:
echo "Total: {$job->requestCounts->total}"; echo "Processing: {$job->requestCounts->processing}"; echo "Succeeded: {$job->requestCounts->succeeded}"; echo "Failed: {$job->requestCounts->failed}"; echo "Expired: {$job->requestCounts->expired}";
Retrieving Results#
Once the batch is Completed, call getResults() to get all the result items back as an array:
use Prism\Prism\Batch\BatchResultStatus; $results = Prism::batch() ->using(Provider::Anthropic) ->getResults($job->id); foreach ($results as $result) { if ($result->status === BatchResultStatus::Succeeded) { echo "{$result->customId}: {$result->text}\n"; echo "Tokens: {$result->usage->promptTokens} in / {$result->usage->completionTokens} out\n"; } else { echo "{$result->customId} failed: {$result->errorMessage}\n"; } }
BatchResultItem properties#
| Property | Type | Description |
|---|---|---|
customId |
string |
The ID you assigned when creating the item |
status |
BatchResultStatus |
Succeeded, Errored, Canceled, or Expired |
text |
?string |
The generated text (null if not succeeded) |
usage |
?Usage |
Token counts |
messageId |
?string |
Provider message ID |
model |
?string |
Model used |
errorType |
?string |
Error code if failed |
errorMessage |
?string |
Human-readable error detail |
Listing Batches#
Browse previously created batches with optional pagination:
$list = Prism::batch() ->using(Provider::OpenAI) ->list(limit: 20); foreach ($list->data as $job) { echo "{$job->id}: {$job->status->name}\n"; } // Next page if ($list->hasMore) { $nextPage = Prism::batch() ->using(Provider::OpenAI) ->list(limit: 20, afterId: $list->lastId); }
Cancelling a Batch#
$job = Prism::batch() ->using(Provider::Anthropic) ->cancel($job->id); echo $job->status->name; // "Cancelling"
Provider-Conditional Options#
Use whenProvider() when your batch logic needs to handle both providers from the same code path:
$job = Prism::batch() ->using($providerName) ->whenProvider('openai', fn ($r) => $r->withProviderOptions(['completion_window' => '24h'])) ->create(items: $items);
Job metadata and limits#
BatchJob exposes id, status, requestCounts, createdAt, expiresAt, endedAt, resultsUrl, inputFileId, outputFileId, errorFileId and errors. Dates, result URLs and file IDs can be null depending on the provider and job state. requestCounts contains processing, succeeded, failed, canceled, expired and total counts. Each errors entry has code, message and nullable line/param fields.
The Anthropic integration refuses more than 100,000 items with PrismBatchRequestLimitExceededException, and by default refuses a JSON payload over 256 MiB with PrismBatchPayloadSizeExceededException. Both extend PrismException. Supply items for Anthropic; OpenAI accepts either items or an inputFileId, never both. Payload checks occur locally before submission; provider limits can impose further restrictions.
Client Options#
$job = Prism::batch() ->using(Provider::Anthropic) ->withClientOptions(['timeout' => 60]) ->withClientRetry(3, 500) ->create(items: $items);
Error Handling#
use Prism\Prism\Exceptions\PrismException; use Throwable; try { $job = Prism::batch() ->using(Provider::Anthropic) ->create(items: $items); } catch (PrismException $e) { Log::error('Batch creation failed', ['error' => $e->getMessage()]); } catch (Throwable $e) { Log::error('Unexpected error', ['error' => $e->getMessage()]); }
Tip
Individual result items can have their own failure status independent of the overall batch status. Always check $result->status on each item — a Completed batch can still contain Errored or Expired result items.