Model Routers: saved routing configurations that pick a model by cost, latency, or quality --- # Model Routers > Route generation requests to the right model automatically across video, image, and audio. Set an optimization preference and a price ceiling once, then call one endpoint per modality without hardcoding a model. Instead of building your integration around a single model, create a **Model Router** and let the platform select the best model for you based on your configured preferences. You define what “best” means for your use case — optimize for cost, latency, or quality — and the router picks the right model for every request. As new models launch, your routing keeps pace with those preferences automatically, so you benefit from the latest models without ever touching your integration. ## What it is A Model Router is a named, reusable configuration you create in the [Developer Portal](https://dev.runway.com/) to capture your routing preferences for your integration. Each router gets a stable config ID that you pass in your requests, so your integration points at your *preference* rather than at a specific model. You can create as many routers as you need — one for each use case. A creative app might run a **Draft** router tuned for latency and a separate **Final Export** router tuned for quality, each called by its own ID and optimized for what that surface actually needs. The same config can serve video, image, and audio: modality is implied by which generate endpoint you call (`/v1/generate/video`, `/v1/generate/image`, or `/v1/generate/audio`). Router endpoints use model-agnostic `aspectRatio` values such as `16:9`. Direct-model endpoints use model-specific `ratio` values such as `1280:720`. These values are not interchangeable. See [Generating through a Model Router](/model-routers/generating.md) for routed requests and the [API reference](/api.md) for the exact request schema. ## How it works A Model Router resolves each request in two steps. First it narrows the catalog to the models that *could* satisfy the request — those that you’ve enabled, support the capabilities required by your request, and would stay under any price cap you’ve set for that modality. Then it picks the best of the remaining models according to your preference for cost, latency, or quality. The JSON response includes which model ran and what it cost. If no model satisfies your request based on the configuration, the request will fail with a [no eligible model error](/model-routers/generating#no-eligible-model). ## When to use one Use a Model Router when you want the platform to select and maintain the model choice for you based on your priorities. Use a [base model](/guides/models) directly when a use case should always run on one specific model. ### Cost predictability Set a maximum spend per generation so a single request can never exceed the budget you’ve defined for that use case. ### Less integration maintenance Configure a router once and call it by ID. You don’t need to update your integration every time a model launches or pricing shifts. ### Transparent decisions Every response reports which model ran and what it cost, so you can verify the router is behaving the way you configured it. ### Automatic access to new models Newly released models become eligible automatically (if you choose), so your routing keeps improving without changes to your code. ## Get started [Configure a Model Router ](/model-routers/configuration)Create a router configuration in the Developer Portal and get a config ID. [Generate through a router ](/model-routers/generating)Call the router endpoint for video, image, or audio with a config ID, read the response, and validate routing with an HTTP dry run. [Using the API ](/guides/using-the-api)How tasks work across the API — submit a request, poll for status, and retrieve your output. --- # Configuring a Model Router > Create and manage Model Router configurations in the Runway Developer Portal. Set an optimization preference, choose which models are eligible, and set per-modality price ceilings, then reference the config by ID in your API calls. A Model Router configuration is a named, reusable set of routing preferences for a specific use case. You create it once in the [Developer Portal](https://dev.runway.com/) and reference it by its config ID in your generation requests, so you don’t repeat routing preferences on every call. ## Create a configuration From the Developer Portal, open **Model Routers** and choose **New config**. A configuration has the following settings: ### Config ID, name, and description Set a **config ID** — a short, immutable identifier (a URL-friendly slug like `preview-fast`) that you pass in your generation requests to apply this config. It’s set once at creation and can’t be changed afterward. You also give the configuration a human-friendly **name** (required) so you can identify it later — for example, `Preview` or `Final Export` — and, optionally, a **description**. ### Eligible models By default, every video, image, and audio model is available to the router, and newly released models become eligible automatically. If you’d rather constrain the pool, a config supports two modes: * **Allow list** — only the models you list are eligible, and new models are never added automatically. Use this when you need a fixed, closed set. * **Deny list** — every model is eligible except the ones you list. New models are included automatically as they launch. Use this to exclude specific models while keeping automatic access to new ones. Models are identified by their model name (for example, `gen4_turbo`, `gpt_image_2`, or `seed_audio`). The allow or deny list can mix modalities; when you generate, the router only considers models that match the endpoint you called — see [Generating through a Model Router](/model-routers/generating). ### Optimization preference Choose the single dimension you want the router to optimize for among eligible models: * **Cost** — prefer the least expensive eligible model. * **Latency** — prefer the fastest eligible model. * **Quality** — prefer the highest-quality eligible model. The router applies this preference only after filtering to models that can satisfy the request, so it always chooses among models that already meet your other constraints. ### Maximum credits per generation Optionally set a cap on how many credits a single generation may cost. Caps are **per modality** — video, image, and audio each have their own ceiling — so a single config can set a different budget for each generation type. Any model whose estimated cost for a given request would exceed that modality’s ceiling is excluded before the optimization preference is applied. If a request can’t be satisfied by any model at or under the ceiling, it’s rejected with an explicit error — see [no eligible model](/model-routers/generating#no-eligible-model). Leave a modality unset to apply no price constraint for that modality. Credits are the same unit used everywhere else in the API; see [Pricing](/guides/pricing) for per-model rates. ## Save and reference a configuration Once saved, a configuration is applied by passing its config ID in your generation requests. You can create a configuration with all defaults and edit it later. To route requests using a config, see [Generating through a Model Router](/model-routers/generating). ## Manage configurations Manage configs from the Model Routers section of the Developer Portal: * **Edit** — Update the name, description, or settings. Editing settings appends a new version; changes apply to requests made after the edit, never retroactively. The config ID is fixed and can’t be changed. * **Delete** — Remove a configuration. A deleted config can no longer be used for generation, so any request referencing its config ID will fail. If you’d rather manage configs programmatically, the same create, update, and delete actions are available through the [Model Router API](/api#tag/Model-Router) and the [Runway SDKs](/api-details/sdks). Caution Deleting a configuration is permanent and takes effect immediately. Any integration still calling that config ID — including ones you may not own — will start failing. If you only want to pause a config, stop referencing it rather than deleting it. --- # Generating through a Model Router > Call the Runway Model Router endpoints with a config ID to route video, image, or audio generation to the right model. Read the response metadata, validate routing with an HTTP dry run, and handle no-eligible-model errors. Once you’ve [created a configuration](/model-routers/configuration), you route a generation by calling the generate endpoint for your modality with your config ID in the `configId` field and a model-agnostic `input` payload: | Modality | Endpoint | | -------- | ------------------------- | | Video | `POST /v1/generate/video` | | Image | `POST /v1/generate/image` | | Audio | `POST /v1/generate/audio` | The router filters to eligible models for that modality, selects one according to your optimization preference, generates the output, and returns the result along with metadata about the model it chose. ## Generate video Pass your config ID in the `configId` field and put generation options under `input`. You don’t specify a `model` — the router selects one for you. * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Route a video generation through a Model Router config try { const task = await client.generate.video .create({ configId: 'preview-fast', input: { // Point this at your own image file referenceImages: [ { uri: 'https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg', role: 'first', }, ], promptText: 'A timelapse on a sunny day with clouds flying by', aspectRatio: '16:9', 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() # Route a video generation through a Model Router config try: task = client.generate.video.create( config_id='preview-fast', input={ # Point this at your own image file 'reference_images': [ { 'uri': 'https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg', 'role': 'first', }, ], 'prompt_text': 'A timelapse on a sunny day with clouds flying by', 'aspect_ratio': '16:9', 'duration': 5, }, ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The video failed to generate.') print(e.task_details) ``` * cURL If you’re not ready to start writing code, you can test the API with cURL. ```sh # Replace the config ID and example URL below with your own curl -X POST https://api.dev.runwayml.com/v1/generate/video \ -d '{ "configId": "preview-fast", "input": { "referenceImages": [ { "uri": "https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg", "role": "first" } ], "promptText": "A timelapse on a sunny day with clouds flying by", "aspectRatio": "16:9", "duration": 5 } }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` ## Generate image Same pattern as video: `configId` plus an `input` object. Image `input` fields include `promptText`, optional `referenceImages`, `aspectRatio`, `resolution`, and `outputCount`. See the [API reference](/api#tag/Model-Router/paths/~1v1~1generate~1image/post) for the full schema. * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Route an image generation through a Model Router config try { const task = await client.generate.image .create({ configId: 'preview-fast', input: { promptText: 'A product photo of a ceramic mug on a marble counter, soft daylight', aspectRatio: '1:1', resolution: '2k', }, }) .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 from runwayml import RunwayML, TaskFailedError client = RunwayML() # Route an image generation through a Model Router config try: task = client.generate.image.create( config_id='preview-fast', input={ 'prompt_text': 'A product photo of a ceramic mug on a marble counter, soft daylight', 'aspect_ratio': '1:1', 'resolution': '2k', }, ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The image failed to generate.') print(e.task_details) ``` * cURL ```sh curl -X POST https://api.dev.runwayml.com/v1/generate/image \ -d '{ "configId": "preview-fast", "input": { "promptText": "A product photo of a ceramic mug on a marble counter, soft daylight", "aspectRatio": "1:1", "resolution": "2k" } }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` ## Generate audio Audio uses `POST /v1/generate/audio`. Set `input.type` to `speech` (speak `promptText` as a script) or `audio` (treat `promptText` as a description of the desired sound). Optional fields include `voice`, `referenceAudios`, `duration`, and `loop` — see the [API reference](/api#tag/Model-Router/paths/~1v1~1generate~1audio/post). * Node ```ts import RunwayML, { TaskFailedError } from '@runwayml/sdk'; const client = new RunwayML(); // Route an audio generation through a Model Router config try { const task = await client.generate.audio .create({ configId: 'preview-fast', input: { type: 'speech', promptText: 'Welcome to the studio. Today we are testing routed audio.', }, }) .waitForTaskOutput(); console.log('Task complete:', task); } catch (error) { if (error instanceof TaskFailedError) { console.error('The audio failed to generate.'); console.error(error.taskDetails); } else { console.error(error); } } ``` * Python ```python from runwayml import RunwayML, TaskFailedError client = RunwayML() # Route an audio generation through a Model Router config try: task = client.generate.audio.create( config_id='preview-fast', input={ 'type': 'speech', 'prompt_text': 'Welcome to the studio. Today we are testing routed audio.', }, ).wait_for_task_output() print('Task complete:', task) except TaskFailedError as e: print('The audio failed to generate.') print(e.task_details) ``` * cURL ```sh curl -X POST https://api.dev.runwayml.com/v1/generate/audio \ -d '{ "configId": "preview-fast", "input": { "type": "speech", "promptText": "Welcome to the studio. Today we are testing routed audio." } }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` ## Understand the response Every successful response includes metadata describing the routing decision, so you can debug behavior and understand why a model was chosen: * The **model** that was actually used. * The **config ID** that was applied. * The **optimization preference** in effect. * The **realized cost** of the generation, in credits. You’re billed for the model the router selects, at that model’s standard rate. See [Pricing](/guides/pricing) for per-model rates. ## Validate with a dry run Today you can dry-run a Model Router request from the API or in the Developer Portal — SDK support is coming soon. Use dry-run requests to test a routing decision without generating or charging credits. To do so, call the generate endpoint with `dryRun: true` (curl or any raw HTTP client): ```sh curl -X POST https://api.dev.runwayml.com/v1/generate/video \ -d '{ "configId": "preview-fast", "dryRun": true, "input": { "referenceImages": [ { "uri": "https://upload.wikimedia.org/wikipedia/commons/8/85/Tour_Eiffel_Wikimedia_Commons_(cropped).jpg", "role": "first" } ], "promptText": "A timelapse on a sunny day with clouds flying by", "aspectRatio": "16:9", "duration": 5 } }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RUNWAYML_API_SECRET" \ -H "X-Runway-Version: 2024-11-06" ``` A dry run returns the same metadata shape as a real generation — the model that would be used, the config ID and preference applied, and an **estimated** cost based on current pricing — but no asset is generated, stored, or returned, and the request is not billed as a generation. If the request and config together would produce a [no eligible model error](#no-eligible-model), the dry run returns that same error, so you see exactly what a real call would do. ## Compose a tool loop A reliable pattern for agents and automation: 1. Create or update a config with `POST /v1/routers` (or patch settings on an existing id). 2. Optionally dry-run with HTTP `dryRun: true` on the modality endpoint you’ll use live (`/v1/generate/video`, `/v1/generate/image`, or `/v1/generate/audio`) and the same `input` you plan to use. 3. Read `routing.model`, `routing.estimatedCost`, and `routing.resolvedSettings` — adjust the config or input if the choice or cost is wrong. 4. Call the live generate endpoint (SDK `generate.video.create`, `generate.image.create`, or `generate.audio.create`, or HTTP without `dryRun`), then wait for the task (`waitForTaskOutput` / `wait_for_task_output`, or poll `GET /v1/tasks/:id`). 5. On a [no eligible model](#no-eligible-model) error, widen the allowlist, raise that modality’s credit ceiling, or soften aspect/duration — then dry-run again. Keep the dry-run and live payloads identical except for `dryRun`, so the routing decision you inspected matches what you bill. ## No eligible model A request can fail if no model satisfies the configuration’s constraints and the request together. The error identifies which constraint(s) emptied the eligible pool — for example, your maximum credits per generation and the requested duration leaving nothing eligible. To resolve it, update the configuration in the Developer Portal to adjust the constraints, commonly by adding additional enabled models or raising the maximum credits per generation for that modality. See [Configuring a Model Router](/model-routers/configuration) for the available settings.