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 아님) - 파일은 하나만 지원됩니다. 필드 이름은
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 (원시 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 | 문자열 | 예 | — | sora-2(720p 전용) 또는 sora-2-pro(720p / 1024p / 1080p 등급) |
prompt | 문자열 | 예 | — | 동영상 설명입니다. 정적인 이미지가 어떻게 애니메이션되어야 하는지(카메라 움직임, 객체 움직임, 조명 변화)에 집중하십시오 |
seconds | 문자열 | 아니요 | "4" | 기간이며 문자열 열거형입니다: "4" / "8" / "12" |
size | 문자열 | 아니요 | 720x1280 | 출력 해상도이며, 반드시 input_reference 이미지 크기와 정확히 같아야 합니다 |
input_reference | 파일 | 예 | — | 참조 이미지 파일입니다: 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".응답 형식
응답 형태는 텍스트-투-비디오와 동일합니다: 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"
이 페이지가 도움이 되었나요?