Migrate from Portkey
Coming from Portkey?One workflow instead of a config header.
Today your request carries a Portkey key, a config with fallback targets, and an @provider/model string. On ModelRiver the same setup is one workflow, and your code sends only its name.
- BYOK
- no token markup
- Free plan stops at 25,000 requests/month
2 lines of code + 1 workflow
Keep your OpenAI SDK. On the client line, change the base URL and key. On the request line, set model to the name of a workflow.
Step 0: create a workflow in the console with your primary model and backups. Its name is what you send as model.
Before
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.portkey.ai/v1",
api_key="unused", # auth goes in x-portkey-api-key
default_headers={
"x-portkey-api-key": os.environ["PORTKEY_API_KEY"],
"x-portkey-config": "pc-support-xxxx",
},
)
resp = client.chat.completions.create(
model="@openai-prod/gpt-4o",
messages=[{"role": "user", "content": "Summarize this ticket."}],
)
print(resp.choices[0].message.content)After
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.modelriver.com/v1",
api_key=os.environ["MODELRIVER_API_KEY"],
)
resp = client.chat.completions.create(
model="support_bot", # workflow: gpt-4o + 2 backups
messages=[{"role": "user", "content": "Summarize this ticket."}],
)
print(resp.choices[0].message.content)Before
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.portkey.ai/v1",
apiKey: "unused", // auth goes in x-portkey-api-key
defaultHeaders: {
"x-portkey-api-key": process.env.PORTKEY_API_KEY!,
"x-portkey-config": "pc-support-xxxx",
},
});
const resp = await client.chat.completions.create({
model: "@openai-prod/gpt-4o",
messages: [{ role: "user", content: "Summarize this ticket." }],
});
console.log(resp.choices[0].message.content);After
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.modelriver.com/v1",
apiKey: process.env.MODELRIVER_API_KEY,
});
const resp = await client.chat.completions.create({
model: "support_bot", // workflow: gpt-4o + 2 backups
messages: [{ role: "user", content: "Summarize this ticket." }],
});
console.log(resp.choices[0].message.content);Before
curl https://api.portkey.ai/v1/chat/completions \
-H "x-portkey-api-key: $PORTKEY_API_KEY" \
-H "x-portkey-config: pc-support-xxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "@openai-prod/gpt-4o",
"messages": [{"role": "user", "content": "Summarize this ticket."}]
}'After
curl 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": "Summarize this ticket."}]
}'What maps to what
Portkey concepts and where they live in ModelRiver.
| Portkey | ModelRiver |
|---|---|
model: "@openai-prod/gpt-4o" | model: "support_bot", the name of a workflowThe AI Provider slug and model both move into the workflow. Your code sends one name. |
| AI Provider (slug + stored credentials) and Integration | Provider credential stored in the projectEncrypted at rest. A workflow picks the provider and model that use it. |
x-portkey-config header or a default config on an API key | No header: the workflow name in modelEverything the config carried moves into the workflow. |
strategy.mode: "fallback" with ordered targets | Primary model plus up to 2 backup modelsTried in order. More than three targets has no direct equivalent. |
on_status_codes | Fixed failover list429, 5xx, 408, 404 and connection errors, including during streaming. Not configurable. |
strategy.mode: "loadbalance" | No equivalentModelRiver does not split traffic across targets. |
| Budget and rate limits on an AI Provider | USD budget per workflow modelHourly, daily, weekly or monthly caps on each model slot. |
| Guardrails on inputs and outputs | Input guardrails on the workflowPattern categories and OpenAI moderation, checked before the provider call. No output checks. |
x-portkey-api-key | Authorization: Bearer with a ModelRiver API keyThe header your OpenAI SDK already sends. |
Map your config
Your fallbacks, output shape and budget move into one named workflow.
Your current Portkey request
{
"strategy": { "mode": "fallback", "on_status_codes": [429, 500, 503] },
"targets": [
{ "override_params": { "model": "@openai-prod/gpt-4o" } },
{ "override_params": { "model": "@anthropic-prod/claude-sonnet-5" } },
{ "override_params": { "model": "@google-prod/gemini-3.5-flash" } }
]
}ModelRiver
The same setup as a ModelRiver workflow
- Primary
- openai/gpt-4o
- Backup 1
- anthropic/claude-sonnet-5
- Backup 2
- google/gemini-3.5-flash
- Structured output
- Optional JSON schema, saved on the workflow
- Budget
- $200 per month on the primary model
- Release
- Draft → Live, roll back any time
Try it with your keys. The Free plan stops at 25,000 requests a month instead of charging overage. Model usage is billed by your provider on your own keys.
Create a workflow freeWhen to stay where you are
Portkey may suit you better if any of these matter most.
You split traffic across targets with
strategy.mode: "loadbalance".You rely on output guardrails, PII checks or prompt-injection checks. ModelRiver checks input only.
You choose which status codes trigger fallback with
on_status_codes.You manage prompt templates in your gateway, or need SSO, audit logs or OpenTelemetry export. ModelRiver does not offer these yet.
You want to self-host the gateway. ModelRiver is hosted only.
What you gain, what you give up
What you gain
- Model, backups, structured output and budget sit behind one name in
model, with no gateway headers on the request. - Draft and Live releases with revision history: test a change before production sees it, then publish or roll back.
- Structured outputs saved on the workflow, with native schema mode for OpenAI, Anthropic, xAI, Mistral and Cohere models.
- Async requests through the native API, with signed webhooks, WebSocket streaming and event-driven backend pipelines.
- Plans count requests, and every request is in the logs for 30 to 180 days depending on plan, with provider, model and a cost estimate.
What you give up
- No load balancing: one primary and up to 2 backups, tried in order.
- Fallback triggers are fixed (429, 5xx, 408, 404 and connection errors); there is no
on_status_codessetting. - Input guardrails only: pattern categories and OpenAI moderation. No output, PII or prompt-injection checks.
| Topic | Portkey | ModelRiver |
|---|---|---|
| How it bills | Plans sized by recorded logs per month, with a free Developer tier and overage on the paid tier. The gateway is also available as open source to host yourself. Source (opens in a new tab) · checked | Monthly plans by request volume, with no markup on tokens. Model usage is billed by your provider on your own keys. |
Compared, with sources
- Portkey attaches routing configs with the
x-portkey-configheader, the SDKconfigoption, or a default config on an API key. ModelRiver reads everything from the workflow named inmodel. Source (opens in a new tab) · checked - Portkey configs are saved in its UI, referenced by ID and updated through its Admin API. ModelRiver workflows have Draft and Live versions with revision history and rollback. Source (opens in a new tab) · checked
- Portkey plans are sized by recorded logs per month. ModelRiver plans count requests and keep request logs for 30 to 180 days depending on plan. Source (opens in a new tab) · checked
- Portkey configs support a
loadbalancemode as well asfallback. ModelRiver tries one primary and up to 2 backups in order, with no load balancing. Source (opens in a new tab) · checked - Portkey falls back on any non-2xx response by default, or on the codes in
on_status_codes. ModelRiver falls back on a fixed list: 429, 5xx, 408, 404 and connection errors. Source (opens in a new tab) · checked - The Portkey gateway describes guardrails that check inputs and outputs. ModelRiver guardrails check input only, with pattern categories and OpenAI moderation. Source (opens in a new tab) · checked
- The Portkey gateway is MIT-licensed and can be self-hosted. ModelRiver is not open source and is not self-hostable. Source (opens in a new tab) · checked
Steps to move over
- Step 1
Add your provider keys
Add a key for each provider behind the AI Providers your configs use, such as
@openai-prod. Keys are encrypted at rest and masked once saved. - Step 2
Turn each config into a workflow
For each config ID your app sends, create a workflow: the first target becomes the primary model, the next two become backups. Name it with letters, digits and underscores, such as
support_bot. - Step 3
Compare the Draft with your config
Run a few prompts you send through Portkey today in the Playground against the Draft. To wire up your app without provider calls, switch the workflow to Testing mode; switch back to Production before you publish.
- Step 4
Publish the workflow Live
API requests run the Live version, so publish before you switch code. The workflow name then does the job your config ID and
@provider/modelstring did together. - Step 5
Swap base URL, key and model
Point your OpenAI SDK at
https://api.modelriver.com/v1with a ModelRiver API key, remove thex-portkey-*headers, and setmodelto the workflow name.base_url="https://api.modelriver.com/v1" api_key=os.environ["MODELRIVER_API_KEY"] model="support_bot" - Step 6
Keep Portkey as a fallback for a while
Keep your current gateway configured in your code as a fallback until you're confident, then remove it. Keep a second client with your Portkey headers and call it if a ModelRiver request fails.
- Step 7
Edit the workflow, not the config
Where you used to update a saved config, edit the workflow Draft, publish it, and roll back to an earlier revision if the change misbehaves.
Frequently asked questions
Do I still send a config header?
No. The workflow named in model holds what your config held: models, backups, structured output and budget. Remove the x-portkey-* headers.
My config uses loadbalance. What then?
ModelRiver has no load balancing. A workflow tries its primary model, then up to 2 backups in order. If weighted splits matter to you, stay on your current setup.
How does on_status_codes map?
It does not. ModelRiver moves to the next backup on 429, 5xx, 408, 404 and connection errors, including during streaming. Other errors are returned to you.
Can I send @openai-prod/gpt-4o as the model?
No. The @provider_slug/ form has no meaning on ModelRiver; model takes a workflow name. If you send a provider model, the 404 response names a workflow in your project that uses it, when one exists.
What happens to budgets I set on AI Providers?
Set a USD budget on each workflow model instead: hourly, daily, weekly or monthly.
What about my guardrails?
ModelRiver input guardrails check pattern categories and run OpenAI moderation before the provider call. There are no output, PII or prompt-injection checks. If you depend on those, keep them where they are.
Bring your keys. Keep your SDK.
Start on the Free plan, create a workflow and point your SDK at it.
Send us your models[] request or config.yaml (remove keys first) and we’ll reply with the matching workflow setup. Get help moving
Checked · Report an inaccuracy
Sources
Portkey is a trademark of its owner. ModelRiver is not affiliated with, endorsed or sponsored by it.