GET /v1/videos/{id}
curl --request GET \
--url https://api.geekhub.mx/v1/videos/{job_id}import requests
url = "https://api.geekhub.mx/v1/videos/{job_id}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.geekhub.mx/v1/videos/{job_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.geekhub.mx/v1/videos/{job_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.geekhub.mx/v1/videos/{job_id}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.geekhub.mx/v1/videos/{job_id}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.geekhub.mx/v1/videos/{job_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyAPI Reference
GET /v1/videos/{id}
Poll a video job status
GET
/
v1
/
videos
/
{job_id}
GET /v1/videos/{id}
curl --request GET \
--url https://api.geekhub.mx/v1/videos/{job_id}import requests
url = "https://api.geekhub.mx/v1/videos/{job_id}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.geekhub.mx/v1/videos/{job_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.geekhub.mx/v1/videos/{job_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.geekhub.mx/v1/videos/{job_id}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.geekhub.mx/v1/videos/{job_id}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.geekhub.mx/v1/videos/{job_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyPath params
string
required
The
id you received when creating the job (UUID format).Response
While still processing:{
"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
}
completed):
{
"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
}
{
"status": "failed",
"video_url": null,
"error": "fal status: ERROR",
"completed": 1782415954
}
How often to poll
Each provider has different latency. Practical recommendation:| Model | Poll every | Typical total time |
|---|---|---|
| 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 (with audio) | 30s | 3-5 min |
| Veo 3 | 20s | 2-4 min |
status: "processing" repeated — that’s not an error.
TS pattern
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
When it finishes, we download the video from the provider and upload it to your Supabase Storage. Thevideo_url is ours, doesn’t expire, and is publicly accessible with a UUID URL (same model as images).
If the upload to Storage fails for some reason, transparent fallback: we return the provider’s URL directly (that one does expire in ~1h-24h depending on the provider).