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

# Run one model over many prompts: POST /v1/batches

> POST /v1/batches runs a Percify model over up to 50 prompts. The server drives it, so the caller can hang up, and budgetCredits is a hard ceiling checked before each run.

`POST https://api.percify.io/v3/playground/v1/batches` runs one model over many prompts. **The server drives the batch, so you can hang up**: each prompt becomes an ordinary generation, charged and refunded on failure exactly as [`POST /v1/run`](/api-reference/generations/run) is. Start it, then poll it, or receive a webhook when it settles.

This is the difference between the API and the in-app [Batch Mode](/create/batch-mode) page, which runs its loop in the browser and stops when you close the tab.

## Start a batch

```bash cURL theme={"system"}
curl -s -X POST https://api.percify.io/v3/playground/v1/batches \
  -H "Authorization: Bearer $PERCIFY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "modelId": "gpt-image-2",
    "prompts": [
      "Studio portrait of a smiling woman in a yellow sweater, plain grey background",
      "The same woman outdoors at golden hour, shallow depth of field",
      "The same woman at a desk, soft window light from the left"
    ],
    "input": { "quality": "medium", "aspect_ratio": "9:16" },
    "budgetCredits": 60,
    "concurrency": 3
  }'
```

### Body

<ResponseField name="modelId" type="string" required>
  A model that takes a text prompt. [`GET /v1/models`](/api-reference/models) lists them.
</ResponseField>

<ResponseField name="prompts" type="string[]" required>
  1 to 50 prompts. Each one becomes a single generation.
</ResponseField>

<ResponseField name="input" type="object">
  Inputs shared by every run, such as quality or aspect ratio. The prompt is filled in per item, so leave it out.
</ResponseField>

<ResponseField name="persona" type="string">
  A saved persona name or id, applied to every run in the set.
</ResponseField>

<ResponseField name="budgetCredits" type="integer">
  A hard ceiling. It is checked against what has actually been charged **before each submission**, so the batch stops itself at the ceiling rather than passing it. Omit it and the budget is the full estimate, which the response returns up front.
</ResponseField>

<ResponseField name="concurrency" type="integer">
  How many run at once, 1 to 5. Default 3.
</ResponseField>

<ResponseField name="webhook" type="string">
  A URL POSTed once when the batch settles, signed with `X-Percify-Signature`. See [Webhooks](/guides/webhooks).
</ResponseField>

A `201` means the batch started. A `400` means no prompts, more than 50, an unknown model, a model with no prompt field, a budget below the cost of one run, or not enough credits.

## Poll a batch

`GET https://api.percify.io/v3/playground/v1/batches/{id}` returns the batch state with per-item status and output URLs.

```bash cURL theme={"system"}
curl -s https://api.percify.io/v3/playground/v1/batches/$BATCH_ID \
  -H "Authorization: Bearer $PERCIFY_API_TOKEN"
```

Counts are read back from the generations themselves rather than from a running tally, so **the answer is correct even for a batch that was interrupted**, and polling a batch that lost its driver resumes the prompts it had not reached. A `404` means no batch with that id on this account.

## Stop a batch

`POST https://api.percify.io/v3/playground/v1/batches/{id}/stop` stops new prompts being submitted. **Generations already in flight still finish and are still charged**, and nothing already produced is lost.

## List recent batches

`GET https://api.percify.io/v3/playground/v1/batches` returns recent batches, newest first. Takes an optional `limit`.

## From MCP instead

The same thing is available to an MCP client as `batch_generate`, with `get_batch`, `list_batches` and `stop_batch`. See [the Percify MCP server](/mcp-server).

## FAQ

<AccordionGroup>
  <Accordion title="What happens if my process dies mid-batch?">
    Nothing is lost. The plan is written down before the first submission, and polling the batch resumes the prompts it had not reached.
  </Accordion>

  <Accordion title="How is a batch charged?">
    Per generation, at the model's normal price, exactly as a single run. A generation that fails is refunded.
  </Accordion>

  <Accordion title="Can I guarantee it will not spend more than a set amount?">
    Yes, that is what `budgetCredits` is for. It is enforced before each submission rather than reported afterwards, so the batch stops at the ceiling instead of passing it.
  </Accordion>

  <Accordion title="What is the largest batch?">
    50 prompts in one call. Start several batches for more.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Batch Mode in the app" icon="grid" href="/create/batch-mode">
    The same idea with a UI, driven by the browser.
  </Card>

  <Card title="Start a single generation" icon="play" href="/api-reference/generations/run">
    POST /v1/run, the call each batch item becomes.
  </Card>

  <Card title="Estimate a cost first" icon="calculator" href="/api-reference/generations/estimate">
    Price a run before you start it. It never charges.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Receive the result instead of polling.
  </Card>
</CardGroup>
