curl --request POST \
--url https://api.apiyi.com/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=sora-2 \
--form 'prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in' \
--form input_reference='@example-file'import requests
url = "https://api.apiyi.com/v1/videos"
files = { "input_reference": ("example-file", open("example-file", "rb")) }
payload = {
"model": "sora-2",
"prompt": "Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in');
form.append('input_reference', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/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.apiyi.com/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 => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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.apiyi.com/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.apiyi.com/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/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.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"completed_at": 1712697900,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}画像から動画へのAPIリファレンス
Sora 2 の画像から動画への API リファレンスとライブプレイグラウンド — 静止画像をアニメーション化するために input_reference を multipart アップロードします。
curl --request POST \
--url https://api.apiyi.com/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=sora-2 \
--form 'prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in' \
--form input_reference='@example-file'import requests
url = "https://api.apiyi.com/v1/videos"
files = { "input_reference": ("example-file", open("example-file", "rb")) }
payload = {
"model": "sora-2",
"prompt": "Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in');
form.append('input_reference', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/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.apiyi.com/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 => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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.apiyi.com/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.apiyi.com/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/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.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"completed_at": 1712697900,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}Bearer sk-xxx)、参照画像をアップロードして、prompt を入力し、モデル / サイズ / 秒数を選んで送信してください。size と完全に一致している必要があります- アップロードする画像のピクセル寸法は
sizeフィールドと一致していなければなりません(例:size=1280x720には 1280×720 の画像が必要です) - 不一致の場合は 400 が返ります:
Inpaint image must match the requested width and height - アップロード前に ffmpeg / Pillow で事前にトリミングしてください
- Content-Type は
multipart/form-dataである必要があります(JSON ではありません) - 対応ファイルは1つのみで、フィールド名は
input_referenceに固定されています - 対応形式:
image/jpeg/image/png/image/webp
コードサンプル
Python (OpenAI SDK ドロップイン)
from openai import OpenAI
import time
client = OpenAI(
api_key="sk-your-api-key",
base_url="https://api.apiyi.com/v1"
)
# Step 1: Submit (the OpenAI SDK auto-handles multipart when input_reference is provided)
with open("./reference.png", "rb") as f:
video = client.videos.create(
model="sora-2",
prompt="Animate this scene: gentle waves lapping against the shore, leaves swaying in the breeze",
seconds="8",
size="1280x720",
input_reference=f
)
print(f"Video ID: {video.id}, status: {video.status}")
# Step 2: Poll
while True:
video = client.videos.retrieve(video.id)
print(f"Status: {video.status}, progress: {getattr(video, 'progress', 0)}%")
if video.status == "completed":
break
if video.status == "failed":
raise RuntimeError(f"Generation failed: {video}")
time.sleep(15)
# Step 3: Download
client.videos.download_content(video.id).write_to_file("output.mp4")
print("Saved: output.mp4")
Python (Raw requests + multipart)
import requests
import time
API_KEY = "sk-your-api-key"
BASE_URL = "https://api.apiyi.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Step 1: Multipart upload (image dimensions must equal size)
with open("./reference.png", "rb") as f:
resp = requests.post(
f"{BASE_URL}/videos",
headers=HEADERS, # Don't manually set Content-Type — requests handles the multipart boundary
data={
"model": "sora-2",
"prompt": "Animate this scene with cinematic camera push-in, soft golden hour lighting",
"seconds": "8",
"size": "1280x720"
},
files={
"input_reference": ("reference.png", f, "image/png")
},
timeout=60 # Multipart uploads of large images can be slow; use a 60-second timeout
).json()
video_id = resp["id"]
print(f"Video ID: {video_id}, status: {resp['status']}")
# Step 2: Poll
deadline = time.time() + 900
while time.time() < deadline:
status_resp = requests.get(f"{BASE_URL}/videos/{video_id}", headers=HEADERS).json()
print(f"Status: {status_resp['status']}, progress: {status_resp.get('progress', 0)}%")
if status_resp["status"] == "completed":
break
if status_resp["status"] == "failed":
raise RuntimeError(f"Generation failed: {status_resp}")
time.sleep(15)
# Step 3: Download
with requests.get(f"{BASE_URL}/videos/{video_id}/content", headers=HEADERS, stream=True) as r:
r.raise_for_status()
with open("output.mp4", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
print("Saved: output.mp4")
cURL
{/* Step 1: Multipart upload + submit */}
curl -X POST "https://api.apiyi.com/v1/videos" \
-H "Authorization: Bearer sk-your-api-key" \
-F "model=sora-2" \
-F "prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic" \
-F "seconds=8" \
-F "size=1280x720" \
-F "input_reference=@./reference.png;type=image/png"
{/* Step 2: Poll */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123" \
-H "Authorization: Bearer sk-your-api-key"
{/* Step 3: Download */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123/content" \
-H "Authorization: Bearer sk-your-api-key" \
-o output.mp4
Node.js (fetch + FormData)
import fs from 'node:fs';
import { fileFromPath } from 'formdata-node/file-from-path';
import { FormData } from 'formdata-node';
const API_KEY = 'sk-your-api-key';
const BASE_URL = 'https://api.apiyi.com/v1';
// Step 1: Multipart upload
const form = new FormData();
form.set('model', 'sora-2');
form.set('prompt', 'Animate this scene with cinematic camera push-in, soft lighting');
form.set('seconds', '8');
form.set('size', '1280x720');
form.set('input_reference', await fileFromPath('./reference.png'));
const submitResp = await fetch(`${BASE_URL}/videos`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` }, // Don't manually set Content-Type
body: form
});
const { id: videoId } = await submitResp.json();
console.log(`Video ID: ${videoId}`);
// Step 2: Poll
let status = 'queued';
while (status !== 'completed' && status !== 'failed') {
await new Promise(r => setTimeout(r, 15000));
const data = await (await fetch(`${BASE_URL}/videos/${videoId}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
})).json();
status = data.status;
console.log(`Status: ${status}, progress: ${data.progress ?? 0}%`);
}
if (status === 'failed') throw new Error('Generation failed');
// Step 3: Download
const contentResp = await fetch(`${BASE_URL}/videos/${videoId}/content`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
fs.writeFileSync('output.mp4', Buffer.from(await contentResp.arrayBuffer()));
console.log('Saved: output.mp4');
ブラウザ JavaScript
{/* Demo only; route through your backend in production to avoid leaking the API key. */}
const fileInput = document.getElementById('refImage'); // <input type="file" />
const file = fileInput.files[0];
const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene, gentle motion');
form.append('seconds', '4');
form.append('size', '1280x720');
form.append('input_reference', file);
const submitResp = await fetch('https://api.apiyi.com/v1/videos', {
method: 'POST',
headers: { 'Authorization': 'Bearer sk-your-api-key' },
body: form
});
const { id } = await submitResp.json();
console.log('Video ID:', id);
{/* After polling completes, route the video URL through a backend proxy to avoid downloading large files in the browser. */}
パラメータ クイックリファレンス
| パラメータ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
model | string | Yes | — | sora-2(720p のみ)または sora-2-pro(720p / 1024p / 1080p の各ティア) |
prompt | string | Yes | — | 動画の説明。静止画像がどのようにアニメーションするか(カメラの動き、オブジェクトの動き、ライティングの変化)に重点を置いてください |
seconds | string | No | "4" | 文字列の列挙型としての期間: "4" / "8" / "12" |
size | string | No | 720x1280 | 出力解像度。input_referenceの画像サイズと完全に一致している必要があります |
input_reference | file | Yes | — | 参照画像ファイル: image/jpeg / image/png / image/webp、サイズは size と完全に一致している必要があります |
input_reference は multipart でアップロードする必要があります — URL と base64 は受け付けられません。参照画像の準備
ターゲット解像度を選びます
size を選びます: 縦長 720x1280、横長 1280x720、Pro 1080p 横長 1920x1080 など。ローカルでピクセル単位まで正確に切り抜きます
from PIL import Image
img = Image.open("source.jpg")
img = img.resize((1280, 720), Image.LANCZOS) # Or crop first then resize to preserve aspect ratio
img.save("reference.png")
ffmpeg -i source.jpg -vf "scale=1280:720" reference.png
適切なフォーマットを選びます
Focus the prompt on "motion" not "appearance"
"Camera slowly pushes in, leaves gently swaying, sunlight flickering through branches".レスポンス形式
レスポンスの形は Text-to-Video と同一です。submit はid + status: "queued" を返し、ポーリングで進行状況を報告し、完了後は /v1/videos/{id}/content 経由で MP4 としてダウンロードします。
{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}
Inpaint image must match the requested width and height— 参照画像のサイズがsizeと一致しません。最も一般的です。 アップロード前にクライアント側でサイズを検証してくださいInvalid file format— アップロードされたファイルが jpeg / png / webp ではないか、破損していますMissing required parameter: input_reference— multipart のフィールド名が誤っています(input_referenceである必要があり、imageやreferenceではありません)seconds must be one of "4", "8", "12"— 文字列の"4"ではなく、整数の4を渡しています
seconds によって課金されます)。参照画像をアップロードしても追加料金はかかりません。料金表 をご覧ください。承認
API Key from the APIYI console (must use Sora2官转 group + usage-based billing)
ボディ
Model ID. sora-2 supports 720p only; sora-2-pro supports 720p / 1024p / 1080p
sora-2, sora-2-pro Video generation prompt. Focus on how the image should animate: camera motion, object motion, lighting changes
"Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
Reference image file used as the video's starting frame / visual anchor.
- Accepted formats:
image/jpeg/image/png/image/webp - Dimensions must equal
size, otherwise you getInpaint image must match the requested width and height - Only one file is supported; field name is fixed as
input_reference
Video duration as string enum: "4" / "8" / "12"
4, 8, 12 Output resolution. Must exactly match the input_reference image dimensions:
sora-2(720p only):720x1280/1280x720sora-2-proadditionally:1024x1792/1792x1024/1080x1920/1920x1080
720x1280, 1280x720, 1024x1792, 1792x1024, 1080x1920, 1920x1080 レスポンス
Task submitted, returns video_id with queued status
Task ID for subsequent polling and download
"video_abc123def456"
Object type, fixed video
"video"
Model ID used for this task
"sora-2"
Task status:
queued— submitted, waiting in queuein_progress— generatingcompleted— done, ready to download (/v1/videos/{id}/content)failed— failed (not billed), safe to retry
queued, in_progress, completed, failed "queued"
Generation progress percentage (0–100), not strictly linear
0
Task creation Unix timestamp (seconds)
1712697600
Task completion Unix timestamp (seconds), present only on completed status
1712697900
Actual output resolution (matches the requested size)
"1280x720"
Actual duration generated (matches the requested seconds)
"8"
Quality tier (standard for sora-2, high for sora-2-pro)
"standard"
このページは役に立ちましたか?