File Deleted
View Diff
1# Video generation with Sora
2
3> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.
4
5## Overview
6
7Sora is OpenAI’s newest frontier in generative media – a state-of-the-art video model capable of creating richly detailed, dynamic clips with audio from natural language or images. Built on years of research into multimodal diffusion and trained on diverse visual data, Sora brings a deep understanding of 3D space, motion, and scene continuity to text-to-video generation.
8
9The [Videos API](https://developers.openai.com/api/reference/resources/videos) exposes these capabilities to developers for the first time, enabling programmatic creation, extension, editing, and management of videos.
10
11You can use it to:
12
13- Create new videos from prompts.
14- Guide a generation with an image reference.
15- Reuse character assets across multiple generations for stronger visual consistency.
16- Continue a completed clip with video extensions.
17- Edit an existing video with targeted changes.
18- Download finished videos and supporting assets.
19- Submit large offline render queues through the [Batch API](https://developers.openai.com/api/docs/guides/batch).
20
21## Models
22
23The second generation Sora model comes in two variants, each tailored for different use cases.
24
25### Sora 2
26
27`sora-2` is designed for **speed and flexibility**. It’s ideal for the exploration phase, when you’re experimenting with tone, structure, or visual style and need quick feedback rather than perfect fidelity.
28
29It generates good quality results quickly, making it well suited for rapid iteration, concepting, and rough cuts. `sora-2` is often more than sufficient for social media content, prototypes, and scenarios where turnaround time matters more than ultra-high fidelity.
30
31### Sora 2 Pro
32
33`sora-2-pro` produces higher quality results. It’s the better choice when you need **production-quality output**.
34
35`sora-2-pro` takes longer to render and is more expensive to run, but it produces more polished, stable results. It’s best for high-resolution cinematic footage, marketing assets, and any situation where visual precision is critical.
36
37Use `sora-2-pro` when you need 1080p exports in `1920x1080` or `1080x1920`.
38
39Both `sora-2` and `sora-2-pro` support `16`- and `20`-second generations.
40
41## Generate a video
42
43Generating a video is an **asynchronous** process:
44
451. When you call the `POST /videos` endpoint, the API returns a job object with a job `id` and an initial `status`.
46
472. You can either poll the `GET /videos/{video_id}` endpoint until the status transitions to completed, or – for a more efficient approach – use webhooks (see the webhooks section below) to be notified automatically when the job finishes.
48
493. Once the job has reached the `completed` state you can fetch the final MP4 file with `GET /videos/{video_id}/content`.
50
51### Start a render job
52
53Start by calling `POST /videos` with a text prompt and the required parameters. The prompt defines the creative look and feel – subjects, camera, lighting, and motion – while parameters like `size` and `seconds` control the video's resolution and length.
54
55Create a video
56
57```javascript
58import OpenAI from "openai";
59
60const openai = new OpenAI();
61
62let video = await openai.videos.create({
63 model: "sora-2",
64 prompt: "A video of the words 'Thank you' in sparkling letters",
65});
66
67console.log("Video generation started: ", video);
68```
69
70```python
71from openai import OpenAI
72
73openai = OpenAI()
74
75video = openai.videos.create(
76 model="sora-2",
77 prompt="A video of a cool cat on a motorcycle in the night",
78)
79
80print("Video generation started:", video)
81```
82
83```go
84package main
85
86import (
87 "context"
88 "fmt"
89
90 "github.com/openai/openai-go/v3"
91)
92
93func main() {
94 client := openai.NewClient()
95 video, err := client.Videos.New(context.Background(), openai.VideoNewParams{
96 Model: openai.VideoModelSora2,
97 Prompt: "A video of the words 'Thank you' in sparkling letters",
98 })
99 if err != nil {
100 panic(err)
101 }
102 fmt.Println("Video generation started:", video)
103}
104```
105
106```java
107import com.openai.client.OpenAIClient;
108import com.openai.client.okhttp.OpenAIOkHttpClient;
109import com.openai.models.videos.VideoCreateParams;
110
111var video =
112 client
113 .videos()
114 .create(
115 VideoCreateParams.builder()
116 .model("sora-2")
117 .prompt("A paper airplane flying over a forest")
118 .build());
119
120System.out.println(video.id());
121```
122
123```ruby
124require "openai"
125
126client = OpenAI::Client.new
127video = client.videos.create(model: "sora-2", prompt: "A paper airplane flying over a forest")
128puts(video.id)
129```
130
131```bash
132curl -X POST "https://api.openai.com/v1/videos" \
133 -H "Authorization: Bearer $OPENAI_API_KEY" \
134 -H "Content-Type: multipart/form-data" \
135 -F prompt="Wide tracking shot of a teal coupe driving through a desert highway, heat ripples visible, hard sun overhead." \
136 -F model="sora-2-pro" \
137 -F size="1280x720" \
138 -F seconds="8" \
139```
140
141
142The response is a JSON object with a unique id and an initial status such as `queued` or `in_progress`. This means the render job has started.
143
144```shell
145{
146 "id": "video_68d7512d07848190b3e45da0ecbebcde004da08e1e0678d5",
147 "object": "video",
148 "created_at": 1758941485,
149 "status": "queued",
150 "model": "sora-2-pro",
151 "progress": 0,
152 "seconds": "8",
153 "size": "1280x720"
154}
155```
156
157### Choose size and duration
158
159Pick the smallest format that meets your production needs:
160
161- Use shorter clips when you are iterating on prompt, motion, or composition.
162- Generate videos up to `20` seconds when you need longer beats, fuller scenes, or fuller spots.
163- Use `sora-2-pro` for higher-resolution exports in `1920x1080` or `1080x1920`.
164
165Longer durations and 1080p jobs can take materially longer to complete than short 720p or 480p renders, so plan for higher latency in user-facing flows.
166
167### Guardrails and restrictions
168
169The API enforces several content restrictions:
170
171- Only content suitable for audiences under 18 (a setting to bypass this restriction will be available in the future).
172- Copyrighted characters and copyrighted music will be rejected.
173- Real people—including public figures—cannot be generated.
174- Character uploads that depict human likeness are blocked by default.
175- Input images with faces of humans are currently rejected.
176
177Make sure prompts, reference images, and transcripts respect these rules to avoid failed generations.
178
179### Effective prompting
180
181For best results, describe **shot type, subject, action, setting, and lighting**. For example:
182
183- _“Wide shot of a child flying a red kite in a grassy park, golden hour sunlight, camera slowly pans upward.”_
184- _“Close-up of a steaming coffee cup on a wooden table, morning light through blinds, soft depth of field.”_
185
186This level of specificity helps the model produce consistent results without inventing unwanted details. For more advanced prompting techniques, please refer to our dedicated Sora 2 [prompting guide](https://developers.openai.com/cookbook/examples/sora/sora2_prompting_guide).
187
188### Monitor progress
189
190Video generation takes time. Depending on model, API load and resolution, **a single render may take several minutes**.
191
192To manage this efficiently, you can poll the API to request status updates or you can get notified via a webhook.
193
194#### Poll the status endpoint
195
196Call `GET /videos/{video_id}` with the id returned from the create call. The response shows the job’s current status, progress percentage (if available), and any errors.
197
198Typical states are `queued`, `in_progress`, `completed`, and `failed`. Poll at a reasonable interval (for example, every 10–20 seconds), use exponential backoff if necessary, and provide feedback to users that the job is still in progress.
199
200Poll the status endpoint
201
202```javascript
203import OpenAI from "openai";
204import { setTimeout as sleep } from "node:timers/promises";
205
206const openai = new OpenAI();
207
208async function main() {
209 let video = await openai.videos.create({
210 model: "sora-2",
211 prompt: "A video of the words 'Thank you' in sparkling letters",
212 });
213
214 while (video.status === "queued" || video.status === "in_progress") {
215 await sleep(2000);
216 video = await openai.videos.retrieve(video.id);
217 }
218
219 if (video.status === "completed") {
220 console.log("Video successfully completed: ", video);
221 } else {
222 console.log("Video creation failed. Status: ", video.status);
223 }
224}
225
226main();
227```
228
229```python
230import asyncio
231
232from openai import AsyncOpenAI
233
234client = AsyncOpenAI()
235
236
237async def main() -> None:
238 video = await client.videos.create_and_poll(
239 model="sora-2",
240 prompt="A video of a cat on a motorcycle",
241 )
242
243 if video.status == "completed":
244 print("Video successfully completed: ", video)
245 else:
246 print("Video creation failed. Status: ", video.status)
247
248
249asyncio.run(main())
250```
251
252```go
253package main
254
255import (
256 "context"
257 "fmt"
258
259 "github.com/openai/openai-go/v3"
260)
261
262func main() {
263 client := openai.NewClient()
264 video, err := client.Videos.NewAndPoll(context.Background(), openai.VideoNewParams{
265 Model: openai.VideoModelSora2,
266 Prompt: "A video of the words 'Thank you' in sparkling letters",
267 }, 2000)
268 if err != nil {
269 panic(err)
270 }
271 if video.Status == openai.VideoStatusCompleted {
272 fmt.Println("Video successfully completed:", video)
273 return
274 }
275 fmt.Println("Video creation failed. Status:", video.Status)
276}
277```
278
279```java
280import com.openai.client.OpenAIClient;
281import com.openai.client.okhttp.OpenAIOkHttpClient;
282import com.openai.models.videos.Video;
283import com.openai.models.videos.VideoCreateParams;
284
285var video =
286 client
287 .videos()
288 .create(
289 VideoCreateParams.builder()
290 .model("sora-2")
291 .prompt("A paper airplane flying over a forest")
292 .build());
293
294while (video.status().equals(Video.Status.QUEUED)
295 || video.status().equals(Video.Status.IN_PROGRESS)) {
296 Thread.sleep(1000);
297 video = client.videos().retrieve(video.id());
298}
299if (!video.status().equals(Video.Status.COMPLETED)) {
300 throw new IllegalStateException("Video generation failed: " + video.status());
301}
302System.out.println("Video completed: " + video.id());
303```
304
305```ruby
306require "openai"
307
308client = OpenAI::Client.new
309video = client.videos.create(model: "sora-2", prompt: "A paper airplane flying over a forest")
310
311while [:queued, :in_progress].include?(video.status)
312 sleep(2)
313 video = client.videos.retrieve(video.id)
314end
315
316unless video.status == OpenAI::Models::Video::Status::COMPLETED
317 raise "Video creation failed. Status: #{video.status}"
318end
319
320puts("Video successfully completed: #{video.id}")
321```
322
323
324Response example:
325
326```shell
327{
328 "id": "video_68d7512d07848190b3e45da0ecbebcde004da08e1e0678d5",
329 "object": "video",
330 "created_at": 1758941485,
331 "status": "in_progress",
332 "model": "sora-2-pro",
333 "progress": 33,
334 "seconds": "8",
335 "size": "1280x720"
336}
337```
338
339#### Use webhooks for notifications
340
341Instead of polling job status repeatedly with `GET`, register a [webhook](https://developers.openai.com/api/docs/guides/webhooks) to be notified automatically when a video generation completes or fails.
342
343Webhooks can be configured in your [webhook settings page](https://platform.openai.com/settings/project/webhooks). When a job finishes, the API emits one of two event types: `video.completed` and `video.failed`. Each event includes the ID of the job that triggered it.
344
345Example webhook payload:
346
347```
348{
349 "id": "evt_abc123",
350 "object": "event",
351 "created_at": 1758941485,
352 "type": "video.completed", // or "video.failed"
353 "data": {
354 "id": "video_abc123"
355 }
356}
357```
358
359### Retrieve results
360
361#### Download the MP4
362
363Once the job reaches status `completed`, fetch the MP4 with `GET /videos/{video_id}/content`. This endpoint streams the binary video data and returns standard content headers, so you can either save the file directly to disk or pipe it to cloud storage.
364
365Download the MP4
366
367```javascript
368import { writeFileSync } from "node:fs";
369
370import OpenAI from "openai";
371
372const openai = new OpenAI();
373
374let video = await openai.videos.create({
375 model: "sora-2",
376 prompt: "A video of the words 'Thank you' in sparkling letters",
377});
378
379console.log("Video generation started: ", video);
380let progress = video.progress ?? 0;
381
382while (video.status === "in_progress" || video.status === "queued") {
383 video = await openai.videos.retrieve(video.id);
384 progress = video.progress ?? 0;
385
386 // Display progress bar
387 const barLength = 30;
388 const filledLength = Math.floor((progress / 100) * barLength);
389 // Simple ASCII progress visualization for terminal output
390 const bar = "=".repeat(filledLength) + "-".repeat(barLength - filledLength);
391 const statusText = video.status === "queued" ? "Queued" : "Processing";
392
393 process.stdout.write(`${statusText}: [${bar}] ${progress.toFixed(1)}%`);
394
395 await new Promise((resolve) => setTimeout(resolve, 2000));
396}
397
398// Clear the progress line and show completion
399process.stdout.write("\n");
400
401if (video.status === "failed") {
402 throw new Error("Video generation failed");
403}
404
405console.log("Video generation completed: ", video);
406
407console.log("Downloading video content...");
408
409const content = await openai.videos.downloadContent(video.id);
410
411const body = content.arrayBuffer();
412const buffer = Buffer.from(await body);
413
414writeFileSync("video.mp4", buffer);
415
416console.log("Wrote video.mp4");
417```
418
419```python
420from openai import OpenAI
421import sys
422import time
423
424
425openai = OpenAI()
426
427video = openai.videos.create(
428 model="sora-2",
429 prompt="A video of a cool cat on a motorcycle in the night",
430)
431
432print("Video generation started:", video)
433
434progress = getattr(video, "progress", 0)
435bar_length = 30
436
437while video.status in ("in_progress", "queued"):
438 # Refresh status
439 video = openai.videos.retrieve(video.id)
440 progress = getattr(video, "progress", 0)
441
442 filled_length = int((progress / 100) * bar_length)
443 bar = "=" * filled_length + "-" * (bar_length - filled_length)
444 status_text = "Queued" if video.status == "queued" else "Processing"
445
446 sys.stdout.write(f"\r{status_text}: [{bar}] {progress:.1f}%")
447 sys.stdout.flush()
448 time.sleep(2)
449
450# Move to next line after progress loop
451sys.stdout.write("\n")
452
453if video.status == "failed":
454 message = getattr(
455 getattr(video, "error", None), "message", "Video generation failed"
456 )
457 raise RuntimeError(message)
458
459print("Video generation completed:", video)
460print("Downloading video content...")
461
462content = openai.videos.download_content(video.id, variant="video")
463content.write_to_file("video.mp4")
464
465print("Wrote video.mp4")
466```
467
468```go
469package main
470
471import (
472 "context"
473 "fmt"
474 "io"
475 "os"
476
477 "github.com/openai/openai-go/v3"
478)
479
480func main() {
481 client := openai.NewClient()
482 video, err := client.Videos.NewAndPoll(context.Background(), openai.VideoNewParams{
483 Model: openai.VideoModelSora2,
484 Prompt: "A video of the words 'Thank you' in sparkling letters",
485 }, 2000)
486 if err != nil {
487 panic(err)
488 }
489 if video.Status != openai.VideoStatusCompleted {
490 panic(fmt.Errorf("video generation failed with status %s", video.Status))
491 }
492
493 response, err := client.Videos.DownloadContent(context.Background(), video.ID, openai.VideoDownloadContentParams{})
494 if err != nil {
495 panic(err)
496 }
497 defer response.Body.Close()
498 file, err := os.Create("video.mp4")
499 if err != nil {
500 panic(err)
501 }
502 if _, err := io.Copy(file, response.Body); err != nil {
503 panic(err)
504 }
505 if err := file.Close(); err != nil {
506 panic(err)
507 }
508 fmt.Println("Wrote video.mp4")
509}
510```
511
512```java
513import com.openai.client.OpenAIClient;
514import com.openai.client.okhttp.OpenAIOkHttpClient;
515import com.openai.models.videos.Video;
516import com.openai.models.videos.VideoCreateParams;
517import java.nio.file.Files;
518import java.nio.file.Path;
519import java.nio.file.StandardCopyOption;
520
521var video =
522 client
523 .videos()
524 .create(
525 VideoCreateParams.builder()
526 .model("sora-2")
527 .prompt("A video of the words 'Thank you' in sparkling letters")
528 .build());
529
530while (video.status().equals(Video.Status.QUEUED)
531 || video.status().equals(Video.Status.IN_PROGRESS)) {
532 Thread.sleep(1000);
533 video = client.videos().retrieve(video.id());
534}
535if (!video.status().equals(Video.Status.COMPLETED)) {
536 throw new IllegalStateException("Video generation failed: " + video.status());
537}
538try (var content = client.videos().downloadContent(video.id())) {
539 Files.copy(content.body(), Path.of("video.mp4"), StandardCopyOption.REPLACE_EXISTING);
540}
541System.out.println("Wrote video.mp4");
542```
543
544```ruby
545require "openai"
546
547client = OpenAI::Client.new
548video = client.videos.create(
549 model: "sora-2",
550 prompt: "A video of the words 'Thank you' in sparkling letters"
551)
552pending_statuses = [
553 OpenAI::Models::Video::Status::QUEUED,
554 OpenAI::Models::Video::Status::IN_PROGRESS
555]
556while pending_statuses.include?(video.status)
557 sleep(2)
558 video = client.videos.retrieve(video.id)
559end
560raise "Video generation failed" if video.status == OpenAI::Models::Video::Status::FAILED
561
562content = client.videos.download_content(video.id)
563File.binwrite("video.mp4", content.read)
564puts("Wrote video.mp4")
565```
566
567```bash
568curl -L "https://api.openai.com/v1/videos/video_abc123/content" \
569 -H "Authorization: Bearer $OPENAI_API_KEY" \
570 --output video.mp4
571```
572
573
574You now have the final video file ready for playback, editing, or distribution. Download URLs are valid for a maximum of 1 hour after generation. If you need long-term storage, copy the file to your own storage system promptly.
575
576#### Download supporting assets
577
578For each completed video, you can also download a **thumbnail** and a **spritesheet**. These are lightweight assets useful for previews, scrubbers, or catalog displays. Use the `variant` query parameter to specify what you want to download. The default is `variant=video` for the MP4.
579
580```bash
581# Download a thumbnail
582curl -L "https://api.openai.com/v1/videos/video_abc123/content?variant=thumbnail" \
583 -H "Authorization: Bearer $OPENAI_API_KEY" \
584 --output thumbnail.webp
585
586# Download a spritesheet
587curl -L "https://api.openai.com/v1/videos/video_abc123/content?variant=spritesheet" \
588 -H "Authorization: Bearer $OPENAI_API_KEY" \
589 --output spritesheet.jpg
590```
591
592
593## Use image references
594
595You can guide a generation with an input image, which acts as **the first frame of your video**. This is useful if you need the output video to preserve the look of a brand asset, a character, or a specific environment.
596
597Choose the `input_reference` format based on the request type:
598
599- Use `input_reference` with an uploaded image in `multipart/form-data` requests.
600- Use `input_reference` with a JSON object in `application/json` requests, including Batch. The JSON form accepts either `file_id` or `image_url`.
601
602The image must match the target video's resolution (`size`).
603
604Supported file formats are `image/jpeg`, `image/png`, and `image/webp`.
605
606```bash
607curl -X POST "https://api.openai.com/v1/videos" \
608 -H "Authorization: Bearer $OPENAI_API_KEY" \
609 -H "Content-Type: multipart/form-data" \
610 -F prompt="She turns around and smiles, then slowly walks out of the frame." \
611 -F model="sora-2-pro" \
612 -F size="1280x720" \
613 -F seconds="8" \
614 -F input_reference="@sample_720p.jpeg;type=image/jpeg"
615```
616
617
618| Input image generated with [OpenAI GPT Image](https://developers.openai.com/api/docs/guides/image-generation) | Generated video using Sora 2 (converted to GIF) |
619| :---------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------: |
620| ![][sora_woman_skyline_original][Download this image](https://cdn.openai.com/API/docs/images/sora/woman_skyline_original_720p.jpeg) | ![][sora_woman_skyline_video] Prompt: _“She turns around and smiles, then slowly walks out of the frame.”_ |
621| ![][sora_monster_original_jpeg][Download this image](https://cdn.openai.com/API/docs/images/sora/monster_original_720p.jpeg) | ![][sora_monster_original_gif] Prompt: _“The fridge door opens. A cute, chubby purple monster comes out of it.”_ |
622
623## Use characters for consistency
624
625Characters let you upload a reusable non-human subject and reference it across multiple generations. This is useful when you want an animal, mascot, or object to keep the same core appearance, styling, and screen presence across several shots.
626
627Character uploads currently work best with short `2`- to `4`-second clips in
628 `16:9` or `9:16`, at `720p` to `1080p`. Character source videos work best when
629 they match the aspect ratio of the requested output. If the aspect ratios
630 differ, the character can appear stretched or distorted. A single video can
631 include up to two characters.
632
633Characters are different from `input_reference`. An image reference conditions
634the opening frame of a single generation, while a character asset can be reused
635across future video requests.
636
637Create the character by uploading a short MP4 clip to `POST /v1/videos/characters`, then include the returned character ID in the `characters` array when you create a video.
638
639Character uploads that depict human likeness are blocked by default. Contact
640 your account manager or [reach out to our sales
641 team](https://openai.com/contact-sales/) to learn more about eligibility for
642 human-likeness access.
643
644```bash
645curl -X POST "https://api.openai.com/v1/videos/characters" \
646 -H "Authorization: Bearer $OPENAI_API_KEY" \
647 -H "Content-Type: multipart/form-data" \
648 -F "video=@character.mp4;type=video/mp4" \
649 -F "name=Mossy"
650```
651
652
653Mention the character name verbatim in your prompt. Passing the character ID
654alone isn't enough to reliably preserve the character in the shot.
655
656Characters can be combined with `input_reference`. Extensions don't support
657characters.
658
659```bash
660curl -X POST "https://api.openai.com/v1/videos" \
661 -H "Authorization: Bearer $OPENAI_API_KEY" \
662 -H "Content-Type: application/json" \
663 -d '{
664 "model": "sora-2",
665 "prompt": "A cinematic tracking shot of Mossy, a moss-covered teapot mascot, weaving through a lantern-lit market at dusk.",
666 "size": "1280x720",
667 "seconds": "8",
668 "characters": [
669 { "id": "char_123" }
670 ]
671 }'
672```
673
674
675## Extend completed videos
676
677Video extensions let you continue an existing completed video and create a new stitched result. Provide the source video in the `video` field to `POST /v1/videos/extensions`, add a prompt describing how the scene should continue, and the API generates the next segment using the full source clip as context.
678
679Use extensions when you want to preserve motion, camera direction, and scene continuity. If you only need to control the opening frame of a new generation, use `input_reference` instead.
680
681Each extension can add up to `20` seconds. A single video can be extended up
682 to six times, for a maximum total length of `120` seconds. Extensions
683 currently accept only a source video and prompt. They don't support characters
684 or image references.
685
686```bash
687curl -X POST "https://api.openai.com/v1/videos/extensions" \
688 -H "Authorization: Bearer $OPENAI_API_KEY" \
689 -H "Content-Type: application/json" \
690 -d '{
691 "video": {
692 "id": "video_abc123"
693 },
694 "prompt": "Continue the scene as the camera rises over the rooftops and reveals the sunrise.",
695 "seconds": "8"
696 }'
697```
698
699
700## Edit existing videos
701
702Editing lets you take an existing video and make targeted adjustments without regenerating everything from scratch. Send `POST /v1/videos/edits` with a prompt and a `video` reference, and the system reuses the original structure, continuity, and composition while applying the modification. This works best when you make a single, well-defined change because smaller, focused edits preserve more of the original fidelity and reduce the risk of introducing artifacts.
703
704Video generations could previously be edited using the remix endpoint, which
705 is being deprecated. Use the edits endpoint for new integrations.
706
707The `video` field accepts either a video ID or an uploaded video. If you pass a
708video ID, the API infers the model from the source video.
709
710Editing uploaded videos is only available to eligible customers. Contact your
711 account manager or [reach out to our sales
712 team](https://openai.com/contact-sales/) if you need this workflow.
713
714```bash
715curl -X POST "https://api.openai.com/v1/videos/edits" \
716 -H "Authorization: Bearer $OPENAI_API_KEY" \
717 -H "Content-Type: application/json" \
718 -d '{
719 "video": {
720 "id": "video_abc123"
721 },
722 "prompt": "Shift the color palette to teal, sand, and rust, with a warm backlight."
723 }'
724```
725
726
727If you upload a new video instead of editing an existing generation, set
728`model` explicitly in the request.
729
730```bash
731curl -X POST "https://api.openai.com/v1/videos/edits" \
732 -H "Authorization: Bearer $OPENAI_API_KEY" \
733 -H "Content-Type: multipart/form-data" \
734 -F "video=@source.mp4;type=video/mp4" \
735 -F "model=sora-2-pro" \
736 -F "prompt=Shift the color palette to teal, sand, and rust, with a warm backlight."
737```
738
739
740Editing is especially valuable for iteration because it lets you refine without discarding what already works. By constraining each edit to one clear adjustment, you keep the visual style, subject consistency, and camera framing stable, while still exploring variations in mood, palette, or staging. This makes it far easier to build polished sequences through small, reliable steps.
741
742| Original video | Edited generated video |
743| :----------------------------: | :-----------------------------------------------------------------------------: |
744| ![][sora_monster_original_gif] | ![][sora_monster_orange] Prompt: _“Change the color of the monster to orange.”_ |
745| ![][sora_monster_original_gif] | ![][sora_monster_2monsters] Prompt: _“A second monster comes out right after.”_ |
746
747## Run video jobs through the Batch API
748
749Use the [Batch API](https://developers.openai.com/api/docs/guides/batch) when you need to queue many video renders for offline processing, review pipelines, or studio workflows. Each line in the batch input file uses the same JSON request body you would send to `POST /v1/videos`, which makes it a good fit for shot lists and scheduled render queues.
750
751For video generation in Batch:
752
753- Batch currently supports `POST /v1/videos` only.
754- Batch requests must use JSON, not multipart.
755- Upload assets ahead of time and reference them from the JSON request body.
756- Use `input_reference` for image-guided generations in Batch. In JSON requests, pass `input_reference` as an object with either `file_id` or `image_url`.
757- Multipart `input_reference` uploads, including video reference inputs, aren't supported in Batch.
758- Batch-generated videos are available for download for up to `24` hours after the batch completes.
759
760```jsonl
761{"custom_id":"shot-001","method":"POST","url":"/v1/videos","body":{"model":"sora-2-pro","prompt":"Slow dolly shot through a miniature paper city at blue hour, soft fog, practical window lights flickering on.","size":"1920x1080","seconds":"20"}}
762{"custom_id":"shot-002","method":"POST","url":"/v1/videos","body":{"model":"sora-2-pro","prompt":"Portrait close-up of a red panda chef plating noodles in a stainless-steel kitchen, shallow depth of field.","size":"1080x1920","seconds":"16"}}
763```
764
765When a batch reaches `completed`, the video jobs in its output have already reached a terminal state such as `completed`, `failed`, or `expired`. Use stable `custom_id` values so you can map batch results back to your internal shot IDs, editorial queue, or asset pipeline, then download final assets with the returned video IDs.
766
767## Maintain your library
768
769Use `GET /videos` to enumerate your videos. The endpoint supports optional query parameters for pagination and sorting.
770
771```bash
772curl "https://api.openai.com/v1/videos?limit=20&after=video_123&order=asc" \
773 -H "Authorization: Bearer $OPENAI_API_KEY" | jq .
774```
775
776
777Use `DELETE /videos/{video_id}` to remove videos you no longer need from OpenAI’s storage.
778
779```bash
780curl -X DELETE "https://api.openai.com/v1/videos/REPLACE_WITH_YOUR_VIDEO_ID" \
781 -H "Authorization: Bearer $OPENAI_API_KEY" | jq .
782```
783
784
785[sora_woman_skyline_original]: https://cdn.openai.com/API/docs/images/sora/sora_woman_skyline_original_2.jpeg
786[sora_woman_skyline_video]: https://cdn.openai.com/API/docs/images/sora/sora_woman_skyline_video.gif
787[sora_monster_original_jpeg]: https://cdn.openai.com/API/docs/images/sora/sora_monster_original_2.jpeg
788[sora_monster_original_gif]: https://cdn.openai.com/API/docs/images/sora/sora_monster_original.gif
789[sora_monster_orange]: https://cdn.openai.com/API/docs/images/sora/sora_monster_orange.gif
790[sora_monster_2monsters]: https://cdn.openai.com/API/docs/images/sora/sora_monster_2monsters.gif