<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Felix</title>
    <description>The latest articles on DEV Community by Felix (@felix99dev).</description>
    <link>https://dev.to/felix99dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4083210%2F1e8afbac-b096-4062-850a-3af8f4d9fec1.jpg</url>
      <title>DEV Community: Felix</title>
      <link>https://dev.to/felix99dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/felix99dev"/>
    <language>en</language>
    <item>
      <title>What is an AI gateway, and what makes one European? OpenRouter vs Opper</title>
      <dc:creator>Felix</dc:creator>
      <pubDate>Sat, 14 Nov 2026 02:43:34 +0000</pubDate>
      <link>https://dev.to/opper/what-is-an-ai-gateway-and-what-makes-one-european-openrouter-vs-opper-2l74</link>
      <guid>https://dev.to/opper/what-is-an-ai-gateway-and-what-makes-one-european-openrouter-vs-opper-2l74</guid>
      <description>&lt;p&gt;An AI gateway is a single API between your app and many LLM providers. Without one, you end up rebuilding the same plumbing: a key per provider, an SDK per provider, retries, fallbacks, and a bill from everyone. A gateway puts all of that behind one endpoint.&lt;/p&gt;

&lt;p&gt;A European AI gateway is one that runs in Europe, specifically the EU, and can keep your requests there, from the gateway itself to the model that answers.&lt;/p&gt;

&lt;p&gt;This is a short, high-level comparison. I use OpenRouter and Opper as the two examples because they keep requests in the EU in different ways, and you can try both with the same OpenAI SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an AI gateway does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One endpoint, many models.&lt;/strong&gt; You send an OpenAI-style request and pick the model by name, and the gateway forwards it to a provider that serves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failover.&lt;/strong&gt; The same open-weight model is often served by several providers, so if one is down or rate-limited the gateway retries on another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key and one bill.&lt;/strong&gt; Your app holds one gateway key instead of ten provider keys, and spend shows up in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing preferences.&lt;/strong&gt; Cheapest, fastest or highest-throughput provider first, depending on what you ask for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers most "should I use a gateway" decisions. Where it gets interesting is the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a gateway European
&lt;/h2&gt;

&lt;p&gt;"EU-hosted" gets used loosely. In practice there are four separate questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Where does the gateway run, and under which company?&lt;/strong&gt; Every prompt passes through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does inference run?&lt;/strong&gt; The same model can be served from the EU by one provider and from the US by another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does the provider keep?&lt;/strong&gt; Logging, retention and training on prompts differ per provider, not per model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens when the allowed route is down?&lt;/strong&gt; The safe behaviour is to refuse the call. The common failure is a silent fallback to a provider in another region, which is exactly what failover is built to do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point 4 is the one people miss. Failover and data residency pull in opposite directions, so a European setup needs a way to say "fail over, but only inside this boundary".&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenRouter and Opper side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;OpenRouter&lt;/th&gt;
&lt;th&gt;Opper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Company&lt;/td&gt;
&lt;td&gt;Stripe (US), which acquired OpenRouter in 2026&lt;/td&gt;
&lt;td&gt;Opper Technology AB, Stockholm, Sweden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI-compatible base URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://openrouter.ai/api/v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://api.opper.ai/v3/compat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keeping a request in the EU&lt;/td&gt;
&lt;td&gt;Call the EU endpoint &lt;code&gt;https://eu.openrouter.ai/api/v1&lt;/code&gt;, Business and Enterprise plans only&lt;/td&gt;
&lt;td&gt;Call an EU-hosted route on any account, or set a model access rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-request data controls&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;provider.zdr&lt;/code&gt;, &lt;code&gt;provider.data_collection&lt;/code&gt;, &lt;code&gt;provider.only&lt;/code&gt; / &lt;code&gt;ignore&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The route you call, &lt;code&gt;provider/model&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Org-wide enforcement&lt;/td&gt;
&lt;td&gt;Privacy settings on the account&lt;/td&gt;
&lt;td&gt;Model access rule on the org or a project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No eligible route left&lt;/td&gt;
&lt;td&gt;The request errors instead of leaving the region&lt;/td&gt;
&lt;td&gt;Refused with &lt;code&gt;403&lt;/code&gt; before it reaches a provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fee on credit purchases&lt;/td&gt;
&lt;td&gt;5.5%, or 8% on Business, which the EU endpoint needs&lt;/td&gt;
&lt;td&gt;3%, EU-hosted routes included (5.5% with Control Plane rules)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On OpenRouter you don't pick a European model for this. You call the EU endpoint with the usual model id, and it only uses provider endpoints in the EU/EEA, the UK or Switzerland, so fewer models are available than on the global endpoint.&lt;/p&gt;

&lt;p&gt;Both fail closed, which is the right call. The difference is where the boundary lives. OpenRouter draws it at the endpoint you call. Opper draws it in a rule on your org or project, so every key and every app under it inherits the same boundary. One more detail from OpenRouter's own docs: with the EU endpoint, prompts and completions stay in the region, while account, billing and other operational records may be processed in the US.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same request on both
&lt;/h2&gt;

&lt;p&gt;Both speak the OpenAI API, so switching is a base URL and a model name.&lt;/p&gt;

&lt;p&gt;OpenRouter, EU endpoint, zero data retention providers only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://eu.openrouter.ai/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"OPENROUTER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mistralai/mistral-small-2603"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Summarise GDPR Article 28 in two sentences."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;extra_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"zdr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data_collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Opper, an EU-hosted route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.opper.ai/v3/compat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"OPPER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mistral/mistral-small-2603"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Mistral Small on Mistral's own EU route&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Summarise GDPR Article 28 in two sentences."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Opper the model id carries the route as &lt;code&gt;provider/model&lt;/code&gt;, and each route's data policy is in the public catalogue. &lt;code&gt;https://api.opper.ai/v3/models?q=mistral-small-2603&lt;/code&gt; lists every route for that model, and for the one above it returns &lt;code&gt;"inference_location": "EU"&lt;/code&gt;, &lt;code&gt;"training": "no"&lt;/code&gt; and &lt;code&gt;"logging": "none"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make it a rule rather than a habit, a model access rule allowlists EU inference locations for the whole org or a project. A call that names a US route then comes back as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;403 model "anthropic/claude-sonnet-5" is blocked by the active model allowlist: inference location US is outside EU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bare model name, like &lt;code&gt;mistral-small-2603&lt;/code&gt; without a provider, still fails over, but only across the allowed routes, and it's refused with &lt;code&gt;403&lt;/code&gt; if none are left. That's point 4 handled in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to pick
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want the biggest community and the most integrations:&lt;/strong&gt; OpenRouter is hard to beat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need EU processing on OpenRouter:&lt;/strong&gt; that means the Business plan, with an 8% fee on every credit purchase against Opper's 3%, and their docs say operational data may still be processed in the US.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want the gateway company, the gateway and inference in Europe, with one rule your whole org inherits:&lt;/strong&gt; that's the case Opper is built for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whichever you pick:&lt;/strong&gt; test point 4 yourself. Block a region and see whether the call fails or quietly goes somewhere else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docs if you want to dig in: &lt;a href="https://openrouter.ai/docs/guides/features/sovereign-ai" rel="noopener noreferrer"&gt;OpenRouter EU in-region routing&lt;/a&gt;, &lt;a href="https://docs.opper.ai/control-plane/rules/model-access" rel="noopener noreferrer"&gt;Opper model access rules&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gdpr</category>
      <category>api</category>
    </item>
    <item>
      <title>Codex CLI with any model: the "codex router" setup in one config block</title>
      <dc:creator>Felix</dc:creator>
      <pubDate>Tue, 25 Aug 2026 09:18:02 +0000</pubDate>
      <link>https://dev.to/opper/codex-cli-with-any-model-the-codex-router-setup-in-one-config-block-3ol7</link>
      <guid>https://dev.to/opper/codex-cli-with-any-model-the-codex-router-setup-in-one-config-block-3ol7</guid>
      <description>&lt;p&gt;OpenAI's Codex CLI is a genuinely good coding agent, but out of the box it runs OpenAI models on OpenAI billing. Sometimes you want Claude Opus for a gnarly refactor, Kimi K2.7 Code for cheap long sessions, or a model served from EU infrastructure because your client asks where tokens go.&lt;/p&gt;

&lt;p&gt;What most people miss: Codex has custom providers built in. It speaks the Responses API to whatever &lt;code&gt;base_url&lt;/code&gt; you give it, so any gateway that implements the Responses API can act as the router behind Codex. No forks, no proxies, one config block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: the config block
&lt;/h2&gt;

&lt;p&gt;Codex reads &lt;code&gt;~/.codex/config.toml&lt;/code&gt;. Add a provider and a profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[model_providers.opper]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Opper"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.opper.ai/v3/compat"&lt;/span&gt;
&lt;span class="py"&gt;env_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"OPPER_API_KEY"&lt;/span&gt;
&lt;span class="py"&gt;wire_api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"responses"&lt;/span&gt;

&lt;span class="nn"&gt;[profiles.opus]&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"anthropic/claude-opus-4-7"&lt;/span&gt;
&lt;span class="py"&gt;model_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"opper"&lt;/span&gt;

&lt;span class="nn"&gt;[profiles.kimi]&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"moonshot/kimi-k3"&lt;/span&gt;
&lt;span class="py"&gt;model_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"opper"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;a href="https://opper.ai" rel="noopener noreferrer"&gt;Opper&lt;/a&gt; here (disclosure: I work there), an EU-hosted gateway with 700+ models behind one API key that implements the Responses API. Export the key and launch with a profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPPER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key"&lt;/span&gt;
codex &lt;span class="nt"&gt;--profile&lt;/span&gt; opus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole router. Yes, that means Claude running inside OpenAI's own CLI, which never stops being funny.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: one command
&lt;/h2&gt;

&lt;p&gt;If you don't want to touch config files, the Opper CLI writes exactly that block for you (with sentinel markers, so it never clobbers your existing config and can cleanly remove itself):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @opperai/cli
opper launch codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It detects Codex (installs it with &lt;code&gt;--install&lt;/code&gt; if missing), configures the provider, and starts it with preset profiles. &lt;code&gt;opper launch codex --model moonshot/kimi-k3&lt;/code&gt; picks a model at launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which models actually make sense in Codex
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openai/gpt-5.3-codex&lt;/code&gt;&lt;/strong&gt;: the model Codex was built for, via API billing. Honest note: if you already have a ChatGPT plan, Codex is included there and that's the cheaper path for this one model. The router play is for everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;anthropic/claude-opus-4-7&lt;/code&gt; / &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;&lt;/strong&gt;: the most common reason people set this up at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;moonshot/kimi-k3&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;alibaba:global/kimi-k2.7-code&lt;/code&gt;&lt;/strong&gt;: strong coding output per dollar for long agent sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU variants&lt;/strong&gt; like &lt;code&gt;vertexai/gemini-3.7-flash-eu&lt;/code&gt; or Claude on European Vertex regions, if tokens staying in Europe is a requirement and not a preference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full catalogue with per-model pricing, context windows and hosting regions is at &lt;a href="https://opper.ai/models" rel="noopener noreferrer"&gt;opper.ai/models&lt;/a&gt;. Every route is labeled with where it runs and what the retention posture is, which is the part my compliance-minded clients actually care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fine print
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex talks to custom providers over the Responses API (&lt;code&gt;wire_api = "responses"&lt;/code&gt;). Chat-completions-only gateways need &lt;code&gt;wire_api = "chat"&lt;/code&gt;, which Codex also supports, but Responses is the native path and what I tested here.&lt;/li&gt;
&lt;li&gt;Billing on the gateway side is pay-as-you-go at provider token rates, and only successful responses are billed, so a failed request that retries elsewhere doesn't double-charge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit something weird with a specific model, tell me in the comments, I run this setup daily.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>codex</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Claude Code with any model: three ways to route it (incl. the 2-minute one)</title>
      <dc:creator>Felix</dc:creator>
      <pubDate>Thu, 20 Aug 2026 16:29:41 +0000</pubDate>
      <link>https://dev.to/opper/claude-code-with-any-model-three-ways-to-route-it-incl-the-2-minute-one-2md2</link>
      <guid>https://dev.to/opper/claude-code-with-any-model-three-ways-to-route-it-incl-the-2-minute-one-2md2</guid>
      <description>&lt;p&gt;Claude Code is the best agentic coding interface right now, but out of the box it only talks to Anthropic's API with Anthropic billing. Sometimes you want the same interface with a different brain: Kimi K3 for cheap long sessions, GPT-5.4 to cross-check a plan, Gemini 3.7 Flash for speed, or simply a model that runs on EU infrastructure because your client requires it.&lt;/p&gt;

&lt;p&gt;There are three ways to do this. I'll go from most configurable to least effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: claude-code-router
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/musistudio/claude-code-router" rel="noopener noreferrer"&gt;claude-code-router&lt;/a&gt; is the community answer, and it's genuinely powerful: per-task routing rules, request transformers, plugins. If you want "background tasks go to a cheap model, planning goes to a big one," this is your tool.&lt;/p&gt;

&lt;p&gt;The tradeoff is that you run and maintain a local proxy with a JSON config, and you still need API keys for every provider you route to. For a team setup that's real overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: point Claude Code at an Anthropic-compatible gateway
&lt;/h2&gt;

&lt;p&gt;Claude Code respects two environment variables, which means any gateway that speaks the Anthropic Messages API can sit behind it. I work at &lt;a href="https://opper.ai" rel="noopener noreferrer"&gt;Opper&lt;/a&gt;, an EU-hosted gateway that serves 700+ models through one API key, so that's what I'll use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.opper.ai/v3/compat"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-opper-api-key&amp;gt;"&lt;/span&gt;
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole setup. Because the gateway also implements Anthropic's model-listing endpoint, the &lt;code&gt;/model&lt;/code&gt; picker inside Claude Code shows the live catalogue, so you can switch to &lt;code&gt;moonshot/kimi-k3&lt;/code&gt;, &lt;code&gt;openai/gpt-5.4&lt;/code&gt; or &lt;code&gt;gemini/gemini-3.5-flash&lt;/code&gt; mid-session instead of editing config files.&lt;/p&gt;

&lt;p&gt;Two things I like about this over a local proxy: there's nothing running on your machine, and billing is unified, one balance across every model with token prices matching what the providers charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: one command
&lt;/h2&gt;

&lt;p&gt;If you don't want to manage env vars at all, the Opper CLI wires everything for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @opperai/cli
opper launch claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;opper launch&lt;/code&gt; starts Claude Code with its inference already routed through the gateway. Pick a model at launch if you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opper launch claude &lt;span class="nt"&gt;--model&lt;/span&gt; moonshot/kimi-k3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything after the agent name is forwarded to Claude Code verbatim, so &lt;code&gt;opper launch claude --resume&lt;/code&gt; works exactly as you'd expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The EU angle
&lt;/h2&gt;

&lt;p&gt;If you build for European customers, this setup solves a second problem for free. Opper runs in the EU, and the catalogue marks which routes are EU-hosted, including EU variants of frontier models (for example &lt;code&gt;vertexai/gemini-3.7-flash-eu&lt;/code&gt;, or Claude served from European Vertex regions). Same Claude Code workflow, but the tokens don't leave Europe, which is the difference between "we should check with legal" and "approved" in a lot of B2B contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You want per-task routing rules and love config: &lt;strong&gt;claude-code-router&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want zero local moving parts and model switching from inside Claude Code: &lt;strong&gt;the two env vars&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want the shortest path: &lt;strong&gt;&lt;code&gt;opper launch claude&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full model catalogue with prices and EU/US hosting per route is at &lt;a href="https://opper.ai/models" rel="noopener noreferrer"&gt;opper.ai/models&lt;/a&gt;. If you try this and something breaks, tell me in the comments, I maintain this setup daily.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>claudecode</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
