---
title: Batches
description: Run up to 500 applicants through one cascade in a single request, track per-row status, cancel, resume or retry, and export the results.
canonical_url: "https://cascades.crscreditapi.com/docs/concepts/batches"
doc_version: "1.0.0"
last_updated: "2026-10-09T18:04:24Z"
source: docs/concepts/batches.md
---

# Batches

A **batch** runs one cascade against many applicants. You submit the rows once; Cascades pre-creates a child execution per row in `queued` state and works through them in the background. Track progress on the batch, drill into rows with the child-execution list, and export everything when it is done.

## How do I create a batch?

`POST /workflows/{id}/batches` with a `rows` array — **up to 500 rows per request** — and an `Idempotency-Key`. Each row carries its own `external_reference` and `applicant`:

```json
{
  "rows": [
    { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone" } },
    { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park" } }
  ],
  "execution_mode": "test",
  "external_reference": "rentvine-batch-001"
}
```

The response is `202 Accepted` with the batch. More than 500 rows is `422 validation_failed` (`rows: max 500 rows per batch`). The idempotency key is compared against a hash of the rows: the same key with the same rows returns the original batch, the same key with different rows returns `409 idempotency_conflict`.

Batches need an **active** cascade, even in test mode (see the next section). `execution_mode: live` needs production access (`403 live_mode_forbidden`). Needs `executions:write`.

## Why does my batch return `409 workflow_inactive`?

The cascade is a draft (`active: false`). A draft refuses every batch create with `409 workflow_inactive`, in test mode too, before the rows are checked. A single trigger is different: `POST /workflows/{id}/trigger` with `execution_mode: test` runs on a draft and answers `202`.

A sandbox account (not yet approved for production) cannot activate a cascade, so its cascades stay drafts and every batch create returns this `409`. To run a batch:

1. Get the account approved for production through the activation form at [/activate](/activate).
2. Activate the cascade: turn on its Status toggle on the Cascades list in the app, or `PATCH /workflows/{id}` with `active: true` from a key made at [/api-keys](/api-keys). Docs keys cannot activate a cascade (`403 activation_forbidden`).
3. Create the batch. Test mode still works on an active cascade, so you can try it before you run live.

Until then, run single test-mode triggers on the draft to check the cascade one applicant at a time.

## What are the batch statuses?

| Status | Meaning |
|---|---|
| `pending` | Accepted, dispatch not started |
| `running` | Rows are being processed |
| `interrupted` | Processing stopped before every row finished (worker restart, streaming disconnect) — resumable |
| `completed` | Every row reached a terminal status, including rows that failed (count them with `failed_count`) |
| `cancelled` | Stopped by `POST /batches/{id}/cancel` |
| `failed` | The batch itself stopped on an unexpected error. Rare; `filter[status]` does not accept it |

`total_count`, `completed_count` and `failed_count` on the batch give you a progress bar without listing rows.

## How do I follow the rows?

`GET /batches/{id}/executions` lists child executions in `row_index` order with each row's `status`, `retry_count`, `external_reference`, `needs_human_review` and `review_status`. Filter with `filter[status]=failed,skipped`. Each child is a normal execution: `GET /executions/{id}` returns its detail and `step_outputs`, and it emits its own `execution.completed` / `execution.failed` webhook with `batch_execution_id` set — so integrations that process batches as a whole can ignore per-row events and watch the batch instead.

## Cancel, resume, retry

- `POST /batches/{id}/cancel` halts dispatch and marks queued and in-flight rows `cancelled`. Only `pending`, `running` and `interrupted` batches can be cancelled (`409 batch_not_cancellable`).
- `POST /batches/{id}/resume` re-queues the remaining rows of an `interrupted` batch (`409 batch_not_resumable` otherwise). The cascade must still be active and live batches still need production access.
- `POST /batches/{id}/retry_failed` re-queues every `failed` row under the per-row retry cap and every `skipped` row, clearing their stale terminal state (`409 batch_not_retryable` when there is nothing to retry).

All three answer `202` with the batch. Resume and retry also answer `409 workflow_inactive` when the cascade was set back to a draft after the batch was created.

## How do I export results?

| Endpoint | Returns |
|---|---|
| `GET /batches/{id}/exports.csv` | One row per child execution: identity, status, decision, headline report fields |
| `GET /batches/{id}/exports.zip` | Error report, per-product CSVs, per-row JSON, and the report PDFs |
| `GET /batches/{id}/adverse_action_notices.zip` | Every applicant's adverse-action notice PDFs (`404 not_found` if none exist yet) |

Add `?include=all` to the CSV or ZIP to include re-evaluation revisions rather than only the latest state per applicant. All three are gated behind `executions:read:full` because the contents are raw applicant data.

## Related

- [Executions](/docs/concepts/executions) — the per-row lifecycle
- [Idempotency](/docs/concepts/idempotency)
- [`POST /workflows/{id}/batches`](/docs/api/batches/create-batch)

## Sitemap

See the full [sitemap](https://cascades.crscreditapi.com/sitemap.md) for all pages.
