This is the full developer documentation for Runway Dev --- # API Documentation > Access complete Runway Dev documentation. Find guides, references and resources to integrate AI video generation into your applications and products. Runway Dev lets you bring our most powerful and popular generative models directly into your apps, products, platforms and websites. [Get started today](https://dev.runwayml.com/). **New:** Seedance 2.5 now supports 1080p. Cinematic video up to 30 seconds with a large reference budget for images, videos, and audio. [Get started with Seedance 2.5 →](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) ## Models [![Seedance 2.5](https://d3phaj0sisr2ct.cloudfront.net/devportal/models-v2/seedance-2-5-gold.jpeg)](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) [![Gen 4.5](https://d3phaj0sisr2ct.cloudfront.net/devportal/models-v2/gen4.5.jpeg)](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) [![Aleph 2.0](https://d3phaj0sisr2ct.cloudfront.net/devportal/models-v2/aleph-2.0-v2.jpeg)](/api/#tag/Start-generating/paths/~1v1~1video_to_video/post) [![GPT Image 2](https://d3phaj0sisr2ct.cloudfront.net/devportal/models-v2/gpt-image-2.jpg)](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) View available models and details [here](guides/models). ## Start Building [Connect Dev MCP ](/guides/mcp)Connect Cursor, Claude Code, or Codex to your Runway Dev account [Using the API ](/guides/using-the-api)Create your first generation on Runway Dev [API Reference ](/api)Read about how to interact with the API [Go-live checklist ](/guides/go-live)Prepare your integration for launch [Runway Agent Skills ](https://github.com/runwayml/skills)Community tools, examples, and agent skills for Runway Dev ## Quickstart Get started by following our [API guide](/guides/using-the-api) or jump right into the example of using the Gen-4.5 model below: * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); try { // Create a new text-to-video task using the "gen4.5" model const task = await client.imageToVideo .create({ model: 'gen4.5', promptText: 'A serene mountain landscape at sunrise with mist rolling through the valleys', ratio: '1280:720', duration: 5, }) .waitForTaskOutput(); console.log('Task complete:', task); console.log('Video URL:', task.output[0]); } catch (error) { if (error instanceof TaskFailedError) { console.error('The video failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() try: task = client.image_to_video.create( model='gen4.5', prompt_text='A serene mountain landscape at sunrise with mist rolling through the valleys', ratio='1280:720', duration=5, ).wait_for_task_output() print('Task complete:', task) print('Video URL:', task.output[0]) except TaskFailedError as e: print('The video failed to generate.') print(e.task_details) ``` ## Enterprise Scale Runway Dev has been used by the world’s largest consumer technology companies to reliably generate millions of videos for their users. To request higher usage than [the self-serve tiers](./usage/tiers#tiers), you can submit an exception request on the usage page of your developer portal. ### Enterprise Benefits In addition to higher rate limits, enterprise customers receive: * Faster support via Slack (or email) channels * Earliest access to new features * Direct access to provide product feedback and request features * Implementation and usage tips * Custom payment terms --- # API Setup & Configuration > Set up Runway Dev for your application. Follow our configuration guide for authentication, environments and initial integration setup. Runway offers the most cutting-edge generative video models. You can start using our models in your application with only a few quick steps. ## Set up an organization First, sign up for an account in [the developer portal](https://dev.runwayml.com/). After signing up, you’ll be presented with an option to create a new organization. An organization corresponds to your integration, and contains resources like API keys and configuration. ### Create a key Once you’ve created an organization, click to the API Keys tab. You’ll create a new key, giving it a name. Call it something descriptive, like “Matt Testing” so you can revoke the key later if needed. Creating and disabling keys needs the Admin or Developer role — see [Organizations and roles](/usage/organizations-and-roles). The key will be presented to you only once: immediately copy it someplace safe, like a password manager. We’ll never return the key in plaintext again; if you lose the key, you’ll need to disable it and create a new one. Removing a user from your organization does not revoke their API key access. Because keys are org-scoped (not user-scoped), you must disable keys separately to fully cut off access. ### Add credits Before you can start using the API, you’ll need to add credits to your organization. Credits are used to pay for the compute resources used by your models. Visit the billing tab in the developer portal to add credits to your organization. A minimum payment of $10 (at $0.01 per credit) is required to get started. Adding credits needs the Admin or Billing admin role. ## Start your integration Now that you have an organization and a key, you can start building on Runway Dev. ### Using your API key When you make requests to the API, you’ll need to include your API key in the headers. Our SDKs will automatically add your key if it’s specified in the `RUNWAYML_API_SECRET` environment variable. You can export this in your shell for test purposes like so: * macOS and Linux ```sh export RUNWAYML_API_SECRET="key_123456789012345678901234567890" ``` * Windows ```powershell setx RUNWAYML_API_SECRET "key_123456789012345678901234567890" ``` In a production environment, do not hard-code your key like this. Instead, securely load your key into environment variables using a secret manager or similar tool. --- # Connect Runway Dev > Connect Cursor, Claude Desktop, Claude Code, or Codex to your Runway Dev account over MCP. OAuth only. No API key is required in the client config. Runway Dev lets a coding agent work against your [Developer Portal](https://dev.runwayml.com/) account from the editor: look up tasks, manage model routers, and read docs without copying an API key into `mcp.json`. This is separate from the generation MCP at [mcp.runwayml.com](https://mcp.runwayml.com), which creates media in chat. Tip You need a Runway Dev account before connecting. [Create one](/guides/setup) if you have not already. The server URL is the same in every client: ```text https://dev.runwayml.com/mcp ``` Apps display the server as **Runway Dev**. CLI server identifiers cannot contain spaces, so the commands below use `runway-dev-mcp`. Caution Complete OAuth yourself in your normal browser. Do not ask an agent to automate the sign-in or approval steps. ## Cursor One click adds the server. Cursor then opens a browser to sign in. [Add Runway Dev to Cursor](https://cursor.com/install-mcp?name=Runway%20Dev\&config=eyJ1cmwiOiJodHRwczovL2Rldi5ydW53YXltbC5jb20vbWNwIiwiYXV0aCI6eyJDTElFTlRfSUQiOiJ0cGNfdHhTS3JHeENNRVVVYU56UjhQTGRjRSJ9fQ%3D%3D) Or paste this into `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project) and restart Cursor: ```json { "mcpServers": { "Runway Dev": { "url": "https://dev.runwayml.com/mcp", "auth": { "CLIENT_ID": "tpc_txSKrGxCMEUUaNzR8PLdcE" } } } } ``` Then open **Settings → MCP**, find **Runway Dev**, and connect. Approve access in the browser when prompted. ## Claude Desktop Claude Desktop uses the same custom connectors as Claude.ai. One click opens **Add custom connector** with the name and URL filled in. [Add Runway Dev to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector\&connectorName=Runway%20Dev\&connectorUrl=https%3A%2F%2Fdev.runwayml.com%2Fmcp) ## Claude Code Claude Code does not use Claude Desktop connectors. Add the server from a terminal: ```sh claude mcp add --transport http --scope user runway-dev-mcp https://dev.runwayml.com/mcp ``` In a session, run `/mcp`, select **runway-dev-mcp**, and authenticate. If Claude Code shows **Needs authentication**: 1. Run `/mcp`. 2. Select **runway-dev-mcp**. 3. Complete authentication in your normal browser. 4. Retry the MCP tool in the same Claude Code session. 5. Request a new session only if the authenticated tools remain unavailable. Claude Code 2.1.186 or newer can also start authentication from a terminal: ```sh claude mcp login runway-dev-mcp ``` Claude Code 2.1.191 or newer supports `--no-browser`. This option prints an authorization URL instead of opening a local browser: ```sh claude mcp login runway-dev-mcp --no-browser ``` ## Codex Codex has a [known scope-discovery issue](https://github.com/openai/codex/issues/15643), so request the Runway scopes explicitly: ```sh codex mcp add runway-dev-mcp --url https://dev.runwayml.com/mcp codex mcp login runway-dev-mcp --scopes openid,profile,email,mcp:read,mcp:write ``` Or add the same URL in `~/.codex/config.toml`: ```toml [mcp_servers.runway-dev-mcp] url = "https://dev.runwayml.com/mcp" ``` Then run `codex mcp login runway-dev-mcp --scopes openid,profile,email,mcp:read,mcp:write` to complete browser login. If Codex connects but reports a missing `mcp:read` scope, replace the cached authorization: ```sh codex mcp logout runway-dev-mcp codex mcp login runway-dev-mcp --scopes openid,profile,email,mcp:read,mcp:write ``` ## After you connect Ask the agent to call `whoami`. You should see your Developer Portal email. If that fails, authenticate with the client steps above and retry in the same session. Do not put an API key in the MCP config. Keys stay in the Developer Portal, where you can rotate them. --- # API Getting Started Guide > Learn how to use Runway Dev for AI video generation. Follow our step-by-step guide to integrate Gen-4, image generation and more into your apps. Before starting, you will need to create a [Runway Dev account](/guides/setup). ### Talking to the API You can use the [Playground](https://dev.runwayml.com/playground) to test your code, or follow the examples below to get started. * [Generating Video](#pill-tab-panel-2) * [Generating Images](#pill-tab-panel-3) In this example, we’ll use the `gen4.5` model to generate a video from an image using the text prompt “A timelapse on a sunny day with clouds flying by”. When building this into your app, you’ll want to replace the `promptImage` with a URL of an image and a `promptText` with your own text prompt. * Node First, you’ll want to install the Runway SDK. You can do this with npm: ```sh npm install --save @runwayml/sdk ``` In your code, you can now import the SDK and start making requests: ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Create a new image-to-video task using the "gen4.5" model try { const task = await client.imageToVideo .create({ model: 'gen4.5', // Point this at your own image file promptImage: 'https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg', promptText: 'A timelapse on a sunny day with clouds flying by', ratio: '1280:720', duration: 5, }) .waitForTaskOutput(); console.log('Task complete:', task); } catch (error) { if (error instanceof TaskFailedError) { console.error('The video failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python First, you’ll want to install the Runway SDK. You can do this with pip: ```sh pip install runwayml ``` In your code, you can now import the SDK and start making requests: ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() # Create a new image-to-video task using the "gen4.5" model try: task = client.image_to_video.create( model='gen4.5', # Point this at your own image file prompt_image='https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg', prompt_text=' timelapse on a sunny day with clouds flying by', ratio='1280:720', duration=5, ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The video failed to generate.') print(e.task_details) ``` * Just testing If you’re not ready to start writing code, you can test the API with cURL. ```sh # Replace the example URL below with your own image URL curl -X POST https://api.dev.runwayml.com/v1/image_to_video \ -d '{ "promptImage": "https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg", "promptText": " timelapse on a sunny day with clouds flying by", "model": "gen4.5", "ratio": "1280:720", "duration": 5 }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` This command will start an image-to-video task using the “gen4.5” model. You’ll see a JSON response with the task ID printed in your terminal, which you can use to fetch the task status. #### Uploading base64 encoded images as data URIs You can also upload base64 encoded images (as a data URI) instead of pointing to an external URL. This can be useful if you’re working with a local image file and want to avoid an extra network round trip to upload the image. To do this, simply pass the base64 encoded image string as a data URI in the `promptImage` field instead of a URL. For more information about file types and size limits, see the [Inputs](/assets/inputs) page. * Node ```ts import fs from 'node:fs'; import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Read the image file into a Buffer. Replace `example.png` with your own image path. const imageBuffer = fs.readFileSync('example.png'); // Convert to a data URI. We're using `image/png` here because the input is a PNG. const dataUri = `data:image/png;base64,${imageBuffer.toString('base64')}`; // Create a new image-to-video task using the "gen4.5" model try { const imageToVideo = await client.imageToVideo .create({ model: 'gen4.5', // Point this at your own image file promptImage: dataUri, promptText: 'A timelapse on a sunny day with clouds flying by', ratio: '1280:720', duration: 5, }) .waitForTaskOutput(); console.log('Task complete:', task); } catch (error) { if (error instanceof TaskFailedError) { console.error('The video failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python import base64 from runwayml import RunwayML client = RunwayML() image = './example.png' # Encode image to a data URI with open(image, "rb") as f: # Read the file as a base64 encoded string base64_image = base64.b64encode(f.read()).decode("utf-8") # Format it as a data URI # We're using `image/png` here because the input is a PNG. data_uri = f"data:image/png;base64,{base64_image}" # Create a new image-to-video task using the "gen4.5" model try: task = client.image_to_video.create( model='gen4.5', # Point this at your own image file prompt_image=data_uri, prompt_text='A timelapse on a sunny day with clouds flying by', ratio='1280:720', duration=5, ).wait_for_task_output() print('Task complete:', task) except runwayml.TaskFailedError as e: print('The video failed to generate.') print(e.task_details) ``` #### Text-to-video (without an input image) Gen-4.5 also supports generating videos from text alone, without requiring an input image. Simply omit the `promptImage` parameter to use text-to-video mode. * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Create a new text-to-video task using the "gen4.5" model try { const task = await client.imageToVideo .create({ model: 'gen4.5', // No promptImage needed for text-to-video promptText: 'A serene mountain landscape at sunrise with mist rolling through the valleys', ratio: '1280:720', duration: 5, }) .waitForTaskOutput(); console.log('Task complete:', task); } catch (error) { if (error instanceof TaskFailedError) { console.error('The video failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() # Create a new text-to-video task using the "gen4.5" model try: task = client.image_to_video.create( model='gen4.5', # No prompt_image needed for text-to-video prompt_text='A serene mountain landscape at sunrise with mist rolling through the valleys', ratio='1280:720', duration=5, ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The video failed to generate.') print(e.task_details) ``` In this example, we’ll use the `gen4_image` model to generate an image of the Eiffel Tower rendered in the style of the painting Starry Night. To do this, we’ll pass in reference images of the Eiffel Tower and Starry Night, and use the `promptText` to specify that we want the Eiffel Tower rendered in the style of Starry Night. Reference images can be referenced in the text prompt using at-mention syntax referencing the `tag` of the reference image. * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Create a new image generation task using the "gen4_image" model try { let task = await client.textToImage .create({ model: 'gen4_image', ratio: '1920:1080', promptText: '@EiffelTower painted in the style of @StarryNight', referenceImages: [ { uri: 'https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg' tag: 'EiffelTower', }, { uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1513px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg', tag: 'StarryNight', }, ], }) .waitForTaskOutput(); console.log('Task complete:', task); console.log('Image URL:', task.output[0]); } catch (error) { if (error instanceof TaskFailedError) { console.error('The image failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() try: task = client.text_to_image.create( model='gen4_image', ratio='1920:1080', prompt_text='@EiffelTower painted in the style of @StarryNight', reference_images=[ { 'uri': 'https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg', 'tag': 'EiffelTower', }, { 'uri': 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1513px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg', 'tag': 'StarryNight', }, ], ).wait_for_task_output() print('Task complete:', task) print('Image URL:', task.output[0]) except TaskFailedError as e: print('The image failed to generate.') print(e.task_details) ``` * Just testing If you’re not ready to start writing code, you can test the API with cURL. ```sh # Replace the example URL below with your own image URL curl -X POST https://api.dev.runwayml.com/v1/text_to_image \ -d '{ "promptText": "@EiffelTower painted in the style of @StarryNight", "model": "gen4_image", "ratio": "1920:1080", "referenceImages": [ { "uri": "https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg", "tag": "EiffelTower" }, { "uri": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1513px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg", "tag": "StarryNight" } ] }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` #### Uploading base64 encoded images as data URIs You can also upload base64 encoded images (as a data URI) instead of pointing to an external URL. This can be useful if you’re working with a local image file and want to avoid an extra network round trip to upload the image. To do this, simply pass the base64 encoded image string as a data URI in the `uri` of a `referenceImage` object instead of a URL. For more information about file types and size limits, see the [Inputs](/assets/inputs) page. In this example, we’ll use the `gen4_image` model to generate an image of a bunny facing the camera. We provide a reference image of a rabbit that we use at-mention syntax to reference in the `promptText`. We also provide an untagged reference image that the model will use to style the output image with. * Node ```ts import fs from 'node:fs'; import path from 'node:path'; import RunwayML, { TaskFailedError } from '@runwayml/sdk'; // Use the `mime-types` package to get the content type of the image file // Install with `npm install mime-types --save` import * as mime from 'mime-types'; const client = new RunwayML(); function getImageAsDataUri(imagePath: string) { // Read the image file const imageBuffer = fs.readFileSync(imagePath); // Convert to base64 const base64String = imageBuffer.toString('base64'); // Get the MIME type of the image file const contentType = mime.lookup(imagePath); return `data:${contentType};base64,${base64String}`; } // Create a new text-to-image task using the "gen4_image" model try { const textToImage = await client.textToImage .create({ model: 'gen4_image', promptText: '@bunny facing the camera', ratio: '1920:1080', referenceImages: [ { uri: getImageAsDataUri('rabbit.png'), tag: 'bunny', }, { uri: getImageAsDataUri('style.png'), }, ], }) .waitForTaskOutput(); console.log('Task complete:', task); } catch (error) { if (error instanceof TaskFailedError) { console.error('The image failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python import base64 import mimetypes from runwayml import RunwayML, TaskFailedError client = RunwayML() def get_image_as_data_uri(image_path: str) -> str: with open(image_path, "rb") as f: base64_image = base64.b64encode(f.read()).decode("utf-8") content_type = mimetypes.guess_file_type(image_path)[0] return f"data:{content_type};base64,{base64_image}" # Create a new text-to-image task using the "gen4_image" model try: task = client.text_to_image.create( model='gen4_image', ratio='1920:1080', prompt_text='@bunny facing the camera', reference_images=[ { 'uri': get_image_as_data_uri('rabbit.png'), 'tag': 'bunny', }, { 'uri': get_image_as_data_uri('style.png'), }, ], ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The image failed to generate.') print(e.task_details) ``` ### Additional Resources * **[Runway Skills Repository](https://github.com/runwayml/skills)** - tools, code examples, and agent skills for Runway Dev --- # Available AI Models > Explore available AI models in Runway Dev. Learn about Gen-4, image generation and other models for video and image creation in your applications. The API exposes the following models. See [pricing](/guides/pricing) for per-model costs. [Create an account](https://dev.runwayml.com/) to start building on Runway Dev. Prefer not to have to decide the right model yourself? Use a [Model Router](/model-routers) to automatically route each request to the best available model based on your cost, latency, or quality preferences. No additional cost. ## Generate Video | Model Name | Input | Output | Learn More | | ------------------- | --------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wan3` | Text or Image | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `seedance2_5` | Text, Image, or Video | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/50488490233363-Creating-with-Seedance-2-0) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `grok_imagine_1_5` | Text or Image | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `seedance2` | Text, Image, or Video | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/50488490233363-Creating-with-Seedance-2-0) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `seedance2_fast` | Text, Image, or Video | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/50488490233363-Creating-with-Seedance-2-0) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `seedance2_mini` | Text, Image, or Video | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/50488490233363-Creating-with-Seedance-2-0) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `h3_max` | Text or Image | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `hailuo3` | Text, Image, or Video | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `aleph2` | Video + Text/Image | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/51683104370451-Creating-with-Edit-Studio) [Reference](/api#tag/Start-generating/paths/~1v1~1video_to_video/post) | | `gen4.5` | Text or Image | Video | [Guide](https://runwayml.com/research/introducing-runway-gen-4.5) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `gen4_turbo` | Image | Video | [Guide](https://help.runwayml.com/hc/en-us/articles/37327109429011-Creating-with-Gen-4) [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `act_two` | Image or Video | Video | [Reference](/api/#tag/Start-generating/paths/~1v1~1character_performance/post) | | `veo3.1` | Text or Image | Video | [Reference](/api/#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `veo3.1_fast` | Text or Image | Video | [Reference](/api/#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `happyhorse_1_0` | Text or Image | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1image_to_video/post) | | `gemini_omni_flash` | Text, Image, or Video | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_video/post) | ### Professional and HDR output formats Runway’s flagship models — Gen-4.5 and Aleph 2.0 — can deliver video in professional formats beyond standard H.264 `.mp4`: ProRes and PNG image sequences for editorial workflows, 10-bit SDR masters for grading, and true HDR on Gen-4.5. Set `outputFormat` on the request to choose a delivery. These formats carry a per-second surcharge — see [pricing](/guides/pricing#professional-and-hdr-output-formats). #### Editorial containers Available on Gen-4.5 and Aleph 2.0: | `outputFormat` | Delivery | | --------------- | ----------------------------------------------------------------------- | | `mp4` (default) | H.264 `.mp4` | | `prores` | ProRes `.mov` | | `png_sequence` | `.zip` of PNG frames (plus a separate `.wav` when the output has audio) | For `prores`, optionally set `proresProfile` (`422 Proxy`, `422 LT`, `422`, `422 HQ`, `4444`, or `4444 XQ`; default `4444`). #### 10-bit SDR | `outputFormat` | Delivery | Models | | ------------------ | ---------------------------- | ------------------ | | `sdr_rec709_10bit` | HEVC Main 10, Rec.709 `.mp4` | Gen-4.5, Aleph 2.0 | #### HDR and high bit-depth (Gen-4.5 only) Gen-4.5 HDR outputs are true HDR renders — graded into BT.2020 with measured HDR10 metadata. Aleph 2.0 does not deliver native HDR. For HDR content with Aleph 2.0 you can chain an edit through [SDR to HDR](#sdr-to-hdr). | `outputFormat` | Delivery | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `hdr10` | HEVC Main 10, BT.2020 + PQ `.mp4` | | `hlg` | HEVC Main 10, BT.2020 + HLG `.mp4` | | `hdr_pq_12bit_master` | 12-bit 4:4:4 BT.2020 + PQ `.mov` (lossless) | | `hdr_prores` | BT.2020 + PQ ProRes `.mov` | | `hdr_png_sequence` | `.zip` of 16-bit PNG frames + colorimetry sidecar (plus a separate `.wav` when the output has audio) | | `hdr_exr_sequence` | `.zip` of half-float OpenEXR frames as linear BT.2020 light (1.0 = 100 nits) + colorimetry sidecar (plus a separate `.wav` when the output has audio) | | `hdr_exr_acescg_sequence_1_3` | The EXR delivery as scene-referred ACEScg for ACES 1.3 pipelines — reads with the stock `ACES - ACEScg` input transform | | `hdr_exr_acescg_sequence_2_0` | The same scene-referred ACEScg delivery for ACES 2.0 pipelines (inverted through the ACES 2.0 Output Transform) | For `hdr_prores`, `proresProfile` accepts `422`, `422 HQ`, or `4444` (default `422 HQ`). The ACEScg EXRs are scene-referred: the delivered picture is inverted through the ACES 1.3 Output Transform (Rec.2100 PQ, 1000-nit), so reading the frames with the stock `ACES - ACEScg` input transform and viewing through your ACES pipeline reproduces the delivered picture — no Read-node changes needed. The `colorimetry.json` sidecar names the ACES version and the inverted transform. ## Real-time | Model Name | Input | Output | Learn More | | -------------- | ------------------- | ------------- | ----------------------------------------------------- | | `gwm1_avatars` | Text (conversation) | Video + Audio | [Guide](/characters) [Reference](/api#tag/Characters) | ## Generate Images Nano Banana models Google’s Nano Banana models keep their Gemini Image names in the API: `gemini_2.5_flash` is Nano Banana, `gemini_image3_pro` is Nano Banana Pro, and `gemini_image3.1_flash` is Nano Banana 2. | Model Name | Input | Output | Learn More | | ------------------------ | ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `gpt_image_2_5_flare` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gpt_image_2_5_sunburst` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `muse_image` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `grok_imagine_image_2` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `seedream5_pro` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `seedream5_lite` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gen4_image` | Text/Image (References) | Image | [Guide](https://help.runwayml.com/hc/en-us/articles/40042718905875-Gen-4-Image-References-Guide) [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gen4_image_turbo` | Text+Image (References) | Image | [Guide](https://help.runwayml.com/hc/en-us/articles/40042718905875-Gen-4-Image-References-Guide) [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gemini_image3_pro` | Text/Image (References) | Image | [Guide](https://help.runwayml.com/hc/en-us/articles/50317213804435-Creating-images-with-Nano-Banana) [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gemini_image3.1_flash` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gpt_image_2` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | | `gemini_2.5_flash` | Text/Image (References) | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_image/post) | ## Upscale Image | Model Name | Input | Output | Learn More | | -------------------------------- | ----- | ------ | --------------------------------------------------------------------- | | `magnific_precision_upscaler_v2` | Image | Image | [Reference](/api#tag/Start-generating/paths/~1v1~1image_upscale/post) | ## Upscale Video Always set `model` to `magnific_video_upscaler_creative` on `POST /v1/video_upscale`. Input videos can be at most 30 seconds. | Model Name | Input | Output | Learn More | | ---------------------------------- | ----- | ------ | --------------------------------------------------------------------- | | `magnific_video_upscaler_creative` | Video | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1video_upscale/post) | ## SDR to HDR Always set `model` to `ruby` on `POST /v1/video_to_hdr`. Converts SDR video into true HDR, keeping the source pixels and audio. Delivers 10-bit HEVC in HDR10 (`outputFormat: "hdr10"`, the default) or HLG (`"hlg"`), a BT.2020 + PQ ProRes `.mov` editorial mezzanine (`"hdr_prores"`, with `proresProfile` of `422`, `422 HQ`, or `4444`; default `422 HQ`), or a `.zip` of half-float OpenEXR frames for compositing, as linear BT.2020 light (`"hdr_exr_sequence"`) or as scene-referred ACEScg for ACES pipelines (`"hdr_exr_acescg_sequence_1_3"`; reads with the stock `ACES - ACEScg` input transform; both zips include colorimetry and provenance sidecars, plus `audio.wav` when the source has audio). Inputs must be SDR, at most 30 seconds, and under 4096 pixels per side; output dimensions may be cropped to a multiple of 16. See [pricing](/guides/pricing#sdr-to-hdr-pricing) for the 20 / 40 credit per-second rates by source resolution. | Model Name | Input | Output | Learn More | | ---------- | ----- | ------ | -------------------------------------------------------------------- | | `ruby` | Video | Video | [Reference](/api#tag/Start-generating/paths/~1v1~1video_to_hdr/post) | ## Generate Audio | Model Name | Input | Output | Learn More | | ---------------------------- | ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `seed_audio` | Text or Audio | Audio | [Text to Speech](/api#tag/Start-generating/paths/~1v1~1text_to_speech/post) [Sound Effect](/api#tag/Start-generating/paths/~1v1~1sound_effect/post) | | `eleven_v3` | Text | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_speech/post) | | `eleven_multilingual_v2` | Text | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1text_to_speech/post) | | `eleven_text_to_sound_v2` | Text | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1sound_effect/post) | | `eleven_voice_isolation` | Audio | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1voice_isolation/post) | | `eleven_voice_dubbing` | Audio | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1voice_dubbing/post) | | `eleven_multilingual_sts_v2` | Audio | Audio | [Reference](/api#tag/Start-generating/paths/~1v1~1speech_to_speech/post) | --- # API Pricing & Costs > Understand Runway Dev pricing and costs. View rates for Gen-4, image generation and other models to budget for your AI video integration project. Each generation you run on Runway Dev costs credits. Credits can be purchased for $0.01 per credit in the developer portal for an organization. Sales tax may apply depending on your location. [Create an account](https://dev.runwayml.com/) to start building on Runway Dev. Generations routed through a [Model Router](/model-routers) are billed at the standard rate of whichever model the router selects. The response metadata reports the model used and the realized cost in credits. ## Video Generation Pricing | Model | Pricing | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `wan3` (480p) | 5 credits per second | | `wan3` (720p) | 10 credits per second | | `wan3` (1080p) | 20 credits per second | | `seedance2_5` (480p) | 20 credits per second of output, plus 10 credits per second of input and reference video (combined video capped at 30 seconds); reference images and audio are free (80 credit minimum per generation) | | `seedance2_5` (720p) | 30 credits per second of output, plus 15 credits per second of input and reference video (combined video capped at 30 seconds); reference images and audio are free (80 credit minimum per generation) | | `seedance2_5` (1080p) | 68 credits per second of output, plus 34 credits per second of input and reference video (combined video capped at 30 seconds); reference images and audio are free (80 credit minimum per generation) | | `grok_imagine_1_5` (480p) | 10 credits per second, plus 1 credit per image or audio reference (including an image-to-video start frame) | | `grok_imagine_1_5` (720p) | 16 credits per second, plus 1 credit per image or audio reference (including an image-to-video start frame) | | `grok_imagine_1_5` (1080p) | 29 credits per second, plus 1 credit per image or audio reference (including an image-to-video start frame) | | `seedance2` (480p/720p) | 36 credits per second | | `seedance2` (1080p) | 40 credits per second | | `seedance2` (4K) | 150 credits per second | | `seedance2_fast` (480p/720p) | 29 credits per second | | `seedance2_mini` (480p/720p) | 16 credits per second (64 credit minimum per generation) | | `h3_max` (480p) | 5 credits per second | | `h3_max` (768p) | 8 credits per second | | `hailuo3` (768P) | 10 credits per second, plus 2 credits per reference image; reference video billed at the same per-second rate as output (capped at 15 seconds) | | `hailuo3` (2K) | 15 credits per second, plus 2 credits per reference image; reference video billed at the same per-second rate as output (capped at 15 seconds) | | `aleph2` | 28 credits per second (56 credit minimum per generation) | | `gen4.5` | 12 credits per second | | `gen4_turbo` | 5 credits per second | | `act_two` | 5 credits per second | | `veo3.1 (audio)` | 40 credits per second | | `veo3.1 (no audio)` | 20 credits per second | | `veo3.1_fast (audio)` | 15 credits per second | | `veo3.1_fast (no audio)` | 10 credits per second | | `happyhorse_1_0` (720p) | 15 credits per second | | `happyhorse_1_0` (1080p) | 30 credits per second | | `gemini_omni_flash` (text to video) | 10 credits per second | | `gemini_omni_flash` (image to video) | 10 credits per second, plus 1 credit for the first-frame image | | `gemini_omni_flash` (video to video) | 11 credits per second of input video (capped at 10 seconds), plus 1 credit per reference image | ### Professional and HDR output formats These formats add a per-second surcharge on top of the model’s rate. Available on Gen-4.5 and Aleph 2.0 — see [models](/guides/models#professional-and-hdr-output-formats) for which `outputFormat` values each model supports. | Formats | Surcharge | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `prores`, `png_sequence` | 5 credits per second | | 10-bit and HDR profiles (`hdr10`, `hlg`, `sdr_rec709_10bit`, `hdr_pq_12bit_master`, `hdr_prores`, `hdr_png_sequence`, `hdr_exr_sequence`, `hdr_exr_acescg_sequence_1_3`, `hdr_exr_acescg_sequence_2_0`) | 20 credits per second; 40 credits per second when the output is larger than 4 megapixels (roughly 4K) | ## Image Generation Pricing | Model | Pricing | | ------------------------ | ------------------------------------------------------------------------------------------------ | | `gen4_image` | 5 credits per 720p image, or 8 credits per 1080p image | | `gen4_image_turbo` | 2 credits per image, any resolution | | `muse_image` | 1 credit per image, any resolution | | `grok_imagine_image_2` | 4–8 credits per image, by quality and resolution (see below) | | `seedream5_pro` | 5 credits per 1K image, 9 credits per 2K image | | `seedream5_lite` | 4 credits per image, any resolution | | `gemini_image3_pro` | 20 credits per 1K or 2K image, 40 credits per 4K image | | `gpt_image_2` | 1–41 credits per image, by quality and resolution (see below) | | `gpt_image_2_5_flare` | 1–76 credits per image, by quality and resolution, plus 1 credit per reference image (see below) | | `gpt_image_2_5_sunburst` | 1–76 credits per image, by quality and resolution, plus 1 credit per reference image (see below) | | `gemini_2.5_flash` | 5 credits per image, any resolution | `grok_imagine_image_2` is billed as `outputCount × perImage + referenceCount × 1`. Reference images cost 1 credit each and are charged once per request, not per output image — for example, four 1K `medium` images with two references is `4 × 6 + 2` = 26 credits. Default quality is `medium`; anything other than `low` (including an omitted `quality`) bills as `medium`. `auto_1k` and `auto_2k` bill at the 1K and 2K tiers. A plain 1K generation is 6 credits (the minimum charge). With `outputCount` capped at 4 and references at 3, the maximum for a single request is 35 credits. | Quality | 1K (incl. `auto_1k`) | 2K (incl. `auto_2k`) | | -------- | -------------------- | -------------------- | | `low` | 4 | 6 | | `medium` | 6 | 8 | `gpt_image_2` is billed as credits per image × `outputCount`. The `auto` resolution is billed at the 4K tier. Default quality is `high`. | Quality | 1K / 2K | 4K (incl. `auto`) | | -------- | ------- | ----------------- | | `low` | 1 | 2 | | `medium` | 5 | 11 | | `high` | 20 | 41 | | `auto` | 20 | 41 | `gpt_image_2_5_flare` and `gpt_image_2_5_sunburst` are billed on the same table, as (credits per image + 1 credit per reference image) × `outputCount` — reference images are charged once per generated image, not once per request. The `auto` resolution is billed at the 4K tier. Default quality is `high`. | Quality | 1K / 2K | 4K (incl. `auto`) | | -------- | ------- | ----------------- | | `low` | 1 | 2 | | `medium` | 5 | 11 | | `high` | 16 | 19 | | `xhigh` | 28 | 34 | | `max` | 63 | 76 | ## Image Upscale Pricing | Model | Pricing | | -------------------------------- | --------------------------------------------------------------- | | `magnific_precision_upscaler_v2` | 25 credits per image, or 150 credits when output exceeds 4096px | ## Video Upscale Pricing `magnific_video_upscaler_creative` is billed per output frame, not per second of video. Credits = `ceil(USD / 0.01)`, where `USD = rate × fps × duration_seconds`. Minimum 1 credit per generation. When `fpsBoost` is enabled, output frame count may differ from input and affect cost. | Resolution | USD per frame | Example (10s @ 30fps) | | ------------ | ------------- | --------------------- | | `720p`, `1k` | $0.007 | 210 credits | | `2k` | $0.009 | 270 credits | | `4k` | $0.012 | 360 credits | ## SDR to HDR Pricing Ruby (`ruby`) converts SDR video to true HDR on `POST /v1/video_to_hdr`. Billed per second of output at the source’s own resolution. | Model | Pricing | | ------ | ----------------------------------------------------------------------------------------------------- | | `ruby` | 20 credits per second; 40 credits per second when the source is larger than 4 megapixels (roughly 4K) | ## Audio Generation Pricing | Model | Pricing | | --------------------------------------------- | --------------------------------------------------------- | | `seed_audio` | 0.25 credits per second (5 credit minimum per generation) | | `eleven_v3` | 1 credit per 50 characters (minimum 1 credit) | | `eleven_multilingual_v2` | 1 credit per 50 characters | | `eleven_text_to_sound_v2 (provided duration)` | 1 credit per second of audio | | `eleven_text_to_sound_v2 (no duration)` | 2 credits | | `eleven_voice_isolation` | 1 credit per 6s of audio | | `eleven_voice_dubbing` | 1 credit per 2s of audio | | `eleven_multilingual_sts_v2` | 1 credit per 3s of audio | ## Real-time Pricing | Model | Pricing | | -------------- | ----------------------------------------------- | | `gwm1_avatars` | 2 credits upfront, then 2 credits per 6 seconds | ## Recipe Pricing | Recipe | Pricing | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `product_ad` | 720p: 200 credits for a 4 second video, plus 36 credits per additional second. 1080p: 216 credits for a 4 second video, plus 40 credits per additional second. (4 second minimum, 15 second maximum per generation) | | `product_swap` | 720p: 212 credits for a 4 second video, plus 36 credits per additional second. 1080p: 228 credits for a 4 second video, plus 40 credits per additional second. (4 second minimum, 15 second maximum per generation) | | `product_ugc` | 720p: 192 credits for a 4 second video, plus 36 credits per additional second. 1080p: 208 credits for a 4 second video, plus 40 credits per additional second. (4 second minimum, 15 second maximum per generation) | | `multi_shot_video` | 720p: 13 credits per second of video. 1080p: 17 credits per second of video. (5 second minimum, 15 second maximum per generation) | | `ad_localization` | 21 credits per localized image | | `marketing_stock_image` | 8 credits per call for prompt processing, plus 1–20 credits per generated image by `quality` (same 1K/2K tiers as [Image Generation Pricing](#image-generation-pricing)) × `outputCount`. Default (`outputCount: 4`, `quality: high`): **88 credits** total. | | `product_campaign_image` | 36 credits per 1080p image (4 images generated per call) | ## Cost calculator You can calculate the cost of your intended usage: Images: 1 x 720p *(5 credits)* + 1 x 1080p *(8 credits)* = $0.13 Videos: WAN 3.0 (480p) (wan3\_480p) 1 video x Duration: 1 =$0.25 --- # Content Moderation System > Learn about Runway's content moderation for API usage. Understand safety guidelines, moderation policies and best practices for responsible AI use. Runway takes the safety of its platform seriously. This means we will moderate certain API requests. If your account makes too many requests that get moderated, we will suspend your account. If needed, you should add moderation before calling the API, to avoid suspension. ## Approach to Trust & Safety Refer to our [help center approach to trust & safety](https://help.runwayml.com/hc/en-us/articles/17944787368595-Runway-s-approach-to-trust-safety). ### Moderated content categories Refer to our [help center FAQ guide on content moderation](https://help.runwayml.com/hc/en-us/articles/21745792516371-Why-is-my-input-getting-content-moderated-and-what-types-of-content-are-blocked). ### Moderated content types Runway moderation will evaluate all elements of your request. This means your requests may be moderated for either an image or text prompt violation. ## Moderation implications ### Recommended moderation approach We recommend reviewing [the categories we block](https://help.runwayml.com/hc/en-us/articles/21745792516371-Why-is-my-input-getting-content-moderated-and-what-types-of-content-are-blocked) to determine what type of, if any, moderation you need in place. Each call to the API defaults to `auto` moderation levels. If you wish to be less strict about preventing generations that include recognizable public figures, add the `contentModeration` object to your image or video generation API requests. See the [API reference](/api/#tag/Start-generating/paths/~1v1~1image_to_video/post) for details. ### Cost of moderated generations Moderated generations have the same credit cost as successful generations. ### Account suspension If your account makes too many moderated requests we will suspend it. You can [appeal an account suspension here](https://help.runwayml.com/hc/en-us/articles/21667383978003-How-can-I-appeal-my-account-s-suspension). Be sure to email from an email account associated with your developer portal login. ## Moderation API responses If a request was moderated, the task status response will return with `"status":"FAILED"`. Additional details describing the moderation appear in the `"failure"` and `"failureCode"` fields. --- # Software Development Kits > Download Runway Dev SDKs for Python, Node.js and more. Use official libraries to integrate AI video generation into your development workflow. ## Available SDKs We provide SDKs as convenient helpers for interacting with our API. These SDKs use best practices and offer type safety, which helps to avoid errors and make it easier to write code. ### Node.js The Node.js SDK includes TypeScript bindings. It is compatible with Node 18 and up, and can be installed with `npm`, `yarn`, or `pnpm`. ### Python The Python SDK includes type annotations compatible with MyPy. It is compatible with Python 3.8 and up. ## Generating content You can create content using our API using the methods documented in the [API reference](/api). For instance, `POST /v1/text_to_image` accepts input and produces an image as output. Each API endpoint for starting a generation is available as a member on the SDKs. Here is a subset of the API endpoints mapped to the SDK methods: * Node | Operation | API endpoint | Node.js SDK method | | --------------------- | -------------------------------- | ------------------------------------ | | Generate an image | `POST /v1/text_to_image` | `client.textToImage.create` | | Generate a video | `POST /v1/image_to_video` | `client.imageToVideo.create` | | Character performance | `POST /v1/character_performance` | `client.characterPerformance.create` | * Python | Operation | API endpoint | Python SDK method | | --------------------- | -------------------------------- | ------------------------------------- | | Generate an image | `POST /v1/text_to_image` | `client.text_to_image.create` | | Generate a video | `POST /v1/image_to_video` | `client.image_to_video.create` | | Character performance | `POST /v1/character_performance` | `client.character_performance.create` | Calling these methods will create a task. A task is a record of the generation operation. The response from the method will look like this: ```plaintext { "id": "17f20503-6c24-4c16-946b-35dbbce2af2f" } ``` The `id` field is the unique identifier for the task. You can use this ID to retrieve the task status and output from the [`GET /v1/tasks/{id}` endpoint](/api#tag/Task-management/paths/~1v1~1tasks~1%7Bid%7D/get), which is available as the `tasks.retrieve` method on the SDKs. * Node ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const task = await client.tasks.retrieve('17f20503-6c24-4c16-946b-35dbbce2af2f'); console.log(task); ``` * Python ```python from runwayml import RunwayML client = RunwayML() task = client.tasks.retrieve('17f20503-6c24-4c16-946b-35dbbce2af2f') print(task) ``` The response from the method will look like this: ```plaintext { "id": "17f20503-6c24-4c16-946b-35dbbce2af2f", "status": "PENDING", "createdAt": "2024-06-27T19:49:32.334Z" } ``` The [API reference](/api#tag/Task-management/paths/~1v1~1tasks~1%7Bid%7D/get) documents the statuses that a task can be in, along with the fields that are available for each status. Tasks are processed asychronously. The `tasks.retrieve` method returns the current status of the task, which you can poll until the task has completed. The task will eventually transition to a `SUCCEEDED`, `CANCELED`, or `FAILED` status. When polling, we recommend using an interval of 5 seconds or more. You should also add jitter, and handle non-200 responses with exponential backoff. Avoid using fixed interval polling (such as with JavaScript’s `setInterval`), since latency from the API can cause the polling to be too frequent. ### Built-in task polling As a convenience, all SDK methods that return a task include a helper method that polls for the task output. This reduces the amount of code you need to write to wait for a task to complete. * Node The `waitForTaskOutput` method is present on the unawaited response from the `create` methods on `textToImage`, `imageToVideo`, and other endpoints to start generations. ```ts // ✅ Call `waitForTaskOutput` on the unawaited response from `create` const imageTask = await client.textToImage .create({ model: 'gen4_image', promptText: 'A beautiful sunset over a calm ocean', ratio: '1360:768', }) .waitForTaskOutput(); console.log(imageTask.output[0]); // Print the URL of the generated image ``` ```ts // ✅ Getting the task ID for bookkeeping purposes // Notice: no `await` here. const imageTask = client.textToImage.create({ model: 'gen4_image', promptText: 'A beautiful sunset over a calm ocean', ratio: '1360:768', }); // Await the output of `create` to get the task ID const taskId = (await imageTask).id; console.log(taskId); // The task ID can be stored in your database, for instance. // Wait for the task to complete. It is safe to await `waitForTaskOutput` // after the output of `create` was awaited above. const completedTask = await imageTask.waitForTaskOutput(); console.log(completedTask.output[0]); // Print the URL of the generated image ``` ```diff // ❌ If you await the response from `create`, the result not have access to // `waitForTaskOutput`. const awaitedImageTask = await client.textToImage.create({ model: 'gen4_image', promptText: 'A beautiful sunset over a calm ocean', ratio: '1360:768', }); -const taskOutput = await awaitedImageTask.waitForTaskOutput(); ``` If the task fails (that is, its status becomes `FAILED`), a `TaskFailedError` will be thrown. You should handle this error appropriately. ```ts import { TaskFailedError } from '@runwayml/sdk'; try { const imageTask = await client.textToImage .create({ model: 'gen4_image', promptText: 'A beautiful sunset over a calm ocean', ratio: '1360:768', }) .waitForTaskOutput(); } catch (error) { if (error instanceof TaskFailedError) { // `taskDetails` contains the output of the tasks.retrieve call. console.error('Task failed:', error.taskDetails); } else { throw error; } } ``` The `waitForTaskOutput` method accepts an optional `options` parameter. This parameter can be used to specify a timeout and an `AbortSignal`. ```ts const imageTask = await client.textToImage .create({ model: 'gen4_image', promptText: 'A beautiful sunset over a calm ocean', ratio: '1360:768', }) .waitForTaskOutput({ // Wait up to 5 minutes for the task to complete timeout: 5 * 60 * 1000, // Abort the task if the request is cancelled abortSignal: myAbortSignal, }); ``` By default, `waitForTaskOutput` will wait for ten minutes before timing out. Upon timeout, a `TaskTimedOutError` will be thrown. Pass `null` to `timeout` to wait indefinitely. Disabling the timeout is not recommended as it may cause your server to experience issues if your Runway Dev organization reaches its concurrency limit or if Runway experiences an outage. It is recommended to use an `AbortSignal` to cancel polling if you are using `waitForTaskOutput` in the handler for an incoming request, such as on a web server. Here is an example of how to correctly integrate this into your application: * Express.js ```ts const runway = new RunwayML(); app.post('/generate-image', (req, res) => { // Create an AbortController that triggers when the request is closed // unexpectedly. const abortController = new AbortController(); req.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: req.body.prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); res.send(imageTask.output[0]); } catch (error) { if (error instanceof TaskFailedError) { res.status(500).send('Task failed'); } else { throw error; } } }); ``` * Koa ```ts const runway = new RunwayML(); app.post('/generate-image', async (ctx) => { // Create an AbortController that triggers when the request is closed // unexpectedly. const abortController = new AbortController(); ctx.req.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: ctx.request.body.prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); ctx.body = imageTask.output[0]; } catch (error) { if (error instanceof TaskFailedError) { ctx.status = 500; ctx.body = 'Task failed'; } else { throw error; } } }); ``` * Socket.io ```ts const runway = new RunwayML(); io.on('connection', (socket) => { // Create an AbortController that triggers when the socket is closed // unexpectedly. const abortController = new AbortController(); socket.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. socket.on('generate-image', async (prompt) => { try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); socket.emit('image', imageTask.output[0]); } catch (error) { if (error instanceof TaskFailedError) { socket.emit('error', 'Task failed'); } else { throw error; } } }); }); ``` Danger Triggering the `abortSignal` passed to `waitForTaskOutput` or hitting the passed `timeout` will not cancel the task. Cancelling the task must be done by invoking the [cancellation endpoint](/api#tag/Task-management/paths/~1v1~1tasks~1%7Bid%7D/delete). In addition to the methods that create new tasks, the `tasks.retrieve` method also returns a promise with a `waitForTaskOutput` method. This method is equivalent to the `waitForTaskOutput` method on the unawaited response from the `create` methods. ```ts const task = await client.tasks .retrieve('17f20503-6c24-4c16-946b-35dbbce2af2f') .waitForTaskOutput(); console.log(task.output[0]); ``` This is useful if you’d like to create a task in one request and wait for its output in another request, or for handling the case where the client disconnected before the task completed. Be aware that you must still add error handling for `TaskFailedError` and `TaskTimeoutError` when using this method. * Python ```ts const runway = new RunwayML(); app.post('/generate-image', (req, res) => { // Create an AbortController that triggers when the request is closed // unexpectedly. const abortController = new AbortController(); req.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: req.body.prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); res.send(imageTask.output[0]); } catch (error) { if (error instanceof TaskFailedError) { res.status(500).send('Task failed'); } else { throw error; } } }); ``` * Express.js ```ts const runway = new RunwayML(); app.post('/generate-image', async (ctx) => { // Create an AbortController that triggers when the request is closed // unexpectedly. const abortController = new AbortController(); ctx.req.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: ctx.request.body.prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); ctx.body = imageTask.output[0]; } catch (error) { if (error instanceof TaskFailedError) { ctx.status = 500; ctx.body = 'Task failed'; } else { throw error; } } }); ``` * Koa ```ts const runway = new RunwayML(); io.on('connection', (socket) => { // Create an AbortController that triggers when the socket is closed // unexpectedly. const abortController = new AbortController(); socket.on('close', () => { abortController.abort(); }); // 🚨 When performing a generation, be sure to add appropriate rate limiting // and other safeguards to prevent abuse. socket.on('generate-image', async (prompt) => { try { const imageTask = await runway.textToImage .create({ model: 'gen4_image', promptText: prompt, ratio: '1360:768', }) .waitForTaskOutput({ abortSignal: abortController.signal }); socket.emit('image', imageTask.output[0]); } catch (error) { if (error instanceof TaskFailedError) { socket.emit('error', 'Task failed'); } else { throw error; } } }); }); ``` * Socket.io The `wait_for_task_output` method is present on the unawaited response from the `create` methods on `text_to_image`, `image_to_video`, and other methods to start generations. ```python from runwayml import RunwayML client = RunwayML() image_task = client.text_to_image.create( model='gen4_image', prompt_text='A beautiful sunset over a calm ocean', ratio='1360:768', ) task_output = image_task.wait_for_task_output() print(task_output.output[0]) ``` If the task fails (that is, its status becomes `FAILED`), a `TaskFailedError` will be raised. You should handle this error appropriately. ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() # 🚨 When performing a generation, be sure to add appropriate rate limiting # and other safeguards to prevent abuse. try: image_task = client.text_to_image.create( model='gen4_image', prompt_text='A beautiful sunset over a calm ocean', ratio='1360:768', ) task_output = image_task.wait_for_task_output() except TaskFailedError as e: print('Task failed:', e.task_details) ``` The `wait_for_task_output` method accepts an optional `timeout` parameter. This parameter specifies the maximum amount of time to wait for the task to complete in seconds. If not specified, the default timeout is ten minutes. Pass `None` to `timeout` to wait indefinitely. Disabling the timeout is not recommended as it may cause your server to experience issues if your Runway API organization reaches its concurrency limit or if Runway experiences an outage. ```python from runwayml import RunwayML client = RunwayML() image_task = client.text_to_image.create( model='gen4_image', prompt_text='A beautiful sunset over a calm ocean', ratio='1360:768', ).wait_for_task_output( # Wait up to 5 minutes for the task to complete timeout=5 * 60, ) ``` When the timeout is reached, a `TaskTimeoutError` will be raised. Caution If the timeout is reached, the task will not be cancelled. Cancelling the task must be done by invoking the [cancellation endpoint](/api#tag/Task-management/paths/~1v1~1tasks~1%7Bid%7D/delete). In addition to the methods that create new tasks, the `tasks.retrieve` method also returns a promise with a `wait_for_task_output` method. This method is equivalent to the `wait_for_task_output` method on the unawaited response from the `create` methods. ```python task_output = await client.tasks.retrieve(image_task.id).wait_for_task_output() print(task_output.output[0]) ``` This is useful if you’d like to create a task in one request and wait for its output in another request, or for handling the case where the client disconnected before the task completed. Be aware that you must still add error handling for `TaskFailedError` and `TaskTimeoutError` when using this method. ## Code Skills & Agent Tool The [Runway Skills repository](https://github.com/runwayml/skills) provides a collection of tools, examples, and agent skills for working with Runway Dev. This includes: * **Claude Code Skills** - Pre-built skills for AI agents to interact with Runway Dev * **Code and Prompt Examples** - Sample implementations and integration patterns --- # API Versioning Policy > Understand API versioning on Runway Dev. Learn about version compatibility, deprecation timelines and how to manage API updates in your apps. The API uses an `X-Runway-Version` HTTP request header to specify which version of the API to use. This is in the form of a date, like `2024-11-06`. When you build an integration, setting a version header changes the default behavior of our API. ## When is a new version number created? We’ll add new features and functionality to the latest existing API version so long as the functionality can be added in a backwards-compatible way. You’ll be able to enjoy these features automatically without having to make changes to your integration. A new version will only be created when we make changes to the API that are not backwards-compatible. Here are a few examples of what might result in a new API version: * **The type of an input parameter is changed.** For instance, if a parameter that was previously a string representing a URL becomes an object containing more information about the asset referenced by the URL. * **The name of an input parameter is changed.** For example, we may restructure an API to better reflect new techniques or model constraints. An input that was previously a boolean to enable a feature may be changed to an object to allow extra configuration to be provided beyond. * **Functionality is removed.** If we deprecate a feature, we’ll create a new API version that removes the functionality. ## Version support We’ll offer support for old API versions for four months after a new version is created. After that date, API requests with an old API version may be rejected. ### Managing versions We strongly suggest using one of the official [Runway Dev SDKs](/api-details/sdks). We’ll publish new major versions of each SDK when a new API version is released. Using [TypeScript](https://www.typescriptlang.org/) or a Python type checker like [MyPy](https://www.mypy-lang.org/), you’ll be able to easily identify any places where your integration needs to be updated. ## `/v1` URLs While we include `/v1/` in our URLs, this is not used for versioning the API. We include this to reserve the ability to offer new functionality concurrently with our `/v1/` endpoints in the future. --- # API Input Parameters > Learn about input parameters for Runway Dev. Understand how to format images, videos, text prompts and other inputs for AI generation requests. When starting tasks on Runway Dev, you’ll often need to provide assets like images. Some restrictions exist for what you can provide. Assets can be provided via URLs or Data URIs. ## Size limits The size limits of inputs differ based on whether they are provided as a URL or as a data URI in the JSON request body. | Input type | URL size limit | Data URI size limit | Ephemeral uploads | | ---------- | -------------- | ------------------- | ----------------- | | Image | 16MB | 5MB | 200MB | | Video | 32MB | 16MB | 200MB | | Audio | 32MB | 16MB | 200MB | ## Inputs using URLs In all cases, URLs must meet some basic minimum requirements: 1. All URLs must be HTTPS. 2. URLs must reference a domain name in the hostname position, not an IP address. 3. The server should respond with valid `Content-Type` and `Content-Length` headers. 4. Redirects are not followed. If the URL returns a 3XX response code, the request is considered failed. 5. The length of any single URL should not exceed `2048` characters. Additionally, the server responding to the request must support HTTP `HEAD` requests. ### `Content-Type` values When specifying a URL, the `Content-Type` response header must be returned, and it must match the media type of your asset. File extensions in URLs are not considered. The `Content-Type`s that are supported are [listed below](#type-specific-requirements) for the supported asset types. Be aware that `application/octet-stream` and other generic values are explicitly not supported. ### User agent Runway will use a `User-Agent` header that starts with `RunwayML API/` when making requests to your server. If you use a scraping-prevention tool or WAF, be sure to allowlist our user agent string prefix. ## Inputs using Data URIs (base64 encoded) A [data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) allows you to pass the base64 encoded assets as part of a request to our API, rather than passing a URL to the asset hosted on another server. This can reduce the complexity of your integration by eliminating an upload step, but it also has a lower limit on the size of the assets that can be provided. Data URIs are accepted in any field where a URL is accepted unless otherwise specified. * An appropriate [content type](https://en.wikipedia.org/wiki/Media_type) must be specified in the data URI. For instance, your data URI for a JPEG should start with something like `data:image/jpg;base64,`. * Data URIs must use the base64 extension. The expected format is `data:content/type;base64,`. * The size limit for your data URI (specified above) is the limit for the **encoded data URI**. Keep in mind that base64-encoding your asset increases its size by about 33%: to fit an image within the **5MB limit**, the binary file must be **3.3MB** or less. Data URIs that do not follow these rules may be rejected with a 400 Bad Request error. ## Inputs using ephemeral uploads See [our documentation on ephemeral uploads](/assets/uploads) for more. ## Considerations of URLs, data URIs, and ephemeral uploads If you do not already have your asset stored in object storage, submitting your asset with a data URI can save you a step. However, the data URI size limit may be too small for some assets. If you cannot be sure that all assets are safely within the un-encoded size limit, you should upload assets to object storage instead. Submitting a request with data URIs instead of URLs will change the performance characteristics of your integration. Data URIs are part of the request payload, and thus cause your HTTP requests to take longer to submit: all data must be sent serially to our servers. Because the request body JSON must be processed before the assets can be extracted from their data URIs, this may increase the latency of our server responses. URLs, on the other hand, can be immediately processed in parallel. This means the time between submitting your HTTP request and the start of processing is likely much lower. However, latency between our servers and the server hosting your assets may increase the total request time. Using a CDN or object storage service will help to minimize this cost. Runway [ephemeral uploads](/assets/uploads) offer the benefits of object storage (like URLs) without needing to set up your own infrastructure. Assets are uploaded from your server to Runway’s object storage, returning a `runway://` URI that can be used anywhere a HTTPS URL could be used. Importantly, these URIs can only be used for 24 hours after being created. It’s also important to note that ephemeral uploads are rate limited, and have a minimum file size of 512 bytes (0.5KB). ## Type-specific requirements ### Images For fields that accept images, the asset must use one of the following encodings, along with the corresponding `Content-Type` header: | Codec | `Content-Type` header | | ----- | --------------------------- | | JPEG | `image/jpg` or `image/jpeg` | | PNG | `image/png` | | WebP | `image/webp` | GIF images are not supported. ### Videos For fields that accept videos, the asset must use one of the following codecs, along with the corresponding `Content-Type` header: | Container format | Usual file extension | Expected content type | Supported codecs | | ---------------- | -------------------- | --------------------- | ------------------------------------------------------------------------------- | | MP4 | `.mp4` | `video/mp4` | H.264, H.265/HEVC, AV1 | | QuickTime | `.mov` | `video/quicktime` | H.264, H.265/HEVC, Apple ProRes (422 Proxy, 422 LT, 422, 422 HQ, 4444, 4444 XQ) | | Matroska | `.mkv` | `video/x-matroska` | H.264, H.265/HEVC, VP8, VP9, AV1 | | WebM | `.webm` | `video/webm` | VP8, VP9, AV1 | | 3GPP | `.3gp` | `video/3gpp` | H.264 | | Ogg | `.ogv` | `video/ogg` | Theora | The following formats are supported but discouraged due to quality, performance, industry support, and file size: | Container format | Usual file extension | Expected content type | Supported codecs | | ---------------- | -------------------- | --------------------- | ----------------------- | | QuickTime | `.mov` | `video/quicktime` | MJPEG | | Matroska | `.mkv` | `video/x-matroska` | MPEG2 (H.262) | | AVI | `.avi` | `video/x-msvideo` | H.264, MJPEG, MSMPEG4v3 | | Flash Video | `.flv` | `video/x-flv` | FLV1, H.264 | | MPEG | `.mpg`, `.mpeg` | `video/mpeg` | MPEG2 (H.262) | Note that file extension is not considered by the API. Use this value for reference only. * **H.264 / AVC** - Most widely supported modern codec * **H.265 / HEVC** - High efficiency successor to H.264 * **AV1** - Royalty-free, modern codec * **VP8 / VP9** - Google’s codecs, primarily for WebM * **Apple ProRes** - Professional editing codec (422 Proxy, 422 LT, 422, 422 HQ, 4444, 4444 XQ) * **MPEG2 (H.262)** - Legacy codec for DVDs and broadcasts (discouraged) * **MJPEG** - Motion JPEG, frame-by-frame compression (discouraged) * **Theora** - Open codec for Ogg containers * **FLV1** - Legacy Flash Video codec (discouraged) * **MSMPEG4v3** - Legacy Microsoft codec (discouraged) Ruby Ruby (`ruby`) on `POST /v1/video_to_hdr` accepts SDR sources only. Videos whose streams are tagged as HDR (a PQ or HLG transfer function, or BT.2020 primaries) are rejected. ::: ### Audio For fields that accept audio, the asset must use one of the following codecs, along with the corresponding `Content-Type` header: | Container format | Usual file extension | Content type | Supported codecs | | ---------------- | -------------------- | ---------------------------------------- | ---------------------- | | MP3 | `.mp3` | `audio/mpeg`, `audio/mp3` | MP3 (MPEG-1/2 Layer 3) | | WAV | `.wav` | `audio/wav`, `audio/wave`, `audio/x-wav` | PCM (uncompressed) | | FLAC | `.flac` | `audio/flac`, `audio/x-flac` | FLAC (lossless) | | M4A | `.m4a` | `audio/mp4`, `audio/x-m4a` | AAC, ALAC | | AAC | `.aac` | `audio/aac`, `audio/x-aac` | AAC (raw) | * **MP3 (MPEG-1/2 Layer 3)** - Universal compatibility, lossy compression * **AAC (Advanced Audio Coding)** - Modern lossy codec, better quality than MP3 at same bitrate * **FLAC (Free Lossless Audio Codec)** - Lossless compression, popular for archival * **PCM (Pulse Code Modulation)** - Uncompressed audio, typically in WAV containers * **ALAC (Apple Lossless Audio Codec)** - Apple’s lossless codec, typically in M4A containers ## Aspect ratios and auto-cropping of inputs ### Video inputs When using video models, be aware of the supported `ratio` parameters, which configures the dimensions of the output files. Examples: * **Gen-4.5 Text-to-Video** supports Landscape `1280:720` and Portrait `720:1280` outputs only. * **Gen-4.5 Image-to-Video** supports Landscape `1280:720` `1584:672` `1104:832`, Portrait `720:1280` `832:1104` `672:1584` and Square `960:960` outputs. * **Gen-4 Turbo** and **Act-Two** support Landscape `1280:720` `1584:672` `1104:832`, Portrait `720:1280` `832:1104` and Square `960:960` outputs. * **Aleph 2.0** preserves the input video’s resolution (up to 1080p). Input videos must be 2–30 seconds and 30 FPS or lower. * **MiniMax H3 Max** supports `resolution` of `480p` or `768p`. Durations are 5–15 seconds. There is no `ratio` parameter. Image-to-video accepts a first frame or first and last keyframes (each at least 256 pixels on both sides); output aspect ratio follows the input image. Optional `promptExpansionMode` is `disabled`, `balanced` (the default), or `quality`. * **WAN 3.0** supports 480p, 720p, and 1080p (default `auto_1080p`). Durations are 2–30 seconds. Up to 10 image references, 5 video references, and 5 audio references (combined video and audio duration each under 15 seconds). Image-to-video accepts a start frame or first/last keyframes; keyframes cannot be mixed with references. * **Seedance 2.5** supports 18 `width:height` ratios across 480p, 720p, and 1080p (default `1280:720`). Durations are 4–30 seconds. 480p 16:9 / 9:16 use `854:480` / `480:854`. Input and reference videos must be at least 480p. Video-to-video supports `mode: "reference"` (default) or `mode: "extend"`. * **Seedance 2.0** supports 24 `width:height` ratios across 480p, 720p, 1080p, and 4K. * **Seedance 2.0 Fast** supports 12 `width:height` ratios across 480p and 720p only. * **Seedance 2.0 Mini** supports 12 `width:height` ratios across 480p and 720p only. * **MiniMax H3** supports `ratio` values `adaptive`, `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, and `9:16`, with `resolution` of `768P` or `2K` (default `2K`). Durations are 5–15 seconds. `adaptive` requires at least one reference image or video. Keyframe (`position: first`/`last`) and unpositioned reference-image modes cannot be mixed; reference audio is only valid with unpositioned reference images (not keyframes). * **Grok Imagine Video 1.5 Text-to-Video** supports `ratio` values `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, and `2:3`, with `resolution` of `480p`, `720p`, or `1080p` (default `480p`). Durations are 1–15 seconds. Up to 7 image references (addressable in the prompt as `[Image 1]`, `[Image 2]`, …) and 3 audio references (each 3–15 seconds). Audio references require at least one image reference, and requests with image references are capped at 720p. * **Grok Imagine Video 1.5 Image-to-Video** uses `resolution` (`480p` / `720p` / `1080p`, default `480p`) to set output quality. Output aspect ratio follows the input image. Only a single `first` frame is supported via `promptImage`. * **HappyHorse 1.0 Text-to-Video** supports 10 `width:height` ratios across 720p and 1080p (`1280:720`, `1920:1080`, `720:1280`, `1080:1920`, `960:960`, `1440:1440`, `1108:832`, `1662:1248`, `832:1108`, `1248:1662`). * **HappyHorse 1.0 Image-to-Video** uses `resolution` (`720P` / `1080P`, default `720P`) to set output quality. Output aspect ratio follows the input image. Prompt images must be at least 300px on each side. * **Gemini Omni Flash Text-to-Video** and **Image-to-Video** support Landscape `1280:720` and Portrait `720:1280` outputs only (always 720p, default `1280:720`). * **Gemini Omni Flash Video-to-Video** has no `ratio` parameter; output is 720p with orientation (landscape 16:9 or portrait 9:16) matched to the input video. Input videos can be at most 10 seconds. * **Magnific Video Upscaler** (`magnific_video_upscaler_creative`) accepts input videos up to 30 seconds. Set `resolution` to `720p`, `1k`, `2k` (default), or `4k` for target output quality. Always include `model: magnific_video_upscaler_creative` on `POST /v1/video_upscale`. If your input asset is not exactly of the `ratio` you specify, the model will auto-crop your asset from the center to the aspect ratio parameter provided. Third party models (e.g., `veo3.1`, `veo3.1_fast`, `wan3`, `h3_max`, `grok_imagine_1_5`, `grok_imagine_image_2`, `muse_image`, `gpt_image_2_5_flare`, `gpt_image_2_5_sunburst`, `happyhorse_1_0`, `gemini_omni_flash`, etc.) may crop or resize inputs in other ways. ### Image references Image references may be resized if they are too large or small to be provided to the model you choose. It is not recommended to provide reference images smaller than 640×640px or larger than 4K. ### Input asset aspect ratio requirements For models that accept image or video references, the asset’s aspect ratio (width ÷ height) must fall within the supported ranges below: | Model | Input type | Min aspect ratio | Max aspect ratio | | --------------------------------- | ----------------------------------- | ---------------- | ---------------- | | Gen-4 Turbo Image-to-Video | Prompt image | 0.5 | 2.358 | | Gen-4 Image-to-Video | Prompt image | 0.5 | 2.358 | | Gen-4.5 Image-to-Video | Prompt image | 0.5 | 2 | | Act-Two | Character image | 0.5 | 2.358 | | Act-Two | Character video | 0.5 | 2.358 | | Act-Two | Reference video | 0.5 | 2.358 | | Seedance 2.5 Image-to-Video | Prompt image (first/last/reference) | 0.4 | 4 | | Seedance 2.5 Text-to-Video | Reference image | 0.4 | 4 | | Seedance 2.5 Video-to-Video | Reference image | 0.4 | 4 | | Seedance 2 Image-to-Video | Prompt image (first/last/reference) | 0.4 | 4 | | Seedance 2 Text-to-Video | Reference image | 0.4 | 4 | | Seedance 2 Video-to-Video | Reference image | 0.4 | 4 | | Seedance 2.0 Fast Image-to-Video | Prompt image (first/last/reference) | 0.4 | 4 | | Seedance 2.0 Fast Text-to-Video | Reference image | 0.4 | 4 | | Seedance 2.0 Fast Video-to-Video | Reference image | 0.4 | 4 | | Seedance 2.0 Mini Image-to-Video | Prompt image (first/last/reference) | 0.4 | 4 | | Seedance 2.0 Mini Text-to-Video | Reference image | 0.4 | 4 | | Seedance 2.0 Mini Video-to-Video | Reference image | 0.4 | 4 | | MiniMax H3 Max Image-to-Video | Prompt image (first/last) | 0.2 | 4 | | MiniMax H3 Image-to-Video | Prompt image (first/last/reference) | 0.2 | 4 | | MiniMax H3 Text-to-Video | Reference image | 0.2 | 4 | | MiniMax H3 Video-to-Video | Reference image | 0.2 | 4 | | Veo 3.1 / 3.1 Fast Image-to-Video | First/last keyframes | 0.5 | 2 | | HappyHorse 1.0 Image-to-Video | Prompt image | 0.55 | 1.8 | | Gemini Omni Flash Image-to-Video | Prompt image | 0.5 | 2 | | Gemini 2.5 Flash Text-to-Image | Reference image | 0.25 | 4 | --- # API Output Formats > Understand Runway Dev output formats and responses. Learn how to handle generated videos, images and metadata from AI generation requests. After a task succeeds, the `GET /v1/tasks/:id` endpoint will return a response like this: ```json { "id": "d2e3d1f4-1b3c-4b5c-8d46-1c1d7ee86892", "status": "SUCCEEDED", "createdAt": "2024-06-27T19:49:32.335Z", "output": [ "https://dnznrvs05pmza.cloudfront.net/output.mp4?_jwt=..." ] } ``` The `output` member will contain one or more URLs that link to the result of your generation. It’s important to note that these URLs are ephemeral: they will expire within 24-48 hours of accessing the API. We expect you to download the data at this endpoint and save it to your own storage. Since these URLs will expire, do not expose them directly in your product. --- # Ephemeral uploads > Upload large files to Runway Dev for processing When providing files as [inputs](/assets/inputs), there are size limits for assets provided by URL or data URI. URLs need to be downloaded and data URIs must be processed as part of the request body: both consume resources at the time a generation is started. To avoid these limits, you can upload ephemeral files to Runway Dev using the uploads API. ## Uploading files You can upload a file to Runway by using our [SDKs](/api-details/sdks/). * Node You can pass Node `fs` streams or `File`s (or [`File`-like objects](https://github.com/runwayml/sdk-node/blob/main/src/internal/to-file.ts#L37-L52)) to `createEphemeral`. ```ts import * as fs from 'node:fs'; import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const myFile = fs.createReadStream('./path/to/file.mp4'); const { uri } = await client.uploads.createEphemeral(myFile); console.log(uri); // runway://... ``` Other files can be uploaded by passing them to the `toFile` helper from `@runwayml/sdk`. Supported types include: * `Blob` and [`Blob`-like objects](https://github.com/runwayml/sdk-node/blob/main/src/internal/to-file.ts#L11-L32) * `Buffer` * `ArrayBuffer` * Typed arrays (e.g., `Uint8Array`) * `DataView` * `Response` and [`Response`-like objects](https://github.com/runwayml/sdk-node/blob/main/src/internal/to-file.ts#L57-L66) * Any [async iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) object that yields `string`s, `Buffer`s, `ArrayBuffer`s, `ArrayBufferView`s, `Blob`-like objects, or `DataView`s. ```ts import * as fs from 'node:fs/promises'; import RunwayML, { toFile } from '@runwayml/sdk'; const client = new RunwayML(); const { uri } = await client.uploads.createEphemeral( toFile( await fs.readFile('./path/to/file.mp4') // You must pass a filename 'file.mp4', ), ); console.log(uri); // runway://... ``` * Python You can pass the following types to `create_ephemeral`: * `pathlib.Path` object or an object that implements `os.PathLike` * An `IOBase` object (e.g., `BytesIO`) with a `name` property containing the filename ```python from pathlib import Path from runwayml import RunwayML client = RunwayML() my_file = Path('./path/to/file.mp4') response = client.uploads.create_ephemeral(file=my_file) print(response.uri) # runway://... ``` If you do not have a `Path` or `IOBase` with a name, you can pass a two-tuple in the form `(filename, content)` where `filename` is the name of the file and `content` is one of: * A file-like object (e.g., the output of `open()`) * `bytes` ```python from runwayml import RunwayML client = RunwayML() with open('./path/to/file.mp4') as my_file: response = client.uploads.create_ephemeral( file=('file.mp4', my_file), ) print(response.uri) # runway://... ``` The resulting `runway://` URI can be used anywhere that a URL or data URI can be used in the API. ### Considerations * `runway://` URIs are only valid for 24 hours. * The maximum uploadable file is 200MB. The minimum uploadable file is 512 bytes. * You must have purchased credits to use this feature. * There is a rate limit on the upload of ephemeral files. Ephemeral uploads may be used multiple times, which can conserve bandwidth. If you intend to use a file multiple times, consider using ephemeral uploads. Be aware, though, that the URI will expire 24 hours after creation and the file must be re-uploaded. ## Using ephemeral uploads without an SDK Tip See the [API Reference](/api#tag/Uploads) for full details on using the `/v1/uploads` endpoint. If you use our REST API directly, you can still use ephemeral uploads. By calling `POST /v1/uploads` with the following JSON body, you can start an upload: ```json { "filename": "filename.mp4", "type": "ephemeral" } ``` * `filename`: A string containing the filename of the file you are uploading. The file extension must be representative of the file’s contents. * `type`: Must be set to `"ephemeral"`. The server will respond with the following details: ```json { "uploadUrl": "https://...", "fields": { ... }, "runwayUri": "runway://..." } ``` The `runwayUri` value is the value that may be used when creating a new generation on Runway Dev. To upload the file to Runway’s servers, create a `POST` request to the URL at `uploadUrl`. Pass the fields in the dictionary `fields` as the multipart form-encoded POST body, and the contents of the file as the `file` field. Once the upload completes successfully, the `runway://` URI is ready to use. If the upload fails, do not retry. Instead, make a new request to `/v1/uploads` and start over. Be sure to follow our guidelines for [handling errors](/errors/errors/) to ensure your integration robustly handles different kinds of failure modes. --- # Runway Characters > Build real-time conversational avatars powered by GWM-1, Runway's General World Model. Deploy custom characters with full control over voice, personality, knowledge, and actions. Build fully custom conversational characters powered by GWM-1, Runway’s General World Model. Generate expressive digital personas from a single image—photorealistic or animated, human or non-human—with full control over voice, personality, knowledge, and actions. No fine-tuning required. ## What you can build ### Customer support Deploy branded characters that maintain your visual identity. Use actual representatives or create stylized brand ambassadors. ### Learning & development Bring training programs to life with interactive tutors and coaches capable of extended educational conversations. ### Brand experiences Your mascots and animated characters can now hold real-time conversations. Your creative vision doesn’t stop at static content. ### Interactive characters Game hosts, dungeon masters, companions, and contextual avatars for immersive experiences. ### Get started [Quickstart ](/characters/quickstart)Build your first app to call a Runway Character in 5 minutes. [Custom Characters ](/characters/create-your-own)Create your own character from a single image — no training required. [Embedded Widget ](/characters/widget)Add a Character to any website with a single script tag — no server required. [Custom Voices ](/characters/custom-voice)Design a new voice from a text prompt or clone one from an audio sample. [LiveKit Agents ](/characters/livekit)Bring your own agent — you control STT, LLM, and TTS, Runway provides the avatar video layer. [ElevenLabs Agents ](/characters/elevenlabs)Connect an ElevenLabs agent — ElevenLabs handles the conversation, Runway renders the Character. --- # Core Concepts > Understand the key concepts behind Runway Characters including avatars, sessions, voice configuration, and the real-time architecture. Preview chat transcripts in the Developer Portal or retrieve them with the conversations API. ## Avatars and Sessions Understanding the distinction between Avatars and Sessions is fundamental to building with the Characters API. **Avatars** are persistent personas with a defined appearance, voice, and personality. Define your Avatar with a single reference image—any visual style works, from photorealistic humans to animated mascots to stylized brand characters. Configure voice, personality, knowledge base, and conversational actions. **Sessions** are live WebRTC connections for real-time conversation. Each Session connects a user to an Avatar for a single interaction. Sessions have a maximum duration of 5 minutes. ## Session lifecycle Sessions progress through a defined set of states: ```plaintext ┌───────────┐ ┌──────────┤ NOT_READY ├──────────┐ │ └─────┬─────┘ │ │ │ │ ▼ ▼ ▼ CANCELLED READY FAILED ┌──┴──┐ │ │ ▼ ▼ RUNNING FAILED ┌──┴──┐ │ │ ▼ ▼ COMPLETED CANCELLED ``` | Status | Description | | ----------- | -------------------------------------------------------------------- | | `NOT_READY` | Session is being provisioned. Poll until ready. | | `READY` | Session is ready to connect. The `sessionKey` is available. | | `RUNNING` | WebRTC connection is active. The conversation is in progress. | | `COMPLETED` | Session ended normally after the conversation finished. | | `FAILED` | Session encountered an error. Check the `failure` field for details. | | `CANCELLED` | Session was explicitly cancelled before completion. | One-time consume Session credentials can only be retrieved once. If the WebRTC connection fails after credentials are consumed, you must create a new Session. ## Creating a Character ### Developer Portal The [Developer Portal](https://dev.runwayml.com/) provides a visual interface for managing Characters: 1. Go to the **Characters** tab 2. Click **Create a Character** 3. Define your Character with a single reference image 4. Configure voice, personality, knowledge base, and conversational actions 5. Preview and test before deploying The portal also lets you preview available voices and access session recordings. Portal = API Everything you see in the Developer Portal can be done via the API. The portal is built on the same public API available to you. ### API Create and manage Avatars programmatically for automated workflows or dynamic Avatar generation: * Node ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const avatar = await client.avatars.create({ name: 'Support Agent', referenceImage: 'https://example.com/avatar.png', voice: { type: 'runway-live-preset', presetId: 'clara', }, personality: 'You are a helpful customer support agent...', }); ``` * Python ```python from runwayml import RunwayML client = RunwayML() avatar = client.avatars.create( name='Support Agent', reference_image='https://example.com/avatar.png', voice={ 'type': 'runway-live-preset', 'preset_id': 'clara', }, personality='You are a helpful customer support agent...', ) ``` See the [API reference](/api) for the complete list of Avatar management endpoints. ## Per-call overrides The `personality` and `startScript` you set on an Avatar are **defaults**. When creating a Session, you can override either field to tailor the conversation for a specific caller — without modifying the Avatar itself. If you omit these fields, the Avatar’s values are used as before. This is fully backward-compatible with existing integrations. This is useful when you want to pass dynamic, user-specific context into each call. For example, a single support agent Avatar can greet every caller by name and adapt to their account details: * Node ```ts const session = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId: 'your-avatar-id' }, personality: `You are a helpful support agent for Acme Corp. The customer's name is ${user.name} and they are on the ${user.plan} plan. Address them by name. Be aware of their plan limits when answering billing questions.`, startScript: `Hi ${user.name}! I'm your Acme support assistant. How can I help you today?`, }); ``` * Python ```python session = client.realtime_sessions.create( model='gwm1_avatars', avatar={ 'type': 'custom', 'avatar_id': 'your-avatar-id' }, personality=f"""You are a helpful support agent for Acme Corp. The customer's name is {user['name']} and they are on the {user['plan']} plan. Address them by name. Be aware of their plan limits when answering billing questions.""", start_script=f"Hi {user['name']}! I'm your Acme support assistant. How can I help you today?", ) ``` | Field | Type | Description | | ------------- | ----------------- | ----------------------------------------------------------------------------- | | `personality` | string (optional) | Overrides the Avatar’s system prompt for this session. Max 10,000 characters. | | `startScript` | string (optional) | Overrides the Avatar’s opening message for this session. Max 2000 characters. | Provisioning time Sessions created with `personality` or `startScript` overrides may take slightly longer to reach the `READY` state compared to sessions that use the Avatar’s defaults. Account for this in your polling logic. ## Reference image guidelines Generate expressive Avatars from a single image with zero fine-tuning required. For best results: * **Any visual style works**: photorealistic humans, animated mascots, stylized brand characters * Use high-quality images with good lighting * Ensure the face is clearly visible and centered * Avoid images with multiple people or obstructions * Recommended aspect ratio: 1088×704 ## Voice configuration Configure your Avatar’s voice using voice presets. Avatars support full conversational expressiveness including natural speech patterns and lip-syncing. Here are some examples: | Preset ID | Name | Style | | ---------- | -------- | ---------------------------- | | `clara` | Clara | Soft, approachable | | `victoria` | Victoria | Firm, professional | | `vincent` | Vincent | Knowledgeable, authoritative | * Node ```ts voice: { type: 'runway-live-preset', presetId: 'clara', } ``` * Python ```python voice={ 'type': 'runway-live-preset', 'preset_id': 'clara', } ``` Preview all available voices in the [Developer Portal](https://dev.runwayml.com/). You can also [design a custom voice from a text prompt or clone one from an audio sample](/characters/custom-voice). ## Conversation transcripts and recordings After a conversation ends, you can review the **transcript** in the [Developer Portal](https://dev.runwayml.com/) by opening your Character and browsing past conversations. If you created the call with `POST /v1/realtime_sessions`, the returned session ID is also the conversation ID you use with the conversations API later. In other words, the `sessionId` from session creation becomes the `conversationId` for transcript and recording retrieval. To retrieve or export transcripts programmatically, call the conversations endpoints on the public API (response shapes and SDK examples are in the [API reference](/api)): ```http GET /v1/avatars/{id}/conversations GET /v1/avatars/{id}/conversations/{conversationId} ``` Use the list endpoint to browse past sessions for a character. Use the conversation detail endpoint to fetch the full `transcript` and, when available, a `recordingUrl` for downloading the conversation recording. * Node ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const avatarId = '550e8400-e29b-41d4-a716-446655440000'; const { id: sessionId } = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId }, }); const conversation = await client.avatars.conversations.retrieve( avatarId, sessionId ); console.log(conversation.transcript); console.log(conversation.recordingUrl); ``` * Python ```python from runwayml import RunwayML client = RunwayML() avatar_id = '550e8400-e29b-41d4-a716-446655440000' session = client.realtime_sessions.create( model='gwm1_avatars', avatar={ 'type': 'custom', 'avatar_id': avatar_id }, ) conversation = client.avatars.conversations.retrieve( avatar_id, session.id ) print(conversation.transcript) print(conversation.recording_url) ``` The `recordingUrl` is temporary. If it has expired, fetch the conversation again to get a fresh download URL. --- # Create Your Own Characters > Create your own Runway character with just one image and video call it in 5 minutes. In this tutorial, we will create our own character and do a video call with it in a React app. We only need one image of the character, and we can start calling the character immediately, no training required. [Create your own characters tutorial](https://www.youtube.com/embed/mXsi2ViqWP0) **Helpful links:** * [Runway Dev account](https://dev.runwayml.com) * [Runway avatar SDK template](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-simple) ### 1. Create a Runway Dev account Create an account at [dev.runwayml.com](https://dev.runwayml.com). ![Runway Dev login page](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/1.png) Once you log in, you will see a “Characters” tab at the top bar. ### 2. Create your own character Click on the “Create a Character” button. ![Characters tab with Create a Character button](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/2.png) ![Avatar creation form](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/10.png) #### 2-1. Upload a character image Here are some tips for choosing a good character image: * Use high-quality, front-facing photos with good lighting * Ensure the face is clearly visible and centered * Avoid images with multiple people or obstructions * Recommended: 16:9 aspect ratio ![Upload character image](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/11.png) #### 2-2. Choose a voice You can click the “play” button to hear each voice option, then select the one you like. There are also options to customize the voice. #### 2-3. Input instructions Input “Instructions” for your character. For example: “Your name is xxx, you are a customer service support for a company called xxx. You help users with their questions.” ![Instructions input](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/12.png) #### 2-4. Input a starting script (optional) If you’d like to tell your character to say something at the beginning of every conversation, input a “Starting script”. For example: “Hello, my name is xxx, how can I help you?” Per-call overrides The instructions and starting script you set here become the defaults for every session. You can override them per call to inject user-specific context — for example, the caller’s name or account details. See [Per-call overrides](/characters/concepts#per-call-overrides) for details. #### 2-5. Upload knowledge (optional) If you have longer text files that your character should know about, feel free to upload a `.txt` file there. For example, a `.txt` file of the product information that your character should know about. #### 2-6. Create the character Click on “Create Character”. You will see a character page like this — copy the “Avatar ID”. It is a UUID that has 32 hex digits (e.g., `8be4df61-93ca-11d2-aa0d-00e098032b8c`). ![Character page showing Avatar ID](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/13.png) ### 3. Use the custom character in your React app If you haven’t already, check out the [previous Quickstart tutorial](/characters/quickstart) or [React app template](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-simple) and follow the instructions in the README to run the app. Once you get the app running, update the code in two places: **1.** Go to `my-avatar-app/app/page.tsx` and update `MY_AVATAR`’s `id` to the ID of the avatar you just created. Optionally, you can also update the `name` or `imageUrl`. ```typescript const MY_AVATAR = { id: "8be4df61-93ca-11d2-aa0d-00e098032b8c", name: "Yining", imageUrl: "https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/tutorial/yining-character.jpeg", }; ``` **2.** Go to `my-avatar-app/app/api/avatar/connect/route.ts` and update the avatar type to `"custom"`. ```typescript const avatar = { type: "custom" as const, avatarId: avatarId }; ``` Now if you run the app again, you should see your own character on the page and you can start a conversation with it. ![Custom character in the app](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/14.png) ![Active video call with custom character](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/15.png) *** That’s it! There are so many applications for custom characters — we’re excited for you to create your own. Please reach out to us if you have any questions via [Runway support](https://help.runwayml.com/hc/en-us/requests/new). ### Next steps [Add Knowledge to Your Character ](/characters/documents)Upload documents to give your character domain-specific expertise. --- # Custom voices > Design a new voice from a text prompt or clone a voice from an audio sample. Assign custom voices to your Characters for a unique, branded sound. The Voices API lets you create custom voices for your Characters. Design an entirely new voice from a text prompt, or clone a voice from an audio sample. Once created, assign the voice to any Avatar. ## Voice design Create a voice by describing the characteristics you want. The prompt should include details like tone, accent, pacing, and personality. * Node ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const voice = await client.voices.create({ name: 'Brand Ambassador', from: { type: 'text', prompt: 'A warm, friendly voice with a slight British accent. Speaks at a measured pace with a professional yet approachable tone.', model: 'eleven_ttv_v3', }, }); console.log('Voice created:', voice.id); ``` * Python ```python from runwayml import RunwayML client = RunwayML() voice = client.voices.create( name='Brand Ambassador', from_={ 'type': 'text', 'prompt': 'A warm, friendly voice with a slight British accent. Speaks at a measured pace with a professional yet approachable tone.', 'model': 'eleven_ttv_v3', }, ) print('Voice created:', voice.id) ``` | Parameter | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------- | | `name` | string | A name for the voice (max 100 characters). | | `from.type` | `"text"` | Indicates voice design from a text prompt. | | `from.prompt` | string | A description of the desired voice characteristics. Must be at least 20 characters. | | `from.model` | string | The voice design model. Use `eleven_ttv_v3` (latest) or `eleven_multilingual_ttv_v2`. | ## Voice cloning Clone a voice from an audio sample. Provide a clear recording with minimal background noise and varied tone for best results. * Node ```ts const voice = await client.voices.create({ name: 'Cloned Narrator', from: { type: 'audio', audio: 'https://example.com/voice-sample.mp3', }, }); console.log('Voice created:', voice.id); ``` * Python ```python voice = client.voices.create( name='Cloned Narrator', from_={ 'type': 'audio', 'audio': 'https://example.com/voice-sample.mp3', }, ) print('Voice created:', voice.id) ``` The audio sample must be between 10 seconds and 5 minutes long, and at most 10 MB. You can pass a public HTTPS URL, a `runway://` upload URI, or a `data:audio/...` data URI. ## Voice status Voice creation is asynchronous. After calling `create`, poll the voice until its status is `READY`. * Node ```ts const voice = await client.voices.retrieve(voiceId); if (voice.status === 'READY') { console.log('Preview:', voice.previewUrl); } ``` * Python ```python voice = client.voices.retrieve(id=voice_id) if voice.status == 'READY': print('Preview:', voice.preview_url) ``` | Status | Description | | ------------ | -------------------------------------------- | | `PROCESSING` | Voice is being generated. Poll until ready. | | `READY` | Voice is ready. A `previewUrl` is available. | | `FAILED` | Generation failed. Check `failureReason`. | ## Assigning a custom voice to an Avatar Once the voice is ready, assign it to an Avatar by setting the voice type to `custom` and providing the voice ID. * Node ```ts await client.avatars.update(avatarId, { voice: { type: 'custom', id: voice.id, }, }); ``` * Python ```python client.avatars.update( avatar_id, voice={ 'type': 'custom', 'id': voice.id, }, ) ``` You can also create a new Avatar with a custom voice directly: * Node ```ts const avatar = await client.avatars.create({ name: 'Support Agent', referenceImage: 'https://example.com/avatar.png', voice: { type: 'custom', id: voice.id, }, personality: 'You are a helpful customer support agent...', }); ``` * Python ```python avatar = client.avatars.create( name='Support Agent', reference_image='https://example.com/avatar.png', voice={ 'type': 'custom', 'id': voice.id, }, personality='You are a helpful customer support agent...', ) ``` ## Listing voices Retrieve all custom voices for your organization. * Node ```ts const voices = await client.voices.list(); for await (const voice of voices) { console.log(voice.name, voice.status); } ``` * Python ```python for voice in client.voices.list(): print(voice.name, voice.status) ``` You can also manage custom voices through the [Developer Portal](https://dev.runwayml.com/). See the [API reference](/api) for all available endpoints. --- # Knowledge base > Give your Avatars domain-specific knowledge using Documents. Upload content that Avatars can reference during conversations. The Documents API lets you give Avatars access to domain-specific knowledge. Upload content that your Avatar can reference during conversations to provide accurate, contextual responses. Token limit Each Avatar supports up to 50,000 tokens of knowledge. We plan to increase this limit in the future. ## Why use a knowledge base A knowledge base helps your Avatar stay on topic and provide accurate information. Common use cases: * **Customer support**: FAQs, product information, company policies * **Quizzes and games**: Question banks, correct answers, scoring rules * **Education**: Course material, reference content, learning objectives * **Brand experiences**: Brand guidelines, messaging, product details ## Supported content | Format | Description | | ---------- | ----------------------------------------------- | | Plain text | Unformatted text content | | Markdown | Structured content with headings and formatting | More formats are planned for future releases. ## Adding knowledge to an Avatar The flow is: create a Document, then link it to your Avatar. ### 1. Create a Document * Node ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const document = await client.documents.create({ name: 'Product FAQ', content: '# Product FAQ\n\n## What is your return policy?\n\nWe offer a 30-day return policy...', }); console.log('Document created:', document.id); ``` * Python ```python from runwayml import RunwayML client = RunwayML() document = client.documents.create( name='Product FAQ', content='# Product FAQ\n\n## What is your return policy?\n\nWe offer a 30-day return policy...', ) print('Document created:', document.id) ``` ### 2. Update a Document You can update a Document’s name, content, or both using the `update` method. * Node ```ts await client.documents.update(document.id, { name: 'Updated Product FAQ', content: '# Product FAQ\n\n## What is your return policy?\n\nWe now offer a 60-day return policy...', }); ``` * Python ```python client.documents.update( id=document.id, name='Updated Product FAQ', content='# Product FAQ\n\n## What is your return policy?\n\nWe now offer a 60-day return policy...', ) ``` Both fields are optional — provide only the fields you want to change. ### 3. Link the Document to your Avatar Update your Avatar to attach the Document. This replaces any existing Document attachments. * Node ```ts await client.avatars.update(avatarId, { documentIds: [document.id], }); ``` * Python ```python client.avatars.update( avatar_id, document_ids=[document.id], ) ``` ### 4. Start a Session The Avatar now has access to the knowledge during conversations. Start a Session as usual: * Node ```ts const session = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId: avatarId, }, }); ``` * Python ```python session = client.realtime_sessions.create( model='gwm1_avatars', avatar={ 'type': 'custom', 'avatar_id': avatar_id, }, ) ``` You can also manage Documents through the [Developer Portal](https://dev.runwayml.com/). See the [API reference](/api) for all available endpoints. --- # ElevenLabs Agents > Use Runway Characters as the visual layer for your ElevenLabs agent. You own STT, LLM, and TTS in ElevenLabs — Runway renders lip-synced Character video. Use Runway Characters with an [ElevenLabs agent](https://elevenlabs.io/docs/eleven-agents/overview) when you already have one and want a custom Character to deliver it. ElevenLabs handles speech recognition, reasoning, and text-to-speech; Runway lip-syncs that audio to your Character over WebRTC and does not run its own conversation model for the session. ## Before you start You’ll need: * A [Runway API key](https://dev.runwayml.com/settings/api-keys) * A **custom Character** your API key owns and is ready to use * An [ElevenLabs API key](https://elevenlabs.io/app/settings/api-keys) with ElevenAgents read permission * An [ElevenLabs agent](https://elevenlabs.io/app/agents/agents) with **Advanced → User input audio format → PCM 16000Hz** Set these server-side environment variables (see the [Next.js example](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-elevenlabs) for a full `.env.example`): ```bash RUNWAYML_API_SECRET=... RUNWAY_AVATAR_ID=... ELEVENLABS_API_KEY=... ELEVENLABS_AGENT_ID=... ``` Server-side only Keep your Runway API secret and ElevenLabs API key on the server. The browser uses the Characters SDK for WebRTC only — no ElevenLabs SDK, and the signed WebSocket URL never goes to the client. ## Guide 1. **Install the Characters SDK** ```bash npm install @runwayml/avatars @runwayml/avatars-react ``` Server helpers such as `createElevenLabsSession` live in `@runwayml/avatars/api`. The React package re-exports them for convenience, but your API route should import from the core package. 2. **Create a server route** `createElevenLabsSession` from `@runwayml/avatars/api` fetches an [ElevenLabs signed URL](https://elevenlabs.io/docs/eleven-agents/customization/authentication), creates a Runway session with `integration: { type: "elevenlabs", signedUrl }`, polls until `READY`, and returns `sessionId`, `sessionKey`, `avatarId`, and `baseUrl` for `AvatarCall`. app/api/avatar/connect/route.ts ```ts // app/api/avatar/connect/route.ts import { createElevenLabsSession } from '@runwayml/avatars/api'; export async function POST() { const session = await createElevenLabsSession({ runwayApiSecret: process.env.RUNWAYML_API_SECRET!, avatarId: process.env.RUNWAY_AVATAR_ID!, elevenLabsApiKey: process.env.ELEVENLABS_API_KEY!, elevenLabsAgentId: process.env.ELEVENLABS_AGENT_ID!, }); return Response.json(session); } ``` Also exported from `@runwayml/avatars-react/api` if you already depend on the React package only. Session create constraints Use a custom `avatarId` your key can access. Do not pass `personality`, `startScript`, or `tools` — the ElevenLabs agent owns conversation config. Without the SDK, run the same three steps manually: fetch the signed URL, create the session, poll until ready. See [Building your integration](/characters/integration) for session lifecycle patterns. 3. **Connect from the client** Pass the JSON from your connect route to `AvatarCall`: ```tsx 'use client'; import { AvatarCall } from '@runwayml/avatars-react'; import '@runwayml/avatars-react/styles.css'; export function Conversation({ sessionId, sessionKey, avatarId, baseUrl }) { return ( ); } ``` `createElevenLabsSession` echoes `avatarId` and `baseUrl` so you can forward the server response without rebuilding those fields. 4. **Test it** Trigger your connect route, then confirm Character video appears while your ElevenLabs agent responds. Mic audio goes Runway → worker → ElevenLabs; agent audio drives lip sync on the way back. Full working app: [Next.js + ElevenLabs example](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-elevenlabs). ## End sessions promptly Runway bills realtime Character sessions while the Character worker is active. End calls via `AvatarCall` `onEnd` and handle errors on your connect route so sessions are not left running. ## Troubleshooting | Symptom | Fix | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `404 Could not find Avatar` | Use a Character ID scoped to the same API key (see [Custom Avatars](/characters/create-your-own)) | | `401` or inactive API key | Check your Runway API key in the [Developer Portal](https://dev.runwayml.com/settings/api-keys) | | Session stuck `NOT_READY` | Retry; confirm your environment has the ElevenLabs integration deployed | | Silent agent / no audio | Set **PCM 16000Hz** input format on the ElevenLabs agent | | `personality… cannot be used with integration` | Remove `personality`, `startScript`, and `tools` from the session create request | | ElevenLabs signed URL fails | Check `xi-api-key`, agent ID, and ElevenAgents read permission | See [Troubleshooting](/characters/troubleshooting) for general Characters SDK debugging. ## Learn more [Next.js + ElevenLabs example ](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-elevenlabs)Full working app with createElevenLabsSession and AvatarCall. [ElevenLabs agent authentication ](https://elevenlabs.io/docs/eleven-agents/customization/authentication)Signed URLs and server-side API key handling for ElevenLabs agents. [LiveKit Agents ](/characters/livekit)Bring your own agent via LiveKit instead of ElevenLabs. [Building your integration ](/characters/integration)Default Runway-owned sessions and custom React UI patterns. --- # Building your integration > Learn how to integrate Runway Avatars into your application with server-side session management and React components. This guide walks through building a complete Avatar integration using Next.js App Router. The same patterns apply to other React frameworks. Keep your API key secure Your `RUNWAYML_API_SECRET` must never be exposed to the client. Always create Sessions server-side. If your key is compromised, rotate it immediately in the [Developer Portal](https://dev.runwayml.com/settings/api-keys). ## Architecture overview Avatar Sessions require a server component to keep your API key secure. The client never sees your Runway API secret. ```plaintext ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Client │ │ Your Server │ │ Runway API │ │ (React App) │ │ (Next.js) │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ 1. Request Session │ │ │ POST /api/avatar/session │ │ │ ─────────────────────────►│ │ │ │ │ │ │ 2. Create Session │ │ │ POST /v1/realtime_sessions │ │ ─────────────────────────►│ │ │ │ │ │ 3. Poll until ready │ │ │ GET /v1/realtime_sessions/:id │ │ ─────────────────────────►│ │ │ │ │ │ 4. Consume credentials │ │ │ POST /v1/realtime_sessions/:id/consume │ │ ─────────────────────────►│ │ │ │ │ 5. Return credentials │◄───────────────────────── │ │◄───────────────────────── │ │ │ │ │ │ 6. WebRTC connection │ │ │ ─────────────────────────────────────────────────────►│ │ │ │ ``` ## Installation Install the Runway server SDK and React client: ```bash npm install @runwayml/sdk @runwayml/avatars-react ``` `@runwayml/avatars-react` includes the framework-agnostic core (`@runwayml/avatars`) automatically. ## Server setup Create an API route that handles Session creation. This endpoint receives an Avatar ID from the client, creates a Session with Runway, polls until it’s ready, consumes the credentials, and returns them to the client. The client can also pass optional `personality` and `startScript` fields to [override the Avatar’s defaults](/characters/concepts#per-call-overrides) for this session — useful for injecting user-specific context like the caller’s name. app/api/avatar/session/route.ts ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); export async function POST(request: Request) { const { avatarId, personality, startScript } = await request.json(); // 1. Create session // To add tool calling, pass a tools array here — see /characters/tools const { id: sessionId } = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId }, personality, startScript, }); // 2. Poll until ready let sessionKey: string | undefined; for (let i = 0; i < 60; i++) { const session = await client.realtimeSessions.retrieve(sessionId); if (session.status === 'READY') { sessionKey = session.sessionKey; break; } if (session.status === 'FAILED') { return Response.json({ error: session.failure }, { status: 500 }); } await new Promise(r => setTimeout(r, 1000)); } if (!sessionKey) { return Response.json({ error: 'Session timed out' }, { status: 504 }); } // 3. Consume session to get connection credentials const consumeResponse = await fetch( `${client.baseURL}/v1/realtime_sessions/${sessionId}/consume`, { method: 'POST', headers: { Authorization: `Bearer ${sessionKey}`, 'X-Runway-Version': '2024-11-06', }, } ); const credentials = await consumeResponse.json(); return Response.json({ sessionId, serverUrl: credentials.url, token: credentials.token, roomName: credentials.roomName, }); } ``` Save the session ID The `sessionId` returned from `client.realtimeSessions.create()` is also the `conversationId` for that call. Persist it with your own call record if you want to later fetch `transcript` or `recordingUrl` from `GET /v1/avatars/{id}/conversations/{conversationId}`. Set your API key as an environment variable: .env.local ```bash RUNWAYML_API_SECRET=your_api_key_here ``` ## Client integration ### Simple: AvatarCall The simplest way to add an Avatar is with the `AvatarCall` component. It handles WebRTC connection and renders a default UI. app/page.tsx ```tsx 'use client'; import { AvatarCall } from '@runwayml/avatars-react'; import '@runwayml/avatars-react/styles.css'; export default function Home() { return ( console.log('Call ended')} onError={(error) => console.error('Error:', error)} /> ); } ``` To use a custom Avatar created in the Developer Portal, replace `"customer-service"` with your Avatar ID. ### Webcam and screen sharing During a call, the Avatar can use **your webcam** and/or **your screen** as visual context—for example walkthroughs, slides, or showing something in frame. Enable that with [`@runwayml/avatars-react`](https://github.com/runwayml/avatars-sdk-react/blob/main/README.md); webcam and screen share are part of the **same realtime Session** as audio (the usual WebRTC call). Custom voice Custom Avatars that use a **custom voice** do not support webcam or screen sharing. Minimal example with the default control bar and screen sharing enabled: ```tsx // app/page.tsx — webcam (default) + optional screen share UI 'use client'; import { AvatarCall, AvatarVideo, ControlBar, ScreenShareVideo, } from '@runwayml/avatars-react'; import '@runwayml/avatars-react/styles.css'; export default function Home() { return ( ); } ``` ### Fully custom: hooks For complete control over the UI, use `AvatarSession` with hooks. This example shows how to build a custom interface with `useAvatarSession` for Session state and `useLocalMedia` for mic and webcam controls: components/CustomAvatarUI.tsx ```tsx 'use client'; import { AvatarSession, AvatarVideo, UserVideo, useAvatarSession, useLocalMedia, } from '@runwayml/avatars-react'; import type { SessionCredentials } from '@runwayml/avatars-react'; function CallUI() { const { state, end } = useAvatarSession(); const { isMicEnabled, toggleMic } = useLocalMedia(); return (
{state === 'connecting' &&
Connecting...
}
); } export function CustomAvatar({ credentials }: { credentials: SessionCredentials }) { return ( ); } ``` For more components, hooks, and examples—including the full **Webcam & Screen Sharing** section—see the [React SDK README](https://github.com/runwayml/avatars-sdk-react/blob/main/README.md). For **client tools** (UI tool calls) and **server tools** (server-side tools with return values), see [Tool calling](/characters/tools). ## Browser support The Avatars SDK uses WebRTC for real-time communication: | Browser | Minimum Version | | ------- | --------------- | | Chrome | 74+ | | Firefox | 78+ | | Safari | 14.1+ | | Edge | 79+ | Users must grant microphone permissions when prompted. Webcam access is required if the user’s video is enabled. *** ### Not using React? Most teams use **`@runwayml/avatars-react`** (above). If you are on Svelte, Vue, or plain HTML, use **`@runwayml/avatars`** — the same Session flow as this guide ([server setup](#server-setup)), without React. ```bash npm install @runwayml/sdk @runwayml/avatars ``` #### Quick start Your server route stays the same. In the browser, `streamTo` runs the consume call, joins the LiveKit room, and attaches avatar video to an element: ```javascript import { streamTo, AvatarEvent } from '@runwayml/avatars'; const credentials = await fetch('/api/avatar/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ avatarId: 'music-superstar' }), }).then((r) => r.json()); const session = await streamTo({ credentials, target: document.getElementById('avatar'), }); session.on(AvatarEvent.Transcript, (entry) => { console.log(entry.role, entry.text); }); document.getElementById('mute')?.addEventListener('click', () => session.mic.toggle()); document.getElementById('end')?.addEventListener('click', () => session.end()); ``` Use **`connect({ credentials })`** when you want a headless session first, then **`session.streamTo(element)`** once you have a video target. #### Examples * [vanilla-js](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/vanilla-js) — single HTML page + Express * [sveltekit](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/sveltekit) — `streamTo` in a Svelte component Full API detail: [`@runwayml/avatars` README](https://github.com/runwayml/avatars-sdk-react/tree/main/packages/core). --- # LiveKit Agents > Use Runway Characters as the visual layer for your own LiveKit Agent. You own the conversational pipeline (STT, LLM, TTS) and Runway provides the lip-synced avatar video. Use Runway Characters with [LiveKit Agents](https://docs.livekit.io/agents/) to build fully custom conversational experiences where you control the entire pipeline. Your agent handles speech-to-text, language model, and text-to-speech. Runway provides the visual layer: audio in, avatar video out. ## Before you start You’ll need: * A [Runway API key](https://dev.runwayml.com/settings/api-keys) * A [LiveKit Cloud](https://cloud.livekit.io) project (or self-hosted LiveKit server) * A [Google Gemini API key](https://ai.google.dev/gemini-api/docs/api-key) (or another LLM/TTS provider) * A preset ID (e.g. `cat-character`) or custom Avatar ID from the [Developer Portal](https://dev.runwayml.com) ## Guide 1. **Install the plugin** * Python ```bash pip install livekit-plugins-runway ``` * Node ```bash npm install @livekit/agents-plugin-runway ``` Set the following in your `.env` file: ```bash RUNWAYML_API_SECRET=... LIVEKIT_URL=... LIVEKIT_API_KEY=... LIVEKIT_API_SECRET=... GOOGLE_API_KEY=... ``` 2. **Add AvatarSession to your agent** * Python agent\_worker.py ```python from dotenv import load_dotenv from livekit.agents import Agent, AgentServer, AgentSession, JobContext, cli from livekit.plugins import google, runway load_dotenv() server = AgentServer() @server.rtc_session() async def entrypoint(ctx: JobContext): session = AgentSession( llm=google.realtime.RealtimeModel(voice="kore"), ) avatar = runway.AvatarSession( preset_id="cat-character", ) await avatar.start(session, room=ctx.room) await session.start( agent=Agent(instructions="Talk to me!"), room=ctx.room, ) session.generate_reply(instructions="Say hello to the user.") if __name__ == "__main__": cli.run_app(server) ``` * Node agent\_worker.ts ```ts import { type JobContext, ServerOptions, cli, defineAgent, voice } from '@livekit/agents'; import * as google from '@livekit/agents-plugin-google'; import * as runway from '@livekit/agents-plugin-runway'; import { fileURLToPath } from 'node:url'; export default defineAgent({ entry: async (ctx: JobContext) => { await ctx.connect(); const session = new voice.AgentSession({ llm: new google.beta.realtime.RealtimeModel({ voice: 'Kore' }), }); const avatar = new runway.AvatarSession({ presetId: 'cat-character', }); await avatar.start(session, ctx.room); await session.start({ agent: new voice.Agent({ instructions: 'Talk to me!' }), room: ctx.room, outputOptions: { syncTranscription: false }, }); session.generateReply({ instructions: 'Say hello to the user.' }); }, }); cli.runApp(new ServerOptions({ agent: fileURLToPath(import.meta.url) })); ``` Use `avatar_id` / `avatarId` instead of `preset_id` / `presetId` to use a custom Character from the [Developer Portal](https://dev.runwayml.com). Note The agent’s TTS drives what the Avatar says, so any voice or personality configured on the Runway Character is bypassed. Runway receives the already-synthesized audio and lip-syncs it to the Character. See the [LiveKit Runway plugin guide](https://docs.livekit.io/agents/models/avatar/plugins/runway/) for the full list of `AvatarSession` parameters. 3. **Test it** Open the [LiveKit Agents Playground](https://docs.livekit.io/agents/start/playground/) to preview your agent without building a frontend. Start a conversation and verify the avatar video track appears alongside your agent’s audio. ## End sessions promptly Runway bills realtime Character sessions while the Runway avatar worker is active. The plugin cancels the Runway realtime session during normal LiveKit job shutdown, so make sure your agent shutdown path runs when the user leaves, your agent disconnects, or your app ends the conversation. Set `max_duration` / `maxDuration` (seconds) in the `AvatarSession` constructor to cap session length. If the job is force-killed before cleanup runs, the Runway session can continue until this limit. ## Handle startup errors `AvatarSession.start()` can fail before the Character joins the LiveKit room, for example if the Runway project has insufficient credits or the session request is invalid. Catch startup errors in your agent and send an application-level message to your frontend so the user does not wait indefinitely for the avatar video track. * Python ```python try: await avatar.start(session, room=ctx.room) except Exception as exc: print(f"failed to start Runway avatar: {exc}") raise ``` * Node ```ts try { await avatar.start(session, ctx.room); } catch (error) { console.error('failed to start Runway avatar', error); throw error; } ``` ## Learn more [LiveKit Runway plugin guide ](https://docs.livekit.io/agents/models/avatar/plugins/runway/)LiveKit's integration guide for the Runway Characters plugin. [LiveKit Agents documentation ](https://docs.livekit.io/agents/)Full reference for the LiveKit Agents framework: models, plugins, room management, and deployment. [Agents Playground ](https://docs.livekit.io/agents/start/playground/)Test your agent in the browser without building a frontend. [Python plugin source ](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-runway)livekit-plugins-runway in the livekit/agents monorepo. [Node plugin source ](https://github.com/livekit/agents-js/tree/main/plugins/runway)@livekit/agents-plugin-runway in the livekit/agents-js monorepo. [ElevenLabs Agents ](/characters/elevenlabs)Bring your own ElevenLabs agent instead of a LiveKit agent. --- # Quickstart > Build a React app that video calls a Runway character in 5 minutes. In this tutorial, we will build a React web app that video calls a Runway character — in under 5 minutes. [Quickstart tutorial](https://www.youtube.com/embed/IRxGEaap4Wc) **Helpful links:** * [Runway Dev account](https://dev.runwayml.com) * [Runway avatar SDK template](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-simple) ### 1. Create a Runway Dev account Create an account at [dev.runwayml.com](https://dev.runwayml.com). ![Runway Dev login page](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/1.png) Once you log in, you will see a “Characters” tab at the top bar, and there are a few preset characters. We are going to video call the character called “Mina”. ![Characters tab showing Mina character](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/2.png) ### 2. Create a new API key Go to the **Manage** tab in the top bar, then click the **New API key** button in the top-right corner. ![Manage tab with New API key button](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/3.png) Give your key a name and copy it to a safe location. Once you close the pop-up, the key value is not available again. You can always create a new key if needed. ![API key name input](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/4.png) ![API key copy dialog](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/5.png) ### 3. Add credits Click on **Billing** in the left sidebar under the **Manage** tab, and add some credits to the account. ![Billing page](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/6.png) ![Adding credits](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/7.png) ### 4. Download the React app template In your terminal, run this command to copy the [template](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-simple) into your local directory: ```bash npx degit runwayml/avatars-sdk-react/examples/nextjs-simple my-avatar-app cd my-avatar-app ``` ### 5. Install packages Make sure you are using **Node.js 18+**, then install dependencies: ```bash npm install ``` ### 6. Set your API key Copy the `.env.example` file as `.env`, and paste your API key: ```plaintext RUNWAYML_API_SECRET=your_api_key_here ``` ### 7. Run the app ```bash npm run dev ``` The server starts at [http://localhost:3000](http://localhost:3000/). Click on the Mina character to start a conversation. ![Character selection screen](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/8.png) ![Active video call](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/9.png) ### Troubleshooting * **API key errors:** * Make sure you copy the entire API key correctly. The key begins with `key_` followed by 128 hex characters. * Make sure the key is currently active. Deactivated keys will be rejected. * **No credits:** Make sure your Runway Dev account has credits before starting a call. *** ### Next steps [Create Your Own Characters ](/characters/create-your-own)Create your own character from a single image — no training required. --- # Screen Sharing and Camera Feed > Share your webcam or screen with a Runway Character so it can see and respond to what is on camera in real time. Runway Characters can take a live **webcam** or **screen share** from your app. The Character sees that feed over the Session, understands what is visible, and responds in real time through Runway Dev — useful for demos, tutoring, games, and design feedback. The overview below shows how Runway Characters work in real time, including seeing the user’s webcam and shared screen. [Runway Characters — webcam and screen sharing demo](https://www.youtube.com/embed/xr0dtsaqxik?rel=0) If you build with the React SDK (`@runwayml/avatars-react`), webcam and screen sharing are built into `AvatarCall`, `ControlBar`, and related components. For the full list of props, hooks (such as `useLocalMedia`), and edge cases, see [**Webcam & screen sharing** in the React SDK README](https://github.com/runwayml/avatars-sdk-react#webcam--screen-sharing). ### Webcam and screen sharing Sharing video opens up visual workflows: identify objects on a desk, run trivia with physical cards ([example](https://x.com/technofantasyy/status/2031124673552097412)), get guidance while you play ([example](https://x.com/iamneubert/status/2031160102452081046)), walk through slides, or ask for reactions to a layout in your design tool. Compatibility Webcam and screen sharing work for **preset** Characters and **custom** Characters that use a **preset voice**. Custom Characters with a **custom voice** do not support webcam or screen sharing. ### Webcam The webcam is on by default when you use the default UI. The `video` prop controls whether the camera starts when the Session connects; `` renders the local preview. Webcam enabled (default layout) ```tsx ``` To join without sending camera video, set `video={false}`: Disable webcam on connect ```tsx ``` ### Screen sharing Pass `showScreenShare` to `ControlBar` so users can start sharing from the built-in controls, and add `` to show the shared content in your layout. Screen share control and preview ```tsx ``` While sharing, the default `ControlBar` shows a banner with a quick **Stop** action. #### Start sharing before the Session connects If you want the browser’s screen-share permission prompt **before** the call connects, capture a `MediaStream` first and pass it as `initialScreenStream`: Pre-captured display media stream ```tsx import { useState } from 'react'; import { AvatarCall, AvatarVideo, ControlBar, ScreenShareVideo } from '@runwayml/avatars-react'; function ScreenShareCall() { const [stream, setStream] = useState(null); async function startWithScreenShare() { const mediaStream = await navigator.mediaDevices.getDisplayMedia({ video: true }); setStream(mediaStream); } if (!stream) { return ; } return ( ); } ``` For programmatic toggles (camera, mic, screen share) inside a Session, use the `useLocalMedia` hook — documented in the [same README section](https://github.com/runwayml/avatars-sdk-react#webcam--screen-sharing). *** ### Next steps [Building your integration ](/characters/integration)Session creation, server routes, and wiring AvatarCall in a real app. [React SDK on GitHub ](https://github.com/runwayml/avatars-sdk-react)Examples, hooks reference, and changelog for @runwayml/avatars-react. --- # Tool calling overview > Let your Character invoke tools during a session — triggering client-side UI, fetching server-side data, or interacting with your page. Tool calling lets the model decide when to invoke a named function during a realtime session. Your Character can trigger UI changes, look up live data, or click elements on the page — making it capable of taking actions, not just speaking. ## How it works 1. **User speaks** “What’s the status of my order 12345?” 2. **Model analyzes intent** The LLM analyzes the request and determines it needs external information to respond accurately. 3. **Tool invocation** The model selects the appropriate tool and generates a structured function call: ```json { "name": "check_order_status", "arguments": { "order_id": "12345" } } ``` 4. **Tool execution** The system executes the tool based on its type: * **Client tools:** your frontend handler runs (e.g. showing a UI overlay) * **Server tools:** an HTTP-style request hits your server, which returns a result 5. **Response integration** The tool result is returned to the model, which incorporates it into a natural response. ## Tool types Runway Characters support two types of tools, each designed for different use cases. You can combine both in the same session. [Client tools ](/characters/tools/client-tools)Tools executed in the browser to drive your UI — overlays, navigation, and page interactions. [Server tools ](/characters/tools/server-tools)Tools executed on your server whose results feed back into the conversation. --- # Best practices > Writing effective descriptions, parameter schemas, and prompting tips for reliable tool calls. Tips for writing reliable tool definitions, staying within limits, and debugging when things go wrong. ## Writing effective descriptions The `description` field on both tools and parameters is how the model decides **when** to invoke a tool and **what arguments** to pass. Vague descriptions lead to missed or incorrect invocations. **Be specific about when the tool should be used:** ```ts // ✅ Good — the model knows exactly when to call this { name: 'check_order_status', description: 'Look up a customer order by ID when the user asks about delivery, tracking, or order updates. Returns the current status and estimated arrival date.', } // ❌ Bad — too vague for reliable invocation { name: 'check_order_status', description: 'Checks orders', } ``` **Be specific about parameter formats:** ```ts // ✅ Good — tells the model what to extract from conversation { type: 'string', name: 'order_id', description: 'The order ID mentioned by the user, typically in the format ORD-12345', } // ❌ Bad — leaves the model guessing { type: 'string', name: 'id', description: 'The ID', } ``` ### Use personality to guide invocation Your Character’s `personality` field can include instructions about when and how to use tools. This is especially useful when the Character has multiple tools and you want predictable behavior: ```ts await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId }, personality: `You are a helpful shopping assistant. When the user asks about an order, use check_order_status to look up the details before answering. When the user asks about products, use search_catalog to find relevant items. Always confirm the order ID before looking it up.`, tools: [/* ... */], }); ``` ## Parameter schema reference Tools accept a `parameters` array where each entry describes one argument. Six types are supported: ### Basic types | Type | Description | Example value | | --------- | -------------- | ------------- | | `string` | Text value | `"ORD-12345"` | | `integer` | Whole number | `42` | | `number` | Decimal number | `3.14` | | `boolean` | True/false | `true` | Each parameter requires a `type`, `name`, and `description`. Set `required: false` to make a parameter optional (defaults to `true`). See the [API reference](/api-reference) for the full schema. ### String with enum String parameters can include an `enum` to restrict values: ```ts { type: 'string', name: 'priority', description: 'Urgency level for the support ticket', enum: ['low', 'medium', 'high', 'critical'], } ``` ### Array Array parameters specify the type of each element: ```ts { type: 'array', name: 'tags', description: 'Keywords to tag the support ticket with', items: { type: 'string' }, } ``` Array items can be `string`, `integer`, `number`, or `boolean`. ### Object Object parameters define nested properties — each one follows the same schema as a top-level parameter: ```ts { type: 'object', name: 'address', description: 'Shipping address for the order', properties: [ { type: 'string', name: 'street', description: 'Street address' }, { type: 'string', name: 'city', description: 'City name' }, { type: 'string', name: 'zip', description: 'ZIP or postal code' }, ], } ``` ### Complete example Here’s a tool with a mix of parameter types: ```ts { type: 'backend_rpc', name: 'create_support_ticket', description: 'Create a support ticket when the user reports a problem. Collect the issue details and priority before calling.', timeoutSeconds: 6, parameters: [ { type: 'string', name: 'subject', description: 'A short summary of the issue', }, { type: 'string', name: 'priority', description: 'Urgency level', enum: ['low', 'medium', 'high'], }, { type: 'array', name: 'tags', description: 'Keywords to categorize the ticket', items: { type: 'string' }, }, { type: 'boolean', name: 'notify_customer', description: 'Whether to send a confirmation email to the user', required: false, }, ], } ``` ## Related [Client tools ](/characters/tools/client-tools)Fire-and-forget tools that drive your UI — modals, navigation, and Page Actions. [Server tools ](/characters/tools/server-tools)Tools executed on your server whose results feed back into the conversation. --- # Client tools > Fire-and-forget tool calls that drive your UI — modals, overlays, navigation, and pre-built Page Actions. Enable your Character to trigger actions and control your application’s user interface — opening modals, updating state, navigating pages, and more. Great for info panels, trivia boards, highlights, game state, or any on-device effect that doesn’t need a server round trip. Unlike [server tools](/characters/tools/server-tools), client tools run entirely in the browser and don’t return results to the conversation. If you need the Character to speak from data your server provides, use [server tools](/characters/tools/server-tools) instead. ## Guide 1. **Define your tools** Use `clientTool` from `@runwayml/avatars-react/api` to define tools. Each tool needs a name, description, and a [Standard Schema](https://standardschema.dev/) (like Zod) for its arguments. ```ts // lib/tools.ts — shared between server and client import { clientTool, type ClientEventsFrom } from '@runwayml/avatars-react/api'; import { z } from 'zod'; export const openModalTool = clientTool('open_modal', { description: 'Open a modal dialog to display additional information', schema: z.object({ title: z.string(), content: z.string(), }), }); export const navigateToPageTool = clientTool('navigate_to_page', { description: 'Navigate the user to a specific page in the application', schema: z.object({ page: z.string() }), }); export const tools = [openModalTool, navigateToPageTool]; export type AppEvents = ClientEventsFrom; ``` When you pass a schema, `useClientEvent` validates incoming args at runtime — malformed events are dropped instead of crashing your UI. 2. **Pass tools at session creation** On your server, pass the tools array when creating the Session: app/api/avatar/session/route.ts ```ts import RunwayML from '@runwayml/sdk'; import { tools } from '@/lib/tools'; const client = new RunwayML(); export async function POST(request: Request) { const { avatarId } = await request.json(); const { id: sessionId } = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId }, tools, }); // Poll and return credentials (see Building your integration) // ... } ``` 3. **Handle events on the client** Inside an `AvatarCall`, `AvatarProvider`, or `AvatarSession`, use hooks to handle incoming tool calls. **Single tool** — `useClientEvent` takes a tool definition and a callback: ```tsx import * as React from 'react'; import { useClientEvent } from '@runwayml/avatars-react'; import { openModalTool } from '@/lib/tools'; function ModalHandler() { const [modal, setModal] = React.useState<{ title: string; content: string } | null>(null); const handleOpenModal = React.useCallback((args: { title: string; content: string }) => { setModal(args); }, []); useClientEvent(openModalTool, handleOpenModal); if (!modal) return null; return (

{modal.title}

{modal.content}

); } ``` **All tools** — `useClientEvents` fires a callback for every tool call: ```tsx import { useClientEvents } from '@runwayml/avatars-react'; import type { AppEvents } from '@/lib/tools'; function EventLogger() { useClientEvents((event) => { console.log('Tool called:', event.tool, event.args); }); return null; } ``` 4. **Test it** Start a conversation and say something like “Tell me more about the premium plan.” You should see a modal appear with the plan details while the Character continues speaking. ## Page Actions The SDK ships with pre-built tools that let the Character interact with your page — clicking buttons, scrolling to sections, and highlighting elements. No custom tool definitions needed. ### Server setup Import `pageActionTools` and pass them when creating the Session: ```ts import { pageActionTools } from '@runwayml/avatars-react/api'; const { id } = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'runway-preset', presetId: 'music-superstar' }, tools: pageActionTools, }); ``` Combine with your own tools by spreading both arrays: ```ts import { pageActionTools } from '@runwayml/avatars-react/api'; import { tools as clientEventTools } from '@/lib/tools'; tools: [...pageActionTools, ...clientEventTools], ``` ### Client setup Drop in the `PageActions` component inside your `AvatarCall`: ```tsx import { AvatarCall, AvatarVideo, ControlBar, PageActions } from '@runwayml/avatars-react'; function App() { return ( ); } ``` The Character can now reference elements by `id` or by a `data-avatar-target` attribute: ```html
...
``` ### Available actions | Action | What it does | | ----------- | ---------------------------------------------------- | | `click` | Calls `.click()` on the target element | | `scroll_to` | Scrolls the target into view with smooth scrolling | | `highlight` | Pulses an outline around the target, then removes it | For styling, configuration, and advanced usage, see the [`PageActions` documentation](https://github.com/runwayml/avatars-sdk-react#pageactions) in the SDK repo. ## Next steps [Server tools ](/characters/tools/server-tools)Tools executed on your server whose results feed back into the conversation. [Best practices ](/characters/tools/best-practices)Parameter schemas, limits, and prompting tips for reliable tool calls. [Example: Client events ](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-client-events)Build a trivia game with client event tools. [SDK reference ](https://github.com/runwayml/avatars-sdk-react)Complete SDK documentation for tool events and hooks. --- # Reference > Limits, tool call history via the Conversations API, and troubleshooting. ## Limits | Resource | Limit | | -------------------------------- | ------------------------------- | | Tools per session | 20 | | Parameters per tool | 20 | | Tool name length | 1–64 characters | | Tool name pattern | `^[a-zA-Z_][a-zA-Z0-9_]*$` | | Tool names | Must be unique within a session | | Description length | 1–1024 characters | | Enum values per string parameter | 20 | | Enum value length | 64 characters | | Nested properties per object | 20 | | Backend RPC timeout | 1–8 seconds (default 4) | | Backend RPC handlers per session | 1 | ## Reading tool call history After a session ends, you can read back tool call history from the [Conversations API](/api-reference#tag/avatar-conversations). The list endpoint includes a `hasTools` flag on each conversation: ```bash curl https://api.dev.runwayml.com/v1/avatar_conversations \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` The detail endpoint includes the full transcript with tool calls and results: ```bash curl https://api.dev.runwayml.com/v1/avatar_conversations/conv_123 \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` The response includes: * **`tools`** — the tools that were configured for the session (type, name, description) * **`transcript`** — each entry can include: * `toolCalls` — tool invocations on assistant turns: `{ id, name, arguments }` * `toolResults` — results returned: `{ id, name, result, error, durationMs }` * `content` — can be `null` for tool-only turns where the Character didn’t speak Example transcript entry with a tool call: ```json { "role": "assistant", "content": null, "timestamp": "2025-01-15T10:30:00Z", "toolCalls": [ { "id": "call_abc123", "name": "check_order_status", "arguments": { "order_id": "ORD-12345" } } ], "toolResults": [ { "id": "call_abc123", "name": "check_order_status", "result": { "status": "shipped", "eta": "2025-01-17" }, "error": null, "durationMs": 230 } ] } ``` ## Troubleshooting Tool not being invoked Make descriptions specific about the trigger (“when the user asks about…”). You can also add guidance in the Character’s `personality` field about when to use each tool. Backend RPC timing out The default timeout is 4 seconds. Increase `timeoutSeconds` (max 8) if your handler makes external API calls. If it consistently takes longer, consider pre-fetching or caching. Backend handler not connecting Verify `RUNWAYML_API_SECRET` is set correctly. The session must be in `READY` or `RUNNING` status before the handler can connect. Only one backend handler can connect per session. Client tool events not arriving Make sure you’re subscribing inside an `AvatarCall`, `AvatarProvider`, or `AvatarSession` component. Check the browser console for WebRTC data channel errors. Unexpected arguments Use Standard Schema (Zod) validation with `clientTool` to catch malformed args at runtime. For backend RPC, validate `args` in your handler and throw descriptive errors. --- # Server tools > Server-side tool calls whose return values feed back into the conversation, letting the Character speak from real data. Connect your Character to external data and systems. Server tools let the Character fetch live data, call APIs, and query databases — with results that feed back into the conversation so it can speak from real information. Unlike [client tools](/characters/tools/client-tools), server tools return results to the LLM. Use them when you need server-side auth, database lookups, or any data that shapes what the Character says next. ## Guide 1. **Declare tools at session creation** On your server, declare `backend_rpc` tools when creating the Session: ```ts import RunwayML from '@runwayml/sdk'; const client = new RunwayML(); const { id: sessionId } = await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId: 'your-avatar-id' }, tools: [ { type: 'backend_rpc', name: 'check_order_status', description: 'Look up a customer order by ID and return the current shipping status', timeoutSeconds: 6, parameters: [ { type: 'string', name: 'order_id', description: 'The order ID, e.g. ORD-12345', }, ], }, ], }); ``` 2. **Set up the RPC handler** Connect the RPC handler using [`@runwayml/avatars-node-rpc`](https://github.com/runwayml/avatars-node-rpc). Pass your API key and session ID — the package calls `/connect_backend` and joins the room automatically: ```ts import { createRpcHandler } from '@runwayml/avatars-node-rpc'; const handler = await createRpcHandler({ apiKey: process.env.RUNWAYML_API_SECRET!, sessionId, tools: { check_order_status: async (args) => { const order = await db.orders.find(String(args.order_id)); return { status: order.status, eta: order.eta }; }, }, onConnected: () => console.log('Connected to session'), onDisconnected: () => console.log('Session ended'), onError: (err) => console.error('RPC error:', err), }); ``` If you’ve already called `/connect_backend` yourself, pass pre-fetched credentials instead: ```ts const handler = await createRpcHandler({ credentials: { url: 'wss://livekit.example.com', token: '', roomName: '', }, tools: { check_order_status: async (args) => { const order = await db.orders.find(String(args.order_id)); return { status: order.status, eta: order.eta }; }, }, }); ``` 3. **Test it** Start a conversation and ask something like “What’s the status of my order 12345?” Your handler should fire, and the Character will respond with the returned data: “Your order has shipped! It should arrive by May 7th.” For the full list of handler options, see the [`@runwayml/avatars-node-rpc` README](https://github.com/runwayml/avatars-node-rpc#api). ## Timeout Server tools accept a `timeoutSeconds` field that controls how long the Character waits for your handler to respond. | Setting | Value | | ------- | ------------- | | Default | **4 seconds** | | Minimum | 1 second | | Maximum | 8 seconds | If your handler doesn’t respond within the timeout, the tool call is treated as failed and the Character continues the conversation without the result. Choose a timeout that covers your expected handler latency with a small buffer. If your backend needs to make external API calls, consider increasing from the default: ```ts { type: 'backend_rpc', name: 'fetch_weather', description: 'Get current weather for a city', timeoutSeconds: 8, parameters: [ { type: 'string', name: 'city', description: 'City name' }, ], } ``` ## Error handling If a tool handler **throws an error**, the error message is sent back to the worker so the model can acknowledge the failure instead of hanging until timeout: ```ts tools: { check_order_status: async (args) => { const order = await db.orders.find(String(args.order_id)); if (!order) { throw new Error('Order not found'); } return { status: order.status, eta: order.eta }; }, }, ``` Other things to know: * **One handler per session** — the API enforces a single backend RPC connection per Session. Attempting a second `/connect_backend` call will be rejected. * **Disconnect handling** — if the handler disconnects mid-session, pending RPC calls will time out. Use the `onDisconnected` callback to detect this. ## Combining with client events You can use both server tools and [client tools](/characters/tools/client-tools) in the same Session. Declare both tool types in the `tools` array: ```ts import { openModalTool } from '@/lib/tools'; await client.realtimeSessions.create({ model: 'gwm1_avatars', avatar: { type: 'custom', avatarId }, tools: [ openModalTool, { type: 'backend_rpc', name: 'check_order_status', description: 'Look up a customer order', parameters: [ { type: 'string', name: 'order_id', description: 'The order ID' }, ], }, ], }); ``` On the client, subscribe to client events as usual. On the server, connect the RPC handler for the backend tools. ## Next steps [Client tools ](/characters/tools/client-tools)Fire-and-forget tools that drive your UI — modals, navigation, and Page Actions. [Best practices ](/characters/tools/best-practices)Parameter schemas, limits, and prompting tips for reliable tool calls. [Example: RPC weather ](https://github.com/runwayml/avatars-sdk-react/tree/main/examples/nextjs-rpc-weather)Weather assistant using backend RPC tools. [SDK reference ](https://github.com/runwayml/avatars-node-rpc)Complete documentation for the Node RPC handler. --- # Troubleshooting > Debugging tips for Runway Avatars integrations. ## Debugging tips ### Enable verbose logging Add error handlers to capture detailed information: ```tsx { console.error('Avatar error:', error); console.error('Error name:', error.name); console.error('Error message:', error.message); if (error.cause) { console.error('Cause:', error.cause); } }} /> ``` ### Check Session state Use the `useAvatarSession` hook to monitor connection state: ```tsx import { useAvatarSession } from '@runwayml/avatars-react'; function DebugInfo() { const { state, sessionId, error } = useAvatarSession(); return (
      {JSON.stringify({ state, sessionId, error: error?.message }, null, 2)}
    
); } ``` ### Test with minimal setup Isolate issues by testing with the simplest possible configuration. **React:** ```bash npx degit runwayml/avatars-sdk-react/examples/nextjs-simple test-app cd test-app npm install # Add your API key to .env npm run dev ``` **Not using React:** ```bash npx degit runwayml/avatars-sdk-react/examples/vanilla-js test-app cd test-app npm install # Add your API key to .env npm run dev ``` See also [Not using React?](/characters/integration#not-using-react) on the integration guide. If the example works but your integration doesn’t, compare the implementations to find differences. ### Webcam, screen share, and permissions Webcam and screen capture need a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS or `localhost`). On plain HTTP (except `localhost`), the browser may not offer camera or display capture at all. For component options and examples, see the [Webcam & Screen Sharing](https://github.com/runwayml/avatars-sdk-react/blob/main/README.md#webcam--screen-sharing) section of the React SDK README and the [integration guide](/characters/integration#webcam-and-screen-sharing). ## Getting help | Resource | Description | | --------------------------------------------------------------- | ----------------------------------------------- | | [Developer Portal](https://dev.runwayml.com/) | Manage Avatars, view logs, access dashboard | | [SDK Repository](https://github.com/runwayml/avatars-sdk-react) | Report bugs, view examples, check releases | | Account Support | Contact your Runway account manager for support | When reporting issues, include: * Browser and version * SDK version (`npm list @runwayml/avatars-react` or `npm list @runwayml/avatars`) * Error messages from browser console * Session ID (if available) * Steps to reproduce --- # How to Invite a Runway Character to a Meeting > Invite a Runway Character to any Zoom, Google Meet, or Microsoft Teams call — it joins as a live participant with real-time video and audio. Invite a Runway Character to any Zoom, Google Meet, or Microsoft Teams meeting. The Character joins as a regular participant — it can see and hear other attendees and responds in real time with lip-synced video and natural audio. The whole setup takes about 60 seconds. [How to invite a Runway Character to a meeting](https://www.youtube.com/embed/IAY6MYxz0KU?rel=0) **Helpful links:** * [Runway Characters Meet Web App](https://runway-characters-meet-production.up.railway.app/) * [Developer Portal](https://dev.runwayml.com) * [Source Code](https://github.com/runwayml/runway-characters-meet) ### Step 1 — Get your API key Go to [dev.runwayml.com](https://dev.runwayml.com) and sign up. Every new account includes 600 free credits — roughly 30 minutes of Character video. Once you’re logged in: 1. Click the **Manage** tab in the top bar. ![Manage tab with New API key button](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/3.png) 2. Click **New API Key** in the top-right corner. 3. Copy the key. ![API key name input](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/4.png) ![API key copy dialog](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/quickstart/5.png) Back in the [Runway Characters Meet web app](https://runway-characters-meet-production.up.railway.app/), paste your API key. It’s saved in your browser and only used to communicate with Runway Dev. ![API key copy paste](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/1.png) ### Step 2 — Add a meeting link Open Zoom, Google Meet, or Microsoft Teams and start or join a call. Copy the meeting invite URL and paste it into the Runway Characters Meet web app. Any standard meeting link works — the app supports all three platforms. ![Meeting URL](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/2.png) ### Step 3 — Pick a Character Choose a Character from the preset dropdown. There are several to try — for example, the cat Character. You can also use a custom Character: 1. Go to the [Developer Portal](https://dev.runwayml.com) and click **Characters**. 2. Create a Character by uploading an image, choosing a voice, and writing a personality prompt. 3. Copy the **Character ID**. 4. Back in the Runway Characters Meet web app, switch to **Custom** and paste the Character ID. ![custom avatar](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/5.png) Personality for meetings Tailor the personality prompt to the meeting context. Here’s a sample system prompt you can use as a starting point: Sample system prompt ```text You are Lydia Marin, a new Member of Technical Staff on the Product team at Runway. This is your first week and you're at your first all-hands meeting. You're a little nervous — you don't want to talk too much or interrupt. IMPORTANT: Do NOT introduce yourself or give your background unless someone specifically asks you to, calls your name, or says something like "Lydia, want to introduce yourself?" or "let's hear from the new hires." Until then, just listen quietly. If someone says hi or welcomes you, just say something brief like "Thanks! Happy to be here" or "Hey! Yeah, excited to be here." Keep it short. When someone DOES ask you to introduce yourself, say something like: "Hi everyone! Super excited to be joining Runway as a Member of Technical Staff on the product engineering team. Before this I was at a startup in Barcelona building collaborative design tools — lots of real-time web stuff, WebSockets, canvas rendering, that kind of thing. I studied CS at Universitat Politècnica de Catalunya and did a stint at Figma in San Francisco before moving back to Europe. I just relocated to New York for this role, which has been a big adjustment — I miss the weather already. Fun fact — I'm a huge home cook, I make my own pasta from scratch almost every weekend, and I'm trying to perfect cacio e pepe. I also have way too many houseplants for a New York apartment. Looking forward to meeting everyone!" When someone speaks to you, acknowledge them by name. You can see each participant's name displayed in the meeting view. Use their name naturally in your response, e.g. "Great question, Sarah" or "Thanks for asking, Mike." ``` ### Step 4 — Send the Character Click **Send Character to Meeting**. The session panel shows the connection progress: 1. Creating Runway session… 2. Waiting for Character to be ready… 3. Bot joining meeting… 4. **Character is live!** This typically takes about 5 seconds. ![send meeting](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/3.png) ### Step 5 — Interact with the Character Switch to your meeting window. The Character appears as a regular participant — it can: * **See** the video feed and read on-screen names * **Hear** everything said in the meeting * **Respond** in real time with lip-synced video, natural speech, gestures, and expressions ![zoom meeting with cat character](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/4.jpg) From the Runway Characters Meet control panel you can **mute** the Character or **end the session** at any time. Ending the session removes the Character from the meeting. ### What it looks like In the demo, the custom Character “Lydia” joins a Zoom meeting and introduces herself — sharing her background, mentioning previous work experience, and answering follow-up questions from other participants, all in real time. The Character can also read participant names from the meeting UI and address people directly. ![zoom meeting with lydia character](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/6.jpg) ![google meeting](https://runway-static-assets.s3.us-east-1.amazonaws.com/calliope-demo/meet-tutorial/7.jpg) ### Supported platforms | Platform | Status | | --------------- | --------- | | Zoom | Supported | | Google Meet | Supported | | Microsoft Teams | Supported | ### Try it yourself | Resource | Link | | ------------------------------ | ------------------------------------------------------------------------------------------------------------- | | Runway Characters Meet web app | [runway-characters-meet-production.up.railway.app](https://runway-characters-meet-production.up.railway.app/) | | API key signup | [dev.runwayml.com](https://dev.runwayml.com) | | Custom Characters | [Create your own](/characters/create-your-own) | | Source code | [github.com/runwayml/runway-characters-meet](https://github.com/runwayml/runway-characters-meet) | If you’d like to build your own app to invite Characters to meetings, check out the README in the [runway-characters-meet](https://github.com/runwayml/runway-characters-meet) repository. *** ### Next steps [Create your own Character ](/characters/create-your-own)Upload an image, pick a voice, and write a personality to build a custom Character for your meetings. --- # Embedded Widget > Add a Runway Character to any website with a single script tag — no server, no API key on the client, no React required. The embedded widget is the simplest way to put a Runway Character on your website. Drop a single script tag into your HTML and your visitors can start a video conversation — no backend, no React, no API key management on the client. [Embed a Character on your website](https://www.youtube.com/embed/k-a7wBVYQrw?rel=0) **Helpful links:** * [Developer Portal](https://dev.runwayml.com) ## When to use the widget | | Widget | [React SDK](/characters/integration) | | ------------------- | ------------------------- | ------------------------------------ | | **Setup** | One script tag | Server route + React | | **Server required** | No | Yes | | **Customization** | Portal config | Full programmatic control | | **Best for** | Marketing & support pages | Custom apps, integrations | Use the widget when you want the lowest-effort integration. Use the [React SDK](/characters/integration) when you need full control over the UI and session lifecycle. Public-facing The widget runs entirely in the browser — there is no server-side API key involved. The **domain allowlist** is the primary security boundary. Only enable the widget for Characters you intend to be publicly accessible. ## Setup ### 1. Open your Character Go to the [Developer Portal](https://dev.runwayml.com/) and click into the Character you want to embed. ![Character detail page in the Developer Portal](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/widget/1-character-detail.webp) ### 2. Configure the Embed tab Navigate to the **Embed** tab. Add at least one allowed origin (e.g. `http://localhost:3000` for local development), then adjust limits and styling to suit your site. See [Configuration](#configuration) below for details on each setting. ![Embed tab showing the embed code snippet, allowed origins, and interface settings](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/widget/2-embed-tab-top.webp) ### 3. Preview and copy the embed code Use the **Preview** to check your changes, then copy the script tag shown at the top of the page. ![Widget preview with the launcher visible in the bottom-right corner](https://runway-static-assets.s3.us-east-1.amazonaws.com/devportal/avatars/widget/5-preview.webp) ### 4. Add the script tag to your site Paste the snippet into your site’s HTML, before the closing `` tag: ```html ``` That’s it — reload the page and the widget launcher appears in the corner. Your visitors can click it to start a conversation with your Character. ## Configuration All configuration is managed through the **Embed** tab in the Developer Portal. ### Allowed origins The widget only loads on origins you explicitly allow. Add your production domain (and any staging/preview domains) in the **Allowed origins** section. Requests from unlisted origins are rejected. ### Limits | Setting | Description | | ---------------- | ------------------------------------------------ | | **Max Duration** | Maximum session length in seconds (default: 120) | | **Max Daily** | Maximum number of calls per day | ### Interface Customize the widget’s look and feel to match your brand: * **Icon** — use the default icon or upload a custom image * **Label** — toggle a text label next to the icon (e.g. “Need help?”) * **Colors** — set icon/label color, background color, CTA colors * **CTA copy** — customize the call-to-action text (e.g. “Ask anything”) * **Layout** — choose between compact and full expanded views, and whether the widget starts expanded * **Video call** — choose between circle and full video call shapes * **Placement** — position the widget (e.g. bottom right) ### Options * **Share screen automatically** — when enabled, the visitor’s screen is shared with the Character automatically when a call starts ## Troubleshooting * **Widget not loading:** Make sure the page’s origin is in the **Allowed origins** list. The origin must match exactly, including protocol and port. * **Script tag placement:** The `