Running a batch jobPublic preview
Create, upload, monitor, and download results for a batch job.
For the complete API reference, including all available parameters and response fields, see the Batch Jobs API reference.
To process a batch job, follow these steps:
- Create a batch job and specify the target API endpoint.
- Upload the input file with your batch requests.
- Monitor job status through webhooks or polling.
- Download the results.
Create a batch job
To start, create a batch job by sending a POST request to /v2/core/batch_. Specify the target endpoint and any processing options:
curl -X POST https://api.stripe.com/v2/core/batch_jobs \ -H "Authorization: Bearer sk_test_..." \ -H "Stripe-Version: 2026-09-30.preview" \ --json '{ "endpoint": { "path": "/v1/subscriptions/:id/migrate", "http_method": "post" }, "skip_validation": false }'
The content type for this request is a JSON file. This returns a batch job object with a ready_ status. The upload URL and its expiration time are in the status_ field:
{ "id": "batchv2_AbCdEfGhIjKlMnOpQrStUvWxYz", "object": "v2.core.batch_job", "created": "2026-03-09T20:55:31.000Z", "skip_validation": false, "status": "ready_for_upload", "status_details": { "ready_for_upload": { "upload_url": { "expires_at": "2026-03-09T21:00:31.000Z", "url": "https://stripeusercontent.com/files/upload/..." } } } }
The contents of status_ depend on the status of the batch job. When status is ready_, status_ contains the presigned upload URL and its expiration timestamp. A presigned URL includes its own authorization, so you can upload your file to it without your API key. The URL accepts only PUT requests and expires 5 minutes after you create the batch job.
Parameters
Upload the input file
After creating the batch job, upload your input file to the URL in status_. Use a PUT request with the file contents:
curl {UPLOAD_URL} \ -X PUT \ -T input.jsonl \ -H "Content-Type: application/jsonlines"
The input file for this request must be a JSONL file, and the content type must be application/jsonlines. After the upload completes, Stripe automatically starts processing. Thereâs no separate start step.
Warning
A 200 response to the PUT comes from storage (Amazon S3), which accepts the raw bytes directly through the presigned URL. It confirms only that the upload succeeded and that the Content-Type header was correct. It doesnât mean Stripe has inspected the file or that the contents passed validation.
File-body validation (valid JSONL, required fields, and so on) runs asynchronously after the upload. When skip_ is false, the job moves to validating and then to validation_ if the contents are invalid.
To learn the outcome, poll the batch job (GET /v2/core/batch_) and inspect its status and status_.
The upload URL expires 5 minutes after batch job creation. Check the expires_ field for the exact deadline. If the URL expires before you upload the file, the job status changes to upload_, and you must create a new batch job. Generate the input file before you create the batch job so you can upload it promptly.
Input file format
The file must be UTF-8 encoded and use JSONL format (newline-delimited JSON, one object per line). Each line represents a single API request to the target endpoint. CSV and other formats arenât supported.
The file is capped at 2 million lines. Stripe counts the lines after the upload completes, and jobs that exceed the cap transition to validation_ status without processing any requests. To process more than 2 million lines, split the workload into multiple batch jobs of at most 2 million lines each and submit them sequentiallyâwait for one job to finish before you create the next. Running several large batch jobs concurrently can be slower and less reliable than running the same work as sequential jobs. If you need more capacity than this, contact Stripe support.
Each JSON object supports these fields:
| Field | Required | Description |
|---|---|---|
id | Yes | A unique identifier to correlate this request with its result. The IDs on the path parameters and the IDs on the endpoint must match, though the user is free to choose how to name them: must match /^[A-Za-z0-9_. |
path_ | Conditional | Path parameters for the endpoint. Required when the endpoint path includes placeholders (for example, :id). The keys in path_ must match the placeholders in the endpoint path exactly. |
params | No | Request body parameters for the API call. Variations can occur based on the API method. |
context | No | A Stripe account ID. Use this to execute the request against a specific account, such as a connected account. |
Example input file
Each example uses JSONL format, with one complete JSON object per line, so you can copy it directly into an input file. To see the example for an endpoint, select its path from the Endpoint list.
For the POST /v1/accounts endpoint:
{"id": "req_001", "params": {"type": "custom", "country": "US", "capabilities": {"card_payments": {"requested": true}}}} {"id": "req_002", "params": {"type": "custom", "country": "GB", "capabilities": {"card_payments": {"requested": true}}}}
Each id must be unique within the file. Stripe uses it to correlate requests with results, because the results file isnât ordered the same way as the input file.
Monitor job status
You can track your batch job by polling the retrieve endpoint or by listening for webhook events. We recommend using webhook events for production integrations.
Poll for status
curl https://api.stripe.com/v2/core/batch_jobs/batchv2_AbCdEfGhIjKlMnOpQrStUvWxYz \ -H "Authorization: Bearer sk_test_..." \ -H "Stripe-Version: 2026-09-30.preview"
The content type for this request is a json file. While the job is running, status_ includes real-time progress counts:
{ "status": "in_progress", "status_details": { "in_progress": { "success_count": "1", "failure_count": "0" } } }
During the validating phase, status_ includes a validated_ field that shows how many rows Stripe has validated so far.
Batch job API calls appear in the Stripe Dashboard or Workbench request logs. The underlying API calls donât appear in the request logs. Use the retrieve endpoint or webhook events to monitor progress. To debug individual request failures, check the results file.
Job lifecycle
After you upload the input file, the batch job progresses through these statuses:
| Status | Description |
|---|---|
ready_ | The batch job was created and is waiting for the input file. |
validating | Stripe accepted the uploaded file and is validating it. Skipped when skip_ is true. |
in_ | Validation passed (or was skipped) and Stripe is processing requests. |
complete | All requests have been processed. Results are available for download. |
cancelling | A cancelation was requested. Stripe is finishing in-flight requests. |
Terminal statuses
| Status | Description |
|---|---|
validation_ | The input file contains errors. No requests were processed. Check the batch job object for error details. This is only applicable when skip_. |
batch_ | An unexpected error occurred during processing. |
cancelled | The batch job was canceled. Partial results might be available. |
upload_ | The upload URL expired before the file was uploaded. Create a new batch job. |
timeout | The batch job exceeded the maximum processing duration of 7 days. Partial results might be available. |
Validation
When skip_ is false (the default), Stripe validates the entire input file before processing any requests. This validation catches errors such as:
- Invalid JSON on any line
- Missing or invalid
idfields - Duplicate IDs
- Missing required
path_for the target endpointparams
For /v1 endpoints, validation also checks the parameters in each request, so unknown parameters and invalid parameter values fail validation. Errors that depend on the state of the objects you reference, such as migrating a subscription that isnât active, only appear when Stripe executes the request. These failures appear in the results file with a non-200 status. To catch them before you run a large batch, test your input with a small batch first.
If validation fails, the status changes to validation_, and Stripe doesnât attempt any requests. The batch job object includes details about the first error it encounters.
When skip_ is true, the job transitions directly from ready_ to in_ after upload. Errors in individual requests appear in the results file instead of blocking the entire batch.
Webhook events
Batch jobs emit v2 thin events for every lifecycle transition. To receive these events, you must configure a v2 event destination.
Batch job events require v2 event destinations. They arenât delivered to v1 webhook endpoints.
The following events are available:
| Event type | Description |
|---|---|
v2. | A batch job was created. |
v2. | The batch job is ready for file upload. |
v2. | File upload complete, validation in progress. |
v2. | Input file validation failed. |
v2. | All requests have been processed. |
v2. | The batch job failed unexpectedly. |
v2. | The batch job was canceled. |
v2. | The batch job exceeded maximum processing duration. |
v2. | The upload URL expired before the file was uploaded. |
v2. | The batch job status or progress changed. |
All batch job events include the metadata you provided when creating the job. Use this to correlate events with your internal systems.
When notification_ is set to {"scope": "all"}, webhooks from the underlying API operations (for example, subscription update events) are suppressed. Batch-level events listed above are always delivered regardless of this setting.
Download the results
When the batch job reaches complete status, the status_ field includes a summary of successes and failures, along with a presigned download URL for the output file:
{ "id": "batchv2_AbCdEfGhIjKlMnOpQrStUvWxYz", "object": "v2.core.batch_job", "created": "2026-03-09T20:55:31.000Z", "skip_validation": true, "status": "complete", "status_details": { "complete": { "success_count": "2", "failure_count": "0", "output_file": { "content_type": "application/jsonlines", "size": "8514", "download_url": { "expires_at": "2026-03-09T22:05:31.000Z", "url": "https://stripeusercontent.com/files/download/..." } } } } }
Download the file using the URL in status_. Stripe provides an output file when the batch job reaches any of these states:
completecancelledtimeoutvalidation_failed
To see when the download URL expires, check the expires_ field for the deadline.
The results file contains both successful and failed requests in a single file. To find failures, filter for rows where status isnât 200.
Results file format
The output file uses JSONL format (one JSON object per line). Each line contains these fields:
Example results file
Successful requests return the full API resource in the response field:
{ "id": "req_001", "response": { "id": "sub_1AbCdEfGhIjKlMn", "object": "subscription", "status": "active", "billing_cycle_anchor": 1710021331, "current_period_end": 1712613331, "current_period_start": 1710021331 }, "status": 200 } { "id": "req_002", "response": { "id": "sub_2BcDeFgHiJkLmNo", "object": "subscription", "status": "active", "billing_cycle_anchor": 1710021331, "current_period_end": 1712613331, "current_period_start": 1710021331 }, "status": 200 }
Failed requests return an error object:
{ "id": "req_003", "response": { "error": { "message": "This subscription cannot be migrated because it is not active. Current status is canceled.", "type": "invalid_request_error", "code": "resource_invalid_state" } }, "status": 400 }
When an input line includes a context, the corresponding output includes it:
{ "id": "req_001", "context": "acct_1234567890", "response": { "id": "sub_1AbCdEfGhIjKlMn", "object": "subscription", "status": "active" }, "status": 200 }
Warning
Stripe doesnât process requests in input order, and the results file doesnât follow the order of the input file. Results from a small test file might appear in order, but donât rely on that for any file size. Always use the id field to match each result to its request.
Create related objects across batches
Each request in a batch runs independently, so a request canât reference an object that another request in the same batch creates. To create related objects, such as customers and their subscriptions, run a separate batch job for each step:
Create a batch job for the first endpoint (for example,
POST /v1/customers). Give each line anidthat identifies the record in your system.{"id": "user_001", "params": {"email": "alice@example.com"}} {"id": "user_002", "params": {"email": "bob@example.com"}}When the first job finishes, download the results file and map each
idto the Stripe object ID inresponse.. Leave lines where theid statusfield isnât200out of the next batch, and handle those failures separately.Create a second batch job for the next endpoint (for example,
POST /v1/subscriptions) and use the customer IDs from the first results file in each line.{"id": "user_001", "params": {"customer": "cus_1AbCdEfGhIjKlMn", "items": [{"price": "price_1AbCdEfGhIjKlMn"}]}} {"id": "user_002", "params": {"customer": "cus_2BcDeFgHiJkLmNo", "items": [{"price": "price_1AbCdEfGhIjKlMn"}]}}
Wait for each job to finish before you create the next one. Because results arenât ordered, always build the mapping from the id field, not from the line position.
Cancel a batch job
You can cancel a batch job that hasnât completed yet by sending a POST request:
curl -X POST https://api.stripe.com/v2/core/batch_jobs/batchv2_AbCdEfGhIjKlMnOpQrStUvWxYz/cancel \ -H "Authorization: Bearer sk_test_..." \ -H "Stripe-Version: 2026-09-30.preview"
Cancelation is asynchronous. The job first transitions to cancelling while in-flight requests finish, then to cancelled. Any partial results from requests processed before cancelation are available in the results file.
Use with Connect
Platform accounts can run batch jobs on behalf of connected accounts. Create the batch job using your platformâs API key as normal. To target a connected account for individual requests in the input file, set the context field to the connected account ID. This is the per-request equivalent of the Stripe-Account header in direct API calls.
curl -X POST https://api.stripe.com/v2/core/batch_jobs \ -H "Authorization: Bearer sk_test_..." \ -H "Stripe-Version: 2026-09-30.preview" \ --json '{ "endpoint": { "path": "/v1/subscriptions/:id/migrate", "http_method": "post" } }'
In the input file, add context to each line that should run as a connected account:
{"id": "req_001", "context": "acct_1AbCdEfGhIjKlMn", "path_params": {"id": "sub_1AbCdEfGhIjKlMn"}, "params": {"billing_cycle_anchor": "unchanged"}} {"id": "req_002", "context": "acct_2BcDeFgHiJkLmNo", "path_params": {"id": "sub_2BcDeFgHiJkLmNo"}, "params": {"billing_cycle_anchor": "unchanged"}} {"id": "req_003", "path_params": {"id": "sub_3CdEfGhIjKlMnOp"}, "params": {"billing_cycle_anchor": "unchanged"}}
Lines without context run as the platform account. Lines with context run as the specified connected account. You can mix both in the same batch file.
