curl --request POST \
--url https://api.hopscotchlabs.ai/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "openai/sora-2",
"prompt": "A paper boat drifting down a rainy street.",
"seconds": "8",
"size": "1280x720"
}
'import requests
url = "https://api.hopscotchlabs.ai/v1/videos"
payload = {
"model": "openai/sora-2",
"prompt": "A paper boat drifting down a rainy street.",
"seconds": "8",
"size": "1280x720"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'openai/sora-2',
prompt: 'A paper boat drifting down a rainy street.',
seconds: '8',
size: '1280x720'
})
};
fetch('https://api.hopscotchlabs.ai/v1/videos', 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.hopscotchlabs.ai/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'openai/sora-2',
'prompt' => 'A paper boat drifting down a rainy street.',
'seconds' => '8',
'size' => '1280x720'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopscotchlabs.ai/v1/videos"
payload := strings.NewReader("{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopscotchlabs.ai/v1/videos")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopscotchlabs.ai/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}"
response = http.request(request)
puts response.read_bodyCreate a video
Create a video job, in OpenAI’s Sora shape (client.videos.create), and answer the provider’s job object. A hold of seconds (4 when absent) times the model’s per-second rate for the requested size is taken before anything is sent to the provider, and stays open until the job ends: a completed job is charged the seconds the provider reports, never above the hold; a failed or provider-confirmed cancelled job is charged nothing; a job not finished three hours after create is charged its full hold. One usage row is written per job, when it settles. The hold is zero on a workspace-owned credential. A model whose rates are duration tiers holds the requested seconds at the tier equal to them, and at the dearest tier when the seconds name none, so a hold never undercharges. A completed job reporting zero seconds releases the hold and records served usage at zero charge; absent or unreadable seconds charge the full hold. Video usage is timestamped when the job settles, so its usage period follows the settled charge. The body may be JSON or multipart/form-data (an input_reference image rides in the form) and is capped at 8 MiB; a larger body is refused 413 before any hold. The job belongs to the account that created it: the follow-up paths answer only that account. Listing (GET /v1/videos) and remix are not served. Each model accepts only the durations and sizes it publishes: a create naming anything else is refused 400 before anything is held. GET /v1/models lists the exact strings this key can call.
curl --request POST \
--url https://api.hopscotchlabs.ai/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "openai/sora-2",
"prompt": "A paper boat drifting down a rainy street.",
"seconds": "8",
"size": "1280x720"
}
'import requests
url = "https://api.hopscotchlabs.ai/v1/videos"
payload = {
"model": "openai/sora-2",
"prompt": "A paper boat drifting down a rainy street.",
"seconds": "8",
"size": "1280x720"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'openai/sora-2',
prompt: 'A paper boat drifting down a rainy street.',
seconds: '8',
size: '1280x720'
})
};
fetch('https://api.hopscotchlabs.ai/v1/videos', 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.hopscotchlabs.ai/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'openai/sora-2',
'prompt' => 'A paper boat drifting down a rainy street.',
'seconds' => '8',
'size' => '1280x720'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopscotchlabs.ai/v1/videos"
payload := strings.NewReader("{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopscotchlabs.ai/v1/videos")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopscotchlabs.ai/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"openai/sora-2\",\n \"prompt\": \"A paper boat drifting down a rainy street.\",\n \"seconds\": \"8\",\n \"size\": \"1280x720\"\n}"
response = http.request(request)
puts response.read_bodyAuthorizations
Body
Create a video job. The body may be JSON or multipart/form-data; an input_reference image rides in the form.
The model to call. A provider-pinned id, {provider}/{model}, is split once on the first slash. A routing-profile slug, profile/{identifier}, tries that profile's models in your own order. unirouter/auto, or its second spelling hopscotch/auto, names whichever route your workspace made its default. A bare model id is refused with 400 and the code model_id_not_pinned. GET /v1/models lists the exact strings this key can call.
"openai/sora-2"
The resolution, such as "1280x720". Required: a create that names none is refused before anything is held. It must have a per-second rate on the model; one without is refused before anything is held. A create naming a size the model does not publish is refused 400 before anything is held.
"1280x720"
Whole seconds of video, 1 or more, as decimal digits without whitespace, hexadecimal or exponent notation (such as "8"); JSON also accepts a positive safe integer. Absent, 4. A hold beyond the safe money range is refused with 400 naming seconds and the cap at the matching published rate, before any credit is held. A create naming a duration the model does not publish is refused 400 before anything is held.
"8"
A reference image, sent as a multipart file part under this name. A model that renders from an image only requires it, and with an accepted image reference prompt is optional. A model that renders from text only refuses it. Each model accepts only the image forms and sizes it publishes.
Response
The provider's video job object (id, status, progress), with external URLs omitted. A recorded job is charged when a terminal status is observed. If the provider returns no readable job id or an id already recorded for that provider, the full hold is charged immediately and usage is recorded under this create request's unique request id.
One video job, as the provider reports it, with external URLs omitted and model rewritten to the public id the create returned. Providers report different extra fields; only the fields below are promised.