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

# Video · overview

> Asynchronous video generation with polling

Unlike chat and images, **video takes 30s to 5min** depending on the model. So the flow is **asynchronous** with two endpoints:

```
POST /v1/videos/generations    → Creates job, returns job_id in ms
GET  /v1/videos/{job_id}        → Current status + video URL if completed
```

## Available models

| ID                 | Provider | Price/sec | Default duration | Max | Audio |
| ------------------ | -------- | --------- | ---------------- | --- | ----- |
| `google/veo-3.1`   | Google   | \$0.40    | 8s               | 8s  | ✅     |
| `google/veo-3`     | Google   | \$0.20    | 8s               | 8s  | ❌     |
| `fal/veo-3.1`      | fal.ai   | \$0.40    | 8s               | 8s  | ✅     |
| `fal/runway-gen-4` | fal.ai   | \$0.05    | 5s               | 10s | ❌     |
| `fal/luma-ray-2`   | fal.ai   | \$0.20    | 5s               | 9s  | ❌     |
| `fal/kling-2`      | fal.ai   | \$0.04    | 5s               | 10s | ❌     |
| `fal/hailuo-02`    | fal.ai   | \$0.04    | 6s               | 10s | ❌     |

## Usage pattern

```python theme={null}
import time
from openai import OpenAI

client = OpenAI(
    base_url="https://api.geekhub.mx/v1",
    api_key="ghub_sk_live_xxx",
)

# 1. Submit
job = client.post("/videos/generations", body={
    "model": "fal/kling-2",
    "prompt": "A hummingbird drinking from a cempasúchil flower at dawn",
    "duration": 5,
})
job_id = job["id"]
print(f"Job created: {job_id}")

# 2. Poll
while True:
    status = client.get(f"/videos/{job_id}")
    if status["status"] == "completed":
        print(f"Done: {status['video_url']}")
        break
    if status["status"] == "failed":
        print(f"Failed: {status['error']}")
        break
    time.sleep(15)  # Every 15 sec
```

<Info>
  Polling is **lazy**: the job doesn't advance on its own (Kling/Veo process on their side). But the provider keeps working even if you don't poll — when you do poll you'll see the updated state.
</Info>

## Job states

| Status       | Meaning                                           |
| ------------ | ------------------------------------------------- |
| `pending`    | Job created in our DB, not yet sent to provider   |
| `processing` | Provider is rendering                             |
| `completed`  | Done. `video_url` points to your Supabase Storage |
| `failed`     | An error occurred. `error` describes the cause    |

We only charge the balance when status reaches `completed` (at model price × duration × markup × FX).

If the job fails, **you're not charged**.

## Storage

Same as images: finished videos upload to your Supabase Storage (`generated-videos`) with persistent public UUID-based URLs.

## Next steps

<CardGroup cols={2}>
  <Card title="POST /v1/videos/generations" icon="upload" href="/en/api-reference/videos/generations">
    Submit a new video job.
  </Card>

  <Card title="GET /v1/videos/{id}" icon="rotate" href="/en/api-reference/videos/poll">
    Poll the job status.
  </Card>
</CardGroup>
