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

# Percify API async jobs: statuses, polling and timeouts

> Every Percify API generation runs asynchronously. Learn the statuses, how to long-poll with ?wait, when jobs time out and how refunds work.

Every Percify API job is asynchronous: the call that starts it returns an id at once, and the file arrives later. Read the result by polling with `?wait=45`, which holds the request on Percify's side until the job ends, or by passing a `webhook` URL so Percify posts the result to you. Failed and timed-out jobs refund their credits automatically.

## Generation statuses

These apply to [`POST /v1/run`](/api-reference/generations/run) and [`GET /v1/generations/{id}`](/api-reference/generations/get).

| Status       | Meaning                                                          | Final |
| ------------ | ---------------------------------------------------------------- | ----- |
| `processing` | The model is working. New runs start here.                       | No    |
| `succeeded`  | Done. Files are in `output.urls`.                                | Yes   |
| `failed`     | Stopped. The reason is in `error` and the credits were refunded. | Yes   |

The OpenAPI document also lists `queued`. Treat it like `processing` if you ever see it.

## Three ways to wait

<Tabs>
  <Tab title="Long-poll (recommended)">
    Call `GET /v1/generations/{id}?wait=45` in a loop. Each call returns as soon as the job ends, or after 45 seconds with `stillRunning: true`. `wait` accepts 5 to 55.

    ```bash theme={"system"}
    curl -s "https://api.percify.io/v3/playground/v1/generations/$GEN_ID?wait=45" \
      -H "Authorization: Bearer $PERCIFY_API_TOKEN"
    ```
  </Tab>

  <Tab title="Short poll">
    Call `GET /v1/generations/{id}` without `wait` every few seconds. Keep each key under 60 requests a minute across all your jobs.
  </Tab>

  <Tab title="Webhook">
    Pass `"webhook": "https://your-server.example/percify"` in the run body. Percify sends one POST when the job succeeds or fails. See [Webhooks](/guides/webhooks).
  </Tab>
</Tabs>

You do not have to poll for a job to finish. Percify also checks running jobs in the background, so outputs are saved and webhooks are sent either way.

## How long can a job run?

A generation that has not finished fails with `Generation timed out` after **15 minutes plus 5 seconds for every second of input audio or video**. Any generation still processing after **30 minutes** is failed as well. In both cases the credits are refunded.

Lip-sync and other video models take the longest, so use `wait` or a webhook for them rather than a tight loop.

## What happens to credits?

| Moment                                                                        | Credits                          |
| ----------------------------------------------------------------------------- | -------------------------------- |
| The run is rejected before it starts (bad input, moderation, balance, limits) | Never charged                    |
| The run starts                                                                | Charged, shown in `creditsSpent` |
| The model cannot start the job                                                | Refunded in the same request     |
| The job fails or times out                                                    | Refunded automatically           |
| You retry with the same `Idempotency-Key`                                     | Not charged again                |

## Retry safely

Network errors and timeouts on your side do not tell you whether a run started. Send an `Idempotency-Key` header on `POST /v1/run`, and reuse the same value when you retry. Percify returns the generation it already created and does not charge twice.

```bash 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: order-1042-voiceover" \
  -d '{"modelId":"zonos2","input":{"text":"Your order is on its way.","audio":"https://example.com/voice-sample.wav"}}'
```

## Longer pipelines

The replication and short-video endpoints run several generations for you and have their own statuses.

| Job             | Poll                                 | Statuses                                           | Final             |
| --------------- | ------------------------------------ | -------------------------------------------------- | ----------------- |
| Video analysis  | `GET /v3/replicate/v1/analyses/{id}` | `analyzing`                                        | `ready`, `failed` |
| Replication run | `GET /v3/replicate/v1/runs/{id}`     | `generating`, `stitching`                          | `done`, `failed`  |
| Short video     | `GET /v3/mascot/api/{jobId}`         | `scripting`, `portrait`, `animating`, `captioning` | `done`, `failed`  |

These endpoints have no `wait` parameter, so poll them every few seconds or pass a `webhook`. Details are on [Analyze and replicate a short video](/api-reference/video-studio/overview) and [Make a short video from a topic](/api-reference/short-videos).

## Related

<CardGroup cols={2}>
  <Card title="Get a generation" href="/api-reference/generations/get">
    The polling endpoint and its fields.
  </Card>

  <Card title="Webhooks" href="/guides/webhooks">
    Payloads and signature headers.
  </Card>

  <Card title="Errors and rate limits" href="/api-reference/errors-and-limits">
    What each failure means.
  </Card>

  <Card title="Code examples" href="/guides/sdk-integration">
    A full run-and-wait loop in three languages.
  </Card>
</CardGroup>
