> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finosu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enroll a List (Batch)

> Send a customer/application list in one request. Finosu processes the list in the background and reports each row’s outcome.

Send the list to **`POST /customers/batch`** with **`scheduleMode: "enroll"`**.
Finosu stores the upload, divides it into internal chunks, and enrolls each
application using the same cadence and validation rules as
[individual enrollment](/api-reference/endpoint/customers/origination-enrollment).
Your integration does not need to make one request per CSV row.

Use one company API key per list. That key selects the company and its configured
portfolio; body fields cannot select a different company or enable another cadence.

## Send a list

Use a stable `Idempotency-Key` header for the **whole list** and a stable
`idempotencyKey` in each row for that **enrollment operation**. These are different
keys with different purposes. Keep customer and application references consistent
with previous CSV uploads.

```bash theme={null}
curl --request POST 'https://api.finosu.com/customers/batch' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Idempotency-Key: origination-list-2026-09-24-001' \
  --header 'Content-Type: application/json' \
  --data '{
    "scheduleMode": "enroll",
    "customers": [
      {
        "idempotencyKey": "application-1001-enrollment-1",
        "id": "customer-1001",
        "loanId": "application-1001",
        "taskType": "PendingLoanNewLead",
        "firstName": "Jane",
        "lastName": "Doe",
        "birthday": "1985-06-15",
        "ssnLastFour": "1234",
        "phoneNumber": "+15125550100",
        "mobilePhone": "+15125550101",
        "timezone": "America/Chicago",
        "daysInApplied": 3
      },
      {
        "idempotencyKey": "application-2001-enrollment-1",
        "id": "customer-2001",
        "loanId": "application-2001",
        "taskType": "PendingLoanReturning",
        "oneOff": true,
        "firstName": "Alex",
        "lastName": "Smith",
        "birthday": "1987-04-12",
        "ssnLastFour": "5678",
        "phoneNumber": "+15125550102",
        "timezone": "America/Chicago"
      }
    ]
  }'
```

The example combines two modes. Each must be enabled for the authenticated
company. Unsupported modes fail for the affected rows; they do not enable new
portfolio capabilities. Returning borrowers without `oneOff` can include
`daysSincePaidOff` for the existing returning cadence.

Row fields follow the [enrollment field contract](/api-reference/endpoint/customers/origination-enrollment#fields),
plus the required `idempotencyKey`. Row-level `scheduleMode` can be omitted;
if present it must be `"enroll"`. The outer `scheduleMode` is always required.

Send `isTest` for test enrollments. The legacy `test` field is deprecated and removed on
**25 October 2026**, the same deadline as customer creation and single enrollment. Either
spelling is accepted until then; conflicting values or explicit `null` flags fail only the
affected row. Omitting both defaults to `false`.

<Warning>
  Always send the outer `scheduleMode: "enroll"`. The existing customer-create
  workflow uses this URL without a mode. Enrollment row keys without the outer
  mode are rejected to prevent accidental customer-create processing.
</Warning>

See [Batch API reference](/api-reference/endpoint/customers/batch) for the shared
request schema and playground.

## Acceptance and progress

A successful submission returns **HTTP `202`**, `Location`, and a job ID:

```json theme={null}
{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "totalCustomers": 2,
  "message": "Batch accepted. Poll Location for progress and per-row results; acceptance does not mean enrollment has completed."
}
```

Poll the `Location` URL with the same company's API key. Start with a few seconds
between polls and increase the interval for longer-running jobs.

```bash theme={null}
curl 'https://api.finosu.com/customers/batch/550e8400-e29b-41d4-a716-446655440000' \
  --header 'X-API-Key: YOUR_API_KEY'
```

The [batch status endpoint](/api-reference/origination/batch-status) returns:

* `status`: `pending`, `processing`, `completed`, `failed`, or `cancelled`.
* `processed`: successfully handled rows, including saved receipt replays and
  applications that were already scheduled.
* `failed`: rejected rows or rows that exhausted processing retries.
* `progressPercentage`: handled rows divided by the submitted count.
* `results`: outcomes ordered by the original zero-based `index`. Each includes
  `customerId`, `loanId`, `status`, `replayed`, and `retryable`. Successful rows
  include the `origination` receipt; failures include `code` and `message`.
* `failures`: the failed rows with their original `index`, customer reference,
  and message, for compatibility with existing batch clients.

Results appear as rows finish. A terminal `completed` job **can have failed rows**
when at least one row succeeded; always inspect `failed` and `results`. A job
with no successful rows ends as `failed`. A failure before the source can be read
may have empty customer/application references; its original `index` still
identifies the row in your submitted list.

A saved schedule does not mean a call has connected. Use the receipt's
`scheduledCallId` to [inspect the current call status](/api-reference/endpoint/scheduled-calls/get-by-id).
Receipts are scheduling snapshots, not current borrower eligibility.

## Retry without duplicate enrollments

* **Submission timeout or `503`:** retry the identical ordered list with the
  same header key. If accepted previously, the same job is returned with
  `Idempotency-Replayed: true`. A changed list under that key returns `409`.
  JSON object-key order is ignored; array order and submitted values must match.
* **Worker interruption:** Finosu resumes processing and skips committed row
  results. Enrollment and its result are committed together.
* **Retryable failed row:** submit that row in a new list with a **new header
  key**, retaining its original row `idempotencyKey` and payload. Replaying a
  terminal list returns the existing job; it does not restart failed rows.
* **Invalid row:** correct the data and submit a new list. A rejected validation
  does not consume an enrollment receipt. If a key has already produced a saved
  receipt, changing its enrollment payload returns a conflict.
* **Contact or application conflict:** review the reason before resubmitting.
  Enrollment cannot clear DNC or restart a finished/paused cadence.

Row keys share the individual enrollment key namespace within the company. A
row submitted through batch and the single-record endpoint with the same key
and normalized enrollment fields returns the same enrollment receipt. Use a
separate row key for each distinct enrollment operation; do not reuse it for
unrelated customers or applications.

## Limits and processing behavior

* Up to **10,000 records** per request and **10 MiB** of serialized customer
  data. Split larger lists into multiple batches with separate header keys.
* Up to **10 batch submissions per minute per company**, including submission
  retries. Honor `Retry-After` on `429` and `503`.
* Finosu processes internal chunks of **25 records** in the background. The
  single-record endpoint's 60-request/minute quota does not apply per batch row.
* Rows and chunks can finish out of order. The list is not one transaction:
  valid rows can succeed while others fail. Do not depend on list order to apply
  multiple changes to the same customer.
* Invalid envelopes return `422` before queuing. Oversized customer data returns
  `413`. Invalid enrollment fields, disabled modes and application conflicts are
  reported per row after acceptance.
* The normal scheduler still enforces contact restrictions, calling hours,
  phone rotation and the configured transfer routing. Batch intake does not
  change those settings or imply immediate calls.

Batch enrollment does not replace the funded/stop workflow or enable LMS notes
and flags. See [funded applications](/api-reference/origination/overview#funded-applications-and-stopping-outreach).
