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

# Generate images

> Render one image, or many, from an image template in a single call

Starts rendering one image per row in `rows`, all from the same image template. Returns immediately with a `bulk_job_id` — track progress via [Get image batch status](/docs/api-reference/image-status/get-image-bulk-job-status) or a [webhook](/docs/guides/webhooks).

<Tip>
  Discovery is shared with video — list image templates and read their schema through [List templates](/docs/api-reference/templates/list-templates) and [Get template schema](/docs/api-reference/templates/get-template-schema) with `?type=IMAGE_TEMPLATE`. Only generation and results have their own paths: `/image-templates/{template_id}/bulk-generate/` and `/image-bulk-jobs/`. The request body is identical to [Generate videos](/docs/api-reference/generation/bulk-generate-videos) — for a single image, call it with one row in `rows`.
</Tip>

<ParamField path="template_id" type="string" required />

<ParamField header="Authorization" type="string" required>
  `Bearer {api_key}`
</ParamField>

## Body

<ParamField body="rows" type="object[]" required>
  A non-empty list of `{variable_name: value}` objects, one per image, up to 20,000 per request. Keys should match [Get template schema](/docs/api-reference/templates/get-template-schema)'s variable names — a row missing a key just leaves that placeholder blank in that row's image.

  `external_id` is a reserved key you can include in any row — your own identifier for that row, echoed back unchanged in the [CSV export](/docs/api-reference/image-status/export-image-bulk-job-csv) and usable with [Get single image status](/docs/api-reference/image-status/get-image-status-by-external-id) to check a row before the batch finishes. Not treated as a template variable.
</ParamField>

<ParamField body="batch_name" type="string">
  Optional label for the batch, shown back in status responses.
</ParamField>

<Info>
  `rows` — not a CSV file or URL — is the only way to submit a batch through this API. Serialize your data directly into the request body.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://dev-backend-ai.truefans.in/api/external/v1/image-templates/{template_id}/bulk-generate/" \
    -H "Authorization: Bearer $TRUEFAN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "rows": [
        {"customer_name": "Anmol", "city": "Mumbai", "external_id": "usr_9182"},
        {"customer_name": "Rahul", "city": "Pune", "external_id": "usr_4471"}
      ],
      "batch_name": "Diwali greetings"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 202 Accepted theme={null}
  {
    "bulk_job_id": "8c1f2e3a-...",
    "status": "pending",
    "row_count": 2,
    "estimated_credits": "0.00724"
  }
  ```

  ```json 400 Bad Request theme={null}
  { "error": "rows is required and must be a non-empty list of objects" }
  ```

  ```json 400 Bad Request — too many rows theme={null}
  { "error": "rows exceeds the maximum of 20000 per batch", "max_rows": 20000 }
  ```

  ```json 401 Unauthorized theme={null}
  { "error": "API key required" }
  ```

  ```json 402 Payment Required theme={null}
  { "error": "insufficient_credits", "required": "0.00724", "available": "0.00100" }
  ```

  ```json 404 Not Found theme={null}
  { "error": "Image template not found" }
  ```

  ```json 422 Unprocessable Entity theme={null}
  { "error": "Image template has no layers or base image configured" }
  ```

  ```json 500 Internal Server Error theme={null}
  { "error": "Failed to start batch" }
  ```

  ```json 503 Service Unavailable theme={null}
  { "error": "Billing service unavailable" }
  ```
</ResponseExample>

## Errors

| Status | Meaning                                                             | What to do                                                                        |
| ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `400`  | `rows` missing, empty, not a list of objects, or over 20,000 rows   | Fix the request body, or split into multiple batches                              |
| `401`  | Missing or invalid API key                                          | Check the `Authorization` header                                                  |
| `402`  | Insufficient credits                                                | Top up the workspace's account                                                    |
| `404`  | Image template not found in this workspace                          | Confirm the `template_id` and that the key belongs to the right workspace         |
| `422`  | Image template has no base image or layers configured               | Fix the template in the editor                                                    |
| `500`  | Failed to prepare or dispatch the batch after credits were reserved | Credits are reversed on a best-effort basis — verify your balance before retrying |
| `503`  | Billing service temporarily unavailable                             | Safe to retry                                                                     |

## Response fields

<ResponseField name="bulk_job_id" type="string">
  Use with [Get image batch status](/docs/api-reference/image-status/get-image-bulk-job-status) to track the whole batch.
</ResponseField>

<ResponseField name="row_count" type="integer">
  Number of images this batch will render — equal to `rows.length`.
</ResponseField>

<ResponseField name="estimated_credits" type="string">
  `0.00181 × variable_count × row_count`, deducted upfront. See [How images are priced](#how-images-are-priced) below.
</ResponseField>

## How images are priced

Images are priced **per variable**, not per render — a flat `0.00181` credits for every variable the template declares, charged on every row.

```
per_row  = 0.00181 × variable_count
estimated_credits = per_row × row_count
```

`variable_count` is the number of variables the **template** declares — the same set of columns [Get template schema](/docs/api-reference/templates/get-template-schema) returns — not the number of keys a given row happens to fill. Every row in a batch is therefore priced identically, and the count is frozen from the template as it existed when you submitted, so editing the template mid-batch cannot change what you are charged.

<Note>
  This is a different model from video, which charges the template's own `estimated_credits` per row. An image batch is typically orders of magnitude cheaper — a 4-variable template over 500 rows costs `0.00181 × 4 × 500 = 3.62` credits.
</Note>

## Getting the results

The status endpoints are shared with video and need no change — a batch of images reports through exactly the same [Get image batch status](/docs/api-reference/image-status/get-image-bulk-job-status), [Get single image status](/docs/api-reference/image-status/get-image-status-by-external-id) and [CSV export](/docs/api-reference/image-status/export-image-bulk-job-csv) you already use.

One difference in the payload: each row carries `output_type: "image"`, and the finished asset lands in **`result_image_url`**. Image rows do not carry `result_video_url` at all — the field is absent, not `null`.

```json Row from an image batch theme={null}
{
  "generation_id": "f339beb9-305d-4a01-89ef-669b7ec70de3",
  "row_index": 0,
  "external_id": "usr_9182",
  "output_type": "image",
  "status": "completed",
  "result_image_url": "https://assets-studio.truefans.in/editor/.../a.png"
}
```

<Warning>
  If you already parse batch results for video, branch on `output_type` and read `result_image_url` on `image` rows. Code that reaches for `result_video_url` unconditionally will find the key missing rather than empty.
</Warning>

In the [CSV export](/docs/api-reference/image-status/export-image-bulk-job-csv), an image batch's final column is `result_image_url` in place of `result_video_url` — the rest of the header (`external_id`, `generation_id`, then the batch's variable names) is unchanged.
