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

# Create a batch

> Create an asynchronous batch job from a JSONL input file.

`POST /api/v1/forward/batches`

Forward validates the input file and JSONL line format, then schedules execution automatically inside the off-peak window. When the batch finishes, `output.jsonl` and `error.jsonl` are produced for download.

## Headers

| Header            | Required | Description                                   |
| ----------------- | -------- | --------------------------------------------- |
| `Authorization`   | Yes      | `Bearer <PAT>`                                |
| `Content-Type`    | Yes      | `application/json`                            |
| `Idempotency-Key` | No       | Optional idempotency key for unsafe requests. |

## Prerequisite: upload the input file

Creating a batch requires a JSONL input file uploaded through the CAS Files API to obtain a `file_id`.

**JSONL format** — one JSON object per line, one line per independent task:

```jsonl theme={null}
{"custom_id": "task-001", "template_id": "tmpl_example001", "identity_id": "idn_example001", "body": {"input": "hello"}}
{"custom_id": "task-002", "template_id": "tmpl_example001", "identity_id": "idn_example001", "body": {"input": "write me a short poem about summer"}}
```

| Field         | Type   | Required | Description                                                                                                 |
| ------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `custom_id`   | string | Yes      | Caller-defined identifier. Must be unique within a single batch and is used to map result lines.            |
| `template_id` | string | Yes      | Forward Template ID that drives execution.                                                                  |
| `identity_id` | string | Yes      | Forward Identity ID that provides the execution identity.                                                   |
| `body`        | object | Yes      | Request body passed to Session. Its shape depends on the Template. Common shape: `{"input": "user input"}`. |

**Upload the file:**

```bash theme={null}
curl -X POST 'https://api.qoder.com/api/v1/cloud/files' \
  -H "Authorization: Bearer $QODER_PAT" \
  -F "file=@batch_input.jsonl" \
  -F "purpose=session_resource"
```

<Note>
  `purpose` must be `session_resource`. Otherwise downloads may fail later.
</Note>

The returned `id` is the `input_file_id` for Create Batch.

## Body parameters

| Parameter           | Type   | Required | Description                                                                                                                                             |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input_file_id`     | string | Yes      | JSONL file ID uploaded via the Files API.                                                                                                               |
| `completion_window` | string | Yes      | Completion window: `24h`, `48h`, or `72h`. The batch moves to `expired` when the window elapses.                                                        |
| `metadata`          | object | No       | Caller-supplied metadata. Up to 16 keys, any JSON value; the serialized object must be ≤ 2KB, each key ≤ 64 characters, and no NUL (U+0000) is allowed. |

## Example request

```bash theme={null}
curl -s -X POST 'https://api.qoder.com/api/v1/forward/batches' \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: idem_batch_001" \
  -d '{
  "input_file_id": "file_input001",
  "completion_window": "24h",
  "metadata": {
    "source": "data_pipeline",
    "job_id": "12345"
  }
}'
```

## Example response

**HTTP 200 OK**

```json theme={null}
{
  "id": "batch_example001",
  "object": "batch",
  "status": "validating",
  "input_file_id": "file_input001",
  "completion_window": "24h",
  "created_at": "2026-07-07T07:25:01Z",
  "expires_at": "2026-07-08T07:25:01Z",
  "request_counts": {
    "total": 0,
    "pending": 0,
    "running": 0,
    "completed": 0,
    "failed": 0,
    "cancelled": 0,
    "expired": 0
  },
  "usage": null
}
```

A newly created batch starts in `validating` and moves to `queued` once validation passes. `output_file_id` and `error_file_id` only appear after the batch reaches a terminal state and are omitted from the create response.

## Response fields

| Field               | Type        | Description                                          |
| ------------------- | ----------- | ---------------------------------------------------- |
| `id`                | string      | Batch ID with prefix `batch_`.                       |
| `object`            | string      | Always `batch`.                                      |
| `status`            | string      | Batch status. See the status table below.            |
| `input_file_id`     | string      | Input JSONL file ID.                                 |
| `completion_window` | string      | Completion window: `24h`, `48h`, or `72h`.           |
| `created_at`        | string      | Creation time, RFC 3339.                             |
| `expires_at`        | string      | Expiration time, `created_at` + `completion_window`. |
| `request_counts`    | object      | Aggregate task counters.                             |
| `usage`             | object/null | Reserved field, always `null` in v1.                 |
| `metadata`          | object      | Caller-supplied metadata.                            |

### Batch status

| Status       | Description                                                    | Type         |
| ------------ | -------------------------------------------------------------- | ------------ |
| `validating` | Validating the input file and JSONL lines.                     | Non-terminal |
| `queued`     | Validated. Waiting for the off-peak window.                    | Non-terminal |
| `processing` | Executing tasks.                                               | Non-terminal |
| `cancelling` | Cancellation in progress. Waiting for running tasks to finish. | Non-terminal |
| `expiring`   | Expiration in progress. Waiting for running tasks to finish.   | Non-terminal |
| `finalizing` | Generating output files.                                       | Non-terminal |
| `completed`  | All tasks executed. Output files generated.                    | Terminal     |
| `failed`     | Input validation failed or unrecoverable system error.         | Terminal     |
| `cancelled`  | Cancelled by the user.                                         | Terminal     |
| `expired`    | The `completion_window` elapsed.                               | Terminal     |

### Request counts

| Field       | Type    | Description                                                   |
| ----------- | ------- | ------------------------------------------------------------- |
| `total`     | integer | Total lines, including lines that failed validation.          |
| `pending`   | integer | Lines waiting to run.                                         |
| `running`   | integer | Lines currently executing.                                    |
| `completed` | integer | Lines that finished successfully.                             |
| `failed`    | integer | Lines that failed permanently, including validation failures. |
| `cancelled` | integer | Lines terminated by cancellation.                             |
| `expired`   | integer | Lines terminated by expiration.                               |

The invariant `total = pending + running + completed + failed + cancelled + expired` always holds.

## Validation rules

Create Batch rejects the following errors upfront rather than enqueuing:

* JSON parse failure → line marked `failed`
* Missing required field → line marked `failed`
* Duplicate `custom_id` → line marked `failed`
* Tool permission policy set to `always_ask` or `always_deny` → line marked `failed` (unattended execution disallows these)

Validation failures on individual lines do not block other lines; they are written together to `error.jsonl`.

## Error codes

| HTTP | Type                    | Code                      | Trigger                                                                        |
| ---- | ----------------------- | ------------------------- | ------------------------------------------------------------------------------ |
| 400  | `invalid_request_error` | `invalid_request`         | `input_file_id` missing, invalid `completion_window`, or `metadata` too large. |
| 429  | `rate_limit_error`      | `rate_limit_exceeded`     | The user has reached the maximum number of unfinished batches.                 |
| 401  | `authentication_error`  | `authentication_required` | PAT is invalid or expired.                                                     |

## Notes

* A single batch supports up to 10,000 JSONL lines.
* Global concurrency is capped at 50 tasks.
* Scheduling runs inside the off-peak window (default 22:00–08:00, configurable server-side).
* After creating a batch, the client should poll `GET /api/v1/forward/batches/{batch_id}` until a terminal state is reached.

## Related

<CardGroup cols={2}>
  <Card title="Get a batch" icon="file-text" href="/cloud-agents/api/forward/batches/get" />

  <Card title="Cancel a batch" icon="xmark" href="/cloud-agents/api/forward/batches/cancel" />
</CardGroup>
