diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 4808d97..b29b3b6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "5.1.0"
+ ".": "5.2.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index dde3b6e..2ac9ca5 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 43
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-b85d7ddcdd90c3009ccbad953a0f5c1015b6f4acc6196ce47dfaff89873e8831.yml
-openapi_spec_hash: c660abf954cb61f065c4f957966776bb
-config_hash: dbcc649d22e217f477258caee20c63d2
+configured_endpoints: 49
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-ae784dbc3407e86a6458d2848f2e9720eea6b3c534c3bbe101b614e3eb547dac.yml
+openapi_spec_hash: e84013ce9a3d7c14175ba3050cbb5bc1
+config_hash: 702846e1d30f519e56e425ca5455febe
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d0e513e..4889d3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Changelog
+## 5.2.0 (2026-06-17)
+
+Full Changelog: [v5.1.0...v5.2.0](https://github.com/runwayml/sdk-python/compare/v5.1.0...v5.2.0)
+
+### Features
+
+* **api:** Recipes, aleph2 range ([529fc1d](https://github.com/runwayml/sdk-python/commit/529fc1da7b279af30f5aef1314ab16a87a3916f1))
+* **client:** add recipe endpoints to config ([26725b1](https://github.com/runwayml/sdk-python/commit/26725b15a27fbf9437a323bb28238e7d4657ed53))
+* **client:** Make recipes waitable ([11dbef5](https://github.com/runwayml/sdk-python/commit/11dbef5fbf6bd4147d4f6b91f43e260c9b4b2318))
+
+
+### Bug Fixes
+
+* **api:** remove range param ([f67c02d](https://github.com/runwayml/sdk-python/commit/f67c02db8769d481109a7d033b8fa030abdeb56e))
+
## 5.1.0 (2026-06-12)
Full Changelog: [v5.0.0...v5.1.0](https://github.com/runwayml/sdk-python/compare/v5.0.0...v5.1.0)
diff --git a/api.md b/api.md
index adfb5c7..c4eaded 100644
--- a/api.md
+++ b/api.md
@@ -235,6 +235,30 @@ Methods:
- client.realtime_sessions.retrieve(id) -> RealtimeSessionRetrieveResponse
- client.realtime_sessions.delete(id) -> None
+# Recipes
+
+Types:
+
+```python
+from runwayml.types import (
+ RecipeMarketingStockImageResponse,
+ RecipeMultiShotVideoResponse,
+ RecipeProductAdResponse,
+ RecipeProductCampaignImageResponse,
+ RecipeProductSwapResponse,
+ RecipeProductUgcResponse,
+)
+```
+
+Methods:
+
+- client.recipes.marketing_stock_image(\*\*params) -> RecipeMarketingStockImageResponse
+- client.recipes.multi_shot_video(\*\*params) -> RecipeMultiShotVideoResponse
+- client.recipes.product_ad(\*\*params) -> RecipeProductAdResponse
+- client.recipes.product_campaign_image(\*\*params) -> RecipeProductCampaignImageResponse
+- client.recipes.product_swap(\*\*params) -> RecipeProductSwapResponse
+- client.recipes.product_ugc(\*\*params) -> RecipeProductUgcResponse
+
# Voices
Types:
diff --git a/pyproject.toml b/pyproject.toml
index bea7473..6e7c386 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runwayml"
-version = "5.1.0"
+version = "5.2.0"
description = "The official Python library for the runwayml API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/runwayml/_client.py b/src/runwayml/_client.py
index e4ce04c..b134578 100644
--- a/src/runwayml/_client.py
+++ b/src/runwayml/_client.py
@@ -39,6 +39,7 @@
tasks,
voices,
avatars,
+ recipes,
uploads,
documents,
workflows,
@@ -62,6 +63,7 @@
from .resources.tasks import TasksResource, AsyncTasksResource
from .resources.voices import VoicesResource, AsyncVoicesResource
from .resources.avatars import AvatarsResource, AsyncAvatarsResource
+ from .resources.recipes import RecipesResource, AsyncRecipesResource
from .resources.uploads import UploadsResource, AsyncUploadsResource
from .resources.documents import DocumentsResource, AsyncDocumentsResource
from .resources.workflows import WorkflowsResource, AsyncWorkflowsResource
@@ -290,6 +292,12 @@ def realtime_sessions(self) -> RealtimeSessionsResource:
return RealtimeSessionsResource(self)
+ @cached_property
+ def recipes(self) -> RecipesResource:
+ from .resources.recipes import RecipesResource
+
+ return RecipesResource(self)
+
@cached_property
def voices(self) -> VoicesResource:
from .resources.voices import VoicesResource
@@ -620,6 +628,12 @@ def realtime_sessions(self) -> AsyncRealtimeSessionsResource:
return AsyncRealtimeSessionsResource(self)
+ @cached_property
+ def recipes(self) -> AsyncRecipesResource:
+ from .resources.recipes import AsyncRecipesResource
+
+ return AsyncRecipesResource(self)
+
@cached_property
def voices(self) -> AsyncVoicesResource:
from .resources.voices import AsyncVoicesResource
@@ -886,6 +900,12 @@ def realtime_sessions(self) -> realtime_sessions.RealtimeSessionsResourceWithRaw
return RealtimeSessionsResourceWithRawResponse(self._client.realtime_sessions)
+ @cached_property
+ def recipes(self) -> recipes.RecipesResourceWithRawResponse:
+ from .resources.recipes import RecipesResourceWithRawResponse
+
+ return RecipesResourceWithRawResponse(self._client.recipes)
+
@cached_property
def voices(self) -> voices.VoicesResourceWithRawResponse:
from .resources.voices import VoicesResourceWithRawResponse
@@ -1037,6 +1057,12 @@ def realtime_sessions(self) -> realtime_sessions.AsyncRealtimeSessionsResourceWi
return AsyncRealtimeSessionsResourceWithRawResponse(self._client.realtime_sessions)
+ @cached_property
+ def recipes(self) -> recipes.AsyncRecipesResourceWithRawResponse:
+ from .resources.recipes import AsyncRecipesResourceWithRawResponse
+
+ return AsyncRecipesResourceWithRawResponse(self._client.recipes)
+
@cached_property
def voices(self) -> voices.AsyncVoicesResourceWithRawResponse:
from .resources.voices import AsyncVoicesResourceWithRawResponse
@@ -1188,6 +1214,12 @@ def realtime_sessions(self) -> realtime_sessions.RealtimeSessionsResourceWithStr
return RealtimeSessionsResourceWithStreamingResponse(self._client.realtime_sessions)
+ @cached_property
+ def recipes(self) -> recipes.RecipesResourceWithStreamingResponse:
+ from .resources.recipes import RecipesResourceWithStreamingResponse
+
+ return RecipesResourceWithStreamingResponse(self._client.recipes)
+
@cached_property
def voices(self) -> voices.VoicesResourceWithStreamingResponse:
from .resources.voices import VoicesResourceWithStreamingResponse
@@ -1339,6 +1371,12 @@ def realtime_sessions(self) -> realtime_sessions.AsyncRealtimeSessionsResourceWi
return AsyncRealtimeSessionsResourceWithStreamingResponse(self._client.realtime_sessions)
+ @cached_property
+ def recipes(self) -> recipes.AsyncRecipesResourceWithStreamingResponse:
+ from .resources.recipes import AsyncRecipesResourceWithStreamingResponse
+
+ return AsyncRecipesResourceWithStreamingResponse(self._client.recipes)
+
@cached_property
def voices(self) -> voices.AsyncVoicesResourceWithStreamingResponse:
from .resources.voices import AsyncVoicesResourceWithStreamingResponse
diff --git a/src/runwayml/_version.py b/src/runwayml/_version.py
index fd68e66..dfda955 100644
--- a/src/runwayml/_version.py
+++ b/src/runwayml/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runwayml"
-__version__ = "5.1.0" # x-release-please-version
+__version__ = "5.2.0" # x-release-please-version
diff --git a/src/runwayml/resources/__init__.py b/src/runwayml/resources/__init__.py
index 257b9c5..01606f2 100644
--- a/src/runwayml/resources/__init__.py
+++ b/src/runwayml/resources/__init__.py
@@ -24,6 +24,14 @@
AvatarsResourceWithStreamingResponse,
AsyncAvatarsResourceWithStreamingResponse,
)
+from .recipes import (
+ RecipesResource,
+ AsyncRecipesResource,
+ RecipesResourceWithRawResponse,
+ AsyncRecipesResourceWithRawResponse,
+ RecipesResourceWithStreamingResponse,
+ AsyncRecipesResourceWithStreamingResponse,
+)
from .uploads import (
UploadsResource,
AsyncUploadsResource,
@@ -292,6 +300,12 @@
"AsyncRealtimeSessionsResourceWithRawResponse",
"RealtimeSessionsResourceWithStreamingResponse",
"AsyncRealtimeSessionsResourceWithStreamingResponse",
+ "RecipesResource",
+ "AsyncRecipesResource",
+ "RecipesResourceWithRawResponse",
+ "AsyncRecipesResourceWithRawResponse",
+ "RecipesResourceWithStreamingResponse",
+ "AsyncRecipesResourceWithStreamingResponse",
"VoicesResource",
"AsyncVoicesResource",
"VoicesResourceWithRawResponse",
diff --git a/src/runwayml/resources/recipes.py b/src/runwayml/resources/recipes.py
new file mode 100644
index 0000000..2fbb794
--- /dev/null
+++ b/src/runwayml/resources/recipes.py
@@ -0,0 +1,1121 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, overload
+
+import httpx
+
+from runwayml.lib.polling import (
+ NewTaskCreatedResponse,
+ AsyncNewTaskCreatedResponse,
+ create_waitable_resource,
+ create_async_waitable_resource,
+)
+
+from ..types import (
+ recipe_product_ad_params,
+ recipe_product_ugc_params,
+ recipe_product_swap_params,
+ recipe_multi_shot_video_params,
+ recipe_marketing_stock_image_params,
+ recipe_product_campaign_image_params,
+)
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import required_args, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.recipe_product_ad_response import RecipeProductAdResponse
+from ..types.recipe_product_ugc_response import RecipeProductUgcResponse
+from ..types.recipe_product_swap_response import RecipeProductSwapResponse
+from ..types.recipe_multi_shot_video_response import RecipeMultiShotVideoResponse
+from ..types.recipe_marketing_stock_image_response import RecipeMarketingStockImageResponse
+from ..types.recipe_product_campaign_image_response import RecipeProductCampaignImageResponse
+
+__all__ = ["RecipesResource", "AsyncRecipesResource"]
+
+
+class RecipesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> RecipesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return RecipesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RecipesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return RecipesResourceWithStreamingResponse(self)
+
+ def marketing_stock_image(
+ self,
+ *,
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ reference_image: recipe_marketing_stock_image_params.ReferenceImage | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate a polished marketing stock image from a text brief and optional brand
+ logo image.
+
+ Args:
+ prompt: Marketing image brief. Describe the subject, audience, channel, desired mood,
+ setting, and any constraints.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ reference_image: Optional brand logo image to guide the generated marketing stock image. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/recipes/marketing_stock_image",
+ body=maybe_transform(
+ {
+ "prompt": prompt,
+ "version": version,
+ "reference_image": reference_image,
+ },
+ recipe_marketing_stock_image_params.RecipeMarketingStockImageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeMarketingStockImageResponse, self._client),
+ )
+
+ @overload
+ def multi_shot_video(
+ self,
+ *,
+ mode: Literal["auto"],
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant0FirstFrame | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate a multi-cut video from a story prompt (auto mode) or a custom shot list
+ (custom mode).
+
+ Args:
+ mode: Workflow mode. `auto` decomposes a story prompt into exactly 5 shots.
+
+ prompt: Story prompt for auto mode.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Total duration of the output video in seconds. Defaults to 10 seconds.
+
+ first_frame: Optional image used as the first frame of the output video. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`,
+ `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`,
+ `1440:1440`) use the pro tier. Defaults to `1280:720`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ ...
+
+ @overload
+ def multi_shot_video(
+ self,
+ *,
+ mode: Literal["custom"],
+ shots: Iterable[recipe_multi_shot_video_params.Variant1Shot],
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant1FirstFrame | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate a multi-cut video from a story prompt (auto mode) or a custom shot list
+ (custom mode).
+
+ Args:
+ mode: Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots.
+
+ shots: Shot list for custom mode (3–5 shots). Per-shot durations must sum to
+ `duration`.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Total duration of the output video in seconds. Defaults to 10 seconds.
+
+ first_frame: Optional image used as the first frame of the output video. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`,
+ `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`,
+ `1440:1440`) use the pro tier. Defaults to `1280:720`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ ...
+
+ @required_args(["mode", "prompt", "version"], ["mode", "shots", "version"])
+ def multi_shot_video(
+ self,
+ *,
+ mode: Literal["auto"] | Literal["custom"],
+ prompt: str | Omit = omit,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant0FirstFrame
+ | recipe_multi_shot_video_params.Variant1FirstFrame
+ | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ shots: Iterable[recipe_multi_shot_video_params.Variant1Shot] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ return self._post(
+ "/v1/recipes/multi_shot_video",
+ body=maybe_transform(
+ {
+ "mode": mode,
+ "prompt": prompt,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "first_frame": first_frame,
+ "ratio": ratio,
+ "shots": shots,
+ },
+ recipe_multi_shot_video_params.RecipeMultiShotVideoParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeMultiShotVideoResponse, self._client),
+ )
+
+ def product_ad(
+ self,
+ *,
+ product_images: Iterable[recipe_product_ad_params.ProductImage],
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ product_info: str | Omit = omit,
+ ratio: Literal[
+ "1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664"
+ ]
+ | Omit = omit,
+ style_images: Iterable[recipe_product_ad_params.StyleImage] | Omit = omit,
+ user_concept: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate a cinematic product ad from product images, optional style references,
+ product info, and creative direction.
+
+ Args:
+ product_images: Product images (1–10). Multiple angles of the same product. All images inform
+ product analysis and reference generation; only the first image is used as the
+ primary product reference in the storyboard grid. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+
+ product_info: Optional product description and specifications to inform creative direction and
+ which product elements to highlight.
+
+ ratio: The resolution of the output video.
+
+ style_images: Optional style reference images (0–4). Defines the visual treatment (lighting,
+ palette, mood). Treated as a moodboard when multiple are provided.
+
+ user_concept: Optional creative direction describing brand voice, product framing, scene
+ specifics, lighting, camera motion, and narrative.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/recipes/product_ad",
+ body=maybe_transform(
+ {
+ "product_images": product_images,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "product_info": product_info,
+ "ratio": ratio,
+ "style_images": style_images,
+ "user_concept": user_concept,
+ },
+ recipe_product_ad_params.RecipeProductAdParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeProductAdResponse, self._client),
+ )
+
+ def product_campaign_image(
+ self,
+ *,
+ image: recipe_product_campaign_image_params.Image,
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate four fashion campaign images from a product image and style brief.
+
+ Args:
+ image: Product image to preserve across the generated campaign. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ prompt: Style / creative brief for the fashion campaign, e.g. "High-key fashion
+ editorial, gorpcore-meets-blokecore-meets-Y2K".
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/recipes/product_campaign_image",
+ body=maybe_transform(
+ {
+ "image": image,
+ "prompt": prompt,
+ "version": version,
+ },
+ recipe_product_campaign_image_params.RecipeProductCampaignImageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeProductCampaignImageResponse, self._client),
+ )
+
+ def product_swap(
+ self,
+ *,
+ new_product_images: Iterable[recipe_product_swap_params.NewProductImage],
+ original_product_image: recipe_product_swap_params.OriginalProductImage,
+ reference_video: recipe_product_swap_params.ReferenceVideo,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ resolution: Literal["720p", "1080p"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Replace the product in a reference video with a new product, preserving camera
+ motion, lighting, and scene composition.
+
+ Args:
+ new_product_images: Reference images of the new product (1–10). Supply multiple angles when the
+ reference video shows the product from different views — optionally label each
+ with `view` ("front", "side", or "back"). A single pre-composed reference sheet
+ is also supported (omit `view`). See [our docs](/assets/inputs#images) on image
+ inputs.
+
+ original_product_image: Image of the original product being swapped out. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ reference_video: Reference video containing the product to swap. Duration must be between 1.8 and
+ 15 seconds. See [our docs](/assets/inputs#videos) on video inputs.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+
+ resolution: Output video resolution. Defaults to 720p.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/recipes/product_swap",
+ body=maybe_transform(
+ {
+ "new_product_images": new_product_images,
+ "original_product_image": original_product_image,
+ "reference_video": reference_video,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "resolution": resolution,
+ },
+ recipe_product_swap_params.RecipeProductSwapParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeProductSwapResponse, self._client),
+ )
+
+ def product_ugc(
+ self,
+ *,
+ character_image: recipe_product_ugc_params.CharacterImage,
+ product_image: recipe_product_ugc_params.ProductImage,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ product_info: str | Omit = omit,
+ ratio: Literal["720:1280", "1080:1920"] | Omit = omit,
+ user_concept: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse:
+ """
+ Generate a vertical user-generated content ad from a character image, product
+ image, product details, and optional creative direction.
+
+ Args:
+ character_image: Image of the character who will appear on camera in the UGC video. Aspect ratio
+ (width / height) must be between 0.4 and 4. See
+ [our docs](/assets/inputs#images) for image input requirements.
+
+ product_image: Image of the product being promoted. Aspect ratio (width / height) must be
+ between 0.4 and 4. See [our docs](/assets/inputs#images) for image input
+ requirements.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 15 seconds.
+
+ product_info: Product details and creative brief — what the product is, key benefits, and any
+ specifics the script should reference.
+
+ ratio: The resolution of the output video.
+
+ user_concept: Optional creative direction for the UGC video — tone, voice register, specific
+ message, or an entire dialog script.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/recipes/product_ugc",
+ body=maybe_transform(
+ {
+ "character_image": character_image,
+ "product_image": product_image,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "product_info": product_info,
+ "ratio": ratio,
+ "user_concept": user_concept,
+ },
+ recipe_product_ugc_params.RecipeProductUgcParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RecipeProductUgcResponse, self._client),
+ )
+
+
+class AsyncRecipesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncRecipesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRecipesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRecipesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return AsyncRecipesResourceWithStreamingResponse(self)
+
+ async def marketing_stock_image(
+ self,
+ *,
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ reference_image: recipe_marketing_stock_image_params.ReferenceImage | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate a polished marketing stock image from a text brief and optional brand
+ logo image.
+
+ Args:
+ prompt: Marketing image brief. Describe the subject, audience, channel, desired mood,
+ setting, and any constraints.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ reference_image: Optional brand logo image to guide the generated marketing stock image. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/recipes/marketing_stock_image",
+ body=await async_maybe_transform(
+ {
+ "prompt": prompt,
+ "version": version,
+ "reference_image": reference_image,
+ },
+ recipe_marketing_stock_image_params.RecipeMarketingStockImageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeMarketingStockImageResponse, self._client),
+ )
+
+ @overload
+ async def multi_shot_video(
+ self,
+ *,
+ mode: Literal["auto"],
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant0FirstFrame | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate a multi-cut video from a story prompt (auto mode) or a custom shot list
+ (custom mode).
+
+ Args:
+ mode: Workflow mode. `auto` decomposes a story prompt into exactly 5 shots.
+
+ prompt: Story prompt for auto mode.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Total duration of the output video in seconds. Defaults to 10 seconds.
+
+ first_frame: Optional image used as the first frame of the output video. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`,
+ `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`,
+ `1440:1440`) use the pro tier. Defaults to `1280:720`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ ...
+
+ @overload
+ async def multi_shot_video(
+ self,
+ *,
+ mode: Literal["custom"],
+ shots: Iterable[recipe_multi_shot_video_params.Variant1Shot],
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant1FirstFrame | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate a multi-cut video from a story prompt (auto mode) or a custom shot list
+ (custom mode).
+
+ Args:
+ mode: Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots.
+
+ shots: Shot list for custom mode (3–5 shots). Per-shot durations must sum to
+ `duration`.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Total duration of the output video in seconds. Defaults to 10 seconds.
+
+ first_frame: Optional image used as the first frame of the output video. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`,
+ `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`,
+ `1440:1440`) use the pro tier. Defaults to `1280:720`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ ...
+
+ @required_args(["mode", "prompt", "version"], ["mode", "shots", "version"])
+ async def multi_shot_video(
+ self,
+ *,
+ mode: Literal["auto"] | Literal["custom"],
+ prompt: str | Omit = omit,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: Literal[5, 10, 15] | Omit = omit,
+ first_frame: recipe_multi_shot_video_params.Variant0FirstFrame
+ | recipe_multi_shot_video_params.Variant1FirstFrame
+ | Omit = omit,
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit,
+ shots: Iterable[recipe_multi_shot_video_params.Variant1Shot] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ return await self._post(
+ "/v1/recipes/multi_shot_video",
+ body=await async_maybe_transform(
+ {
+ "mode": mode,
+ "prompt": prompt,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "first_frame": first_frame,
+ "ratio": ratio,
+ "shots": shots,
+ },
+ recipe_multi_shot_video_params.RecipeMultiShotVideoParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeMultiShotVideoResponse, self._client),
+ )
+
+ async def product_ad(
+ self,
+ *,
+ product_images: Iterable[recipe_product_ad_params.ProductImage],
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ product_info: str | Omit = omit,
+ ratio: Literal[
+ "1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664"
+ ]
+ | Omit = omit,
+ style_images: Iterable[recipe_product_ad_params.StyleImage] | Omit = omit,
+ user_concept: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate a cinematic product ad from product images, optional style references,
+ product info, and creative direction.
+
+ Args:
+ product_images: Product images (1–10). Multiple angles of the same product. All images inform
+ product analysis and reference generation; only the first image is used as the
+ primary product reference in the storyboard grid. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+
+ product_info: Optional product description and specifications to inform creative direction and
+ which product elements to highlight.
+
+ ratio: The resolution of the output video.
+
+ style_images: Optional style reference images (0–4). Defines the visual treatment (lighting,
+ palette, mood). Treated as a moodboard when multiple are provided.
+
+ user_concept: Optional creative direction describing brand voice, product framing, scene
+ specifics, lighting, camera motion, and narrative.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/recipes/product_ad",
+ body=await async_maybe_transform(
+ {
+ "product_images": product_images,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "product_info": product_info,
+ "ratio": ratio,
+ "style_images": style_images,
+ "user_concept": user_concept,
+ },
+ recipe_product_ad_params.RecipeProductAdParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeProductAdResponse, self._client),
+ )
+
+ async def product_campaign_image(
+ self,
+ *,
+ image: recipe_product_campaign_image_params.Image,
+ prompt: str,
+ version: Literal["2026-06", "unsafe-latest"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate four fashion campaign images from a product image and style brief.
+
+ Args:
+ image: Product image to preserve across the generated campaign. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ prompt: Style / creative brief for the fashion campaign, e.g. "High-key fashion
+ editorial, gorpcore-meets-blokecore-meets-Y2K".
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/recipes/product_campaign_image",
+ body=await async_maybe_transform(
+ {
+ "image": image,
+ "prompt": prompt,
+ "version": version,
+ },
+ recipe_product_campaign_image_params.RecipeProductCampaignImageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeProductCampaignImageResponse, self._client),
+ )
+
+ async def product_swap(
+ self,
+ *,
+ new_product_images: Iterable[recipe_product_swap_params.NewProductImage],
+ original_product_image: recipe_product_swap_params.OriginalProductImage,
+ reference_video: recipe_product_swap_params.ReferenceVideo,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ resolution: Literal["720p", "1080p"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Replace the product in a reference video with a new product, preserving camera
+ motion, lighting, and scene composition.
+
+ Args:
+ new_product_images: Reference images of the new product (1–10). Supply multiple angles when the
+ reference video shows the product from different views — optionally label each
+ with `view` ("front", "side", or "back"). A single pre-composed reference sheet
+ is also supported (omit `view`). See [our docs](/assets/inputs#images) on image
+ inputs.
+
+ original_product_image: Image of the original product being swapped out. See
+ [our docs](/assets/inputs#images) on image inputs.
+
+ reference_video: Reference video containing the product to swap. Duration must be between 1.8 and
+ 15 seconds. See [our docs](/assets/inputs#videos) on video inputs.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+
+ resolution: Output video resolution. Defaults to 720p.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/recipes/product_swap",
+ body=await async_maybe_transform(
+ {
+ "new_product_images": new_product_images,
+ "original_product_image": original_product_image,
+ "reference_video": reference_video,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "resolution": resolution,
+ },
+ recipe_product_swap_params.RecipeProductSwapParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeProductSwapResponse, self._client),
+ )
+
+ async def product_ugc(
+ self,
+ *,
+ character_image: recipe_product_ugc_params.CharacterImage,
+ product_image: recipe_product_ugc_params.ProductImage,
+ version: Literal["2026-06", "unsafe-latest"],
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
+ product_info: str | Omit = omit,
+ ratio: Literal["720:1280", "1080:1920"] | Omit = omit,
+ user_concept: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Generate a vertical user-generated content ad from a character image, product
+ image, product details, and optional creative direction.
+
+ Args:
+ character_image: Image of the character who will appear on camera in the UGC video. Aspect ratio
+ (width / height) must be between 0.4 and 4. See
+ [our docs](/assets/inputs#images) for image input requirements.
+
+ product_image: Image of the product being promoted. Aspect ratio (width / height) must be
+ between 0.4 and 4. See [our docs](/assets/inputs#images) for image input
+ requirements.
+
+ version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or
+ "unsafe-latest" to track the newest stable version (may break without notice).
+
+ audio: Whether to generate audio for the video.
+
+ duration: Duration of the output video in seconds (4–15). Defaults to 15 seconds.
+
+ product_info: Product details and creative brief — what the product is, key benefits, and any
+ specifics the script should reference.
+
+ ratio: The resolution of the output video.
+
+ user_concept: Optional creative direction for the UGC video — tone, voice register, specific
+ message, or an entire dialog script.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/recipes/product_ugc",
+ body=await async_maybe_transform(
+ {
+ "character_image": character_image,
+ "product_image": product_image,
+ "version": version,
+ "audio": audio,
+ "duration": duration,
+ "product_info": product_info,
+ "ratio": ratio,
+ "user_concept": user_concept,
+ },
+ recipe_product_ugc_params.RecipeProductUgcParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RecipeProductUgcResponse, self._client),
+ )
+
+
+class RecipesResourceWithRawResponse:
+ def __init__(self, recipes: RecipesResource) -> None:
+ self._recipes = recipes
+
+ self.marketing_stock_image = to_raw_response_wrapper(
+ recipes.marketing_stock_image,
+ )
+ self.multi_shot_video = to_raw_response_wrapper(
+ recipes.multi_shot_video,
+ )
+ self.product_ad = to_raw_response_wrapper(
+ recipes.product_ad,
+ )
+ self.product_campaign_image = to_raw_response_wrapper(
+ recipes.product_campaign_image,
+ )
+ self.product_swap = to_raw_response_wrapper(
+ recipes.product_swap,
+ )
+ self.product_ugc = to_raw_response_wrapper(
+ recipes.product_ugc,
+ )
+
+
+class AsyncRecipesResourceWithRawResponse:
+ def __init__(self, recipes: AsyncRecipesResource) -> None:
+ self._recipes = recipes
+
+ self.marketing_stock_image = async_to_raw_response_wrapper(
+ recipes.marketing_stock_image,
+ )
+ self.multi_shot_video = async_to_raw_response_wrapper(
+ recipes.multi_shot_video,
+ )
+ self.product_ad = async_to_raw_response_wrapper(
+ recipes.product_ad,
+ )
+ self.product_campaign_image = async_to_raw_response_wrapper(
+ recipes.product_campaign_image,
+ )
+ self.product_swap = async_to_raw_response_wrapper(
+ recipes.product_swap,
+ )
+ self.product_ugc = async_to_raw_response_wrapper(
+ recipes.product_ugc,
+ )
+
+
+class RecipesResourceWithStreamingResponse:
+ def __init__(self, recipes: RecipesResource) -> None:
+ self._recipes = recipes
+
+ self.marketing_stock_image = to_streamed_response_wrapper(
+ recipes.marketing_stock_image,
+ )
+ self.multi_shot_video = to_streamed_response_wrapper(
+ recipes.multi_shot_video,
+ )
+ self.product_ad = to_streamed_response_wrapper(
+ recipes.product_ad,
+ )
+ self.product_campaign_image = to_streamed_response_wrapper(
+ recipes.product_campaign_image,
+ )
+ self.product_swap = to_streamed_response_wrapper(
+ recipes.product_swap,
+ )
+ self.product_ugc = to_streamed_response_wrapper(
+ recipes.product_ugc,
+ )
+
+
+class AsyncRecipesResourceWithStreamingResponse:
+ def __init__(self, recipes: AsyncRecipesResource) -> None:
+ self._recipes = recipes
+
+ self.marketing_stock_image = async_to_streamed_response_wrapper(
+ recipes.marketing_stock_image,
+ )
+ self.multi_shot_video = async_to_streamed_response_wrapper(
+ recipes.multi_shot_video,
+ )
+ self.product_ad = async_to_streamed_response_wrapper(
+ recipes.product_ad,
+ )
+ self.product_campaign_image = async_to_streamed_response_wrapper(
+ recipes.product_campaign_image,
+ )
+ self.product_swap = async_to_streamed_response_wrapper(
+ recipes.product_swap,
+ )
+ self.product_ugc = async_to_streamed_response_wrapper(
+ recipes.product_ugc,
+ )
diff --git a/src/runwayml/resources/video_to_video.py b/src/runwayml/resources/video_to_video.py
index 8a41f07..ac68f45 100644
--- a/src/runwayml/resources/video_to_video.py
+++ b/src/runwayml/resources/video_to_video.py
@@ -57,10 +57,11 @@ def create(
self,
*,
model: Literal["aleph2"],
- prompt_text: str,
video_uri: str,
content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit,
keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit,
+ prompt_text: str | Omit = omit,
+ ratio: str | Omit = omit,
seed: int | Omit = omit,
target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -74,9 +75,6 @@ def create(
This endpoint will start a new task to generate a video from a video.
Args:
- prompt_text: A non-empty string up to 1000 characters describing what should appear in the
- output.
-
video_uri: A HTTPS URL.
content_moderation: Settings that affect the behavior of the content moderation system.
@@ -84,6 +82,9 @@ def create(
keyframes: Timed guidance images placed at specific points in the input video. Up to 5
keyframes.
+ prompt_text: An optional string up to 1000 characters describing what should appear in the
+ output.
+
seed: If unspecified, a random number is chosen. Varying the seed integer is a way to
get different results for the same other request parameters. Using the same seed
integer for an identical request will produce similar results.
@@ -246,21 +247,17 @@ def create(
"""
...
- @required_args(["model", "prompt_text", "video_uri"], ["model", "prompt_video"])
+ @required_args(["model", "video_uri"], ["model", "prompt_video"])
def create(
self,
*,
model: Literal["aleph2"] | Literal["seedance2"] | Literal["seedance2_fast"],
- prompt_text: str | Omit = omit,
video_uri: str | Omit = omit,
content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit,
keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit,
- seed: int | Omit = omit,
- target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
- prompt_video: str | Omit = omit,
- audio: bool | Omit = omit,
- duration: int | Omit = omit,
- ratio: Literal[
+ prompt_text: str | Omit = omit,
+ ratio: str
+ | Literal[
"992:432",
"864:496",
"752:560",
@@ -295,6 +292,11 @@ def create(
"720:1280",
]
| Omit = omit,
+ seed: int | Omit = omit,
+ target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
+ prompt_video: str | Omit = omit,
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
reference_audio: Iterable[video_to_video_create_params.Seedance2ReferenceAudio]
| Iterable[video_to_video_create_params.Seedance2FastReferenceAudio]
| Omit = omit,
@@ -316,16 +318,16 @@ def create(
body=maybe_transform(
{
"model": model,
- "prompt_text": prompt_text,
"video_uri": video_uri,
"content_moderation": content_moderation,
"keyframes": keyframes,
+ "prompt_text": prompt_text,
+ "ratio": ratio,
"seed": seed,
"target_aspect_ratio": target_aspect_ratio,
"prompt_video": prompt_video,
"audio": audio,
"duration": duration,
- "ratio": ratio,
"reference_audio": reference_audio,
"references": references,
"reference_videos": reference_videos,
@@ -366,10 +368,11 @@ async def create(
self,
*,
model: Literal["aleph2"],
- prompt_text: str,
video_uri: str,
content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit,
keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit,
+ prompt_text: str | Omit = omit,
+ ratio: str | Omit = omit,
seed: int | Omit = omit,
target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -383,9 +386,6 @@ async def create(
This endpoint will start a new task to generate a video from a video.
Args:
- prompt_text: A non-empty string up to 1000 characters describing what should appear in the
- output.
-
video_uri: A HTTPS URL.
content_moderation: Settings that affect the behavior of the content moderation system.
@@ -393,6 +393,9 @@ async def create(
keyframes: Timed guidance images placed at specific points in the input video. Up to 5
keyframes.
+ prompt_text: An optional string up to 1000 characters describing what should appear in the
+ output.
+
seed: If unspecified, a random number is chosen. Varying the seed integer is a way to
get different results for the same other request parameters. Using the same seed
integer for an identical request will produce similar results.
@@ -555,21 +558,17 @@ async def create(
"""
...
- @required_args(["model", "prompt_text", "video_uri"], ["model", "prompt_video"])
+ @required_args(["model", "video_uri"], ["model", "prompt_video"])
async def create(
self,
*,
model: Literal["aleph2"] | Literal["seedance2"] | Literal["seedance2_fast"],
- prompt_text: str | Omit = omit,
video_uri: str | Omit = omit,
content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit,
keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit,
- seed: int | Omit = omit,
- target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
- prompt_video: str | Omit = omit,
- audio: bool | Omit = omit,
- duration: int | Omit = omit,
- ratio: Literal[
+ prompt_text: str | Omit = omit,
+ ratio: str
+ | Literal[
"992:432",
"864:496",
"752:560",
@@ -604,6 +603,11 @@ async def create(
"720:1280",
]
| Omit = omit,
+ seed: int | Omit = omit,
+ target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit,
+ prompt_video: str | Omit = omit,
+ audio: bool | Omit = omit,
+ duration: int | Omit = omit,
reference_audio: Iterable[video_to_video_create_params.Seedance2ReferenceAudio]
| Iterable[video_to_video_create_params.Seedance2FastReferenceAudio]
| Omit = omit,
@@ -625,16 +629,16 @@ async def create(
body=await async_maybe_transform(
{
"model": model,
- "prompt_text": prompt_text,
"video_uri": video_uri,
"content_moderation": content_moderation,
"keyframes": keyframes,
+ "prompt_text": prompt_text,
+ "ratio": ratio,
"seed": seed,
"target_aspect_ratio": target_aspect_ratio,
"prompt_video": prompt_video,
"audio": audio,
"duration": duration,
- "ratio": ratio,
"reference_audio": reference_audio,
"references": references,
"reference_videos": reference_videos,
diff --git a/src/runwayml/types/__init__.py b/src/runwayml/types/__init__.py
index dc527e6..f51f5af 100644
--- a/src/runwayml/types/__init__.py
+++ b/src/runwayml/types/__init__.py
@@ -28,17 +28,23 @@
from .voice_retrieve_response import VoiceRetrieveResponse as VoiceRetrieveResponse
from .avatar_retrieve_response import AvatarRetrieveResponse as AvatarRetrieveResponse
from .document_create_response import DocumentCreateResponse as DocumentCreateResponse
+from .recipe_product_ad_params import RecipeProductAdParams as RecipeProductAdParams
from .avatar_get_usage_response import AvatarGetUsageResponse as AvatarGetUsageResponse
+from .recipe_product_ugc_params import RecipeProductUgcParams as RecipeProductUgcParams
from .avatar_video_create_params import AvatarVideoCreateParams as AvatarVideoCreateParams
from .document_retrieve_response import DocumentRetrieveResponse as DocumentRetrieveResponse
+from .recipe_product_ad_response import RecipeProductAdResponse as RecipeProductAdResponse
+from .recipe_product_swap_params import RecipeProductSwapParams as RecipeProductSwapParams
from .sound_effect_create_params import SoundEffectCreateParams as SoundEffectCreateParams
from .workflow_retrieve_response import WorkflowRetrieveResponse as WorkflowRetrieveResponse
from .image_upscale_create_params import ImageUpscaleCreateParams as ImageUpscaleCreateParams
+from .recipe_product_ugc_response import RecipeProductUgcResponse as RecipeProductUgcResponse
from .text_to_image_create_params import TextToImageCreateParams as TextToImageCreateParams
from .text_to_video_create_params import TextToVideoCreateParams as TextToVideoCreateParams
from .voice_dubbing_create_params import VoiceDubbingCreateParams as VoiceDubbingCreateParams
from .avatar_video_create_response import AvatarVideoCreateResponse as AvatarVideoCreateResponse
from .image_to_video_create_params import ImageToVideoCreateParams as ImageToVideoCreateParams
+from .recipe_product_swap_response import RecipeProductSwapResponse as RecipeProductSwapResponse
from .sound_effect_create_response import SoundEffectCreateResponse as SoundEffectCreateResponse
from .text_to_speech_create_params import TextToSpeechCreateParams as TextToSpeechCreateParams
from .video_to_video_create_params import VideoToVideoCreateParams as VideoToVideoCreateParams
@@ -50,24 +56,34 @@
from .image_to_video_create_response import ImageToVideoCreateResponse as ImageToVideoCreateResponse
from .organization_retrieve_response import OrganizationRetrieveResponse as OrganizationRetrieveResponse
from .realtime_session_create_params import RealtimeSessionCreateParams as RealtimeSessionCreateParams
+from .recipe_multi_shot_video_params import RecipeMultiShotVideoParams as RecipeMultiShotVideoParams
from .speech_to_speech_create_params import SpeechToSpeechCreateParams as SpeechToSpeechCreateParams
from .text_to_speech_create_response import TextToSpeechCreateResponse as TextToSpeechCreateResponse
from .video_to_video_create_response import VideoToVideoCreateResponse as VideoToVideoCreateResponse
from .avatar_conversation_list_params import AvatarConversationListParams as AvatarConversationListParams
from .voice_isolation_create_response import VoiceIsolationCreateResponse as VoiceIsolationCreateResponse
from .realtime_session_create_response import RealtimeSessionCreateResponse as RealtimeSessionCreateResponse
+from .recipe_multi_shot_video_response import RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse
from .speech_to_speech_create_response import SpeechToSpeechCreateResponse as SpeechToSpeechCreateResponse
from .avatar_conversation_list_response import AvatarConversationListResponse as AvatarConversationListResponse
from .organization_retrieve_usage_params import OrganizationRetrieveUsageParams as OrganizationRetrieveUsageParams
from .realtime_session_retrieve_response import RealtimeSessionRetrieveResponse as RealtimeSessionRetrieveResponse
from .character_performance_create_params import CharacterPerformanceCreateParams as CharacterPerformanceCreateParams
+from .recipe_marketing_stock_image_params import RecipeMarketingStockImageParams as RecipeMarketingStockImageParams
from .organization_retrieve_usage_response import OrganizationRetrieveUsageResponse as OrganizationRetrieveUsageResponse
+from .recipe_product_campaign_image_params import RecipeProductCampaignImageParams as RecipeProductCampaignImageParams
from .avatar_conversation_retrieve_response import (
AvatarConversationRetrieveResponse as AvatarConversationRetrieveResponse,
)
from .character_performance_create_response import (
CharacterPerformanceCreateResponse as CharacterPerformanceCreateResponse,
)
+from .recipe_marketing_stock_image_response import (
+ RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse,
+)
from .workflow_invocation_retrieve_response import (
WorkflowInvocationRetrieveResponse as WorkflowInvocationRetrieveResponse,
)
+from .recipe_product_campaign_image_response import (
+ RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse,
+)
diff --git a/src/runwayml/types/organization_retrieve_usage_response.py b/src/runwayml/types/organization_retrieve_usage_response.py
index bea62de..e077547 100644
--- a/src/runwayml/types/organization_retrieve_usage_response.py
+++ b/src/runwayml/types/organization_retrieve_usage_response.py
@@ -57,6 +57,11 @@ class ResultUsedCredit(BaseModel):
"happyhorse_1_0",
"aleph2",
"product_swap",
+ "product_ad",
+ "multi_shot_video",
+ "product_ugc",
+ "marketing_stock_image",
+ "product_campaign_image",
]
"""The model that credits were spent on."""
@@ -113,6 +118,11 @@ class OrganizationRetrieveUsageResponse(BaseModel):
"happyhorse_1_0",
"aleph2",
"product_swap",
+ "product_ad",
+ "multi_shot_video",
+ "product_ugc",
+ "marketing_stock_image",
+ "product_campaign_image",
]
]
"""The list of models with usage during the queried time range."""
diff --git a/src/runwayml/types/recipe_marketing_stock_image_params.py b/src/runwayml/types/recipe_marketing_stock_image_params.py
new file mode 100644
index 0000000..c316f87
--- /dev/null
+++ b/src/runwayml/types/recipe_marketing_stock_image_params.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RecipeMarketingStockImageParams", "ReferenceImage"]
+
+
+class RecipeMarketingStockImageParams(TypedDict, total=False):
+ prompt: Required[str]
+ """Marketing image brief.
+
+ Describe the subject, audience, channel, desired mood, setting, and any
+ constraints.
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ reference_image: Annotated[ReferenceImage, PropertyInfo(alias="referenceImage")]
+ """Optional brand logo image to guide the generated marketing stock image.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+
+class ReferenceImage(TypedDict, total=False):
+ """Optional brand logo image to guide the generated marketing stock image.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
diff --git a/src/runwayml/types/recipe_marketing_stock_image_response.py b/src/runwayml/types/recipe_marketing_stock_image_response.py
new file mode 100644
index 0000000..ecb1cdc
--- /dev/null
+++ b/src/runwayml/types/recipe_marketing_stock_image_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeMarketingStockImageResponse"]
+
+
+class RecipeMarketingStockImageResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/recipe_multi_shot_video_params.py b/src/runwayml/types/recipe_multi_shot_video_params.py
new file mode 100644
index 0000000..9e1e738
--- /dev/null
+++ b/src/runwayml/types/recipe_multi_shot_video_params.py
@@ -0,0 +1,121 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = [
+ "RecipeMultiShotVideoParams",
+ "Variant0",
+ "Variant0FirstFrame",
+ "Variant1",
+ "Variant1Shot",
+ "Variant1FirstFrame",
+]
+
+
+class Variant0(TypedDict, total=False):
+ mode: Required[Literal["auto"]]
+ """Workflow mode. `auto` decomposes a story prompt into exactly 5 shots."""
+
+ prompt: Required[str]
+ """Story prompt for auto mode."""
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ audio: bool
+ """Whether to generate audio for the video."""
+
+ duration: Literal[5, 10, 15]
+ """Total duration of the output video in seconds. Defaults to 10 seconds."""
+
+ first_frame: Annotated[Variant0FirstFrame, PropertyInfo(alias="firstFrame")]
+ """Optional image used as the first frame of the output video.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"]
+ """Output dimensions as width:height.
+
+ 720p ratios (`1280:720`, `720:1280`, `960:960`) use the standard tier; 1080p
+ ratios (`1920:1080`, `1080:1920`, `1440:1440`) use the pro tier. Defaults to
+ `1280:720`.
+ """
+
+
+class Variant0FirstFrame(TypedDict, total=False):
+ """Optional image used as the first frame of the output video.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class Variant1(TypedDict, total=False):
+ mode: Required[Literal["custom"]]
+ """Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots."""
+
+ shots: Required[Iterable[Variant1Shot]]
+ """Shot list for custom mode (3–5 shots).
+
+ Per-shot durations must sum to `duration`.
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ audio: bool
+ """Whether to generate audio for the video."""
+
+ duration: Literal[5, 10, 15]
+ """Total duration of the output video in seconds. Defaults to 10 seconds."""
+
+ first_frame: Annotated[Variant1FirstFrame, PropertyInfo(alias="firstFrame")]
+ """Optional image used as the first frame of the output video.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"]
+ """Output dimensions as width:height.
+
+ 720p ratios (`1280:720`, `720:1280`, `960:960`) use the standard tier; 1080p
+ ratios (`1920:1080`, `1080:1920`, `1440:1440`) use the pro tier. Defaults to
+ `1280:720`.
+ """
+
+
+class Variant1Shot(TypedDict, total=False):
+ duration: Required[int]
+ """Duration of this shot in seconds."""
+
+ prompt: Required[str]
+ """Shot description prompt."""
+
+
+class Variant1FirstFrame(TypedDict, total=False):
+ """Optional image used as the first frame of the output video.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+RecipeMultiShotVideoParams: TypeAlias = Union[Variant0, Variant1]
diff --git a/src/runwayml/types/recipe_multi_shot_video_response.py b/src/runwayml/types/recipe_multi_shot_video_response.py
new file mode 100644
index 0000000..bf74b03
--- /dev/null
+++ b/src/runwayml/types/recipe_multi_shot_video_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeMultiShotVideoResponse"]
+
+
+class RecipeMultiShotVideoResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/recipe_product_ad_params.py b/src/runwayml/types/recipe_product_ad_params.py
new file mode 100644
index 0000000..3b72e39
--- /dev/null
+++ b/src/runwayml/types/recipe_product_ad_params.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RecipeProductAdParams", "ProductImage", "StyleImage"]
+
+
+class RecipeProductAdParams(TypedDict, total=False):
+ product_images: Required[Annotated[Iterable[ProductImage], PropertyInfo(alias="productImages")]]
+ """Product images (1–10).
+
+ Multiple angles of the same product. All images inform product analysis and
+ reference generation; only the first image is used as the primary product
+ reference in the storyboard grid. See [our docs](/assets/inputs#images) on image
+ inputs.
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ audio: bool
+ """Whether to generate audio for the video."""
+
+ duration: int
+ """Duration of the output video in seconds (4–15). Defaults to 10 seconds."""
+
+ product_info: Annotated[str, PropertyInfo(alias="productInfo")]
+ """
+ Optional product description and specifications to inform creative direction and
+ which product elements to highlight.
+ """
+
+ ratio: Literal["1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664"]
+ """The resolution of the output video."""
+
+ style_images: Annotated[Iterable[StyleImage], PropertyInfo(alias="styleImages")]
+ """Optional style reference images (0–4).
+
+ Defines the visual treatment (lighting, palette, mood). Treated as a moodboard
+ when multiple are provided.
+ """
+
+ user_concept: Annotated[str, PropertyInfo(alias="userConcept")]
+ """
+ Optional creative direction describing brand voice, product framing, scene
+ specifics, lighting, camera motion, and narrative.
+ """
+
+
+class ProductImage(TypedDict, total=False):
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class StyleImage(TypedDict, total=False):
+ uri: Required[str]
+ """A HTTPS URL."""
diff --git a/src/runwayml/types/recipe_product_ad_response.py b/src/runwayml/types/recipe_product_ad_response.py
new file mode 100644
index 0000000..5e1e45e
--- /dev/null
+++ b/src/runwayml/types/recipe_product_ad_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeProductAdResponse"]
+
+
+class RecipeProductAdResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/recipe_product_campaign_image_params.py b/src/runwayml/types/recipe_product_campaign_image_params.py
new file mode 100644
index 0000000..e5ff539
--- /dev/null
+++ b/src/runwayml/types/recipe_product_campaign_image_params.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["RecipeProductCampaignImageParams", "Image"]
+
+
+class RecipeProductCampaignImageParams(TypedDict, total=False):
+ image: Required[Image]
+ """Product image to preserve across the generated campaign.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ prompt: Required[str]
+ """Style / creative brief for the fashion campaign, e.g.
+
+ "High-key fashion editorial, gorpcore-meets-blokecore-meets-Y2K".
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+
+class Image(TypedDict, total=False):
+ """Product image to preserve across the generated campaign.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
diff --git a/src/runwayml/types/recipe_product_campaign_image_response.py b/src/runwayml/types/recipe_product_campaign_image_response.py
new file mode 100644
index 0000000..5a4dee4
--- /dev/null
+++ b/src/runwayml/types/recipe_product_campaign_image_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeProductCampaignImageResponse"]
+
+
+class RecipeProductCampaignImageResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/recipe_product_swap_params.py b/src/runwayml/types/recipe_product_swap_params.py
new file mode 100644
index 0000000..21382e7
--- /dev/null
+++ b/src/runwayml/types/recipe_product_swap_params.py
@@ -0,0 +1,81 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RecipeProductSwapParams", "NewProductImage", "OriginalProductImage", "ReferenceVideo"]
+
+
+class RecipeProductSwapParams(TypedDict, total=False):
+ new_product_images: Required[Annotated[Iterable[NewProductImage], PropertyInfo(alias="newProductImages")]]
+ """Reference images of the new product (1–10).
+
+ Supply multiple angles when the reference video shows the product from different
+ views — optionally label each with `view` ("front", "side", or "back"). A single
+ pre-composed reference sheet is also supported (omit `view`). See
+ [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ original_product_image: Required[Annotated[OriginalProductImage, PropertyInfo(alias="originalProductImage")]]
+ """Image of the original product being swapped out.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ reference_video: Required[Annotated[ReferenceVideo, PropertyInfo(alias="referenceVideo")]]
+ """Reference video containing the product to swap.
+
+ Duration must be between 1.8 and 15 seconds. See
+ [our docs](/assets/inputs#videos) on video inputs.
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ audio: bool
+ """Whether to generate audio for the video."""
+
+ duration: int
+ """Duration of the output video in seconds (4–15). Defaults to 10 seconds."""
+
+ resolution: Literal["720p", "1080p"]
+ """Output video resolution. Defaults to 720p."""
+
+
+class NewProductImage(TypedDict, total=False):
+ uri: Required[str]
+ """A HTTPS URL."""
+
+ view: Literal["front", "side", "back"]
+ """Optional view label for this reference (front, side, or back).
+
+ Omit when supplying a single reference sheet or when view labels are unknown.
+ """
+
+
+class OriginalProductImage(TypedDict, total=False):
+ """Image of the original product being swapped out.
+
+ See [our docs](/assets/inputs#images) on image inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class ReferenceVideo(TypedDict, total=False):
+ """Reference video containing the product to swap.
+
+ Duration must be between 1.8 and 15 seconds. See [our docs](/assets/inputs#videos) on video inputs.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
diff --git a/src/runwayml/types/recipe_product_swap_response.py b/src/runwayml/types/recipe_product_swap_response.py
new file mode 100644
index 0000000..2152bc6
--- /dev/null
+++ b/src/runwayml/types/recipe_product_swap_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeProductSwapResponse"]
+
+
+class RecipeProductSwapResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/recipe_product_ugc_params.py b/src/runwayml/types/recipe_product_ugc_params.py
new file mode 100644
index 0000000..c45bed9
--- /dev/null
+++ b/src/runwayml/types/recipe_product_ugc_params.py
@@ -0,0 +1,73 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RecipeProductUgcParams", "CharacterImage", "ProductImage"]
+
+
+class RecipeProductUgcParams(TypedDict, total=False):
+ character_image: Required[Annotated[CharacterImage, PropertyInfo(alias="characterImage")]]
+ """Image of the character who will appear on camera in the UGC video.
+
+ Aspect ratio (width / height) must be between 0.4 and 4. See
+ [our docs](/assets/inputs#images) for image input requirements.
+ """
+
+ product_image: Required[Annotated[ProductImage, PropertyInfo(alias="productImage")]]
+ """Image of the product being promoted.
+
+ Aspect ratio (width / height) must be between 0.4 and 4. See
+ [our docs](/assets/inputs#images) for image input requirements.
+ """
+
+ version: Required[Literal["2026-06", "unsafe-latest"]]
+ """Workflow version.
+
+ Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to
+ track the newest stable version (may break without notice).
+ """
+
+ audio: bool
+ """Whether to generate audio for the video."""
+
+ duration: int
+ """Duration of the output video in seconds (4–15). Defaults to 15 seconds."""
+
+ product_info: Annotated[str, PropertyInfo(alias="productInfo")]
+ """
+ Product details and creative brief — what the product is, key benefits, and any
+ specifics the script should reference.
+ """
+
+ ratio: Literal["720:1280", "1080:1920"]
+ """The resolution of the output video."""
+
+ user_concept: Annotated[str, PropertyInfo(alias="userConcept")]
+ """
+ Optional creative direction for the UGC video — tone, voice register, specific
+ message, or an entire dialog script.
+ """
+
+
+class CharacterImage(TypedDict, total=False):
+ """Image of the character who will appear on camera in the UGC video.
+
+ Aspect ratio (width / height) must be between 0.4 and 4. See [our docs](/assets/inputs#images) for image input requirements.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class ProductImage(TypedDict, total=False):
+ """Image of the product being promoted.
+
+ Aspect ratio (width / height) must be between 0.4 and 4. See [our docs](/assets/inputs#images) for image input requirements.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
diff --git a/src/runwayml/types/recipe_product_ugc_response.py b/src/runwayml/types/recipe_product_ugc_response.py
new file mode 100644
index 0000000..fc0d0bc
--- /dev/null
+++ b/src/runwayml/types/recipe_product_ugc_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["RecipeProductUgcResponse"]
+
+
+class RecipeProductUgcResponse(BaseModel):
+ id: str
+ """The ID of the task that was created. Use this to retrieve the task later."""
diff --git a/src/runwayml/types/video_to_video_create_params.py b/src/runwayml/types/video_to_video_create_params.py
index cea862a..4156d31 100644
--- a/src/runwayml/types/video_to_video_create_params.py
+++ b/src/runwayml/types/video_to_video_create_params.py
@@ -28,12 +28,6 @@
class Variant0(TypedDict, total=False):
model: Required[Literal["aleph2"]]
- prompt_text: Required[Annotated[str, PropertyInfo(alias="promptText")]]
- """
- A non-empty string up to 1000 characters describing what should appear in the
- output.
- """
-
video_uri: Required[Annotated[str, PropertyInfo(alias="videoUri")]]
"""A HTTPS URL."""
@@ -46,6 +40,14 @@ class Variant0(TypedDict, total=False):
Up to 5 keyframes.
"""
+ prompt_text: Annotated[str, PropertyInfo(alias="promptText")]
+ """
+ An optional string up to 1000 characters describing what should appear in the
+ output.
+ """
+
+ ratio: str
+
seed: int
"""If unspecified, a random number is chosen.
diff --git a/tests/api_resources/test_recipes.py b/tests/api_resources/test_recipes.py
new file mode 100644
index 0000000..6288565
--- /dev/null
+++ b/tests/api_resources/test_recipes.py
@@ -0,0 +1,809 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from runwayml import RunwayML, AsyncRunwayML
+from tests.utils import assert_matches_type
+from runwayml.types import (
+ RecipeProductAdResponse,
+ RecipeProductUgcResponse,
+ RecipeProductSwapResponse,
+ RecipeMultiShotVideoResponse,
+ RecipeMarketingStockImageResponse,
+ RecipeProductCampaignImageResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRecipes:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_marketing_stock_image(self, client: RunwayML) -> None:
+ recipe = client.recipes.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_marketing_stock_image_with_all_params(self, client: RunwayML) -> None:
+ recipe = client.recipes.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ reference_image={"uri": "https://example.com/file"},
+ )
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_marketing_stock_image(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_marketing_stock_image(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_multi_shot_video_overload_1(self, client: RunwayML) -> None:
+ recipe = client.recipes.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_multi_shot_video_with_all_params_overload_1(self, client: RunwayML) -> None:
+ recipe = client.recipes.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ audio=True,
+ duration=5,
+ first_frame={"uri": "https://example.com/file"},
+ ratio="1280:720",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_multi_shot_video_overload_1(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_multi_shot_video_overload_1(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_multi_shot_video_overload_2(self, client: RunwayML) -> None:
+ recipe = client.recipes.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_multi_shot_video_with_all_params_overload_2(self, client: RunwayML) -> None:
+ recipe = client.recipes.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ audio=True,
+ duration=5,
+ first_frame={"uri": "https://example.com/file"},
+ ratio="1280:720",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_multi_shot_video_overload_2(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_multi_shot_video_overload_2(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_product_ad(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_product_ad_with_all_params(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ audio=True,
+ duration=4,
+ product_info="productInfo",
+ ratio="1280:720",
+ style_images=[{"uri": "https://example.com/file"}],
+ user_concept="userConcept",
+ )
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_product_ad(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_product_ad(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_product_campaign_image(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_product_campaign_image(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_product_campaign_image(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_product_swap(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_product_swap_with_all_params(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_swap(
+ new_product_images=[
+ {
+ "uri": "https://example.com/file",
+ "view": "front",
+ }
+ ],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ audio=True,
+ duration=4,
+ resolution="720p",
+ )
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_product_swap(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_product_swap(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_product_ugc(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_method_product_ugc_with_all_params(self, client: RunwayML) -> None:
+ recipe = client.recipes.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ audio=True,
+ duration=4,
+ product_info="productInfo",
+ ratio="720:1280",
+ user_concept="userConcept",
+ )
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_raw_response_product_ugc(self, client: RunwayML) -> None:
+ response = client.recipes.with_raw_response.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = response.parse()
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ def test_streaming_response_product_ugc(self, client: RunwayML) -> None:
+ with client.recipes.with_streaming_response.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = response.parse()
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncRecipes:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_marketing_stock_image(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_marketing_stock_image_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ reference_image={"uri": "https://example.com/file"},
+ )
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_marketing_stock_image(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_marketing_stock_image(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.marketing_stock_image(
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_multi_shot_video_with_all_params_overload_1(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ audio=True,
+ duration=5,
+ first_frame={"uri": "https://example.com/file"},
+ ratio="1280:720",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.multi_shot_video(
+ mode="auto",
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_multi_shot_video_with_all_params_overload_2(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ audio=True,
+ duration=5,
+ first_frame={"uri": "https://example.com/file"},
+ ratio="1280:720",
+ )
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.multi_shot_video(
+ mode="custom",
+ shots=[
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ {
+ "duration": 1,
+ "prompt": "xxx",
+ },
+ ],
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_product_ad(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_product_ad_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ audio=True,
+ duration=4,
+ product_info="productInfo",
+ ratio="1280:720",
+ style_images=[{"uri": "https://example.com/file"}],
+ user_concept="userConcept",
+ )
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_product_ad(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_product_ad(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.product_ad(
+ product_images=[{"uri": "https://example.com/file"}],
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductAdResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_product_campaign_image(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_product_campaign_image(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_product_campaign_image(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.product_campaign_image(
+ image={"uri": "https://example.com/file"},
+ prompt="x",
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_product_swap(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_product_swap_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_swap(
+ new_product_images=[
+ {
+ "uri": "https://example.com/file",
+ "view": "front",
+ }
+ ],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ audio=True,
+ duration=4,
+ resolution="720p",
+ )
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_product_swap(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_product_swap(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.product_swap(
+ new_product_images=[{"uri": "https://example.com/file"}],
+ original_product_image={"uri": "https://example.com/file"},
+ reference_video={"uri": "https://example.com/file"},
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_product_ugc(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_method_product_ugc_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ recipe = await async_client.recipes.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ audio=True,
+ duration=4,
+ product_info="productInfo",
+ ratio="720:1280",
+ user_concept="userConcept",
+ )
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_raw_response_product_ugc(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.recipes.with_raw_response.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_product_ugc(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.recipes.with_streaming_response.product_ugc(
+ character_image={"uri": "https://example.com/file"},
+ product_image={"uri": "https://example.com/file"},
+ version="2026-06",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recipe = await response.parse()
+ assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_video_to_video.py b/tests/api_resources/test_video_to_video.py
index 151169a..10a6894 100644
--- a/tests/api_resources/test_video_to_video.py
+++ b/tests/api_resources/test_video_to_video.py
@@ -21,7 +21,6 @@ class TestVideoToVideo:
def test_method_create_overload_1(self, client: RunwayML) -> None:
video_to_video = client.video_to_video.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
)
assert_matches_type(VideoToVideoCreateResponse, video_to_video, path=["response"])
@@ -30,7 +29,6 @@ def test_method_create_overload_1(self, client: RunwayML) -> None:
def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> None:
video_to_video = client.video_to_video.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
content_moderation={"public_figure_threshold": "auto"},
keyframes=[
@@ -39,6 +37,8 @@ def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> Non
"uri": "https://example.com/file",
}
],
+ prompt_text="x",
+ ratio="ratio",
seed=0,
target_aspect_ratio="16:9",
)
@@ -48,7 +48,6 @@ def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> Non
def test_raw_response_create_overload_1(self, client: RunwayML) -> None:
response = client.video_to_video.with_raw_response.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
)
@@ -61,7 +60,6 @@ def test_raw_response_create_overload_1(self, client: RunwayML) -> None:
def test_streaming_response_create_overload_1(self, client: RunwayML) -> None:
with client.video_to_video.with_streaming_response.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
) as response:
assert not response.is_closed
@@ -210,7 +208,6 @@ class TestAsyncVideoToVideo:
async def test_method_create_overload_1(self, async_client: AsyncRunwayML) -> None:
video_to_video = await async_client.video_to_video.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
)
assert_matches_type(VideoToVideoCreateResponse, video_to_video, path=["response"])
@@ -219,7 +216,6 @@ async def test_method_create_overload_1(self, async_client: AsyncRunwayML) -> No
async def test_method_create_with_all_params_overload_1(self, async_client: AsyncRunwayML) -> None:
video_to_video = await async_client.video_to_video.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
content_moderation={"public_figure_threshold": "auto"},
keyframes=[
@@ -228,6 +224,8 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn
"uri": "https://example.com/file",
}
],
+ prompt_text="x",
+ ratio="ratio",
seed=0,
target_aspect_ratio="16:9",
)
@@ -237,7 +235,6 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn
async def test_raw_response_create_overload_1(self, async_client: AsyncRunwayML) -> None:
response = await async_client.video_to_video.with_raw_response.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
)
@@ -250,7 +247,6 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncRunwayML)
async def test_streaming_response_create_overload_1(self, async_client: AsyncRunwayML) -> None:
async with async_client.video_to_video.with_streaming_response.create(
model="aleph2",
- prompt_text="x",
video_uri="https://example.com/video.mp4",
) as response:
assert not response.is_closed
diff --git a/uv.lock b/uv.lock
index c6c62ac..ee1b946 100644
--- a/uv.lock
+++ b/uv.lock
@@ -886,7 +886,7 @@ wheels = [
[[package]]
name = "runwayml"
-version = "4.18.0"
+version = "5.1.0"
source = { editable = "." }
dependencies = [
{ name = "anyio", version = "4.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },