---
title: "Recalls Batch"
description: "Check thousands of vehicles for safety recalls in a single batch by submitting VINs and polling for results."
canonical_url: "https://docs.carsxe.com/docs/products/recalls-batch"
markdown_url: "https://docs.carsxe.com/docs/products/recalls-batch.md"
last_updated: "1980-01-01"
---

# Recalls Batch
URL: /docs/products/recalls-batch
LLM index: /llms.txt
Description: Check thousands of vehicles for safety recalls in a single batch by submitting VINs and polling for results.

Submit up to 10,000 VINs in one request for bulk recall checking. Unlike the single-VIN [Recalls](/docs/products/recalls) API, this endpoint is built for high-volume workflows — fleet management, dealership inventory scans, and wholesale auction processing.

<Note>
  Recalls Batch is **asynchronous**: **submit** your VINs, **poll** for status (or use a **webhook**), then **retrieve** results when the job is `completed` or `partial`. Uncached VINs are queued for full processing; turnaround is typically **30–60 minutes** depending on batch size and load.
</Note>

## How it works

1. **Submit** — `POST` VINs (JSON array, inline CSV, or HTTPS URL to a CSV). You receive a `batchId` immediately (**202 Accepted**).
2. **Poll** — Call the status endpoint with `batchId` until `completed`, `partial`, or `failed` (or wait for your customer webhook).
3. **Retrieve** — `GET` results as JSON, or download CSV from `/v1/recalls-batch/download`.

**Caching:** Recently checked VINs may be served from cache. If every VIN in the batch is cached, the submit response can already show `status: "completed"` with full `processedVins` — no `uploading` / `processing` phase.

**Full processing path:** Uncached VINs move through `uploading`, then `processing`, while CarsXE prepares and runs the batch; results are written when processing finishes.

For interactive try-it-live, see the [API Reference](/api-reference/recalls/submit-a-bulk-recalls-batch).

## Parameters

Provide at least one of `vins`, `csv`, or `csvUrl`. You can combine them — all VINs are merged and deduplicated. The combined total must not exceed 10,000.

| Parameter | Required | Description |
|---|---|---|
| `vins` | No* | JSON array of 17-character VIN strings |
| `csv` | No* | Inline CSV text (one VIN per line, or a single `vin` column) |
| `csvUrl` | No* | HTTPS URL to a CSV file (max 5 MB). Supported hosts: Google Sheets, Google Cloud Storage, AWS S3, Dropbox, Azure Blob, DigitalOcean Spaces, and Box. Google Sheets and Dropbox sharing links are auto-converted to direct download |
| `webhookUrl` | No | HTTPS URL for a customer webhook when the batch finishes |
| `key` | Yes | Your CarsXE API key (query parameter; official SDKs may send `x-api-key` instead) |

\* At least one of `vins`, `csv`, or `csvUrl` is required.

## Step 1: Submit a batch

<CodeGroup title="Submit a batch" tag="POST" label="/v1/recalls-batch/submit">

```bash
curl -X POST "https://api.carsxe.com/v1/recalls-batch/submit?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vins": [
      "1HGBH41JXMN109186",
      "5YJSA1E26HF000001",
      "1C4JJXR64PW696340"
    ],
    "webhookUrl": "https://your-server.com/webhook"
  }'
```

```js
import { CarsXE } from "carsxe-api";

const carsxe = new CarsXE("YOUR_API_KEY");

try {
  const response = await carsxe.submitBulkRecallBatch({
    vins: [
      "1HGBH41JXMN109186",
      "5YJSA1E26HF000001",
      "1C4JJXR64PW696340",
    ],
    webhookUrl: "https://your-server.com/webhook",
  });
  console.log(response.data?.batchId);
} catch (error) {
  console.error(error);
}
```

```python
import asyncio
from carsxe_api import CarsXE

async def main():
    async with CarsXE(api_key="YOUR_API_KEY") as carsxe:
        response = await carsxe.submit_bulk_recall_batch(
            vins=[
                "1HGBH41JXMN109186",
                "5YJSA1E26HF000001",
                "1C4JJXR64PW696340",
            ],
            webhook_url="https://your-server.com/webhook",
        )
        print(response.data.batch_id)

asyncio.run(main())
```

```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use CarsxeDeveloper\Carsxe\Carsxe;

$carsxe = new Carsxe("YOUR_API_KEY");

try {
    $response = $carsxe->submitBulkRecallBatch([
        'vins' => [
            '1HGBH41JXMN109186',
            '5YJSA1E26HF000001',
            '1C4JJXR64PW696340',
        ],
        'webhookUrl' => 'https://your-server.com/webhook',
    ]);
    print_r($response);
} catch (Exception $error) {
    echo "Error: " . $error->getMessage();
}
```

```ruby
require "carsxe"

carsxe = Carsxe::CarsXE.new(api_key: "YOUR_API_KEY")

begin
  response = carsxe.submit_bulk_recall_batch(
    "vins" => [
      "1HGBH41JXMN109186",
      "5YJSA1E26HF000001",
      "1C4JJXR64PW696340",
    ],
    "webhookUrl" => "https://your-server.com/webhook"
  )
  puts response
rescue StandardError => error
  puts "Error: #{error.message}"
end
```

```go
package main

import (
	"fmt"
	"github.com/carsxe/carsxe-go-package"
)

func main() {
	client := carsxe.New("YOUR_API_KEY")
	response := client.SubmitBulkRecallBatch(map[string]interface{}{
		"vins": []string{
			"1HGBH41JXMN109186",
			"5YJSA1E26HF000001",
			"1C4JJXR64PW696340",
		},
		"webhookUrl": "https://your-server.com/webhook",
	})
	fmt.Println(response)
}
```

```java
import io.github.carsxe.CarsXE;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        CarsXE carsxe = new CarsXE("YOUR_API_KEY");

        try {
            Map<String, Object> response = carsxe.submitBulkRecallBatch(Map.of(
                "vins", List.of(
                    "1HGBH41JXMN109186",
                    "5YJSA1E26HF000001",
                    "1C4JJXR64PW696340"
                ),
                "webhookUrl", "https://your-server.com/webhook"
            ));
            System.out.println(response);
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}
```

```swift
import carsxe

let carsxe = CarsXE(apiKey: "YOUR_API_KEY")

do {
    let response = try carsxe.submitBulkRecallBatch([
        "vins": [
            "1HGBH41JXMN109186",
            "5YJSA1E26HF000001",
            "1C4JJXR64PW696340",
        ],
        "webhookUrl": "https://your-server.com/webhook",
    ])
    print(response)
} catch {
    print("Error: \(error)")
}
```

```csharp
using carsxe;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        CarsXE carsxe = new CarsXE("YOUR_API_KEY");

        try
        {
            var response = await carsxe.SubmitBulkRecallBatch(new Dictionary<string, object>
            {
                { "vins", new[] {
                    "1HGBH41JXMN109186",
                    "5YJSA1E26HF000001",
                    "1C4JJXR64PW696340",
                }},
                { "webhookUrl", "https://your-server.com/webhook" },
            });
            Console.WriteLine(response);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}
```

```bash {{ title: "Response (202 Accepted)" }}
{
  "success": true,
  "data": {
    "batchId": "brb_mnablbn7_wvbaqv",
    "status": "uploading",
    "totalVins": 3,
    "processedVins": 0,
    "hitCount": 0,
    "hitRate": 0,
    "createdAt": "2026-03-24T10:00:00.000Z",
    "updatedAt": "2026-03-24T10:00:00.000Z"
  },
  "message": "Batch submitted and queued for processing. Poll the status endpoint or wait for webhook notification."
}
```

</CodeGroup>

### Submit with a CSV URL

```bash
# Google Sheets sharing link (auto-converted to CSV export)
curl -X POST "https://api.carsxe.com/v1/recalls-batch/submit?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csvUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit"
  }'
```

### Submit response fields

Successful submission returns **HTTP 202** with:

<Properties>
  <Property name="success" type="boolean">
    `true` when the batch was accepted.
  </Property>
  <Property name="data.batchId" type="string">
    Unique identifier for your batch. Use this for status, results, and download.
  </Property>
  <Property name="data.status" type="string">
    `uploading` (batch being prepared), `processing` (recall check in progress), or `completed` if all VINs were satisfied from cache.
  </Property>
  <Property name="data.totalVins" type="number">
    Number of unique VINs in the batch (after deduplication).
  </Property>
  <Property name="data.processedVins" type="number">
    VINs already reflected in this response (e.g. cache hits); increases again when the batch completes.
  </Property>
  <Property name="data.hitCount" type="number">
    Count of VINs with at least one **safety** recall (aligned with `hasRecalls` on results).
  </Property>
  <Property name="data.hitRate" type="number">
    Percentage `(hitCount / processedVins) × 100` when processing is done; may be `0` on the initial 202 until completion.
  </Property>
  <Property name="data.createdAt" type="string">
    ISO 8601 timestamp when the batch was created.
  </Property>
  <Property name="data.updatedAt" type="string">
    ISO 8601 timestamp of the last job update.
  </Property>
</Properties>

## Step 2: Check status

Poll until the batch reaches a terminal state. Recommended interval: every 30–60 seconds while `uploading` or `processing`.

| Status | Description |
|---|---|
| `pending` | Reserved for future use; batches from this API typically start as `uploading` or `completed` |
| `uploading` | Batch accepted; CarsXE is preparing it for processing |
| `processing` | Recall check in progress |
| `completed` | All VINs processed; results are ready |
| `partial` | Results available, but fewer VIN rows than `totalVins` (treat as complete for retrieval) |
| `failed` | Processing failed; see `errorMessage` |

<CodeGroup title="Check batch status" tag="GET" label="/v1/recalls-batch/status">

```bash
curl -G "https://api.carsxe.com/v1/recalls-batch/status" \
  -d key=YOUR_API_KEY \
  -d batchId=brb_mnablbn7_wvbaqv
```

```js
import { CarsXE } from "carsxe-api";

const carsxe = new CarsXE("YOUR_API_KEY");

try {
  const status = await carsxe.getBulkRecallBatchStatus("brb_mnablbn7_wvbaqv");
  console.log(status.data?.status);
  console.log(`${status.data?.processedVins}/${status.data?.totalVins}`);
} catch (error) {
  console.error(error);
}
```

```python
import asyncio
from carsxe_api import CarsXE

async def main():
    async with CarsXE(api_key="YOUR_API_KEY") as carsxe:
        status = await carsxe.get_bulk_recall_batch_status("brb_mnablbn7_wvbaqv")
        print(status.data.status)
        print(f"{status.data.processed_vins}/{status.data.total_vins}")

asyncio.run(main())
```

```bash {{ title: "Response" }}
{
  "success": true,
  "data": {
    "batchId": "brb_mnablbn7_wvbaqv",
    "numericBatchId": 200426,
    "status": "completed",
    "totalVins": 3,
    "processedVins": 3,
    "hitCount": 2,
    "hitRate": 66.67,
    "createdAt": "2026-03-24T10:00:00.000Z",
    "updatedAt": "2026-03-24T10:16:43.000Z",
    "completedAt": "2026-03-24T10:16:43.000Z",
    "errorMessage": null
  }
}
```

</CodeGroup>

### Status response fields

<Properties>
  <Property name="data.batchId" type="string">
    The batch identifier.
  </Property>
  <Property name="data.numericBatchId" type="number">
    Optional numeric correlation id assigned when results are finalized.
  </Property>
  <Property name="data.status" type="string">
    Current processing status (see table above).
  </Property>
  <Property name="data.totalVins" type="number">
    Total VINs submitted in this batch.
  </Property>
  <Property name="data.processedVins" type="number">
    Number of VINs with rows in the merged result set.
  </Property>
  <Property name="data.hitCount" type="number">
    VINs with at least one **safety** recall.
  </Property>
  <Property name="data.hitRate" type="number">
    Percentage of processed VINs with a safety recall hit (0–100, two decimal places).
  </Property>
  <Property name="data.completedAt" type="string">
    ISO 8601 timestamp when processing finished (omitted while in progress).
  </Property>
  <Property name="data.errorMessage" type="string">
    Error details if status is `failed`.
  </Property>
</Properties>

## Step 3: Retrieve results

Once status is `completed` or `partial`, fetch full recall rows as JSON or download CSV.

<CodeGroup title="Get results (JSON)" tag="GET" label="/v1/recalls-batch/results">

```bash
curl -G "https://api.carsxe.com/v1/recalls-batch/results" \
  -d key=YOUR_API_KEY \
  -d batchId=brb_mnablbn7_wvbaqv
```

```js
import { CarsXE } from "carsxe-api";

const carsxe = new CarsXE("YOUR_API_KEY");

try {
  const results = await carsxe.getBulkRecallBatchResults("brb_mnablbn7_wvbaqv");

  for (const result of results.data?.results ?? []) {
    console.log(`${result.vin}: ${result.recallCount} rows`);
    if (result.hasRecalls) {
      for (const recall of result.recalls) {
        console.log(
          `  - ${recall.recallNhtsaNumber ?? recall.recallOemNumber}: ${recall.recallTitle}`,
        );
      }
    }
  }
} catch (error) {
  console.error(error);
}
```

```python
import asyncio
from carsxe_api import CarsXE

async def main():
    async with CarsXE(api_key="YOUR_API_KEY") as carsxe:
        results = await carsxe.get_bulk_recall_batch_results("brb_mnablbn7_wvbaqv")

        for result in results.data["results"]:
            print(f"{result.vin}: {result.recall_count} rows")
            if result.has_recalls:
                for recall in result.recalls:
                    rid = recall.recall_nhtsa_number or recall.recall_oem_number
                    print(f"  - {rid}: {recall.recall_title}")

asyncio.run(main())
```

```bash {{ title: "Response (illustrative)" }}
{
  "success": true,
  "data": {
    "job": {
      "batchId": "brb_mnablbn7_wvbaqv",
      "numericBatchId": 200426,
      "status": "completed",
      "totalVins": 3,
      "processedVins": 3,
      "hitCount": 2,
      "hitRate": 66.67,
      "createdAt": "2026-03-24T10:00:00.000Z",
      "completedAt": "2026-03-24T10:16:43.000Z"
    },
    "results": [
      {
        "vin": "1HGBH41JXMN109186",
        "hasRecalls": true,
        "recallCount": 1,
        "recalls": [
          {
            "recallNhtsaNumber": "20V123000",
            "vehicleMake": "Honda",
            "recallTitle": "Passenger frontal air bag inflator",
            "recallDescription": "Takata front passenger air bag inflator may rupture.",
            "recallRiskDescription": "Rupture may cause injury from metal fragments.",
            "recallRemedyDescription": "Dealers will replace the inflator, free of charge.",
            "recallType": "Safety",
            "recallStatus": "Open",
            "recallIssueDate": "2020-03-15",
            "isRemedied": false
          }
        ]
      },
      {
        "vin": "5YJSA1E26HF000001",
        "hasRecalls": false,
        "recallCount": 0,
        "recalls": []
      }
    ]
  }
}
```

</CodeGroup>

### Results shape

Each VIN has `vin`, `hasRecalls` (`true` if there is at least one **safety** recall), `recallCount`, and `recalls` — an array of **sparse** camelCase objects:

- Only **non-empty** string fields and booleans are included per recall.
- Dealer and batch metadata (`dealerName`, `dealerCode`, batch name/date fields) are **not** exposed in JSON.

Common keys when present: `recallNhtsaNumber`, `recallOemNumber`, `recallTitle`, `recallDescription`, `recallRiskDescription`, `recallRemedyDescription`, `recallType`, `recallState`, `recallStatus`, `recallIssueDate`, `severityCode`, `vehicleYear`, `vehicleMake`, `vehicleModel`, `isRemedied`.

### CSV download

`GET /v1/recalls-batch/download` with the same `key` and `batchId`. Returns `text/csv` with a `Content-Disposition` filename. SDKs also expose a download URL helper (`getBulkRecallBatchDownloadUrl` / `get_bulk_recall_batch_download_url`).

```bash
curl -G "https://api.carsxe.com/v1/recalls-batch/download" \
  -d key=YOUR_API_KEY \
  -d batchId=brb_mnablbn7_wvbaqv \
  -o recalls_brb_mnablbn7_wvbaqv.csv
```

## Webhook notifications

If you pass `webhookUrl` on submit, CarsXE sends an **HTTPS POST** with `Content-Type: application/json` when the batch reaches a terminal state (`completed` or `partial`). Redirects are not followed. Respond quickly — the request times out on CarsXE’s side after about **30 seconds**.

```json
{
  "event": "bulk_recall_batch_complete",
  "batchId": "brb_mnablbn7_wvbaqv",
  "status": "completed",
  "totalVins": 100,
  "processedVins": 100,
  "hitCount": 25,
  "hitRate": 25,
  "downloadUrl": "https://api.carsxe.com/v1/recalls-batch/download?key=…&batchId=…",
  "timestamp": "2026-03-24T10:16:43.000Z"
}
```

The `downloadUrl` includes your API key in the query string so you can fetch the CSV without assembling the URL. Your API key is **not** repeated as a separate JSON field.

## Complete example: submit, poll, and retrieve

<CodeGroup title="Full workflow">

```js
import { CarsXE } from "carsxe-api";

const carsxe = new CarsXE("YOUR_API_KEY");

const submitResponse = await carsxe.submitBulkRecallBatch({
  vins: [
    "1HGBH41JXMN109186",
    "5YJSA1E26HF000001",
    "1C4JJXR64PW696340",
  ],
});
const batchId = submitResponse.data?.batchId;
if (!batchId) throw new Error("No batchId");
console.log(`Batch submitted: ${batchId}`);

let status;
do {
  await new Promise((r) => setTimeout(r, 30_000));
  status = await carsxe.getBulkRecallBatchStatus(batchId);
  console.log(`Status: ${status.data?.status}`);
} while (
  status.data?.status === "processing" ||
  status.data?.status === "uploading"
);

if (status.data?.status === "completed" || status.data?.status === "partial") {
  const results = await carsxe.getBulkRecallBatchResults(batchId);
  console.log(`Processed: ${results.data?.job.processedVins} VINs`);
  console.log(`Safety recall hits: ${results.data?.job.hitCount}`);

  for (const result of results.data?.results ?? []) {
    if (result.hasRecalls) {
      console.log(`${result.vin}: ${result.recallCount} recall row(s)`);
    }
  }
}
```

```python
import asyncio
from carsxe_api import CarsXE

async def main():
    async with CarsXE(api_key="YOUR_API_KEY") as carsxe:
        submit = await carsxe.submit_bulk_recall_batch(
            vins=[
                "1HGBH41JXMN109186",
                "5YJSA1E26HF000001",
                "1C4JJXR64PW696340",
            ],
        )
        batch_id = submit.data.batch_id
        print(f"Batch submitted: {batch_id}")

        while True:
            await asyncio.sleep(30)
            status = await carsxe.get_bulk_recall_batch_status(batch_id)
            print(f"Status: {status.data.status}")
            if status.data.status not in ("processing", "uploading"):
                break

        if status.data.status in ("completed", "partial"):
            results = await carsxe.get_bulk_recall_batch_results(batch_id)
            job = results.data["job"]
            print(f"Processed: {job.processed_vins} VINs")
            print(f"Safety recall hits: {job.hit_count}")

            for result in results.data["results"]:
                if result.has_recalls:
                    print(f"{result.vin}: {result.recall_count} recall row(s)")

asyncio.run(main())
```

</CodeGroup>

## Errors

| Status | When it happens |
|---|---|
| `400` | Missing VINs, too many VINs (>10,000), invalid VIN, invalid/disallowed/oversized `csvUrl`, or invalid `webhookUrl` |
| `401` | Missing or invalid API key, or inactive account |
| `404` | Batch not found or you don't have access |
| `405` | Submit called with a method other than POST |
| `409` | Batch still processing (results/download only) |
| `429` | Usage limit exceeded |
| `500` | Internal storage or batch handoff error — retry later |
| `502` | Server could not fetch the CSV from `csvUrl` |

See the [Errors guide](/docs/guides/errors) for general error handling guidance.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
