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

app.py
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

app.py
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

app.ts
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

app.ts
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

request.sh
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

request.sh
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.

PortkeyModelRiver
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 IntegrationProvider 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 keyNo header: the workflow name in modelEverything the config carried moves into the workflow.
strategy.mode: "fallback" with ordered targetsPrimary model plus up to 2 backup modelsTried in order. More than three targets has no direct equivalent.
on_status_codesFixed 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 ProviderUSD budget per workflow modelHourly, daily, weekly or monthly caps on each model slot.
Guardrails on inputs and outputsInput guardrails on the workflowPattern categories and OpenAI moderation, checked before the provider call. No output checks.
x-portkey-api-keyAuthorization: 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

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 free

When to stay where you are

Portkey may suit you better if any of these matter most.

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_codes setting.
  • Input guardrails only: pattern categories and OpenAI moderation. No output, PII or prompt-injection checks.
TopicPortkeyModelRiver
How it billsPlans 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-config header, the SDK config option, or a default config on an API key. ModelRiver reads everything from the workflow named in model. 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 loadbalance mode as well as fallback. 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

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

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

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

  4. 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/model string did together.

  5. Step 5

    Swap base URL, key and model

    Point your OpenAI SDK at https://api.modelriver.com/v1 with a ModelRiver API key, remove the x-portkey-* headers, and set model to the workflow name.

    base_url="https://api.modelriver.com/v1"
    api_key=os.environ["MODELRIVER_API_KEY"]
    model="support_bot"
  6. 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.

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