# Async API

Source: https://docs.prodia.com/reference/async/

The asynchronous polling-style API enables easy integration of long duration
workflows, particularly video generation. Unlike the synchronous `/v2/job`
endpoint which blocks until completion, the async API returns immediately with a
job ID that you can poll to check progress and retrieve outputs.

> caution:
>
> This API is still in beta!

## Endpoints

### `POST /v2/job/async` Create Async Job

Creates a new async job and returns immediately with the job metadata. The job
will be processed in the background.

**Request:** Same as synchronous `/v2/job` - supports both `application/json`
and `multipart/form-data` content types. The `?price=true` query parameter is
also supported (see [Query Parameters](https://docs.prodia.com/reference/inference/#query-parameters)).

**Response:** `201 Created` with the full job JSON. The response includes the
server-generated `id` field that you need for polling:

```json title="201 response"
{
  "type": "inference.veo.fast.txt2vid.v1",
  "id": "c83d7027-240a-484a-aeba-96014e568711",
  "created_at": "2025-01-01T00:00:14.885Z",
  "updated_at": "2025-01-01T00:00:14.885Z",
  "expires_at": "2025-01-01T00:01:14.885Z",
  "state": {
    "current": "processing"
  },
  "config": {
    "prompt": "Running in the woods with a dog.",
    "aspect_ratio": "16:9",
    "resolution": "720p",
    "generate_audio": true
  }
}
```

> note:
>
> Not all job types support async execution. If a job type doesn't permit async,
> you'll receive a `400 Bad Request` with a message indicating you should use the
> sync API instead.

### `GET /v2/job/async/:id/job.state.current` Get Job State

Returns the current state of the job as plain text. Use this for efficient
polling.

**States:**

- `processing` - Job is currently being processed
- `processed` - Job completed successfully
- `failed` - Job encountered an error

> note:
>
> Async jobs use the state `processed` (not `completed`) when successful. This
> differs from the synchronous API which uses `completed`.

### `GET /v2/job/async/:id/job.json` Get Job Metadata

Returns the full job JSON including configuration, timestamps, metrics, and
error information (if failed).

### `GET /v2/job/async/:id/output` List Output Files

Returns a list of output files produced by the job.

### `GET /v2/job/async/:id/output/:filename` Download Output

Downloads a specific output file (e.g., `video.mp4`).

## Job Expiry

Async job results are stored temporarily and *expire after 1 hour*. After
expiry, all GET endpoints for that job will return `404 Not Found`. Clients
should download outputs promptly after the job completes.

The `expires_at` field in the job result indicates the exact expiry time.

## Status Codes

| Code  | Description                                                               |
| ----- | ------------------------------------------------------------------------- |
| `201` | Job created successfully (POST /v2/job/async)                             |
| `200` | Request successful (GET endpoints)                                        |
| `400` | Bad request - invalid job configuration or job type doesn't support async |
| `401` | Unauthorized - missing or invalid authentication                          |
| `404` | Job not found or expired                                                  |
| `429` | Too many requests - exceeded async job limit or no capacity               |

## Example

### requests

Create the following `main.py`

```python title="main.py"
import json
import os
import sys
import time

import requests
from requests.adapters import HTTPAdapter, Retry

prodia_token = os.getenv('PRODIA_TOKEN')
prodia_url = 'https://inference.prodia.com/v2/job/async'

session = requests.Session()
retries = Retry(allowed_methods=None, status_forcelist=Retry.RETRY_AFTER_STATUS_CODES)
session.mount('http://', HTTPAdapter(max_retries=retries))
session.mount('https://', HTTPAdapter(max_retries=retries))
session.headers.update({'Authorization': f"Bearer {prodia_token}"})

headers = {
    'Accept': 'video/mp4',
}

job = json.loads('''{
  "type": "inference.veo.fast.txt2vid.v1",
  "config": {
    "prompt": "Running in the woods with a dog.",
    "aspect_ratio": "16:9",
    "resolution": "720p",
    "generate_audio": true
  }
}''')

res = session.post(prodia_url, headers=headers, json=job)
print(f"Request ID: {res.headers['x-request-id']}")
print(f"Status: {res.status_code}")

if res.status_code != 201:
    print(res.text)
    sys.exit(1)

job = res.json()
job_id = job['id']
job_status = job['state']['current']
print(f"Job ID: {job_id}")
print(f"Job Status: {job_status}")

while job_status == 'processing':
    time.sleep(1)
    res = session.get(f"{prodia_url}/{job_id}/job.state.current")
    if res.status_code != 200:
        print(res.text)
        sys.exit(1)
    job_status = res.text
    print(f"Job Status: {job_status}")

if job_status != 'processed':
    res = session.get(f"{prodia_url}/{job_id}/job.json")
    if res.status_code != 200:
        print(res.text)
        sys.exit(1)
    print(res.json())
    sys.exit(1)

res = session.get(f"{prodia_url}/{job_id}/output/video.mp4")
if res.status_code != 200:
    print(res.text)
    sys.exit(1)

with open('output.mp4', 'wb') as f:
    f.write(res.content)

print('Output file: output.mp4')
```

### axios

Create the following `main.js`

```javascript title="main.js"
const axios = require('axios');
const axiosRetry = require('axios-retry').default;
const fs = require('node:fs');

async function main() {
    axiosRetry(axios, {
        retries: 3,
        retryCondition: (error) => {
            return [413, 429, 503].includes(error.response?.status);
        },
    });

    let job = {
      "type": "inference.veo.fast.txt2vid.v1",
      "config": {
        "prompt": "Running in the woods with a dog.",
        "aspect_ratio": "16:9",
        "resolution": "720p",
        "generate_audio": true
      }
    };

    let res = await axios({
        method: 'POST',
        url: 'https://inference.prodia.com/v2/job/async',
        headers: {
            'Accept': 'video/mp4',
            'Authorization': `Bearer ${process.env.PRODIA_TOKEN}`,
        },
        data: job,
    });

    job = res.data;
    let jobStatus = job.state.current;
    console.log(`Job ID: ${job.id}`);
    console.log(`Job Status: ${jobStatus}`);

    while (jobStatus == 'processing') {
        await new Promise(resolve => setTimeout(resolve, 1000));
        res = await axios({
            method: 'GET',
            url: `https://inference.prodia.com/v2/job/async/${job.id}/job.state.current`,
            headers: {
                'Authorization': `Bearer ${process.env.PRODIA_TOKEN}`,
            },
        });
        jobStatus = res.data;
        console.log(`Job Status: ${jobStatus}`)
    }

    if (jobStatus != 'processed') {
        console.log(job);
        process.exit(1);
    }

    res = await axios({
        method: 'GET',
        url: `https://inference.prodia.com/v2/job/async/${job.id}/output/video.mp4`,
        headers: {
            'Authorization': `Bearer ${process.env.PRODIA_TOKEN}`,
        },
        responseType: 'arraybuffer',
    });

    fs.writeFileSync('output.mp4', res.data);
    console.log('Output file: output.mp4');
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
```

> note:
>
> Outputs are only available for 1 hour. See [Job Expiry](https://docs.prodia.com/reference/async/#job-expiry) above.
