ModelRiver speaks the OpenAI Chat Completions protocol, so most code written for another OpenAI-compatible gateway keeps its SDK. What changes is where the routing lives. Other gateways usually take a provider model in model (openai/gpt-4o) and routing rules in the request, a header or a config file. In ModelRiver, model is the name of a workflow, and the workflow holds the primary model, up to two backups, structured output, guardrails, budgets and its release history.
This page is the technical reference. For a side-by-side overview of each platform, see Migrate to ModelRiver.
What changes in your code
Three values change. Everything else in the request (messages, temperature, max_tokens, tools, stream) stays as it is.
| Setting | Before | After |
|---|---|---|
| Base URL | Your gateway's URL | https://api.modelriver.com/v1 |
| API key | Your gateway key | A ModelRiver project key, mr_live_… |
model | A provider model, preset or alias | A workflow name, e.g. support_bot |
Before you switch, create a workflow and add your provider keys to the project. Creating a workflow publishes its first Live version, so it is callable right away. Workflow names use letters, digits and underscores.
Concept mapping
| What you have today | In ModelRiver |
|---|---|
provider/model string in model (openai/gpt-4o) | The workflow's primary model. Send the workflow name instead |
Fallback list in the request (models: [...], providerOptions.gateway.models) | The workflow's Backup 1 and Backup 2. ModelRiver tries the primary, then each backup in order |
Saved preset, virtual model or alias (@preset/…, vmc/…, LiteLLM model_name) | A workflow. The name you send stays the same while you edit and publish new versions |
Routing config in a header (x-portkey-config) | Nothing in the request. The workflow holds the routing |
| Provider keys stored in the gateway | Provider keys stored in your ModelRiver project (BYOK). Providers bill you directly; ModelRiver adds no token markup |
| Versioned config or preset history | Draft and Live releases with rollback |
response.model showing the provider model | response.model is the workflow name; the provider and model that answered are in response headers |
| Load balancing across keys or weighted targets | No equivalent today. A workflow runs the primary first and moves to a backup only on failure |
| Prepaid credits or one bill for all providers | No equivalent. Each provider bills your own account |
Responses API (/v1/responses) | Not supported. Use Chat Completions; see OpenAI compatibility |
A workflow holds one primary and up to two backups. If your fallback list is longer, keep the three you rely on most.
Before and after, by gateway
The "before" snippets show each gateway's documented syntax, per their docs as of Nov 2026. Check their current docs for the exact options your setup uses.
OpenRouter
Before: a provider model in model, backups in models.
1from openai import OpenAI2 3client = OpenAI(4 base_url="https://openrouter.ai/api/v1",5 api_key="OPENROUTER_API_KEY",6)7 8response = client.chat.completions.create(9 model="openai/gpt-4o",10 extra_body={"models": ["anthropic/claude-sonnet-4", "google/gemini-2.5-flash"]},11 messages=[{"role": "user", "content": "Summarize this ticket"}],12)After: create a workflow support_bot with primary openai/gpt-4o, Backup 1 anthropic/claude-sonnet-4, Backup 2 google/gemini-2.5-flash. Then:
1from openai import OpenAI2 3client = OpenAI(4 base_url="https://api.modelriver.com/v1",5 api_key="mr_live_YOUR_API_KEY",6)7 8response = client.chat.completions.create(9 model="support_bot",10 messages=[{"role": "user", "content": "Summarize this ticket"}],11)If you call a preset (model="@preset/support-bot"), create a workflow with the preset's models and send its name. OpenRouter docs: Quickstart, Model fallbacks.
Vercel AI Gateway
Before: a provider/model string in the AI SDK, with fallback models in providerOptions.gateway.
1import { generateText } from "ai";2 3const { text } = await generateText({4 model: "openai/gpt-4o",5 prompt: "Summarize this ticket",6 providerOptions: {7 gateway: {8 models: ["anthropic/claude-sonnet-4", "google/gemini-2.5-flash"],9 },10 },11});After: the same workflow support_bot, called through the OpenAI provider. Use .chat(): since AI SDK 5 the default model call uses the Responses API, which ModelRiver doesn't support.
1import { createOpenAI } from "@ai-sdk/openai";2import { generateText } from "ai";3 4const modelriver = createOpenAI({5 baseURL: "https://api.modelriver.com/v1",6 apiKey: process.env.MODELRIVER_API_KEY,7});8 9const { text } = await generateText({10 model: modelriver.chat("support_bot"),11 prompt: "Summarize this ticket",12});A virtual model (vmc/<slug>) maps to a workflow the same way: recreate its models and options in a workflow and send the workflow name. If you call the gateway's OpenAI-compatible endpoint (https://ai-gateway.vercel.sh/v1) instead of the AI SDK, follow the OpenRouter example above. Vercel docs: Provider options, Virtual models.
Portkey
Before: the routing config travels in the x-portkey-config header.
1import json2from openai import OpenAI3 4config = {5 "strategy": {"mode": "fallback"},6 "targets": [7 {"override_params": {"model": "@openai-prod/gpt-4o"}},8 {"override_params": {"model": "@anthropic-prod/claude-sonnet-4"}},9 ],10}11 12client = OpenAI(13 base_url="https://api.portkey.ai/v1",14 api_key="unused",15 default_headers={16 "x-portkey-api-key": "PORTKEY_API_KEY",17 "x-portkey-config": json.dumps(config),18 },19)20 21response = client.chat.completions.create(22 model="gpt-4o",23 messages=[{"role": "user", "content": "Summarize this ticket"}],24)After: each target becomes a workflow slot (primary openai/gpt-4o, Backup 1 anthropic/claude-sonnet-4), and the headers go away.
1from openai import OpenAI2 3client = OpenAI(4 base_url="https://api.modelriver.com/v1",5 api_key="mr_live_YOUR_API_KEY",6)7 8response = client.chat.completions.create(9 model="support_bot",10 messages=[{"role": "user", "content": "Summarize this ticket"}],11)If you reference a saved config by ID, map that config to one workflow. Portkey docs: Fallbacks.
LiteLLM
Before: a self-hosted proxy with aliases and fallbacks in config.yaml.
1model_list:2 - model_name: support-bot3 litellm_params:4 model: openai/gpt-4o5 api_key: os.environ/OPENAI_API_KEY6 - model_name: support-bot-backup7 litellm_params:8 model: anthropic/claude-sonnet-49 api_key: os.environ/ANTHROPIC_API_KEY10 11litellm_settings:12 fallbacks: [{"support-bot": ["support-bot-backup"]}]1client = OpenAI(base_url="http://0.0.0.0:4000", api_key="sk-litellm-key")2response = client.chat.completions.create(model="support-bot", messages=messages)After: each model_name with its fallbacks becomes one workflow. The provider keys from litellm_params go into your ModelRiver project.
1client = OpenAI(base_url="https://api.modelriver.com/v1", api_key="mr_live_YOUR_API_KEY")2response = client.chat.completions.create(model="support_bot", messages=messages)LiteLLM aliases often contain hyphens. support-bot still resolves to a workflow named support_bot, but send the exact name. If you use LiteLLM's Python SDK rather than the proxy, keep the openai/ prefix so it calls an OpenAI-compatible endpoint: model="openai/support_bot" with api_base="https://api.modelriver.com/v1". LiteLLM docs: Proxy reliability and fallbacks.
See which model ran
The response model is always the workflow name. To see which provider and model answered, read the response headers:
| Header | Value |
|---|---|
x-modelriver-workflow | The workflow that handled the request |
x-modelriver-revision | The workflow revision that ran |
x-modelriver-model-resolved-from | The name you sent, when it was resolved to a different spelling (e.g. support-bot → support_bot) |
x-modelriver-provider | The provider that answered |
x-modelriver-model | The provider model that answered; a backup's model when a backup answered |
curl -si https://api.modelriver.com/v1/chat/completions \ -H "Authorization: Bearer $MODELRIVER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "support_bot", "messages": [{"role": "user", "content": "Hi"}]}' \ | grep -i '^x-modelriver-'Request Logs show every attempt for each request, including the ones that failed over.
First errors and fixes
A model value that doesn't match a workflow returns 404 with code: "model_not_found" and a modelriver object that names the reason and suggests workflows. The SDK error message carries the same text.
1{2 "error": {3 "message": "'openai/gpt-4o' is a provider model. ModelRiver's model field takes a workflow name; the workflow picks provider, model and fallbacks. Create one in Console → Workflows. List yours: GET /v1/models",4 "type": "invalid_request_error",5 "param": "model",6 "code": "model_not_found",7 "modelriver": {8 "reason": "provider_model",9 "suggestions": [],10 "docs_url": "https://modelriver.com/docs/api/gateway-migration"11 }12 }13}reason | Example message | Fix |
|---|---|---|
provider_model | 'gpt-4o' is a provider model. ModelRiver's model field takes a workflow name. Workflow 'support_bot' in this project runs openai/gpt-4o — send model="support_bot". | Send the suggested workflow name. With no suggestion, create a workflow with that model |
near_miss | Workflow 'suport_bot' not found. Did you mean 'support_bot'? Names use letters, digits and underscores. | Fix the spelling |
near_miss (case) | Workflow 'Support_Bot' not found. Names are case-sensitive — did you mean 'support_bot'? | Match the case exactly |
ambiguous | 'my-wf' matches more than one workflow: 'my_wf', 'mywf'. Send the exact name. | Send one of the listed names |
unknown | Workflow 'x' not found. This project has: a, b, c (+N more — GET /v1/models). | Check the API key's project, or pick a listed name |
missing_live | Workflow 'support_bot' has no Live version. Publish it in Console → Workflows. | Publish the workflow |
none_callable | No workflow in this project accepts completion requests. Create a completion workflow in Console → Workflows. | Create a workflow with the matching request type |
no_workflows | This project has no workflows yet. In ModelRiver, model is a workflow name, not a provider model. Create a workflow in Console → Workflows, then send its name. | Create your first workflow |
Two other first-day errors:
401with{"error": "Invalid or missing API key"}: the request still carries your old gateway key, or the header isn'tAuthorization: Bearer mr_live_…. This body is a plain string, not the OpenAI error object.404onPOST /v1/responses: your SDK is calling the Responses API. Switch to Chat Completions (modelriver.chat(…)in the Vercel AI SDK,set_default_openai_api("chat_completions")in the OpenAI Agents SDK).
GET /v1/models lists the names you can send. See Error handling for every other status code.
Switch over safely
- Add your provider keys to the ModelRiver project.
- Create one workflow per preset, alias or fallback list, and run it in test mode or the Playground.
- Change the base URL, API key and
modelin one code path. - Keep your current gateway configured in your code as a fallback until you're confident, then remove it.
- Edit workflows as Drafts and publish them to Live when the change is ready. Roll back from History if needed.
Next steps
- OpenAI compatibility: Endpoints,
/v1/modelsand SDK setup - Build a workflow: Primary, backups, structured output and budgets
- Releases: Draft, Live and rollback
- Migrate to ModelRiver: Platform-by-platform comparison
OpenRouter, Vercel, Portkey and LiteLLM are trademarks of their respective owners. ModelRiver is not affiliated with, endorsed or sponsored by them.