> ## Documentation Index
> Fetch the complete documentation index at: https://hobbyist-e43fa225.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Sora 2 video generation overview (preview)

> Learn about Sora 2, an AI model for generating realistic and imaginative video scenes from text instructions, including safety, limitations, and supported features.

export const ZonePivot = ({group, options = [], defaultValue, label = "Choose an experience"}) => {
  const values = options.map(option => option.id);
  const optionKey = options.map(option => `${option.id}:${option.title}`).join("|");
  const [activePivot, setActivePivot] = useState(defaultValue || values[0]);
  const slugify = value => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
  const resolvePivot = () => {
    if (typeof window === "undefined") return defaultValue || values[0];
    const params = new URLSearchParams(window.location.search);
    const requested = params.get("pivots");
    if (requested) {
      const requestedIds = requested.split(",").map(value => value.trim()).filter(Boolean);
      const match = requestedIds.find(id => values.includes(id));
      if (match) return match;
    }
    const hash = window.location.hash.replace(/^#/, "");
    if (hash) {
      const match = options.find(option => option.id === hash || slugify(option.title) === hash);
      if (match) return match.id;
    }
    try {
      const stored = window.localStorage.getItem(`foundry-zone-pivot:${group}`);
      if (values.includes(stored)) return stored;
    } catch {
      return defaultValue || values[0];
    }
    return defaultValue || values[0];
  };
  const publishPivotChange = value => {
    if (typeof window === "undefined") return;
    window.dispatchEvent(new CustomEvent("foundry-zone-pivot-change", {
      detail: {
        group,
        value
      }
    }));
  };
  const syncTableOfContents = () => {
    if (typeof window === "undefined") return;
    window.requestAnimationFrame(() => {
      const toc = document.getElementById("table-of-contents-content");
      if (!toc) return;
      const links = Array.from(toc.querySelectorAll('a[href^="#"]'));
      for (const link of links) {
        const item = link.closest("li");
        const rawId = link.getAttribute("href")?.slice(1);
        if (!item || !rawId) continue;
        let id = rawId;
        try {
          id = decodeURIComponent(rawId);
        } catch {}
        item.style.display = document.getElementById(id) ? "" : "none";
      }
    });
  };
  useEffect(() => {
    const resolvedPivot = resolvePivot();
    setActivePivot(resolvedPivot);
    publishPivotChange(resolvedPivot);
    window.setTimeout(syncTableOfContents, 0);
  }, [group, defaultValue, values.join("|"), optionKey]);
  const selectPivot = value => {
    setActivePivot(value);
    if (typeof window !== "undefined") {
      try {
        window.localStorage.setItem(`foundry-zone-pivot:${group}`, value);
      } catch {}
      const url = new URL(window.location.href);
      const current = url.searchParams.get("pivots");
      const preserved = current ? current.split(",").map(id => id.trim()).filter(id => id && !values.includes(id)) : [];
      url.searchParams.set("pivots", [...preserved, value].join(","));
      window.history.replaceState(null, "", `${url.pathname}${url.search}${url.hash}`);
    }
    publishPivotChange(value);
    window.setTimeout(syncTableOfContents, 0);
  };
  if (options.length < 2) return null;
  return <div className="not-prose my-6 border-b border-slate-200 pb-3 dark:border-slate-800">
      <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
        {label}
      </div>
      <div className="flex flex-wrap gap-2" role="tablist" aria-label={label}>
        {options.map(option => {
    const selected = option.id === activePivot;
    return <button key={option.id} type="button" role="tab" aria-selected={selected} onClick={() => selectPivot(option.id)} className={`rounded-md border px-3 py-1.5 text-sm font-medium transition ${selected ? "border-slate-900 bg-slate-900 text-white shadow-sm dark:border-slate-100 dark:bg-slate-100 dark:text-slate-950" : "border-slate-200 bg-white text-slate-700 hover:border-slate-400 hover:text-slate-950 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-200 dark:hover:border-slate-500"}`}>
              {option.title}
            </button>;
  })}
      </div>
    </div>;
};

export const ZoneContent = ({group, value, options = [], values = [], defaultValue, children}) => {
  const optionKey = options.map(option => `${option.id}:${option.title}`).join("|");
  const [activePivot, setActivePivot] = useState(defaultValue || values[0]);
  const slugify = value => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
  const resolvePivot = () => {
    if (typeof window === "undefined") return defaultValue || values[0];
    const params = new URLSearchParams(window.location.search);
    const requested = params.get("pivots");
    if (requested) {
      const requestedIds = requested.split(",").map(value => value.trim()).filter(Boolean);
      const match = requestedIds.find(id => values.includes(id));
      if (match) return match;
    }
    const hash = window.location.hash.replace(/^#/, "");
    if (hash) {
      const match = options.find(option => option.id === hash || slugify(option.title) === hash);
      if (match) return match.id;
    }
    try {
      const stored = window.localStorage.getItem(`foundry-zone-pivot:${group}`);
      if (values.includes(stored)) return stored;
    } catch {
      return defaultValue || values[0];
    }
    return defaultValue || values[0];
  };
  useEffect(() => {
    setActivePivot(resolvePivot());
  }, [group, defaultValue, values.join("|"), optionKey]);
  useEffect(() => {
    const onPivotChange = event => {
      if (event.detail?.group === group && values.includes(event.detail.value)) {
        setActivePivot(event.detail.value);
      }
    };
    window.addEventListener("foundry-zone-pivot-change", onPivotChange);
    return () => window.removeEventListener("foundry-zone-pivot-change", onPivotChange);
  }, [group, values.join("|")]);
  if (activePivot !== value) return null;
  return <>{children}</>;
};

Sora 2 is an AI model from OpenAI that creates realistic and imaginative video scenes from text instructions and/or input images or video. The model can generate a wide range of video content, including realistic scenes, animations, and special effects. It supports several video resolutions and durations.

## Capabilities

* **Modalities**: text → video, image → video, video (generated) → video
* **Audio**: Sora 2 supports audio generation in output videos (similar to the Sora app).
* **Remix**: Sora 2 introduces the ability to remix existing videos by making targeted adjustments instead of regenerating from scratch.
* **Responsible AI and video generation**: Azure OpenAI's video generation models include built-in Responsible AI (RAI) protections to help ensure safe and compliant use. Sora 2 blocks all IP and photorealistic content.

  In addition, Azure provides input and output moderation across all image generation models, along with Azure-specific safeguards such as content filtering and abuse monitoring. These systems help detect and prevent the generation or misuse of harmful, unsafe, or policy-violating content.

  Customers can learn more about these safeguards and how to customize them on the [Content filtering](https://learn.microsoft.com/azure/ai-foundry/openai/concepts/content-filter) page.

## Model details

Sora 2 uses the Azure OpenAI [v1 API](../api-version-lifecycle), aligning with OpenAI's native Sora 2 schema.

| Aspect                     | **Sora 2**                                                                                                       |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Model type**             | Adapts OpenAI's latest Sora 2 API using [v1 API](../api-version-lifecycle)                                       |
| **Modalities supported**   | text → video, image → video, video (generated) → video                                                           |
| **Audio generation**       | ✅ Supported in outputs                                                                                           |
| **Remix capability**       | ✅ Supported — make targeted edits to existing videos                                                             |
| **Performance & fidelity** | Enhanced realism, physics, and temporal consistency                                                              |
| **Billing**                | [Per second billing information](https://azure.microsoft.com/pricing/details/cognitive-services/openai-service/) |

## Quickstart

Generate video clips using the Azure OpenAI service. Video generation is an asynchronous process. You create a job request with your text prompt and video format specifications, and the model processes the request in the background. You check the status of the video generation job and, once it finishes, retrieve the generated video through a download URL. The example uses the Sora 2 model.

<ZonePivot group="ai-foundry-portal__rest-api" options={[{"id": "rest-api", "title": "REST API"}, {"id": "ai-foundry-portal", "title": "Foundry portal"}]} defaultValue="rest-api" />

<ZoneContent group="ai-foundry-portal__rest-api" value="rest-api" options={[{"id": "rest-api", "title": "REST API"}, {"id": "ai-foundry-portal", "title": "Foundry portal"}]} values={["rest-api", "ai-foundry-portal"]} defaultValue="rest-api">
  ### Prerequisites

  * An Azure subscription. [Create one for free](https://azure.microsoft.com/pricing/purchase-options/azure-account?cid=msft_learn).
  * <a href="https://www.python.org/" target="_blank">Python 3.8 or later version</a>. We recommend using Python 3.10 or later, but having at least Python 3.8 is required. If you don't have a suitable version of Python installed, you can follow the instructions in the [VS Code Python Tutorial](https://code.visualstudio.com/docs/python/python-tutorial#_install-a-python-interpreter) for the easiest way of installing Python on your operating system.
  * An Azure OpenAI resource created in one of the supported regions. For more information about region availability, see the [models and versions documentation](https://learn.microsoft.com/azure/ai-foundry/openai/concepts/models#video-generation-models).
  * Then, you need to deploy a `sora` (Sora 2) model with your Azure OpenAI resource. For more information, see [Create a resource and deploy a model with Azure OpenAI](https://learn.microsoft.com/en-us/azure/foundry-classic/openai/how-to/create-resource).

  ## Microsoft Entra ID prerequisites

  For the recommended keyless authentication with Microsoft Entra ID, you need to:

  * Install the [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) used for keyless authentication with Microsoft Entra ID.
  * Assign the `Cognitive Services User` role to your user account. You can assign roles in the Azure portal under **Access control (IAM)** > **Add role assignment**.

  ### Setup

  1. Create a new folder `video-generation-quickstart` and go to the quickstart folder with the following command:

     ```shell theme={null}
     mkdir video-generation-quickstart && cd video-generation-quickstart
     ```

  2. Create a virtual environment. If you already have Python 3.10 or higher installed, you can create a virtual environment using the following commands:

     **Windows**

     ```bash theme={null}
     py -3 -m venv .venv
     .venv\scripts\activate
     ```

     **Linux**

     ```bash theme={null}
     python3 -m venv .venv
     source .venv/bin/activate
     ```

     **macOS**

     ```bash theme={null}
     python3 -m venv .venv
     source .venv/bin/activate
     ```

     Activating the Python environment means that when you run `python` or `pip` from the command line, you then use the Python interpreter contained in the `.venv` folder of your application. You can use the `deactivate` command to exit the python virtual environment, and can later reactivate it when needed.

  <Tip>
    We recommend that you create and activate a new Python environment to use to install the packages you need for this tutorial. Don't install packages into your global python installation. You should always use a virtual or conda environment when installing python packages, otherwise you can break your global installation of Python.
  </Tip>

  1. Install the required packages.

     **Microsoft Entra ID**

     ```console theme={null}
     pip install requests azure-identity
     ```

     The [azure-identity](https://learn.microsoft.com/python/api/overview/azure/identity-readme) package provides `DefaultAzureCredential` for secure, keyless authentication.

     **API key**

     ```console theme={null}
     pip install requests
     ```

     The [requests](https://requests.readthedocs.io/) library handles HTTP calls to the REST API.

  ### Retrieve resource information

  You need to retrieve the following information to authenticate your application with your Azure OpenAI resource:

  <Tabs>
    <Tab title="Microsoft Entra ID">
      | Variable name                  | Value                                                                                                                                                                                                     |
      | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `AZURE_OPENAI_ENDPOINT`        | This value can be found in the **Keys and Endpoint** section when examining your resource from the Azure portal.                                                                                          |
      | `AZURE_OPENAI_DEPLOYMENT_NAME` | This value will correspond to the custom name you chose for your deployment when you deployed a model. This value can be found under **Resource Management** > **Model Deployments** in the Azure portal. |

      Learn more about [keyless authentication](https://learn.microsoft.com/azure/ai-services/authentication) and [setting environment variables](https://learn.microsoft.com/azure/ai-services/cognitive-services-environment-variables).
    </Tab>

    <Tab title="API key">
      | Variable name                  | Value                                                                                                                                                                                                     |
      | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `AZURE_OPENAI_ENDPOINT`        | This value can be found in the **Keys and Endpoint** section when examining your resource from the Azure portal.                                                                                          |
      | `AZURE_OPENAI_API_KEY`         | This value can be found in the **Keys and Endpoint** section when examining your resource from the Azure portal. You can use either `KEY1` or `KEY2`.                                                     |
      | `AZURE_OPENAI_DEPLOYMENT_NAME` | This value will correspond to the custom name you chose for your deployment when you deployed a model. This value can be found under **Resource Management** > **Model Deployments** in the Azure portal. |

      Learn more about [finding API keys](https://learn.microsoft.com/azure/ai-services/cognitive-services-environment-variables) and [setting environment variables](https://learn.microsoft.com/azure/ai-services/cognitive-services-environment-variables).
    </Tab>
  </Tabs>

  ### Generate video with Sora 2

  You can generate a video with the Sora 2 model by creating a video generation job, polling for its status, and retrieving the generated video. The following code shows how to do this via the REST API using Python.

  ### Choose your input type

  Sora 2 supports three input modes:

  | Input type              | Best for                                                | Example use case                     |
  | ----------------------- | ------------------------------------------------------- | ------------------------------------ |
  | **Text prompt only**    | Creating entirely new scenes from descriptions          | "A cat playing piano in a jazz bar"  |
  | **Image + text prompt** | Animating a still image or using it as a starting frame | Bring a product photo to life        |
  | **Video + text prompt** | Extending or modifying existing video footage           | Add visual effects to existing clips |

  ### Set up authentication

  1. Create the `sora-quickstart.py` file and add the following code to authenticate your resource:

     **Microsoft Entra ID**

     ```python theme={null}
     import json
     import requests
     import time
     import os
     from azure.identity import DefaultAzureCredential

     # Set environment variables or edit the corresponding values here.
     endpoint = os.environ.get('AZURE_OPENAI_ENDPOINT')
     deployment_name = os.environ.get('AZURE_OPENAI_DEPLOYMENT_NAME')
     if not endpoint or not deployment_name:
         raise ValueError("Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME.")

     # Keyless authentication
     credential = DefaultAzureCredential()
     token = credential.get_token("https://ai.azure.com/.default")

     # Video generation uses 'preview' as the API version during the preview period
     api_version = 'preview'
     headers= { "Authorization": f"Bearer {token.token}", "Content-Type": "application/json" }
     ```

     **API key**

     ```python theme={null}
     import json
     import requests
     import time
     import os

     # Set environment variables or edit the corresponding values here.
     endpoint = os.environ.get('AZURE_OPENAI_ENDPOINT')
     deployment_name = os.environ.get('AZURE_OPENAI_DEPLOYMENT_NAME')
     api_key = os.environ.get('AZURE_OPENAI_API_KEY')
     if not endpoint or not deployment_name or not api_key:
         raise ValueError(
             "Set AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME, and AZURE_OPENAI_API_KEY."
         )

     # Video generation uses 'preview' as the API version during the preview period
     api_version = 'preview'
     headers= { "api-key": api_key, "Content-Type": "application/json" }
     ```

  ### Create the video generation job

  1. Add the code to create and monitor the video generation job. Choose the input type that matches your use case.

     **Text prompt**

     ```python theme={null}
     # 1. Create a video generation job
     create_url = f"{endpoint}/openai/v1/video/generations/jobs?api-version={api_version}"
     body = {
         "prompt": "A cat playing piano in a jazz bar.",
         "width": 480,
         "height": 480,
         "n_seconds": 5,
         "model": deployment_name
     }
     response = requests.post(create_url, headers=headers, json=body)
     response.raise_for_status()
     print("Full response JSON:", response.json())
     job_id = response.json()["id"]
     print(f"Job created: {job_id}")

     # 2. Poll for job status
     status_url = f"{endpoint}/openai/v1/video/generations/jobs/{job_id}?api-version={api_version}"
     status=None
     while status not in ("succeeded", "failed", "cancelled"):
         time.sleep(5)  # Wait before polling again
         status_response = requests.get(status_url, headers=headers).json()
         status = status_response.get("status")
         print(f"Job status: {status}")
         
     # 3. Retrieve generated video 
     if status == "succeeded":
         generations = status_response.get("generations", [])
         if generations:
             print(f"✅ Video generation succeeded.")
             generation_id = generations[0].get("id")
             video_url = f"{endpoint}/openai/v1/video/generations/{generation_id}/content/video?api-version={api_version}"
             video_response = requests.get(video_url, headers=headers)
             if video_response.ok:
                 output_filename = "output.mp4"
                 with open(output_filename, "wb") as file:
                     file.write(video_response.content)
                     print(f'Generated video saved as "{output_filename}"')
         else:
             raise Exception("No generations found in job result.")
     else:
         raise Exception(f"Job didn't succeed. Status: {status}")
     ```

     **Image prompt**

     Replace the `"file_name"` field in `"inpaint_items"` with the name of your input image file. Also replace the construction of the `files` array, which associates the path to the actual file with the filename that the API uses.

     Use the `"crop_bounds"` data (image crop distances, from each direction, as a fraction of the total image dimensions) to specify which part of the image should be used in video generation.

     You can optionally set the `"frame_index"` to the frame in the generated video where your image should appear (the default is 0, the start of the video).

     The `"n_variants"` parameter specifies how many different video variations to generate from the same prompt (1 to 4). Each variant provides a unique interpretation of your input.

     ```python theme={null}
     # 1. Create a video generation job with image inpainting (multipart upload)
     create_url = f"{endpoint}/openai/v1/video/generations/jobs?api-version={api_version}"

     # Flatten the body for multipart/form-data
     data = {
         "prompt": "A serene forest scene transitioning into autumn",
         "height": str(1080),
         "width": str(1920),
         "n_seconds": str(10),
         "n_variants": str(1),
         "model": deployment_name,
         # inpaint_items must be JSON string
         "inpaint_items": json.dumps([
             {
                 "frame_index": 0,
                 "type": "image",
                 "file_name": "dog_swimming.jpg",
                 "crop_bounds": {
                     "left_fraction": 0.1,
                     "top_fraction": 0.1,
                     "right_fraction": 0.9,
                     "bottom_fraction": 0.9
                 }
             }
         ])
     }

     # Replace with your own image file path
     with open("dog_swimming.jpg", "rb") as image_file:
         files = [
             ("files", ("dog_swimming.jpg", image_file, "image/jpeg"))
         ]
         multipart_headers = {k: v for k, v in headers.items() if k.lower() != "content-type"}
         response = requests.post(
             create_url,
             headers=multipart_headers,
             data=data,
             files=files
         )

     if not response.ok:
         print("Error response:", response.status_code, response.text)
         response.raise_for_status()
     print("Full response JSON:", response.json())
     job_id = response.json()["id"]
     print(f"Job created: {job_id}")

     # 2. Poll for job status
     status_url = f"{endpoint}/openai/v1/video/generations/jobs/{job_id}?api-version={api_version}"
     status = None
     while status not in ("succeeded", "failed", "cancelled"):
         time.sleep(5)
         status_response = requests.get(status_url, headers=headers).json()
         status = status_response.get("status")
         print(f"Job status: {status}")

     # 3. Retrieve generated video
     if status == "succeeded":
         generations = status_response.get("generations", [])
         if generations:
             generation_id = generations[0].get("id")
             video_url = f"{endpoint}/openai/v1/video/generations/{generation_id}/content/video?api-version={api_version}"
             video_response = requests.get(video_url, headers=headers)
             if video_response.ok:
                 output_filename = "output.mp4"
                 with open(output_filename, "wb") as file:
                     file.write(video_response.content)
                     print(f'✅ Generated video saved as "{output_filename}"')
         else:
             raise Exception("No generations found in job result.")
     else:
         raise Exception(f"Job didn't succeed. Status: {status}")
     ```

     **Video prompt**

     Replace the `"file_name"` field in `"inpaint_items"` with the name of your input video file. Also replace the construction of the `files` array, which associates the path to the actual file with the filename that the API uses.

     Use the `"crop_bounds"` data (image crop distances, from each direction, as a fraction of the total frame dimensions) to specify which part of the video frame should be used in video generation.

     You can optionally set the `"frame_index"` to the frame in the generated video where your input video should start (the default is 0, the beginning).

     ```python theme={null}
     # 1. Create a video generation job with video inpainting (multipart upload)
     create_url = f"{endpoint}/openai/v1/video/generations/jobs?api-version={api_version}"

     # Flatten the body for multipart/form-data
     data = {
         "prompt": "A serene forest scene transitioning into autumn",
         "height": str(1080),
         "width": str(1920),
         "n_seconds": str(10),
         "n_variants": str(1),
         "model": deployment_name,
         # inpaint_items must be JSON string
         "inpaint_items": json.dumps([
             {
                 "frame_index": 0,
                 "type": "video",
                 "file_name": "dog_swimming.mp4",
                 "crop_bounds": {
                     "left_fraction": 0.1,
                     "top_fraction": 0.1,
                     "right_fraction": 0.9,
                     "bottom_fraction": 0.9
                 }
             }
         ])
     }

     # Replace with your own video file path
     with open("dog_swimming.mp4", "rb") as video_file:
         files = [
             ("files", ("dog_swimming.mp4", video_file, "video/mp4"))
         ]
         multipart_headers = {k: v for k, v in headers.items() if k.lower() != "content-type"}
         response = requests.post(
             create_url,
             headers=multipart_headers,
             data=data,
             files=files
         )

     if not response.ok:
         print("Error response:", response.status_code, response.text)
         response.raise_for_status()
     print("Full response JSON:", response.json())
     job_id = response.json()["id"]
     print(f"Job created: {job_id}")

     # 2. Poll for job status
     status_url = f"{endpoint}/openai/v1/video/generations/jobs/{job_id}?api-version={api_version}"
     status = None
     while status not in ("succeeded", "failed", "cancelled"):
         time.sleep(5)
         status_response = requests.get(status_url, headers=headers).json()
         status = status_response.get("status")
         print(f"Job status: {status}")

     # 3. Retrieve generated video
     if status == "succeeded":
         generations = status_response.get("generations", [])
         if generations:
             generation_id = generations[0].get("id")
             video_url = f"{endpoint}/openai/v1/video/generations/{generation_id}/content/video?api-version={api_version}"
             video_response = requests.get(video_url, headers=headers)
             if video_response.ok:
                 output_filename = "output.mp4"
                 with open(output_filename, "wb") as file:
                     file.write(video_response.content)
                     print(f'✅ Generated video saved as "{output_filename}"')
         else:
             raise Exception("No generations found in job result.")
     else:
         raise Exception(f"Job didn't succeed. Status: {status}")
     ```
  2. Run the Python file.

     ```shell theme={null}
     python sora-quickstart.py
     ```

     Video generation typically takes **1 to 5 minutes** depending on the resolution and duration. You should see status updates in your terminal as the job progresses through `queued`, `preprocessing`, `running`, `processing`, and finally `succeeded`.

  ### Output

  The output will show the full response JSON from the video generation job creation request, including the job ID and status.

  ```json theme={null}
  {
      "object": "video.generation.job",
      "id": "task_01jwcet0eje35tc5jy54yjax5q",
      "status": "queued",
      "created_at": 1748469875,
      "finished_at": null,
      "expires_at": null,
      "generations": [],
      "prompt": "A cat playing piano in a jazz bar.",
      "model": "<your-deployment-name>",
      "n_variants": 1,
      "n_seconds": 5,
      "height": 480,
      "width": 480,
      "failure_reason": null
  }
  ```

  The generated video will be saved as `output.mp4` in the current directory.

  ```text theme={null}
  Job created: task_01jwcet0eje35tc5jy54yjax5q
  Job status: preprocessing
  Job status: running
  Job status: processing
  Job status: succeeded
  ✅ Video generation succeeded.
  Generated video saved as "output.mp4"
  ```

  ## Troubleshooting

  If you encounter issues, check the following common problems and solutions:

  | Error                                  | Cause                                      | Solution                                                                                                                      |
  | -------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
  | `401 Unauthorized`                     | Invalid or expired credentials             | For Microsoft Entra ID, run `az login` to refresh your token. For API key, verify `AZURE_OPENAI_API_KEY` is correct.          |
  | `403 Forbidden`                        | Missing role assignment                    | Assign the **Cognitive Services User** role to your account in the Azure portal.                                              |
  | `404 Not Found`                        | Incorrect endpoint or deployment name      | Verify `AZURE_OPENAI_ENDPOINT` includes your resource name and `AZURE_OPENAI_DEPLOYMENT_NAME` matches your Sora 2 deployment. |
  | `429 Too Many Requests`                | Rate limit exceeded                        | Wait and retry, or request a quota increase in the Azure portal.                                                              |
  | `400 Bad Request` with dimension error | Unsupported resolution                     | Use a supported resolution: 480×480, 720×720, 1080×1080, 1280×720, or 1920×1080.                                              |
  | Job status `failed`                    | Content policy violation or internal error | Check `failure_reason` in the response. Modify your prompt if it triggered content filtering.                                 |
  | Timeout during polling                 | Long generation time                       | Videos can take up to 5 minutes. Increase your polling timeout or check job status manually.                                  |

  <Tip>
    To debug authentication issues, test your credentials with a simple API call first:

    ```python theme={null}
    # Test endpoint connectivity
    test_url = f"{endpoint}/openai/deployments?api-version=2024-02-01"
    response = requests.get(test_url, headers=headers)
    print(response.status_code, response.text)
    ```
  </Tip>
</ZoneContent>

<ZoneContent group="ai-foundry-portal__rest-api" value="ai-foundry-portal" options={[{"id": "rest-api", "title": "REST API"}, {"id": "ai-foundry-portal", "title": "Foundry portal"}]} values={["rest-api", "ai-foundry-portal"]} defaultValue="rest-api">
  ### Prerequisites

  * An Azure subscription. [Create one for free](https://azure.microsoft.com/pricing/purchase-options/azure-account?cid=msft_learn).
  * An Azure OpenAI resource created in a supported region. See [Region availability](https://learn.microsoft.com/azure/ai-foundry/openai/concepts/models#model-summary-table-and-region-availability). For more information, see [Create a resource and deploy a model with Azure OpenAI](https://learn.microsoft.com/en-us/azure/foundry-classic/openai/how-to/create-resource).

  ### Go to Microsoft Foundry portal

  Browse to the [Foundry portal](https://ai.azure.com/?cid=learnDocs) and sign in with the credentials associated with your Azure OpenAI resource. During or after the sign-in workflow, select the appropriate directory, Azure subscription, and Azure OpenAI resource.

  From the Foundry landing page, create or select a new project. Navigate to the **Models + endpoints** page on the left nav. Select **Deploy model** and then choose the Sora 2 video generation model from the list. Complete the deployment process.

  On the model's page, select **Open in playground**.

  ### Try out video generation

  Start exploring Sora 2 video generation with a no-code approach through the **Video playground**. Enter your prompt into the text box and select **Generate**. Video generation typically takes **1 to 5 minutes** depending on your settings. When the AI-generated video is ready, it appears on the page.

  <Note>
    The content generation APIs come with a content moderation filter. If Azure OpenAI recognizes your prompt as harmful content, it doesn't return a generated video. For more information, see [Content filtering](https://learn.microsoft.com/en-us/azure/foundry-classic/foundry-models/concepts/content-filter).
  </Note>

  In the **Video playground**, you can also view Python and cURL code samples, which are prefilled according to your settings. Select the code button at the top of your video playback pane. You can use this code to write an application that completes the same task.
</ZoneContent>

### Clean up resources

If you want to clean up and remove an Azure OpenAI resource, you can delete the resource. Before deleting the resource, you must first delete any deployed models.

* [Azure portal](../../../ai-services/multi-service-resource)
* [Azure CLI](../../../ai-services/multi-service-resource)

## Responsible AI and video generation

Azure OpenAI's image generation models include built-in Responsible AI (RAI) protections to help ensure safe and compliant use.

In addition, Azure provides input and output moderation across all image generation models, along with Azure-specific safeguards such as content filtering and abuse monitoring. These systems help detect and prevent the generation or misuse of harmful, unsafe, or policy-violating content.

Currently the Sora 2 API enforces several content restrictions:

* Only content suitable for audiences under 18 (a setting to bypass this restriction will be available in the future).
* Copyrighted characters and copyrighted music will be rejected.
* Real people—including public figures—cannot be generated.
* Input images with faces of humans are currently rejected.

Make sure prompts, reference images, and transcripts respect these rules to avoid failed generations.

## Sora 2 API reference

The Sora 2 API provides 5 endpoints, each with distinct capabilities.

* **Create Video**: Start a new render job from a prompt, with optional reference inputs or a remix ID.
* **Get Video Status**: Retrieve the current state of a render job and monitor its progress
* **Download Video**: Fetch the finished MP4 once the job is completed.
* **List Videos**: Enumerate your videos with pagination for history, dashboards, or housekeeping.
* **Delete Videos**: Delete an individual video ID from Azure OpenAI’s storage

### API parameters

| Parameter                                      | Type              | **Sora 2**                                                                                                                                                                                       |
| ---------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Prompt**                                     | String (required) | Natural-language description of the shot. Include shot type, subject, action, setting, lighting, and any desired camera motion to reduce ambiguity. Keep it *single-purpose* for best adherence. |
| **Model**                                      | String (optional) | `Sora-2` (default)                                                                                                                                                                               |
| **Size (Output resolution in width × height)** | String (optional) | Portrait: `720×1280`  <br /> Landscape: `1280×720`  <br /> **Default:** 720×1280                                                                                                                 |
| **Seconds**                                    | String (optional) | `4 / 8 / 12`  <br /> **Default:** 4                                                                                                                                                              |
| **Input reference**                            | File (optional)   | Single reference image used as a visual anchor for the first frame. <br /> Accepted MIME types: `image/jpeg`, `image/png`, `image/webp`. Must match size exactly.                                |
| **Remix\_video\_id**                           | String (optional) | ID of a previously completed video (e.g., `video_...`) to reuse structure, motion, and framing. Same as Sora 2                                                                                   |

Sora 2 API uses the [v1 API](../api-version-lifecycle) and has the same structure as the [OpenAI API](https://platform.openai.com/docs/guides/video-generation).

### videos.create()

You'll need to update to the latest version of the OpenAI client with `pip install openai --upgrade` to prevent `AttributeError: 'OpenAI' object has no attribute 'videos'`.

<Tabs>
  <Tab title="Microsoft Entra ID">
    ```python theme={null}
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cool cat on a motorcycle in the night",
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="API Key">
    ```python theme={null}
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cool cat on a motorcycle in the night",
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Environment Variables">
    If you use the default environment variables of:

    * `OPENAI_BASE_URL`
    * `OPENAI_API_KEY`

    These environment variables are automatically used by the client with no further configuration required.

    | Environment Variable | Value                                                    |
    | -------------------- | -------------------------------------------------------- |
    | `OPENAI_BASE_URL`    | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` |
    | `OPENAI_API_KEY`     | Azure OpenAI or Foundry API key.                         |

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cool cat on a motorcycle in the night",
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null}
    Video generation started: Video(id='video_68f10985d6c4819097007665bdcfba5f', completed_at=None, created_at=1760627077, error=None, expires_at=None, model='sora-2', object='video', progress=0, remixed_from_video_id=None, seconds='4', size='720x1280', status='queued')
    ```
  </Tab>
</Tabs>

### Create a video and poll job status

Call `GET /videos/{video_id}` with the ID returned from the create call. The response shows the job’s current status, progress percentage, and any errors.

Expected states are `queued`, `in_progress`, `completed`, and `failed`.

<Tabs>
  <Tab title="Microsoft Entra ID">
    **Synchronous:**

    Use this version if testing in Jupyter Notebooks to avoid `RuntimeError: asyncio.run() cannot be called from a running event loop`

    ```python theme={null}
    import time
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
        base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
        api_key=token_provider,
    )

    # Create the video (don't use create_and_poll)
    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cat on a motorcycle",
    )

    print(f"Video creation started. ID: {video.id}")
    print(f"Initial status: {video.status}")

    # Poll every 20 seconds
    while video.status not in ["completed", "failed", "cancelled"]:
        print(f"Status: {video.status}. Waiting 20 seconds...")
        time.sleep(20)

        # Retrieve the latest status
        video = client.videos.retrieve(video.id)

    # Final status
    if video.status == "completed":
        print("Video successfully completed!")
        print(video)
    else:
        print(f"Video creation ended with status: {video.status}")
        print(video)
    ```

    **Async:**

    ```python theme={null}
    import asyncio
    from openai import AsyncOpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = AsyncOpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    async def main() -> None:
        video = await client.videos.create_and_poll(
            model="sora-2", # Replace with Sora 2 model deployment name
            prompt="A video of a cat on a motorcycle",
        )

        if video.status == "completed":
            print("Video successfully completed: ", video)
        else:
            print("Video creation failed. Status: ", video.status)

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="API Key">
    **Synchronous:**

    Use this version if testing in Jupyter Notebooks to avoid `RuntimeError: asyncio.run() cannot be called from a running event loop`

    ```python theme={null}
    import asyncio
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    # Create the video (don't use create_and_poll)
    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cat on a motorcycle",
    )

    print(f"Video creation started. ID: {video.id}")
    print(f"Initial status: {video.status}")

    # Poll every 20 seconds
    while video.status not in ["completed", "failed", "cancelled"]:
        print(f"Status: {video.status}. Waiting 20 seconds...")
        time.sleep(20)

        # Retrieve the latest status
        video = client.videos.retrieve(video.id)

    # Final status
    if video.status == "completed":
        print("Video successfully completed!")
        print(video)
    else:
        print(f"Video creation ended with status: {video.status}")
        print(video)
    ```

    **Async:**

    ```python theme={null}
    import asyncio
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    async def main() -> None:
        video = await client.videos.create_and_poll(
            model="sora-2", # Replace with Sora 2 model deployment name 
            prompt="A video of a cat on a motorcycle",
        )

        if video.status == "completed":
            print("Video successfully completed: ", video)
        else:
            print("Video creation failed. Status: ", video.status)

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="Environment Variables">
    If you use the default environment variables of:

    * `OPENAI_BASE_URL`
    * `OPENAI_API_KEY`

    These environment variables are automatically used by the client with no further configuration required.

    | Environment Variable | Value                                                    |
    | -------------------- | -------------------------------------------------------- |
    | `OPENAI_BASE_URL`    | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` |
    | `OPENAI_API_KEY`     | Azure OpenAI or Foundry API key.                         |

    **Synchronous:**

    Use this version if testing in Jupyter Notebooks to avoid `RuntimeError: asyncio.run() cannot be called from a running event loop`

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    # Create the video (don't use create_and_poll)
    video = client.videos.create(
        model="sora-2", # Replace with Sora 2 model deployment name
        prompt="A video of a cat on a motorcycle",
    )

    print(f"Video creation started. ID: {video.id}")
    print(f"Initial status: {video.status}")

    # Poll every 20 seconds
    while video.status not in ["completed", "failed", "cancelled"]:
        print(f"Status: {video.status}. Waiting 20 seconds...")
        time.sleep(20)

        # Retrieve the latest status
        video = client.videos.retrieve(video.id)

    # Final status
    if video.status == "completed":
        print("Video successfully completed!")
        print(video)
    else:
        print(f"Video creation ended with status: {video.status}")
        print(video)
    ```

    **Async:**

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    async def main() -> None:
        video = await client.videos.create_and_poll(
            model="sora-2", # Replace with Sora 2 model deployment name
            prompt="A video of a cat on a motorcycle",
        )

        if video.status == "completed":
            print("Video successfully completed: ", video)
        else:
            print("Video creation failed. Status: ", video.status)

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="Response">
    Response will vary based on if the synchronous or asynchronous version of the code is used.

    ```json theme={null}
    Video creation started. ID: video_68f10c5428708190a98980c2d2b21a78
    Initial status: queued
    Status: queued. Waiting 20 seconds...
    Status: in_progress. Waiting 20 seconds...
    Status: in_progress. Waiting 20 seconds...
    Status: in_progress. Waiting 20 seconds...
    Video successfully completed!
    Video(id='video_68f10c5428708190a98980c2d2b21a78', completed_at=1760627863, created_at=1760627796, error=None, expires_at=1760714196, model='sora-2', object='video', progress=100, remixed_from_video_id=None, seconds='4', size='720x1280', status='completed')
    ```
  </Tab>
</Tabs>

### Download video

<Tabs>
  <Tab title="Microsoft Entra ID">
    ```python theme={null}
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    video_id = "your_video_id_here"

    content = client.videos.download_content(video_id, variant="video")
    content.write_to_file("video.mp4")

    print("Saved video.mp4")
    ```
  </Tab>

  <Tab title="API Key">
    ```python theme={null}
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    video_id = "your_video_id_here"

    content = client.videos.download_content(video_id, variant="video")
    content.write_to_file("video.mp4")

    print("Saved video.mp4")

    ```
  </Tab>

  <Tab title="Environment Variables">
    If you use the default environment variables of:

    * `OPENAI_BASE_URL`
    * `OPENAI_API_KEY`

    These environment variables are automatically used by the client with no further configuration required.

    | Environment Variable | Value                                                    |
    | -------------------- | -------------------------------------------------------- |
    | `OPENAI_BASE_URL`    | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` |
    | `OPENAI_API_KEY`     | Azure OpenAI or Foundry API key.                         |

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    video_id = "your_video_id_here"

    content = client.videos.download_content(video_id, variant="video")
    content.write_to_file("video.mp4")

    print("Saved video.mp4")

    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null}
    Saved video.mp4
    ```
  </Tab>
</Tabs>

### Video generation from reference source

The `input_reference` parameter allows you to transform existing images using Sora 2. The resolution of the source image and final video must match. Supported values are `720x1280`, and `1280x720`.

<Tabs>
  <Tab title="Microsoft Entra ID">
    **Local reference file:**

    ```python theme={null}
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    # With local file
    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=open("test.png", "rb"), # This assumes the image test.png is in the same directory as the executing code
    )

    print("Video generation started:", video)

    ```

    **URL based reference file:**

    ```python theme={null}
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider
    import requests
    from io import BytesIO

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    # With image URL
    image_url = "https://path-to-your-file/image_file_name.jpg"
    response = requests.get(image_url)
    image_data = BytesIO(response.content)
    image_data.name = "image_file_name.jpg"

    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=image_data,
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="API Key">
    **Local reference file:**

    ```python theme={null}
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    # With local file
    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=open("test.png", "rb"), # This assumes the image test.png is in the same directory as the executing code
    )

    print("Video generation started:", video)

    ```

    **URL based reference file:**

    ```python theme={null}
    import os
    from openai import OpenAI
    import requests
    from io import BytesIO

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    # With image URL
    image_url = "https://path-to-your-file/image_file_name.jpg"
    response = requests.get(image_url)
    image_data = BytesIO(response.content)
    image_data.name = "image_file_name.jpg"

    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=image_data,
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Environment Variables">
    If you use the default environment variables of:

    * `OPENAI_BASE_URL`
    * `OPENAI_API_KEY`

    These environment variables are automatically used by the client with no further configuration required.

    | Environment Variable | Value                                                    |
    | -------------------- | -------------------------------------------------------- |
    | `OPENAI_BASE_URL`    | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` |
    | `OPENAI_API_KEY`     | Azure OpenAI or Foundry API key.                         |

    **Local reference file:**

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    # With local file
    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=open("test.png", "rb"), # This assumes the image test.png is in the same directory as the executing code
    )

    print("Video generation started:", video)

    ```

    **URL based reference file:**

    ```python theme={null}
    from openai import OpenAI
    import requests
    from io import BytesIO

    client = OpenAI()

    # With image URL
    image_url = "https://path-to-your-file/image_file_name.jpg"
    response = requests.get(image_url)
    image_data = BytesIO(response.content)
    image_data.name = "image_file_name.jpg"

    video = client.videos.create(
        model="sora-2",
        prompt="Describe your desired output within the context of the reference image/video",
        size="1280x720",
        seconds=8,
        input_reference=image_data,
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null}
    Video generation started: Video(id='video_68ff672709d481908f1fa7c53265d835', completed_at=None, created_at=1761568551, error=None, expires_at=None, model='sora-2', object='video', progress=0, remixed_from_video_id=None, seconds='8', size='1280x720', status='queued')
    ```
  </Tab>
</Tabs>

### Remix video

The remix feature allows you to modify specific aspects of an existing video while preserving its core elements. By referencing the previous video `id` from a successfully completed generation, and supplying an updated prompt the system maintains the original video's framework, scene transitions, and visual layout while implementing your requested changes. For optimal results, limit your modifications to one clearly articulated adjustment—narrow, precise edits retain greater fidelity to the source material and minimize the likelihood of generating visual defects.

<Tabs>
  <Tab title="Microsoft Entra ID">
    ```python theme={null}
    from openai import OpenAI
    from azure.identity import DefaultAzureCredential, get_bearer_token_provider

    token_provider = get_bearer_token_provider(
        DefaultAzureCredential(), "https://ai.azure.com/.default"
    )

    client = OpenAI(  
      base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",  
      api_key=token_provider,
    )

    video = client.videos.remix(
        video_id="<previous_video_id>",
        prompt="Shift the color palette to teal, sand, and rust, with a warm backlight."
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="API Key">
    ```python theme={null}
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
    )

    video = client.videos.remix(
        video_id="<previous_video_id>",
        prompt="Shift the color palette to teal, sand, and rust, with a warm backlight."
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Environment Variables">
    If you use the default environment variables of:

    * `OPENAI_BASE_URL`
    * `OPENAI_API_KEY`

    These environment variables are automatically used by the client with no further configuration required.

    | Environment Variable | Value                                                    |
    | -------------------- | -------------------------------------------------------- |
    | `OPENAI_BASE_URL`    | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` |
    | `OPENAI_API_KEY`     | Azure OpenAI or Foundry API key.                         |

    ```python theme={null}
    from openai import OpenAI

    client = OpenAI()

    video = client.videos.remix(
        video_id="<previous_video_id>",
        prompt="Shift the color palette to teal, sand, and rust, with a warm backlight."
    )

    print("Video generation started:", video)
    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null}
    Video generation started: Video(id='video_68ff7cef76cc8190b7eab9395e936d9e', completed_at=None, created_at=1761574127, error=None, expires_at=None, model='sora-2', object='video', progress=0, remixed_from_video_id='video_68ff61490a908190a6808139c0c753d0', seconds='8', size='1280x720', status='queued')
    ```
  </Tab>
</Tabs>

## Best practices for prompts

Write text prompts in English or other Latin script languages for the best video generation performance.

## Limitations

### Content quality limitations

Sora 2 might have difficulty with complex physics, causal relationships (for example, bite marks on a cookie), spatial reasoning (for example, knowing left from right), and precise time-based event sequencing such as camera movement.

### Technical limitations

Sora 2 video generation is currently in preview. Keep the following limitations in mind:

* Sora 2 supports the following output resolution dimensions:
  480x480, 480x854, 854x480, 720x720, 720x1280, 1280x720, 1080x1080, 1080x1920, 1920x1080.
* Sora 2 can produce videos between 1 and 20 seconds long.
* You can request multiple video variants in a single job: for 1080p resolutions, this feature is disabled; for 720p, the maximum is two variants; for other resolutions, the maximum is four variants.
* You can have two video creation jobs running at the same time. You must wait for one of the jobs to finish before you can create another.
* Jobs are available for up to 24 hours after they're created. After that, you must create a new job to generate the video again.
* You can use up to two images as input (the generated video interpolates content between them).
* You can use one video up to five seconds as input.

## Related content

* [Image generation quickstart](/models/dall-e)

- Learn more about Azure OpenAI [deployment types](/models/deployment-types).
- Learn more about Azure OpenAI [quotas and limits](/models/quotas-limits).
