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

# GET /v1/videos/{id}

> Polea el estado de un job de video

## Path params

<ParamField path="job_id" type="string" required>
  El `id` que recibiste al crear el job (formato UUID).
</ParamField>

## Response

Cuando aún está procesando:

```json theme={null}
{
  "id": "e8c96506-72a1-429f-a7ed-4bb0a42541be",
  "object": "video.job",
  "status": "processing",
  "model": "fal/kling-3-pro",
  "prompt": "...",
  "video_url": null,
  "duration": 5,
  "error": null,
  "created": 1782415166,
  "completed": null
}
```

Cuando termina (`completed`):

```json theme={null}
{
  "id": "e8c96506-72a1-429f-a7ed-4bb0a42541be",
  "object": "video.job",
  "status": "completed",
  "model": "fal/kling-3-pro",
  "prompt": "...",
  "video_url": "https://supabase.geekhub.mx/storage/v1/object/public/generated-videos/<org>/<job>/video.mp4",
  "duration": 5,
  "error": null,
  "created": 1782415166,
  "completed": 1782415954
}
```

Cuando falla:

```json theme={null}
{
  "status": "failed",
  "video_url": null,
  "error": "fal status: ERROR",
  "completed": 1782415954
}
```

## Cuánto polear

Cada provider tiene latencia distinta. Recomendación práctica:

| Modelo              | Polea cada | Tiempo típico total |
| ------------------- | ---------- | ------------------- |
| Kling 2             | 15s        | 2-5 min             |
| Hailuo 02           | 15s        | 2-4 min             |
| Runway Gen-4        | 10s        | 30s-2 min           |
| Luma Ray 2          | 10s        | 1-3 min             |
| Veo 3.1 (con audio) | 30s        | 3-5 min             |
| Veo 3               | 20s        | 2-4 min             |

Si polea más rápido que el provider procesa, simplemente verás `status: "processing"` repetido — no es un error.

## Patrón TS

```typescript theme={null}
const job = await fetch("https://api.geekhub.mx/v1/videos/generations", {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify({ model: "fal/kling-3-pro", prompt: "...", duration: 5 }),
}).then(r => r.json());

const jobId = job.id;

while (true) {
  const status = await fetch(`https://api.geekhub.mx/v1/videos/${jobId}`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  }).then(r => r.json());

  if (status.status === "completed") {
    console.log("Done:", status.video_url);
    break;
  }
  if (status.status === "failed") {
    console.error("Failed:", status.error);
    break;
  }
  await new Promise(r => setTimeout(r, 15000)); // 15s
}
```

## Storage

Cuando termina, descargamos el video del provider y lo subimos a tu Supabase Storage. La `video_url` es **nuestra**, no expira y es accesible públicamente con URL UUID (mismo modelo que imágenes).

Si el upload a Storage falla por alguna razón, fallback transparente: te regresamos la URL del provider directo (esa sí expira en \~1h-24h dependiendo del provider).
