The Recalls Batch API allows you to submit up to 10,000 VINs in a single request for bulk recall checking. Unlike the standard [Vehicle Recalls API](/docs/v1/vehicle-recalls) which checks one VIN at a time, the batch API is designed for high-volume operations like fleet management, dealership inventory scans, and wholesale auction processing.

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

---

## How It Works

1. **Submit** — POST your VINs (JSON array, inline CSV, or HTTPS URL to a CSV). You get a `batchId` immediately. The HTTP status is **202 Accepted** when the batch is accepted.
2. **Poll** — Call the status endpoint with `batchId` until status is `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:** VINs that were checked recently may be served from cache. If **every** VIN in the batch is cached, the submit response can already show `status: "completed"` with full `processedVins` and 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.

---

## Step 1: Submit a Batch

<Row>
  <Col>
    Send a POST request with VINs to start a batch recall check. You can provide VINs in three ways — use whichever is most convenient, or even combine them:

    ### Input methods (provide at least one)

    <Properties>
      <Property name="vins" type="string[]">
        JSON array of 17-character VIN strings.
      </Property>
      <Property name="csv" type="string">
        Inline CSV text containing VINs (one per line, or a single `vin` column).
      </Property>
      <Property name="csvUrl" type="string">
        HTTPS URL to a CSV file containing VINs. Supported hosts: Google Sheets, Google Cloud Storage, AWS S3, Dropbox, Azure Blob, DigitalOcean Spaces, and Box. Google Sheets sharing links and Dropbox sharing links are auto-converted to direct download format. Max file size: 5 MB.
      </Property>
    </Properties>

    <Note>
      You can combine input methods — for example, pass some VINs in `vins` and a `csvUrl` for the rest. All VINs are merged and deduplicated before processing. The combined total must not exceed 10,000.
    </Note>

    ### Required query parameter

    <Properties>
      <Property name="key" type="string">
        Your CarsXE API key (query parameter). Official SDKs may send the same key in the `x-api-key` header instead.
      </Property>
    </Properties>

    ### Optional attributes

    <Properties>
      <Property name="webhookUrl" type="string">
        HTTPS URL for a **customer** webhook: CarsXE POSTs a JSON payload when the batch finishes (see [Webhook notifications](#webhook-notifications)). Must be a valid HTTPS URL allowed by CarsXE validation rules.
      </Property>
    </Properties>

    ---

    ### Submit response

    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>

  </Col>
  <Col sticky>
    <CodeGroup title="Request — JSON array" tag="POST" label="/v1/recalls-batch/submit">
      ```bash
      curl -X POST "https://api.carsxe.com/v1/recalls-batch/submit?key=CARSXE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "vins": [
            "1HGBH41JXMN109186",
            "5YJSA1E26HF000001",
            "1C4JJXR64PW696340"
          ],
          "webhookUrl": "https://your-server.com/webhook"
        }'
      ```

      ```js
      import { CarsXE } from "@carsxe/sdk";

      const carsxe = new CarsXE({ apiKey: "CARSXE_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 import CarsXE

      async def main():
          async with CarsXE(api_key="CARSXE_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())
      ```
    </CodeGroup>

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

      # AWS S3 signed URL
      curl -X POST "https://api.carsxe.com/v1/recalls-batch/submit?key=CARSXE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "csvUrl": "https://my-bucket.s3.amazonaws.com/vins.csv?X-Amz-Signature=..."
        }'

      # Dropbox sharing link (auto-converted to direct download)
      curl -X POST "https://api.carsxe.com/v1/recalls-batch/submit?key=CARSXE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "csvUrl": "https://www.dropbox.com/s/abc123/vins.csv"
        }'
      ```

      ```js
      import { CarsXE } from "@carsxe/sdk";

      const carsxe = new CarsXE({ apiKey: "CARSXE_API_KEY" });

      try {
        // Paste a Google Sheets sharing link directly
        const response = await carsxe.submitBulkRecallBatch({
          csvUrl: "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
        });
        console.log(response.data?.batchId);
      } catch (error) {
        console.error(error);
      }
      ```

      ```python
      import asyncio
      from carsxe import CarsXE

      async def main():
          async with CarsXE(api_key="CARSXE_API_KEY") as carsxe:
              response = await carsxe.submit_bulk_recall_batch(
                  csv_url="https://docs.google.com/spreadsheets/d/SHEET_ID/export?format=csv",
              )
              print(response.data.batch_id)

      asyncio.run(main())
      ```
    </CodeGroup>

    <CodeGroup title="Response (202 Accepted)">
      ```json showLineNumbers
      {
        "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>

  </Col>
</Row>

---

## Step 2: Check Status

<Row>
  <Col>
    Poll the status endpoint to check if your batch has finished processing.

    ### Required attributes

    <Properties>
      <Property name="key" type="string">
        Your CarsXE API key.
      </Property>
      <Property name="batchId" type="string">
        The batch ID returned from the submit endpoint.
      </Property>
    </Properties>

    ---

    ### Status values

    | Status | Description |
    | :--- | :--- |
    | `pending` | Reserved for future use; batches created through this API typically start as `uploading` or `completed`. |
    | `uploading` | Batch accepted; CarsXE is preparing the batch for processing. |
    | `processing` | Recall check in progress. Poll every 30–60 seconds. |
    | `completed` | All VINs processed. Results are ready. |
    | `partial` | Results available, but fewer VIN rows were returned than `totalVins` (treat as complete for retrieval). |
    | `failed` | Processing failed. See `errorMessage`. |

    ---

    ### Status response attributes

    <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 (`undefined` / omitted while in progress).
      </Property>
      <Property name="data.errorMessage" type="string">
        Error details if status is `failed`.
      </Property>
    </Properties>

  </Col>
  <Col sticky>
    <CodeGroup title="Request" tag="GET" label="/v1/recalls-batch/status">
      ```bash
      curl -G "https://api.carsxe.com/v1/recalls-batch/status" \
        -d key=CARSXE_API_KEY \
        -d batchId=brb_mnablbn7_wvbaqv
      ```

      ```js
      import { CarsXE } from "@carsxe/sdk";

      const carsxe = new CarsXE({ apiKey: "CARSXE_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 import CarsXE

      async def main():
          async with CarsXE(api_key="CARSXE_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())
      ```
    </CodeGroup>

    <CodeGroup title="Response">
      ```json showLineNumbers
      {
        "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>

  </Col>
</Row>

---

## Step 3: Retrieve Results

<Row>
  <Col>
    Once the batch status is `completed` or `partial`, fetch full recall rows as JSON or download CSV.

    ### Required attributes

    <Properties>
      <Property name="key" type="string">
        Your CarsXE API key.
      </Property>
      <Property name="batchId" type="string">
        The batch ID returned from the submit endpoint.
      </Property>
    </Properties>

    ---

    ### Results shape

    Each VIN has `vin`, `hasRecalls` (true if there is at least one **safety** recall), `recallCount`, and `recalls` — an array of **sparse** objects in **camelCase**, mirroring CarsXE’s bulk recall row layout:

    - 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 include (when present): `recallNhtsaNumber`, `recallOemNumber`, `recallTitle`, `recallDescription`, `recallRiskDescription`, `recallRemedyDescription`, `recallType`, `recallState`, `recallStatus`, `recallIssueDate`, `recallRefreshDate`, `severityCode`, `vehicleYear`, `vehicleMake`, `vehicleModel`, `isRemedied`, optional `field1`–`field10`, etc. See the `@carsxe/shared` type `BulkRecallBatchRow` for the full schema; the API returns the trimmed `BulkRecallBatchRowApi` projection.

    ---

    ### CSV download

    GET `/v1/recalls-batch/download` with the same `key` and `batchId`. Returns `text/csv` with a `Content-Disposition` filename. You can also build the URL with `getBulkRecallBatchDownloadUrl` (JS) or `get_bulk_recall_batch_download_url` (Python).

  </Col>
  <Col sticky>
    <CodeGroup title="Request — JSON" tag="GET" label="/v1/recalls-batch/results">
      ```bash
      curl -G "https://api.carsxe.com/v1/recalls-batch/results" \
        -d key=CARSXE_API_KEY \
        -d batchId=brb_mnablbn7_wvbaqv
      ```

      ```js
      import { CarsXE } from "@carsxe/sdk";

      const carsxe = new CarsXE({ apiKey: "CARSXE_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 import CarsXE

      async def main():
          async with CarsXE(api_key="CARSXE_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())
      ```
    </CodeGroup>

    <CodeGroup title="Request — CSV download" tag="GET" label="/v1/recalls-batch/download">
      ```bash
      curl -G "https://api.carsxe.com/v1/recalls-batch/download" \
        -d key=CARSXE_API_KEY \
        -d batchId=brb_mnablbn7_wvbaqv \
        -o recalls_brb_mnablbn7_wvbaqv.csv
      ```
    </CodeGroup>

    <CodeGroup title="Response — JSON (illustrative)">
      ```json showLineNumbers
      {
        "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>

  </Col>
</Row>

---

## 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. The request times out on CarsXE’s side after about **30 seconds** — respond quickly on your server.

<CodeGroup>
```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"
}
```
</CodeGroup>

The `downloadUrl` includes your API key in the query string so you can fetch the CSV without assembling the URL yourself. 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/sdk";

  const carsxe = new CarsXE({ apiKey: "CARSXE_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 import CarsXE

  async def main():
      async with CarsXE(api_key="CARSXE_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>

---

## Error responses

| Status | Error | Description |
| :--- | :--- | :--- |
| 400 | `BULK_RECALL_BATCH_MISSING_VINS` | No VINs provided. Supply `vins`, `csv`, or `csvUrl`. |
| 400 | `BULK_RECALL_BATCH_TOO_MANY_VINS` | Combined VIN count exceeds 10,000. |
| 400 | `BULK_RECALL_BATCH_INVALID_VIN` | One or more VINs are not 17 valid characters. |
| 400 | `BULK_RECALL_BATCH_CSV_URL_INVALID` | The `csvUrl` is not a valid HTTPS URL. |
| 400 | `BULK_RECALL_BATCH_CSV_URL_HOST_NOT_ALLOWED` | The `csvUrl` host is not allowed. |
| 400 | `BULK_RECALL_BATCH_CSV_URL_TOO_LARGE` | The CSV at `csvUrl` exceeds the size limit (5 MB). |
| 400 | `BULK_RECALL_BATCH_CSV_URL_NOT_TEXT` | The URL did not return a plain text CSV. |
| 400 | `BULK_RECALL_BATCH_CSV_URL_HTTP_ERROR` | The storage server returned an HTTP error (e.g. expired signed URL). |
| 400 | `BULK_RECALL_BATCH_CSV_URL_REDIRECT_ERROR` | Too many redirects or an invalid redirect when fetching `csvUrl`. |
| 400 | `BULK_RECALL_BATCH_CSV_URL_TIMEOUT` | Downloading the CSV from `csvUrl` timed out. |
| 400 | `BULK_RECALL_BATCH_INVALID_WEBHOOK_URL` | The `webhookUrl` is not a valid allowed HTTPS URL. |
| 401 | `MISSING_API_KEY` | No API key provided. |
| 401 | `USER_NOT_FOUND` | Invalid API key. |
| 401 | `USER_NOT_ACTIVE` | Account is not active. |
| 404 | `BULK_RECALL_BATCH_NOT_FOUND` | Batch not found or you don't have access. |
| 405 | `ONLY_POST_ALLOWED` | Submit endpoint requires POST. |
| 409 | `BULK_RECALL_BATCH_NOT_COMPLETE` | Batch still processing (results/download only). |
| 429 | _(usage message)_ | Plan or usage limit exceeded (when usage metering applies). |
| 500 | `BULK_RECALL_BATCH_STORAGE_ERROR` | Internal storage or batch handoff error. Retry later. |
| 502 | `BULK_RECALL_BATCH_CSV_URL_FETCH_FAILED` | Server could not fetch the CSV from `csvUrl`. |

---

<FAQ faqs={[
  { question: "How many VINs can I submit in one batch?",
  answer: "You can submit up to 10,000 VINs per batch. Duplicate VINs are automatically removed."},

  {question: "What are the different ways to submit VINs?",
  answer: "You can pass VINs in three ways: as a JSON array in the 'vins' field, as inline CSV text in the 'csv' field, or as an HTTPS URL to a CSV file in the 'csvUrl' field. You can combine multiple methods in one request — all VINs are merged and deduplicated."},

  {question: "What URL formats work for csvUrl?",
  answer: "The URL must use HTTPS and point to one of these supported hosts: Google Cloud Storage (storage.googleapis.com), Google Sheets (docs.google.com — sharing links are auto-converted to CSV export), Google Drive (drive.google.com), AWS S3 (*.s3.amazonaws.com), Dropbox (www.dropbox.com — sharing links are auto-converted to direct download), Azure Blob (*.blob.core.windows.net), DigitalOcean Spaces (*.digitaloceanspaces.com), or Box (dl.boxcloud.com). The file must be under 5 MB. You can paste a Google Sheets sharing URL or a Dropbox sharing link directly — the server auto-converts them to the correct download format. Other hosts will return an error."},

  {question: "How long does processing take?",
  answer: "Typically 30-60 minutes when VINs need full batch processing, depending on batch size and load. Fully cached batches can return completed immediately on submit."},

  {question: "Can I submit multiple batches at the same time?",
  answer: "Yes. Each batch has its own batchId."},

  {question: "What happens if some VINs can't be checked?",
  answer: "You may see status 'partial' with fewer processed rows than totalVins. Retrieve results for the rows that returned."},

  {question: "What do hitCount and hasRecalls mean?",
  answer: "Both reflect at least one open safety-type recall row for that VIN in the bulk result set."},

  {question: "Can I download results as CSV instead of JSON?",
  answer: "Yes. Use GET /v1/recalls-batch/download with key and batchId, or use the downloadUrl from your completion webhook."},

  {question: "Do I need to poll, or can I use webhooks?",
  answer: "Either. Provide webhookUrl on submit to receive bulk_recall_batch_complete with a downloadUrl when the job finishes."},

  {question: "What's the difference between this and the single Vehicle Recalls API?",
  answer: "The single Recalls API checks one VIN synchronously. The batch API accepts up to 10,000 VINs asynchronously for fleet and inventory workflows."},

  {question: "Does this API require authentication?",
  answer: "Yes. Pass your CarsXE API key as the 'key' query parameter on direct HTTP calls, or use an official SDK (which typically sends x-api-key)."}
]} hidePadding />
