---
title: Generating through a Model Router
description: 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.

<Tabs>
  <TabItem label="Node">
    <Code code={codeSample`
    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);
  }
}

`.trim()} lang="ts" />
```

  </TabItem>
  <TabItem label="Python">
    <Code code={codeSample`
    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)

`.trim()} lang="python" />
```

  </TabItem>
  <TabItem label="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"
```
````

  </TabItem>
</Tabs>

# Generate image

Same pattern as video: `configId` plus an `input` object. Image `input` fields include `promptText`, optional `referenceImages`, `aspectRatio`, `resolution`, and `outputCount`. See the <a href="/api#tag/Model-Router/paths/~1v1~1generate~1image/post">API reference</a> for the full schema.

<Tabs>
  <TabItem label="Node">
    <Code code={codeSample`
    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);
  }
}

`.trim()} lang="ts" />
```

  </TabItem>
  <TabItem label="Python">
    <Code code={codeSample`
    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)

`.trim()} lang="python" />
```

  </TabItem>
  <TabItem label="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"
    ```
  </TabItem>
</Tabs>

# 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 <a href="/api#tag/Model-Router/paths/~1v1~1generate~1audio/post">API reference</a>.

<Tabs>
  <TabItem label="Node">
    <Code code={codeSample`
    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);
  }
}

`.trim()} lang="ts" />
```

  </TabItem>
  <TabItem label="Python">
    <Code code={codeSample`
    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)

`.trim()} lang="python" />
```

  </TabItem>
  <TabItem label="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"
    ```
  </TabItem>
</Tabs>

# 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.

<Aside type="tip">
  Dry-run requests are still subject to standard API rate limits.
</Aside>

# 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.
