> ## 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.

# Start a generation: POST /v1/run

> POST /v1/run starts an image, video or audio generation on any Percify model. Send modelId and input; it returns a generation id to poll.

`POST https://api.percify.io/v3/playground/v1/run` starts one generation on a Percify model and returns right away with a generation `id` and the credits it charged. The result is not in this response: read it with [`GET /v1/generations/{id}`](/api-reference/generations/get) or receive it on a [webhook](/guides/webhooks).

```http theme={"system"}
POST https://api.percify.io/v3/playground/v1/run
Authorization: Bearer pk_live_…
Content-Type: application/json
```

## Headers

<ParamField header="Authorization" type="string" required>
  `Bearer pk_live_…`. See [Authentication](/percify/api-auth).
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Any unique string you choose, such as a UUID. If you send the same key again, Percify returns the generation it already created instead of starting and charging a second one. Keys are kept per account; the first 255 characters count.
</ParamField>

## Body

<ParamField body="modelId" type="string" required>
  A model id from [`GET /v1/models`](/api-reference/models), for example `gpt-image-2`, `zonos2` or `infinitetalk-fast`. An id that is not in the public catalog returns `400` with the list of available ids.
</ParamField>

<ParamField body="input" type="object" required>
  The model's inputs. Each model publishes its own JSON Schema as `input_schema` in `GET /v1/models/{id}`. Required fields must be present, values of `enum` fields must match the listed options, and media fields take URLs Percify can download, so use public `https` links.
</ParamField>

<ParamField body="webhook" type="string">
  An `https` URL that Percify POSTs to once when the generation succeeds or fails. It must resolve to a public address. See [Webhooks](/guides/webhooks).
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  `true` when the run started.
</ResponseField>

<ResponseField name="data.id" type="string">
  The generation id, a UUID. Keep it to poll the result.
</ResponseField>

<ResponseField name="data.modelId" type="string">
  The model id you sent.
</ResponseField>

<ResponseField name="data.status" type="string">
  `processing` for a new run. See [statuses](/api-reference/async-jobs#generation-statuses).
</ResponseField>

<ResponseField name="data.output" type="object | null">
  `null` until the run succeeds.
</ResponseField>

<ResponseField name="data.error" type="string | null">
  `null` for a new run.
</ResponseField>

<ResponseField name="data.creditsSpent" type="integer">
  Credits taken from your balance for this run. They are refunded if it fails.
</ResponseField>

<ResponseField name="data.createdAt" type="string">
  When the run started, as an ISO 8601 timestamp.
</ResponseField>

## Example

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -s -X POST https://api.percify.io/v3/playground/v1/run \
    -H "Authorization: Bearer $PERCIFY_API_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 5d0f3c1e-portrait-001" \
    -d '{
      "modelId": "gpt-image-2",
      "input": {
        "prompt": "Studio portrait of a smiling woman in a yellow sweater, plain grey background",
        "quality": "low",
        "aspect_ratio": "2:3"
      }
    }'
  ```

  ```javascript Node.js theme={"system"}
  const res = await fetch("https://api.percify.io/v3/playground/v1/run", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PERCIFY_API_TOKEN}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "5d0f3c1e-portrait-001",
    },
    body: JSON.stringify({
      modelId: "gpt-image-2",
      input: {
        prompt: "Studio portrait of a smiling woman in a yellow sweater, plain grey background",
        quality: "low",
        aspect_ratio: "2:3",
      },
    }),
  });
  const { data } = await res.json();
  console.log(data.id, data.status, data.creditsSpent);
  ```

  ```python Python theme={"system"}
  import os, requests

  res = requests.post(
      "https://api.percify.io/v3/playground/v1/run",
      headers={
          "Authorization": f"Bearer {os.environ['PERCIFY_API_TOKEN']}",
          "Idempotency-Key": "5d0f3c1e-portrait-001",
      },
      json={
          "modelId": "gpt-image-2",
          "input": {
              "prompt": "Studio portrait of a smiling woman in a yellow sweater, plain grey background",
              "quality": "low",
              "aspect_ratio": "2:3",
          },
      },
  )
  data = res.json()["data"]
  print(data["id"], data["status"], data["creditsSpent"])
  ```
</CodeGroup>

```json 201 Response theme={"system"}
{
  "success": true,
  "data": {
    "id": "3f6c2a9e-8b1d-4c57-9a0e-2d4b7f1c6e83",
    "modelId": "gpt-image-2",
    "status": "processing",
    "output": null,
    "error": null,
    "creditsSpent": 4,
    "createdAt": "2026-09-16T09:41:07.512Z"
  }
}
```

## What happens before a run is charged

Percify checks these in order and returns an error without charging when one fails:

1. The key is valid and under 60 requests a minute.
2. The model id is in the public catalog.
3. The `webhook`, when given, is a valid `https` URL on a public host, and `input` is an object.
4. If the `Idempotency-Key` was used before, the existing generation is returned here.
5. Your account is under 60 generation starts a minute.
6. Required inputs are present and `enum` values are valid.
7. For models billed by audio or video length, the media URL can be reached and read.
8. The key's **Monthly credit cap** allows the run.
9. Text prompts and image inputs pass content moderation.
10. Your balance covers the price.

If the model cannot start the job after the charge, the credits are refunded in the same request and you get a `503` (retry later) or `400`.

## Errors

| Status | Message starts with                                                                             | What to do                                                                                                                                 |
| ------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `Unknown model '…'. Available models: …`                                                        | Use an id from `GET /v1/models`.                                                                                                           |
| `400`  | `input must be an object`                                                                       | Send `input` as a JSON object.                                                                                                             |
| `400`  | `Missing required field: …`                                                                     | Add the field named in the message.                                                                                                        |
| `400`  | `<field> must be one of: …`                                                                     | Use one of the listed values.                                                                                                              |
| `400`  | `webhook must use https` and other `webhook …` messages                                         | Use a public `https` URL.                                                                                                                  |
| `400`  | `audio URL is not reachable` or `Unsupported media format`                                      | Host the file on a public `https` URL as `.mp3`, `.wav` or `.mp4`.                                                                         |
| `400`  | `Prompt rejected by content moderation` or `Image rejected by content moderation`               | Change the prompt or image. Nothing was charged.                                                                                           |
| `400`  | `Not enough credits`                                                                            | The message says how many credits the run needs and how many you have. Top up at [app.percify.io/billing](https://app.percify.io/billing). |
| `429`  | `API key rate limit exceeded`, `Too many generations` or `API key monthly credit limit reached` | Wait about a minute and retry, or use a key with a higher cap.                                                                             |
| `503`  | `The model is at capacity`                                                                      | Credits were refunded. Retry after a short wait.                                                                                           |

The full list is on [Errors and rate limits](/api-reference/errors-and-limits).

## Tips

* Price a run first with [`POST /v1/estimate`](/api-reference/generations/estimate). It takes the same body and never charges.
* A single URL sent to a field that expects a list, such as `input_images`, is treated as a one-item list.
* SVG image links are converted to PNG before the model sees them.
* Send an `Idempotency-Key` whenever your code retries after a timeout. Without it, a retry starts and charges a second run.

## Related

<CardGroup cols={2}>
  <Card title="Get a generation" href="/api-reference/generations/get">
    Poll or long-poll until the output is ready.
  </Card>

  <Card title="List models" href="/api-reference/models">
    Model ids, input schemas and pricing types.
  </Card>

  <Card title="Estimate cost" href="/api-reference/generations/estimate">
    The exact credit price, with no charge.
  </Card>

  <Card title="Webhooks" href="/guides/webhooks">
    Get the result pushed to your server.
  </Card>
</CardGroup>
