diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 411256b..34a3350 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "4.1.0"
+ ".": "4.2.0"
}
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 d06330d..c92a015 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 4.2.0 (2026-06-17)
+
+Full Changelog: [v4.1.0...v4.2.0](https://github.com/runwayml/sdk-node/compare/v4.1.0...v4.2.0)
+
+### Features
+
+* **api:** Recipes, aleph2 range ([05ae79c](https://github.com/runwayml/sdk-node/commit/05ae79c87f5bfc60545c5e8271a9f19c29728085))
+* **client:** add recipe endpoints to config ([8509e2b](https://github.com/runwayml/sdk-node/commit/8509e2bf6a6350873e69186e01a6bf71121f0a1c))
+* **client:** Make recipes waitable ([2806992](https://github.com/runwayml/sdk-node/commit/2806992850b4bacdd24afa5154e63cbcb2c06e52))
+
+
+### Bug Fixes
+
+* **api:** remove range param ([a602a48](https://github.com/runwayml/sdk-node/commit/a602a48b1a6eef9bdc845f09851fa58100c2d155))
+* **client:** send content-type header for requests with an omitted optional body ([b26f126](https://github.com/runwayml/sdk-node/commit/b26f126323c83fca99555e7b897fcda048ccd098))
+
## 4.1.0 (2026-06-12)
Full Changelog: [v4.0.0...v4.1.0](https://github.com/runwayml/sdk-node/compare/v4.0.0...v4.1.0)
diff --git a/api.md b/api.md
index 972ec7a..cc70b48 100644
--- a/api.md
+++ b/api.md
@@ -202,6 +202,26 @@ Methods:
- client.realtimeSessions.retrieve(id) -> RealtimeSessionRetrieveResponse
- client.realtimeSessions.delete(id) -> void
+# Recipes
+
+Types:
+
+- RecipeMarketingStockImageResponse
+- RecipeMultiShotVideoResponse
+- RecipeProductAdResponse
+- RecipeProductCampaignImageResponse
+- RecipeProductSwapResponse
+- RecipeProductUgcResponse
+
+Methods:
+
+- client.recipes.marketingStockImage({ ...params }) -> RecipeMarketingStockImageResponse
+- client.recipes.multiShotVideo({ ...params }) -> RecipeMultiShotVideoResponse
+- client.recipes.productAd({ ...params }) -> RecipeProductAdResponse
+- client.recipes.productCampaignImage({ ...params }) -> RecipeProductCampaignImageResponse
+- client.recipes.productSwap({ ...params }) -> RecipeProductSwapResponse
+- client.recipes.productUgc({ ...params }) -> RecipeProductUgcResponse
+
# Voices
Types:
diff --git a/package.json b/package.json
index b6efd39..f33eab9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@runwayml/sdk",
- "version": "4.1.0",
+ "version": "4.2.0",
"description": "The official TypeScript library for the RunwayML API",
"author": "RunwayML ",
"types": "dist/index.d.ts",
diff --git a/src/client.ts b/src/client.ts
index 112e7a7..40b43c9 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -77,6 +77,21 @@ import {
RealtimeSessionRetrieveResponse,
RealtimeSessions,
} from './resources/realtime-sessions';
+import {
+ RecipeMarketingStockImageParams,
+ RecipeMarketingStockImageResponse,
+ RecipeMultiShotVideoParams,
+ RecipeMultiShotVideoResponse,
+ RecipeProductAdParams,
+ RecipeProductAdResponse,
+ RecipeProductCampaignImageParams,
+ RecipeProductCampaignImageResponse,
+ RecipeProductSwapParams,
+ RecipeProductSwapResponse,
+ RecipeProductUgcParams,
+ RecipeProductUgcResponse,
+ Recipes,
+} from './resources/recipes';
import { SoundEffect, SoundEffectCreateParams, SoundEffectCreateResponse } from './resources/sound-effect';
import {
SpeechToSpeech,
@@ -816,11 +831,19 @@ export class RunwayML {
return () => controller.abort();
}
- private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): {
+ private buildBody({ options }: { options: FinalRequestOptions }): {
bodyHeaders: HeadersLike;
body: BodyInit | undefined;
} {
+ const { body, headers: rawHeaders } = options;
if (!body) {
+ // A resource method always passes a `body` key when its operation defines a
+ // request body, even if the caller omitted an optional body param. Keep the
+ // content-type for those, and only elide it for operations with no body at
+ // all (e.g. GET/DELETE).
+ if (body == null && 'body' in options) {
+ return this.#encoder({ body, headers: buildHeaders([rawHeaders]) });
+ }
return { bodyHeaders: undefined, body: undefined };
}
const headers = buildHeaders([rawHeaders]);
@@ -934,6 +957,7 @@ export class RunwayML {
avatarVideos: API.AvatarVideos = new API.AvatarVideos(this);
documents: API.Documents = new API.Documents(this);
realtimeSessions: API.RealtimeSessions = new API.RealtimeSessions(this);
+ recipes: API.Recipes = new API.Recipes(this);
voices: API.Voices = new API.Voices(this);
uploads: API.Uploads = new API.Uploads(this);
workflows: API.Workflows = new API.Workflows(this);
@@ -958,6 +982,7 @@ RunwayML.AvatarConversations = AvatarConversations;
RunwayML.AvatarVideos = AvatarVideos;
RunwayML.Documents = Documents;
RunwayML.RealtimeSessions = RealtimeSessions;
+RunwayML.Recipes = Recipes;
RunwayML.Voices = Voices;
RunwayML.Uploads = Uploads;
RunwayML.Workflows = Workflows;
@@ -1090,6 +1115,22 @@ export declare namespace RunwayML {
type RealtimeSessionCreateParams as RealtimeSessionCreateParams,
};
+ export {
+ Recipes as Recipes,
+ type RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse,
+ type RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse,
+ type RecipeProductAdResponse as RecipeProductAdResponse,
+ type RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse,
+ type RecipeProductSwapResponse as RecipeProductSwapResponse,
+ type RecipeProductUgcResponse as RecipeProductUgcResponse,
+ type RecipeMarketingStockImageParams as RecipeMarketingStockImageParams,
+ type RecipeMultiShotVideoParams as RecipeMultiShotVideoParams,
+ type RecipeProductAdParams as RecipeProductAdParams,
+ type RecipeProductCampaignImageParams as RecipeProductCampaignImageParams,
+ type RecipeProductSwapParams as RecipeProductSwapParams,
+ type RecipeProductUgcParams as RecipeProductUgcParams,
+ };
+
export {
Voices as Voices,
type VoiceCreateResponse as VoiceCreateResponse,
diff --git a/src/resources/index.ts b/src/resources/index.ts
index 7c4a343..f35b8fb 100644
--- a/src/resources/index.ts
+++ b/src/resources/index.ts
@@ -58,6 +58,21 @@ export {
type RealtimeSessionRetrieveResponse,
type RealtimeSessionCreateParams,
} from './realtime-sessions';
+export {
+ Recipes,
+ type RecipeMarketingStockImageResponse,
+ type RecipeMultiShotVideoResponse,
+ type RecipeProductAdResponse,
+ type RecipeProductCampaignImageResponse,
+ type RecipeProductSwapResponse,
+ type RecipeProductUgcResponse,
+ type RecipeMarketingStockImageParams,
+ type RecipeMultiShotVideoParams,
+ type RecipeProductAdParams,
+ type RecipeProductCampaignImageParams,
+ type RecipeProductSwapParams,
+ type RecipeProductUgcParams,
+} from './recipes';
export { SoundEffect, type SoundEffectCreateResponse, type SoundEffectCreateParams } from './sound-effect';
export {
SpeechToSpeech,
diff --git a/src/resources/organization.ts b/src/resources/organization.ts
index 3b6c23c..943c99a 100644
--- a/src/resources/organization.ts
+++ b/src/resources/organization.ts
@@ -139,6 +139,11 @@ export interface OrganizationRetrieveUsageResponse {
| 'happyhorse_1_0'
| 'aleph2'
| 'product_swap'
+ | 'product_ad'
+ | 'multi_shot_video'
+ | 'product_ugc'
+ | 'marketing_stock_image'
+ | 'product_campaign_image'
>;
results: Array;
@@ -207,7 +212,12 @@ export namespace OrganizationRetrieveUsageResponse {
| 'klingO3_4k'
| 'happyhorse_1_0'
| 'aleph2'
- | 'product_swap';
+ | 'product_swap'
+ | 'product_ad'
+ | 'multi_shot_video'
+ | 'product_ugc'
+ | 'marketing_stock_image'
+ | 'product_campaign_image';
}
}
}
diff --git a/src/resources/recipes.ts b/src/resources/recipes.ts
new file mode 100644
index 0000000..e64ca4f
--- /dev/null
+++ b/src/resources/recipes.ts
@@ -0,0 +1,565 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { APIResource } from '../core/resource';
+import { RequestOptions } from '../internal/request-options';
+import { APIPromiseWithAwaitableTask, wrapAsWaitableResource } from '../lib/polling';
+
+export class Recipes extends APIResource {
+ /**
+ * Generate a polished marketing stock image from a text brief and optional brand
+ * logo image.
+ */
+ marketingStockImage(
+ body: RecipeMarketingStockImageParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/marketing_stock_image', { body, ...options }),
+ );
+ }
+
+ /**
+ * Generate a multi-cut video from a story prompt (auto mode) or a custom shot list
+ * (custom mode).
+ */
+ multiShotVideo(
+ body: RecipeMultiShotVideoParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/multi_shot_video', { body, ...options }),
+ );
+ }
+
+ /**
+ * Generate a cinematic product ad from product images, optional style references,
+ * product info, and creative direction.
+ */
+ productAd(
+ body: RecipeProductAdParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/product_ad', { body, ...options }),
+ );
+ }
+
+ /**
+ * Generate four fashion campaign images from a product image and style brief.
+ */
+ productCampaignImage(
+ body: RecipeProductCampaignImageParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/product_campaign_image', { body, ...options }),
+ );
+ }
+
+ /**
+ * Replace the product in a reference video with a new product, preserving camera
+ * motion, lighting, and scene composition.
+ */
+ productSwap(
+ body: RecipeProductSwapParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/product_swap', { body, ...options }),
+ );
+ }
+
+ /**
+ * Generate a vertical user-generated content ad from a character image, product
+ * image, product details, and optional creative direction.
+ */
+ productUgc(
+ body: RecipeProductUgcParams,
+ options?: RequestOptions,
+ ): APIPromiseWithAwaitableTask {
+ return wrapAsWaitableResource(this._client)(
+ this._client.post('/v1/recipes/product_ugc', { body, ...options }),
+ );
+ }
+}
+
+export interface RecipeMarketingStockImageResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeMultiShotVideoResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeProductAdResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeProductCampaignImageResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeProductSwapResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeProductUgcResponse {
+ /**
+ * The ID of the task that was created. Use this to retrieve the task later.
+ */
+ id: string;
+}
+
+export interface RecipeMarketingStockImageParams {
+ /**
+ * Marketing image brief. Describe the subject, audience, channel, desired mood,
+ * setting, and any constraints.
+ */
+ prompt: string;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Optional brand logo image to guide the generated marketing stock image. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ referenceImage?: RecipeMarketingStockImageParams.ReferenceImage;
+}
+
+export namespace RecipeMarketingStockImageParams {
+ /**
+ * Optional brand logo image to guide the generated marketing stock image. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ export interface ReferenceImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+}
+
+export type RecipeMultiShotVideoParams =
+ | RecipeMultiShotVideoParams.Variant0
+ | RecipeMultiShotVideoParams.Variant1;
+
+export declare namespace RecipeMultiShotVideoParams {
+ export interface Variant0 {
+ /**
+ * Workflow mode. `auto` decomposes a story prompt into exactly 5 shots.
+ */
+ mode: 'auto';
+
+ /**
+ * Story prompt for auto mode.
+ */
+ prompt: string;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Whether to generate audio for the video.
+ */
+ audio?: boolean;
+
+ /**
+ * Total duration of the output video in seconds. Defaults to 10 seconds.
+ */
+ duration?: 5 | 10 | 15;
+
+ /**
+ * Optional image used as the first frame of the output video. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ firstFrame?: Variant0.FirstFrame;
+
+ /**
+ * 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`.
+ */
+ ratio?: '1280:720' | '720:1280' | '960:960' | '1920:1080' | '1080:1920' | '1440:1440';
+ }
+
+ export namespace Variant0 {
+ /**
+ * Optional image used as the first frame of the output video. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ export interface FirstFrame {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+ }
+
+ export interface Variant1 {
+ /**
+ * Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots.
+ */
+ mode: 'custom';
+
+ /**
+ * Shot list for custom mode (3–5 shots). Per-shot durations must sum to
+ * `duration`.
+ */
+ shots: Array;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Whether to generate audio for the video.
+ */
+ audio?: boolean;
+
+ /**
+ * Total duration of the output video in seconds. Defaults to 10 seconds.
+ */
+ duration?: 5 | 10 | 15;
+
+ /**
+ * Optional image used as the first frame of the output video. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ firstFrame?: Variant1.FirstFrame;
+
+ /**
+ * 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`.
+ */
+ ratio?: '1280:720' | '720:1280' | '960:960' | '1920:1080' | '1080:1920' | '1440:1440';
+ }
+
+ export namespace Variant1 {
+ export interface Shot {
+ /**
+ * Duration of this shot in seconds.
+ */
+ duration: number;
+
+ /**
+ * Shot description prompt.
+ */
+ prompt: string;
+ }
+
+ /**
+ * Optional image used as the first frame of the output video. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ export interface FirstFrame {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+ }
+}
+
+export interface RecipeProductAdParams {
+ /**
+ * 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.
+ */
+ productImages: Array;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Whether to generate audio for the video.
+ */
+ audio?: boolean;
+
+ /**
+ * Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+ */
+ duration?: number;
+
+ /**
+ * Optional product description and specifications to inform creative direction and
+ * which product elements to highlight.
+ */
+ productInfo?: string;
+
+ /**
+ * The resolution of the output video.
+ */
+ ratio?:
+ | '1280:720'
+ | '720:1280'
+ | '960:960'
+ | '834:1112'
+ | '1920:1080'
+ | '1080:1920'
+ | '1440:1440'
+ | '1248:1664';
+
+ /**
+ * Optional style reference images (0–4). Defines the visual treatment (lighting,
+ * palette, mood). Treated as a moodboard when multiple are provided.
+ */
+ styleImages?: Array;
+
+ /**
+ * Optional creative direction describing brand voice, product framing, scene
+ * specifics, lighting, camera motion, and narrative.
+ */
+ userConcept?: string;
+}
+
+export namespace RecipeProductAdParams {
+ export interface ProductImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+
+ export interface StyleImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+}
+
+export interface RecipeProductCampaignImageParams {
+ /**
+ * Product image to preserve across the generated campaign. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ image: RecipeProductCampaignImageParams.Image;
+
+ /**
+ * Style / creative brief for the fashion campaign, e.g. "High-key fashion
+ * editorial, gorpcore-meets-blokecore-meets-Y2K".
+ */
+ prompt: string;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+}
+
+export namespace RecipeProductCampaignImageParams {
+ /**
+ * Product image to preserve across the generated campaign. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ export interface Image {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+}
+
+export interface RecipeProductSwapParams {
+ /**
+ * 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.
+ */
+ newProductImages: Array;
+
+ /**
+ * Image of the original product being swapped out. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ originalProductImage: RecipeProductSwapParams.OriginalProductImage;
+
+ /**
+ * 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.
+ */
+ referenceVideo: RecipeProductSwapParams.ReferenceVideo;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Whether to generate audio for the video.
+ */
+ audio?: boolean;
+
+ /**
+ * Duration of the output video in seconds (4–15). Defaults to 10 seconds.
+ */
+ duration?: number;
+
+ /**
+ * Output video resolution. Defaults to 720p.
+ */
+ resolution?: '720p' | '1080p';
+}
+
+export namespace RecipeProductSwapParams {
+ export interface NewProductImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+
+ /**
+ * Optional view label for this reference (front, side, or back). Omit when
+ * supplying a single reference sheet or when view labels are unknown.
+ */
+ view?: 'front' | 'side' | 'back';
+ }
+
+ /**
+ * Image of the original product being swapped out. See
+ * [our docs](/assets/inputs#images) on image inputs.
+ */
+ export interface OriginalProductImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+
+ /**
+ * 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.
+ */
+ export interface ReferenceVideo {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+}
+
+export interface RecipeProductUgcParams {
+ /**
+ * 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.
+ */
+ characterImage: RecipeProductUgcParams.CharacterImage;
+
+ /**
+ * 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.
+ */
+ productImage: RecipeProductUgcParams.ProductImage;
+
+ /**
+ * 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).
+ */
+ version: '2026-06' | 'unsafe-latest';
+
+ /**
+ * Whether to generate audio for the video.
+ */
+ audio?: boolean;
+
+ /**
+ * Duration of the output video in seconds (4–15). Defaults to 15 seconds.
+ */
+ duration?: number;
+
+ /**
+ * Product details and creative brief — what the product is, key benefits, and any
+ * specifics the script should reference.
+ */
+ productInfo?: string;
+
+ /**
+ * The resolution of the output video.
+ */
+ ratio?: '720:1280' | '1080:1920';
+
+ /**
+ * Optional creative direction for the UGC video — tone, voice register, specific
+ * message, or an entire dialog script.
+ */
+ userConcept?: string;
+}
+
+export namespace RecipeProductUgcParams {
+ /**
+ * 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.
+ */
+ export interface CharacterImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+
+ /**
+ * 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.
+ */
+ export interface ProductImage {
+ /**
+ * A HTTPS URL.
+ */
+ uri: string;
+ }
+}
+
+export declare namespace Recipes {
+ export {
+ type RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse,
+ type RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse,
+ type RecipeProductAdResponse as RecipeProductAdResponse,
+ type RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse,
+ type RecipeProductSwapResponse as RecipeProductSwapResponse,
+ type RecipeProductUgcResponse as RecipeProductUgcResponse,
+ type RecipeMarketingStockImageParams as RecipeMarketingStockImageParams,
+ type RecipeMultiShotVideoParams as RecipeMultiShotVideoParams,
+ type RecipeProductAdParams as RecipeProductAdParams,
+ type RecipeProductCampaignImageParams as RecipeProductCampaignImageParams,
+ type RecipeProductSwapParams as RecipeProductSwapParams,
+ type RecipeProductUgcParams as RecipeProductUgcParams,
+ };
+}
diff --git a/src/resources/video-to-video.ts b/src/resources/video-to-video.ts
index 3d742f4..f907deb 100644
--- a/src/resources/video-to-video.ts
+++ b/src/resources/video-to-video.ts
@@ -15,7 +15,6 @@ export class VideoToVideo extends APIResource {
* ```ts
* const videoToVideo = await client.videoToVideo.create({
* model: 'aleph2',
- * promptText: 'x',
* videoUri: 'https://example.com/video.mp4',
* });
* ```
@@ -46,12 +45,6 @@ export declare namespace VideoToVideoCreateParams {
export interface Variant0 {
model: 'aleph2';
- /**
- * A non-empty string up to 1000 characters describing what should appear in the
- * output.
- */
- promptText: string;
-
/**
* A HTTPS URL.
*/
@@ -68,6 +61,17 @@ export declare namespace VideoToVideoCreateParams {
*/
keyframes?: Array;
+ /**
+ * An optional string up to 1000 characters describing what should appear in the
+ * output.
+ */
+ promptText?: string;
+
+ /**
+ * @deprecated
+ */
+ ratio?: string;
+
/**
* 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
diff --git a/src/version.ts b/src/version.ts
index be6bae1..f9ec1c7 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = '4.1.0'; // x-release-please-version
+export const VERSION = '4.2.0'; // x-release-please-version
diff --git a/tests/api-resources/recipes.test.ts b/tests/api-resources/recipes.test.ts
new file mode 100644
index 0000000..814acb0
--- /dev/null
+++ b/tests/api-resources/recipes.test.ts
@@ -0,0 +1,162 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import RunwayML from '@runwayml/sdk';
+
+const client = new RunwayML({
+ apiKey: 'My API Key',
+ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
+});
+
+describe('resource recipes', () => {
+ test('marketingStockImage: only required params', async () => {
+ const responsePromise = client.recipes.marketingStockImage({ prompt: 'x', version: '2026-06' });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('marketingStockImage: required and optional params', async () => {
+ const response = await client.recipes.marketingStockImage({
+ prompt: 'x',
+ version: '2026-06',
+ referenceImage: { uri: 'https://example.com/file' },
+ });
+ });
+
+ test('multiShotVideo: only required params', async () => {
+ const responsePromise = client.recipes.multiShotVideo({
+ mode: 'auto',
+ prompt: 'x',
+ version: '2026-06',
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('multiShotVideo: required and optional params', async () => {
+ const response = await client.recipes.multiShotVideo({
+ mode: 'auto',
+ prompt: 'x',
+ version: '2026-06',
+ audio: true,
+ duration: 5,
+ firstFrame: { uri: 'https://example.com/file' },
+ ratio: '1280:720',
+ });
+ });
+
+ test('productAd: only required params', async () => {
+ const responsePromise = client.recipes.productAd({
+ productImages: [{ uri: 'https://example.com/file' }],
+ version: '2026-06',
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('productAd: required and optional params', async () => {
+ const response = await client.recipes.productAd({
+ productImages: [{ uri: 'https://example.com/file' }],
+ version: '2026-06',
+ audio: true,
+ duration: 4,
+ productInfo: 'productInfo',
+ ratio: '1280:720',
+ styleImages: [{ uri: 'https://example.com/file' }],
+ userConcept: 'userConcept',
+ });
+ });
+
+ test('productCampaignImage: only required params', async () => {
+ const responsePromise = client.recipes.productCampaignImage({
+ image: { uri: 'https://example.com/file' },
+ prompt: 'x',
+ version: '2026-06',
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('productCampaignImage: required and optional params', async () => {
+ const response = await client.recipes.productCampaignImage({
+ image: { uri: 'https://example.com/file' },
+ prompt: 'x',
+ version: '2026-06',
+ });
+ });
+
+ test('productSwap: only required params', async () => {
+ const responsePromise = client.recipes.productSwap({
+ newProductImages: [{ uri: 'https://example.com/file' }],
+ originalProductImage: { uri: 'https://example.com/file' },
+ referenceVideo: { uri: 'https://example.com/file' },
+ version: '2026-06',
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('productSwap: required and optional params', async () => {
+ const response = await client.recipes.productSwap({
+ newProductImages: [{ uri: 'https://example.com/file', view: 'front' }],
+ originalProductImage: { uri: 'https://example.com/file' },
+ referenceVideo: { uri: 'https://example.com/file' },
+ version: '2026-06',
+ audio: true,
+ duration: 4,
+ resolution: '720p',
+ });
+ });
+
+ test('productUgc: only required params', async () => {
+ const responsePromise = client.recipes.productUgc({
+ characterImage: { uri: 'https://example.com/file' },
+ productImage: { uri: 'https://example.com/file' },
+ version: '2026-06',
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('productUgc: required and optional params', async () => {
+ const response = await client.recipes.productUgc({
+ characterImage: { uri: 'https://example.com/file' },
+ productImage: { uri: 'https://example.com/file' },
+ version: '2026-06',
+ audio: true,
+ duration: 4,
+ productInfo: 'productInfo',
+ ratio: '720:1280',
+ userConcept: 'userConcept',
+ });
+ });
+});
diff --git a/tests/api-resources/video-to-video.test.ts b/tests/api-resources/video-to-video.test.ts
index 78a99d1..2d3fdf9 100644
--- a/tests/api-resources/video-to-video.test.ts
+++ b/tests/api-resources/video-to-video.test.ts
@@ -11,7 +11,6 @@ describe('resource videoToVideo', () => {
test('create: only required params', async () => {
const responsePromise = client.videoToVideo.create({
model: 'aleph2',
- promptText: 'x',
videoUri: 'https://example.com/video.mp4',
});
const rawResponse = await responsePromise.asResponse();
@@ -26,10 +25,11 @@ describe('resource videoToVideo', () => {
test('create: required and optional params', async () => {
const response = await client.videoToVideo.create({
model: 'aleph2',
- promptText: 'x',
videoUri: 'https://example.com/video.mp4',
contentModeration: { publicFigureThreshold: 'auto' },
keyframes: [{ seconds: 0, uri: 'https://example.com/file' }],
+ promptText: 'x',
+ ratio: 'ratio',
seed: 0,
targetAspectRatio: '16:9',
});