<?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: Seven</title>
    <description>The latest articles on DEV Community by Seven (@seven7763).</description>
    <link>https://dev.to/seven7763</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%2F4025722%2F90454edb-090f-4e2c-95d7-1d90108f07d4.jpg</url>
      <title>DEV Community: Seven</title>
      <link>https://dev.to/seven7763</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seven7763"/>
    <language>en</language>
    <item>
      <title>OpenAI cut prices 80%. Here's why I'm not celebrating.</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Fri, 31 Jul 2026 16:23:40 +0000</pubDate>
      <link>https://dev.to/seven7763/openai-cut-prices-80-heres-why-im-not-celebrating-449o</link>
      <guid>https://dev.to/seven7763/openai-cut-prices-80-heres-why-im-not-celebrating-449o</guid>
      <description>&lt;p&gt;This week, OpenAI slashed Luna (GPT-5.6) pricing by 80% — from $7 to $1.40 per million tokens. Anthropic shipped Opus 5 at half the price of its predecessor. The LLM API market is in a genuine price war, and for once, developers are winning.&lt;/p&gt;

&lt;p&gt;I should be celebrating. I'm not. Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden math of cheaper models
&lt;/h2&gt;

&lt;p&gt;When official API prices drop, the economics of model-swapping change in a way nobody talks about.&lt;/p&gt;

&lt;p&gt;A bad relay has a simple business model: bill you for Model X, serve you Model Y, pocket the difference. When Model X costs $15/M tokens and Model Y costs $0.15/M, the margin is $14.85. That's already attractive.&lt;/p&gt;

&lt;p&gt;When Model X drops to $1.40? The absolute margin shrinks — and that's what most people see. But the &lt;em&gt;relative&lt;/em&gt; margin — the ratio of what you're charged vs what's actually served — can actually grow if budget models get cheaper still. And they have.&lt;/p&gt;

&lt;p&gt;The point isn't the exact math. The point is: &lt;strong&gt;price competition between legitimate providers doesn't eliminate the incentive to swap models. It often masks it.&lt;/strong&gt; When everyone's prices are dropping, a relay that's 30% cheaper than official looks like aggressive discounting, not fraud.&lt;/p&gt;

&lt;h2&gt;
  
  
  "What model are you?" doesn't work
&lt;/h2&gt;

&lt;p&gt;If you're still asking an API endpoint to identify itself, stop. That's a system prompt — trivially spoofed. Every relay operator knows to make the model say "I am Claude, created by Anthropic."&lt;/p&gt;

&lt;p&gt;What works: &lt;strong&gt;behavioral fingerprinting at temp=0.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Give the model a fixed set of tasks that different models handle differently — reasoning problems, long-context recall, strict JSON formatting, refusal boundaries. Run them at temperature 0 to remove sampling noise. Diff the results against the official API.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. Here's the actual tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/seven7763/llm-honesty-probe
&lt;span class="nb"&gt;cd &lt;/span&gt;llm-honesty-probe
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="nt"&gt;--self-test&lt;/span&gt; &lt;span class="nt"&gt;--card&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs a battery of deterministic probes and produces a verdict card: PASS, SUSPICIOUS, or DEGRADED — with the specific probes that triggered each finding. No API key leaves your machine. No judgment calls required. The self-test runs without any API key and takes about 30 seconds.&lt;/p&gt;

&lt;p&gt;To test your actual endpoint:&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;LLM_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-endpoint/v1"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-your-key"&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="nt"&gt;--compare&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this matters more now, not less
&lt;/h2&gt;

&lt;p&gt;The price war is genuinely good news. Lower costs mean more experiments, more products, more people who can afford to build with LLMs. I want all of that.&lt;/p&gt;

&lt;p&gt;But cheaper official APIs also mean more users signing up for the first time — users who don't know what "Claude" is supposed to sound like, what GPT-5.6's reasoning depth should feel like, or what latency to expect. These are exactly the users a model-swapping relay wants: no baseline for comparison.&lt;/p&gt;

&lt;p&gt;The defense isn't trust. It's &lt;strong&gt;verification that costs less than being wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test your endpoint right now
&lt;/h2&gt;

&lt;p&gt;If you're using any third-party API endpoint — mine included — run the probe. The self-test takes 30 seconds. The compare against your endpoint takes about 2 minutes.&lt;/p&gt;

&lt;p&gt;If everything passes, you've got evidence — not proof, but signals — that you're getting what you pay for. If something flags, you know exactly which probe to investigate.&lt;/p&gt;

&lt;p&gt;Either way, you know more than you did before. That's the whole point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, an OpenAI-compatible LLM gateway. I also maintain &lt;code&gt;llm-honesty-probe&lt;/code&gt;, the open-source tool mentioned in this post. The probe treats every endpoint identically — including daoxe. Point it at anyone. If it flags something on my own service, I want to know before you do.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The price war is real. The trust problem is real. They're related, and fixing one doesn't automatically fix the other.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploy a real AI chat app in one click — key stays server-side (Vercel/Render/Railway)</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Tue, 21 Jul 2026 14:40:49 +0000</pubDate>
      <link>https://dev.to/seven7763/deploy-a-real-ai-chat-app-in-one-click-key-stays-server-side-vercelrenderrailway-391k</link>
      <guid>https://dev.to/seven7763/deploy-a-real-ai-chat-app-in-one-click-key-stays-server-side-vercelrenderrailway-391k</guid>
      <description>&lt;p&gt;Most "deploy an AI app in 5 minutes" tutorials quietly do the one thing you must never do: put an API key where a browser can see it. This post is three one-click starters that are correct by construction — the key is a &lt;strong&gt;deploy-time environment variable, server-side only&lt;/strong&gt;, never in client code, never committed to git.&lt;/p&gt;

&lt;p&gt;Each template is &lt;strong&gt;provider-neutral&lt;/strong&gt;: it defaults to an OpenAI-compatible endpoint (&lt;code&gt;https://daoxe.com/v1&lt;/code&gt;) but points at any compatible endpoint by changing one variable. Pick the one that matches what you're building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=daoxe-deploy-templates" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, the OpenAI-compatible gateway these default to. Everything below works against any compatible endpoint — the default and the "powered by" link are one-line changes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The rule that makes these safe
&lt;/h2&gt;

&lt;p&gt;An LLM API key is a &lt;strong&gt;billing credential&lt;/strong&gt;. Anything shipped to a browser is public. So every template here follows the same discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DAOXE_API_KEY&lt;/code&gt; (and any tokens) are &lt;strong&gt;deploy-time env vars&lt;/strong&gt;, declared in each platform's config with &lt;strong&gt;no baked-in value&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.example&lt;/code&gt; files ship &lt;strong&gt;empty placeholders&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The key is used &lt;strong&gt;server-side only&lt;/strong&gt; — an Edge function, a proxy server, or a bot process holds it; the browser never sees it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a tutorial ever tells you to paste your key into front-end JavaScript, close the tab.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 1 — Streaming chat UI on Vercel
&lt;/h2&gt;

&lt;p&gt;A minimal chat UI backed by an Edge function that holds the key and streams tokens back to the browser. Good for: a demo, an internal tool, a landing-page assistant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/seven7763/daoxe-vercel-chat" rel="noopener noreferrer"&gt;seven7763/daoxe-vercel-chat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy:&lt;/strong&gt; click the Vercel button in the repo README; Vercel prompts you for two env vars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DAOXE_API_KEY&lt;/code&gt; — your key.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DAOXE_MODEL&lt;/code&gt; — an exact id from &lt;code&gt;GET /v1/models&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it — you get a URL with a working streaming chat. The browser calls &lt;em&gt;your&lt;/em&gt; Edge function; the Edge function calls the gateway with the key. The key never leaves the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser ──▶ /api/chat (Edge, holds key) ──▶ https://daoxe.com/v1/chat/completions
        ◀── streamed tokens ◀──────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional &lt;code&gt;DAOXE_ALLOWED_MODELS&lt;/code&gt; lets you restrict which models the function will serve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 2 — OpenAI-compatible proxy on Render
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't want a UI — you want to &lt;strong&gt;share one key with a team or a script&lt;/strong&gt; without handing out the real credential. This template is a tiny OpenAI-compatible proxy: it exposes &lt;code&gt;/v1/chat/completions&lt;/code&gt;, injects the real key server-side, and gates access with a token you hand out instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/seven7763/daoxe-render-proxy" rel="noopener noreferrer"&gt;seven7763/daoxe-render-proxy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy:&lt;/strong&gt; click the Render button in the repo README; set &lt;code&gt;DAOXE_API_KEY&lt;/code&gt; (the real key) and a share token. Now teammates point their OpenAI-compatible tools at &lt;em&gt;your&lt;/em&gt; proxy URL with the share token — they get model access, never your billing credential. It also serves a live &lt;code&gt;/healthz&lt;/code&gt; page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 3 — Discord bot on Railway
&lt;/h2&gt;

&lt;p&gt;A Discord bot that answers &lt;code&gt;/ask&lt;/code&gt; with an LLM. Good for: team assistants, support bots, community helpers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/seven7763/daoxe-railway-discord-bot" rel="noopener noreferrer"&gt;seven7763/daoxe-railway-discord-bot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy:&lt;/strong&gt; from the public repo, open Railway → &lt;em&gt;New Project&lt;/em&gt; → &lt;em&gt;Deploy from GitHub&lt;/em&gt; (or create a Template once), then set &lt;code&gt;DAOXE_API_KEY&lt;/code&gt;, &lt;code&gt;DAOXE_MODEL&lt;/code&gt;, and your Discord bot token. The bot process holds the key; Discord users just type &lt;code&gt;/ask&lt;/code&gt;. (This one has a single runtime dependency, &lt;code&gt;discord.js&lt;/code&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Env vars cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DAOXE_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;all three&lt;/td&gt;
&lt;td&gt;Billing credential. Server-side only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DAOXE_MODEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vercel-chat, railway bot&lt;/td&gt;
&lt;td&gt;Exact id from &lt;code&gt;GET /v1/models&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DAOXE_ALLOWED_MODELS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vercel-chat (optional)&lt;/td&gt;
&lt;td&gt;Comma-separated allowlist.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share / Discord tokens&lt;/td&gt;
&lt;td&gt;render proxy / railway bot&lt;/td&gt;
&lt;td&gt;Separate from the billing key.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Repos are public. Vercel/Render one-click buttons live in each README; Railway is "Deploy from GitHub" from the public repo — no private template id required.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What this buys you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Three one-click AI starters: &lt;strong&gt;Vercel&lt;/strong&gt; (streaming chat), &lt;strong&gt;Render&lt;/strong&gt; (OpenAI-compatible proxy), &lt;strong&gt;Railway&lt;/strong&gt; (Discord bot).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key never in the browser&lt;/strong&gt; — the failure mode most tutorials ship by accident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key → many models&lt;/strong&gt; behind a single OpenAI-compatible base URL, with fallback and one bill if you want it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't trust the pitch — deploy one template, open the network tab, and confirm the key is not in any client request. Then point the same templates at &lt;em&gt;any&lt;/em&gt; OpenAI-compatible endpoint and compare.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>serverless</category>
      <category>javascript</category>
    </item>
    <item>
      <title>One key, two ecosystems: routing between DeepSeek/Kimi/Qwen and Claude/GPT/Gemini by task</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:40:57 +0000</pubDate>
      <link>https://dev.to/seven7763/one-key-two-ecosystems-routing-between-deepseekkimiqwen-and-claudegptgemini-by-task-2kk3</link>
      <guid>https://dev.to/seven7763/one-key-two-ecosystems-routing-between-deepseekkimiqwen-and-claudegptgemini-by-task-2kk3</guid>
      <description>&lt;p&gt;Picking a single model family for an app is a false choice. Some Chinese-origin models are strong and cost-efficient at certain tasks; some Western models lead at others. If you're building outside mainland China, the practical move isn't to bet on one — it's to &lt;strong&gt;route each request to the right one&lt;/strong&gt;, and make switching a one-line change instead of a new integration.&lt;/p&gt;

&lt;p&gt;This guide shows how to do that through a single &lt;strong&gt;OpenAI-compatible endpoint&lt;/strong&gt;, so DeepSeek, Kimi, Qwen, Claude, GPT and Gemini all sit behind one key and one bill — and how to choose between them per task without guessing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure &amp;amp; scope:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, an OpenAI-compatible gateway. It is &lt;strong&gt;not available in mainland China&lt;/strong&gt; — this article is written for developers &lt;em&gt;outside&lt;/em&gt; it (overseas devs, overseas Chinese, and users in Russia, Brazil, Vietnam, Turkey and elsewhere) who want access to both ecosystems in one place. Everything here is standard OpenAI-compatible config; point it at any endpoint that carries the models you want.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why route at all
&lt;/h2&gt;

&lt;p&gt;Three reasons a single-model app leaves value on the table:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task fit.&lt;/strong&gt; Models have uneven strengths — reasoning, long-context, code, multilingual, structured output, vision. The best model for summarizing a 200-page PDF isn't necessarily the best for a tricky refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; For high-volume, low-stakes calls (classification, extraction, routing itself), a cheaper model is often &lt;em&gt;good enough&lt;/em&gt;, and the savings compound. Reserve the expensive model for the calls that need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience.&lt;/strong&gt; If one provider is rate-limited or degraded, being able to fail over to another family keeps you shipping.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The blocker has always been integration cost: different SDKs, keys, billing, and response quirks per vendor. An OpenAI-compatible endpoint removes it — model choice becomes a string.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mechanism: model choice is a string
&lt;/h2&gt;

&lt;p&gt;Every call uses the same shape; only the &lt;code&gt;model&lt;/code&gt; field changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://daoxe.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"&amp;lt;MODEL_ID&amp;gt;","messages":[{"role":"user","content":"..."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List what your key can actually call — the authoritative, account-scoped source is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://daoxe.com/v1/models &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use those &lt;strong&gt;exact ids&lt;/strong&gt;. Families you'll typically see include Chinese-origin ones (DeepSeek, Kimi, Qwen, and others) and Western ones (Claude, GPT, Gemini). Don't hardcode ids from this post — they drift; read them from &lt;code&gt;/v1/models&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A simple task-based router (Python)
&lt;/h2&gt;

&lt;p&gt;You don't need a framework. A dict from task → model id gets you 90% of the value:&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="kn"&gt;import&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;httpx&lt;/span&gt;

&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAOXE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Map task types to EXACT ids from GET /v1/models (examples — replace with yours).
&lt;/span&gt;&lt;span class="n"&gt;ROUTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheap_bulk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# high-volume extraction/classification
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;long_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kimi-k2.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# very long inputs
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="c1"&gt;# hard multi-step problems
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# tricky refactors
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# fast, inexpensive fallback
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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="n"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheap_bulk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extract the invoice total as JSON.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prove or disprove: ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it's one endpoint and one key, adding a route is adding a dict entry — no new SDK, no new credential. Add a &lt;code&gt;try/except&lt;/code&gt; that falls back to another family on error and you've got cheap resilience too.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to choose the route (without vibes)
&lt;/h2&gt;

&lt;p&gt;Don't route on reputation — route on evidence for &lt;em&gt;your&lt;/em&gt; workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build a tiny eval set&lt;/strong&gt; of real prompts per task type with known-good answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run each candidate model&lt;/strong&gt; through it, and record accuracy alongside latency (p50/p95) and token cost. (A small harness does this; I wrote up a reproducible gateway benchmark separately.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick the cheapest model that clears your quality bar&lt;/strong&gt; for each task — not the most expensive one you can afford.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-check periodically.&lt;/strong&gt; New model versions ship constantly; a route that was optimal last quarter may not be now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On price specifically: I'm not going to quote numbers here because they change. Check the provider's pricing page and put real &lt;code&gt;$/1M&lt;/code&gt; figures next to your latency and accuracy — the right route is a three-way trade-off, not a single-axis choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Don't forget to verify what you're getting
&lt;/h2&gt;

&lt;p&gt;Routing across families makes verification &lt;em&gt;more&lt;/em&gt; important, not less: with several models behind one endpoint, you want to confirm each id actually serves what it claims and isn't being silently swapped or truncated. Test &lt;strong&gt;behavior&lt;/strong&gt;, not the model's self-report — a tokenizer fingerprint, a capability-floor check, and a long-context needle test catch the common cheats. (There are open-source probes for this; I covered the method in a separate post.) A good endpoint is fine with you running these against it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest positioning
&lt;/h2&gt;

&lt;p&gt;A few things to be straight about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is for outside mainland China.&lt;/strong&gt; daoxe doesn't serve mainland China; the audience here is overseas developers and users in regions where getting &lt;em&gt;both&lt;/em&gt; ecosystems in one place — and paying for it — is otherwise fiddly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Chinese vs Western" is about model origin, not a quality ranking.&lt;/strong&gt; Route on measured task fit and cost, not on where a model came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key is a convenience, not magic.&lt;/strong&gt; It removes integration and billing friction; it doesn't make a small model into a big one. Match the model to the task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;daoxe is the endpoint I use for this — OpenAI-compatible at &lt;code&gt;https://daoxe.com/v1&lt;/code&gt;, native Anthropic Messages for Claude-native tools, one key across many models (Chinese-origin and Western), one bill, and a design meant to be verified. See the &lt;a href="https://daoxe.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; for current models and prices; use &lt;code&gt;GET /v1/models&lt;/code&gt; for what your key can call.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't pick one family — &lt;strong&gt;route each task&lt;/strong&gt; to the best/cheapest model.&lt;/li&gt;
&lt;li&gt;One OpenAI-compatible endpoint makes model choice a &lt;strong&gt;string&lt;/strong&gt;: one key, one bill, no per-vendor SDKs.&lt;/li&gt;
&lt;li&gt;Choose routes on a &lt;strong&gt;small eval set&lt;/strong&gt; (accuracy + p50/p95 + $/1M), not reputation; re-check as versions ship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt; each model behaviorally, and remember this is for use &lt;strong&gt;outside mainland China&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>deepseek</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Add an AI chat widget to any website — without leaking your API key</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:31:19 +0000</pubDate>
      <link>https://dev.to/seven7763/add-an-ai-chat-widget-to-any-website-without-leaking-your-api-key-28gk</link>
      <guid>https://dev.to/seven7763/add-an-ai-chat-widget-to-any-website-without-leaking-your-api-key-28gk</guid>
      <description>&lt;p&gt;Adding an AI chat box to a website is a five-minute job — and most five-minute tutorials get the one important thing wrong: they put an API key in the front-end. This walkthrough uses a tiny, dependency-free widget that &lt;em&gt;refuses&lt;/em&gt; to let you do that, and shows the two correct ways to handle the key depending on whether the page is yours or public.&lt;/p&gt;

&lt;p&gt;The widget is ~11 KB of vanilla JS — no build step, no framework — and it talks to any &lt;strong&gt;OpenAI-compatible&lt;/strong&gt; Chat Completions endpoint. I'll use daoxe as the default (disclosure below), but it's one line to point it anywhere.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=daoxe-chat-widget" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, the widget's default endpoint. Nothing here is locked to it — change one line and it talks to any compatible endpoint.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The uncomfortable truth: anything in the browser is public
&lt;/h2&gt;

&lt;p&gt;An LLM API key is a &lt;strong&gt;billing credential&lt;/strong&gt;. Anything you ship to a browser — including a key in your JS bundle — is readable by every visitor. So the widget deliberately has &lt;strong&gt;no &lt;code&gt;apiKey&lt;/code&gt; option&lt;/strong&gt; in its config. Instead you pick one of two modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Who holds the key&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;th&gt;Trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;byok&lt;/code&gt;&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td&gt;the visitor pastes &lt;strong&gt;their own&lt;/strong&gt; key; kept only in &lt;em&gt;their&lt;/em&gt; browser&lt;/td&gt;
&lt;td&gt;a personal/local page only &lt;em&gt;you&lt;/em&gt; use; dashboards; internal tools&lt;/td&gt;
&lt;td&gt;needs the endpoint to allow browser &lt;strong&gt;CORS&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;proxy&lt;/code&gt;&lt;/strong&gt; (recommended for public sites)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;your server&lt;/strong&gt; injects the key; the browser never sees it&lt;/td&gt;
&lt;td&gt;any public website&lt;/td&gt;
&lt;td&gt;you run a tiny proxy (examples included)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That single design choice is the whole point. If a widget lets you drop your own key into a public page's source, it's shipping your credential to the world.&lt;/p&gt;




&lt;h2&gt;
  
  
  A) BYOK mode — for a page that is yours
&lt;/h2&gt;

&lt;p&gt;For a personal dashboard or an internal tool, the visitor supplies their own key, stored only in their browser's &lt;code&gt;localStorage&lt;/code&gt; and sent straight to the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"daoxe-widget.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;DaoxeChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_DAOXE_MODEL_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// exact id from GET /v1/models&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ask me anything&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only works if the endpoint returns permissive &lt;strong&gt;CORS&lt;/strong&gt; headers (many gateways don't enable CORS on &lt;code&gt;/chat/completions&lt;/code&gt; — that's what proxy mode is for). If you see a "network/CORS" error, switch to proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  B) Proxy mode — for public sites
&lt;/h2&gt;

&lt;p&gt;For anything public, the browser talks to &lt;em&gt;your&lt;/em&gt; route, and your server adds the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"daoxe-widget.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;DaoxeChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;proxy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// a route you control that adds the key&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_DAOXE_MODEL_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ask us anything&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ready-to-run proxies ship in &lt;code&gt;examples/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Worker&lt;/strong&gt; — deploy in minutes; key stored as a Worker secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node/Express&lt;/strong&gt; — reads &lt;code&gt;DAOXE_API_KEY&lt;/code&gt; from the environment and streams straight through.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both keep the key server-side and can optionally allow-list which models the proxy will forward. In production, restrict &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; to your domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor ──▶ /api/chat (your proxy, holds key) ──▶ https://daoxe.com/v1/chat/completions
        ◀── streamed tokens ◀───────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Security details worth knowing
&lt;/h2&gt;

&lt;p&gt;A chat widget renders untrusted model output into your page, so two things matter beyond the key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The widget escapes all model output&lt;/strong&gt; before rendering — it doesn't &lt;code&gt;innerHTML&lt;/code&gt; raw model text. A tiny markdown-ish renderer handles code/bold/newlines on already-escaped text, and it does &lt;strong&gt;not&lt;/strong&gt; execute HTML returned by the model. (If you build your own widget, do this — model output is untrusted input.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock down the proxy.&lt;/strong&gt; Restrict CORS to your domain and optionally allow-list models so a leaked proxy URL can't be used to run arbitrary expensive models on your bill.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configuration highlights
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DaoxeChat.init({ ... })&lt;/code&gt; takes the usual knobs — &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;subtitle&lt;/code&gt;, &lt;code&gt;greeting&lt;/code&gt;, &lt;code&gt;accent&lt;/code&gt;, &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;startOpen&lt;/code&gt;, &lt;code&gt;systemPrompt&lt;/code&gt;, and (BYOK only) &lt;code&gt;temperature&lt;/code&gt; / &lt;code&gt;maxTokens&lt;/code&gt;. Two worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;model&lt;/code&gt; is &lt;strong&gt;required&lt;/strong&gt; — an exact id available to the key/account. Don't hardcode a list from a blog post; the authoritative list is &lt;code&gt;GET {baseUrl}/models&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;storageKey&lt;/code&gt; controls the localStorage key for BYOK; set it to &lt;code&gt;null&lt;/code&gt; to never persist the visitor's key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The widget is minimal by design: no file uploads, no image input, no tool calls, no cross-page persistence. Streaming uses the standard OpenAI SSE format and falls back to a single JSON read if an endpoint doesn't stream.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why an OpenAI-compatible endpoint helps here
&lt;/h2&gt;

&lt;p&gt;Because the widget speaks the standard Chat Completions shape, the &lt;em&gt;same&lt;/em&gt; embed can serve GPT, Claude, Gemini or DeepSeek by changing a model id — one key, one bill, and you can repoint &lt;code&gt;baseUrl&lt;/code&gt; to any provider you trust and verify. daoxe is the default (&lt;code&gt;https://daoxe.com/v1&lt;/code&gt;, one key across many models, native Anthropic Messages too) and is &lt;strong&gt;not available in mainland China&lt;/strong&gt; — but the widget privileges nothing; &lt;code&gt;baseUrl&lt;/code&gt; and the "Powered by" link are one-line changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, ~11 KB, no build, any OpenAI-compatible endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never&lt;/strong&gt; put your key in front-end code. Use &lt;strong&gt;BYOK&lt;/strong&gt; (your pages) or &lt;strong&gt;proxy&lt;/strong&gt; (public sites).&lt;/li&gt;
&lt;li&gt;Proxy examples included (Cloudflare Worker, Node/Express); lock CORS to your domain in prod.&lt;/li&gt;
&lt;li&gt;Escape model output; require an exact &lt;code&gt;model&lt;/code&gt; id from &lt;code&gt;/v1/models&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>One API key, three terminals: driving many models from the shell with llm, mods and aichat</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:24:18 +0000</pubDate>
      <link>https://dev.to/seven7763/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat-c0b</link>
      <guid>https://dev.to/seven7763/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat-c0b</guid>
      <description>&lt;p&gt;The fastest way to use an LLM is often not a chat window — it's a pipe. &lt;code&gt;git diff | ... "write a commit message"&lt;/code&gt;, &lt;code&gt;... "explain this error" &amp;lt; stderr.log&lt;/code&gt;, &lt;code&gt;cat main.py | ... "find the bug"&lt;/code&gt;. Three of the best command-line tools for this — &lt;strong&gt;&lt;code&gt;llm&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;mods&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;aichat&lt;/code&gt;&lt;/strong&gt; — all accept a custom &lt;strong&gt;OpenAI-compatible endpoint&lt;/strong&gt;, which means one key can back all three and give you Claude, GPT, Gemini and more from the shell.&lt;/p&gt;

&lt;p&gt;This is the terminal setup, end to end. It's genuinely tool-agnostic: every config below works against any OpenAI-compatible endpoint. I use daoxe as the example endpoint (disclosure below); swap the base URL for your own.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, an OpenAI-compatible gateway (it also speaks Anthropic Messages natively). Everything here is standard OpenAI-compatible config — point these tools at whatever endpoint you trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One convention across all three: &lt;strong&gt;the key lives in an environment variable&lt;/strong&gt;, &lt;code&gt;DAOXE_API_KEY&lt;/code&gt;, never on the command line (so it doesn't land in your shell history). And model ids are account-scoped — list yours with &lt;code&gt;curl https://daoxe.com/v1/models -H "Authorization: Bearer $DAOXE_API_KEY"&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;llm&lt;/code&gt; (Simon Willison) — the extensible one
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llm&lt;/code&gt; is the most extensible of the three: a pluggy-based plugin system, a local SQLite log of everything, templates, embeddings. There are two ways to add an OpenAI-compatible gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A — zero-YAML via a plugin
&lt;/h3&gt;

&lt;p&gt;There's a small plugin, &lt;strong&gt;&lt;code&gt;llm-daoxe&lt;/code&gt;&lt;/strong&gt;, that registers the gateway's models and adds a command to pull your account's real model list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llm &lt;span class="nb"&gt;install &lt;/span&gt;llm-daoxe           &lt;span class="c"&gt;# (coming soon on PyPI; until then: clone + `pip install -e .`)&lt;/span&gt;
llm keys &lt;span class="nb"&gt;set &lt;/span&gt;daoxe              &lt;span class="c"&gt;# store the key (or export DAOXE_API_KEY=...)&lt;/span&gt;
llm daoxe models &lt;span class="nt"&gt;--refresh&lt;/span&gt;     &lt;span class="c"&gt;# pull your account's real models from GET /v1/models, and cache them&lt;/span&gt;
llm &lt;span class="nt"&gt;-m&lt;/span&gt; daoxe/claude-sonnet-4-6 &lt;span class="s2"&gt;"In one line, what's an OpenAI-compatible gateway?"&lt;/span&gt;
git diff | llm &lt;span class="nt"&gt;-m&lt;/span&gt; daoxe/gpt-5.5 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"Write a concise commit message."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model ids are namespaced as &lt;code&gt;daoxe/&amp;lt;model&amp;gt;&lt;/code&gt; so they don't collide with other plugins' ids. The key is read only from &lt;code&gt;llm&lt;/code&gt;'s key store or &lt;code&gt;DAOXE_API_KEY&lt;/code&gt;, and it's never printed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B — no plugin, native config
&lt;/h3&gt;

&lt;p&gt;Don't want a plugin? &lt;code&gt;llm&lt;/code&gt; natively reads custom OpenAI-compatible models from &lt;code&gt;extra-openai-models.yaml&lt;/code&gt; in its config dir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe/claude-sonnet-4-6&lt;/span&gt;
  &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;
  &lt;span class="na"&gt;api_base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;
  &lt;span class="na"&gt;api_key_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe&lt;/span&gt;          &lt;span class="c1"&gt;# matches `llm keys set daoxe`&lt;/span&gt;
  &lt;span class="na"&gt;can_stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe/gpt-5.5&lt;/span&gt;
  &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-5.5&lt;/span&gt;
  &lt;span class="na"&gt;api_base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;
  &lt;span class="na"&gt;api_key_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin's advantage is just convenience: zero YAML plus &lt;code&gt;llm daoxe models --refresh&lt;/code&gt; to auto-discover your account's models. Both paths use &lt;code&gt;llm&lt;/code&gt;'s built-in OpenAI transport, so streaming and tools behave the same as with official models.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;mods&lt;/code&gt; (charmbracelet) — the pipe-native one
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mods&lt;/code&gt; is built for Unix pipes and looks great doing it. Open its config with &lt;code&gt;mods --settings&lt;/code&gt; and add a provider under &lt;code&gt;apis:&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe&lt;/span&gt;            &lt;span class="c1"&gt;# optional: make it the default so you can skip -a&lt;/span&gt;
&lt;span class="na"&gt;apis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;daoxe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;
    &lt;span class="na"&gt;api-key-env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DAOXE_API_KEY&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                  &lt;span class="c1"&gt;# use real ids from GET /v1/models; max-input-chars is a client-side value&lt;/span&gt;
      &lt;span class="na"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daoxe-sonnet"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;max-input-chars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600000&lt;/span&gt;
      &lt;span class="na"&gt;gpt-5.5&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daoxe-gpt"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;max-input-chars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-daoxe-key
mods &lt;span class="nt"&gt;-a&lt;/span&gt; daoxe &lt;span class="nt"&gt;-m&lt;/span&gt; claude-sonnet-4-6 &lt;span class="s2"&gt;"explain this diff"&lt;/span&gt; &amp;lt; patch.diff
mods &lt;span class="nt"&gt;--model&lt;/span&gt; daoxe-sonnet &lt;span class="s2"&gt;"summarize"&lt;/span&gt; &amp;lt; README.md      &lt;span class="c"&gt;# via alias&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; &lt;code&gt;mods&lt;/code&gt; requires &lt;code&gt;api-key-env&lt;/code&gt; to point at an environment variable that &lt;strong&gt;already exists&lt;/strong&gt;, or it complains about a missing key. &lt;code&gt;max-input-chars&lt;/code&gt; is a client-side truncation setting — tune it to your model, it's not a claim about the gateway.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;aichat&lt;/code&gt; (sigoden) — the all-in-one one
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;aichat&lt;/code&gt; is a single binary that's a CLI, a REPL, and a local server. It uses an &lt;code&gt;openai-compatible&lt;/code&gt; client type. Edit &lt;code&gt;~/.config/aichat/config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;clients&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai-compatible&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daoxe&lt;/span&gt;
    &lt;span class="na"&gt;api_base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;
    &lt;span class="c1"&gt;# omit api_key and aichat auto-reads DAOXE_API_KEY (client name → {NAME}_API_KEY)&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# real ids from GET /v1/models; tune capabilities per model&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;
        &lt;span class="na"&gt;max_input_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200000&lt;/span&gt;
        &lt;span class="na"&gt;supports_function_calling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-5.5&lt;/span&gt;
        &lt;span class="na"&gt;max_input_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;272000&lt;/span&gt;
        &lt;span class="na"&gt;supports_function_calling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-daoxe-key
aichat &lt;span class="nt"&gt;-m&lt;/span&gt; daoxe:claude-sonnet-4-6 &lt;span class="s2"&gt;"explain this code"&lt;/span&gt; &amp;lt; main.py
aichat &lt;span class="nt"&gt;--list-models&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;daoxe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Nice touch:&lt;/em&gt; because the client is named &lt;code&gt;daoxe&lt;/code&gt;, &lt;code&gt;aichat&lt;/code&gt; automatically looks for &lt;code&gt;DAOXE_API_KEY&lt;/code&gt; — you can omit the &lt;code&gt;api_key&lt;/code&gt; field entirely. Model selection is &lt;code&gt;client:model&lt;/code&gt;, e.g. &lt;code&gt;daoxe:claude-sonnet-4-6&lt;/code&gt;.&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Superpower&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scripting, logging, plugins&lt;/td&gt;
&lt;td&gt;SQLite log of every call; huge plugin ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;quick pipes&lt;/td&gt;
&lt;td&gt;gorgeous TUI output, alias/fallback config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aichat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;interactive + server&lt;/td&gt;
&lt;td&gt;REPL, roles, and a built-in local API server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They're not mutually exclusive — I keep all three configured against the same key. The same mental model applies to other CLIs too: anything that takes an OpenAI-compatible &lt;code&gt;base_url&lt;/code&gt; + key + model id works the same way (point the base URL at the &lt;code&gt;/v1&lt;/code&gt; root, use a real id from &lt;code&gt;/v1/models&lt;/code&gt;, keep the key in an env var). Verify each tool's current config fields before you rely on them — these tools move fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The reason one key matters here
&lt;/h2&gt;

&lt;p&gt;CLI users tend to script things and forget them: a cron job that summarizes logs, a git hook that drafts commit messages, a shell function that explains errors. When each of those needs a different vendor key, it's a mess of secrets and bills. One OpenAI-compatible key across &lt;code&gt;llm&lt;/code&gt;, &lt;code&gt;mods&lt;/code&gt;, and &lt;code&gt;aichat&lt;/code&gt; — and across every script that calls them — means &lt;strong&gt;one secret to rotate, one bill to read, and model choice as a string.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;daoxe is the endpoint I point them at: &lt;code&gt;https://daoxe.com/v1&lt;/code&gt;, one key across many models, plus native Anthropic Messages if a tool wants Claude's protocol. It's &lt;strong&gt;not available in mainland China&lt;/strong&gt;. As always, none of the config above is daoxe-specific — and because it's a standard endpoint, you can benchmark and verify it rather than trust it.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llm&lt;/code&gt;, &lt;code&gt;mods&lt;/code&gt;, and &lt;code&gt;aichat&lt;/code&gt; all take a custom OpenAI-compatible endpoint — one key backs all three.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llm&lt;/code&gt;: a plugin (&lt;code&gt;llm-daoxe&lt;/code&gt;, coming soon) &lt;em&gt;or&lt;/em&gt; &lt;code&gt;extra-openai-models.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mods&lt;/code&gt;: an &lt;code&gt;apis:&lt;/code&gt; block; &lt;code&gt;api-key-env&lt;/code&gt; must point at an existing env var.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aichat&lt;/code&gt;: an &lt;code&gt;openai-compatible&lt;/code&gt; client; name it &lt;code&gt;daoxe&lt;/code&gt; and it auto-reads &lt;code&gt;DAOXE_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep the key in an env var, use exact ids from &lt;code&gt;/v1/models&lt;/code&gt;, and pipe away.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cli</category>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Bring your own key to GitHub Copilot: Chat, CLI, App and SDK (with the honest caveat)</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:13:54 +0000</pubDate>
      <link>https://dev.to/seven7763/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk-with-the-honest-caveat-mod</link>
      <guid>https://dev.to/seven7763/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk-with-the-honest-caveat-mod</guid>
      <description>&lt;p&gt;Most people don't know Copilot can do this yet: as of 2026, GitHub Copilot has GA'd &lt;strong&gt;Bring Your Own Key (BYOK)&lt;/strong&gt;. You can point Copilot Chat (in VS Code), the Copilot CLI, the Copilot app, and the Copilot SDK at &lt;em&gt;any&lt;/em&gt; OpenAI-compatible endpoint, Anthropic, or Azure — including local runtimes and third-party gateways.&lt;/p&gt;

&lt;p&gt;That's a big deal if you already pay for Copilot but want to run its chat and agent features on a model or a gateway of your choice. This guide covers all four surfaces, with the exact steps and one honest caveat that trips people up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, an OpenAI-compatible gateway that also speaks the Anthropic Messages protocol. It happens to line up neatly with Copilot's three wire protocols, so I use it as the example — but everything here works with any compatible endpoint. Substitute your own base URL.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The caveat, first (so nobody's misled)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;BYOK affects Copilot Chat and agent sessions. It does not change inline completions.&lt;/strong&gt; Those gray "Tab to accept" suggestions still run on Copilot's own infrastructure and are unaffected by your base URL. If your goal is to change what powers &lt;em&gt;chat/agent&lt;/em&gt;, BYOK is for you. If you wanted to swap the model behind inline completions, BYOK won't do it. That's the honest scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a gateway fits Copilot's three protocols
&lt;/h2&gt;

&lt;p&gt;Copilot's BYOK supports three provider "types", and a good multi-protocol gateway hits all three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openai&lt;/code&gt; → the gateway's &lt;code&gt;/v1/chat/completions&lt;/code&gt; (default).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openai&lt;/code&gt; with &lt;code&gt;wireApi: "responses"&lt;/code&gt; → the gateway's &lt;code&gt;/v1/responses&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anthropic&lt;/code&gt; → the gateway's &lt;code&gt;/v1/messages&lt;/code&gt; (Claude's native protocol).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So one key can serve GPT-shaped, Responses-shaped, and Claude-native requests, depending on which model you pick. Now the four ways to wire it.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Copilot CLI (easiest — just env vars)
&lt;/h2&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;COPILOT_PROVIDER_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://daoxe.com/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;COPILOT_PROVIDER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;     &lt;span class="c"&gt;# your gateway key, from an env var&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;COPILOT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt-5.5                        &lt;span class="c"&gt;# any exact id from GET /v1/models&lt;/span&gt;
&lt;span class="c"&gt;# optional: COPILOT_PROVIDER_TYPE=openai (default) | anthropic | azure&lt;/span&gt;
copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude's native protocol, set &lt;code&gt;COPILOT_PROVIDER_TYPE=anthropic&lt;/code&gt; and use the gateway's Anthropic entry point. &lt;strong&gt;Confirm the exact path with &lt;code&gt;curl&lt;/code&gt; first&lt;/strong&gt; — many gateways take the host root (&lt;code&gt;https://daoxe.com&lt;/code&gt;) and let the client append &lt;code&gt;/v1/messages&lt;/code&gt;; some want &lt;code&gt;/v1&lt;/code&gt;. Don't assume; check your account.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Copilot Chat (VS Code)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Command Palette → &lt;strong&gt;&lt;code&gt;Chat: Manage Language Models&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;OpenAI Compatible / Custom Endpoint&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;https://daoxe.com/v1&lt;/code&gt; (it must serve &lt;code&gt;/chat/completions&lt;/code&gt;). VS Code probes &lt;code&gt;GET /models&lt;/code&gt; to fill the model dropdown — if your gateway supports it, models auto-populate; otherwise type the Model ID by hand.&lt;/li&gt;
&lt;li&gt;Paste your &lt;strong&gt;API Key&lt;/strong&gt;, pick a &lt;strong&gt;Model ID&lt;/strong&gt; (e.g. &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;) → Add Model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fimg%2Fcopilot-byok-vscode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fimg%2Fcopilot-byok-vscode.png" alt="Manage Language Models in VS Code" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The model now shows up in the Copilot Chat model picker alongside the built-ins.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Copilot App (desktop)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Settings → Model Providers → Add provider.&lt;/strong&gt; Fill in the endpoint &lt;code&gt;https://daoxe.com/v1&lt;/code&gt; and your API key (or choose the &lt;code&gt;anthropic&lt;/code&gt; type for Claude-native). Your models then appear in the model picker next to Copilot's hosted ones; the key is stored in your OS keychain.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Copilot SDK (for integrators)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// or "anthropic"&lt;/span&gt;
  &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// full path; for anthropic, use your account's path&lt;/span&gt;
  &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DAOXE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;wireApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// use "responses" when you need Responses-API behavior&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Getting the model ids right
&lt;/h2&gt;

&lt;p&gt;Copilot only calls what you tell it to, so use the &lt;strong&gt;exact&lt;/strong&gt; ids your endpoint exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://daoxe.com/v1/models &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guessed names fail silently or fall back. A gateway with an &lt;strong&gt;account-scoped&lt;/strong&gt; &lt;code&gt;/v1/models&lt;/code&gt; is ideal here, because the list you see is exactly the list you can call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model dropdown empty in VS Code?&lt;/strong&gt; Your endpoint may not expose &lt;code&gt;GET /models&lt;/code&gt;, or the key lacks access. Type the id manually and confirm the key with the &lt;code&gt;curl&lt;/code&gt; above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;401 / auth errors?&lt;/strong&gt; Check whether the provider expects &lt;code&gt;Authorization: Bearer&lt;/code&gt; (OpenAI type) vs &lt;code&gt;x-api-key&lt;/code&gt; (some Anthropic setups). Match the provider type to the protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic type 404s?&lt;/strong&gt; It's almost always the base URL shape — root vs &lt;code&gt;/v1&lt;/code&gt;. Verify against your gateway's docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat works but inline completions "didn't change"?&lt;/strong&gt; Expected — see the caveat. Inline stays on Copilot's models.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What to look for in a BYOK target
&lt;/h2&gt;

&lt;p&gt;Since you're choosing the backend now, pick one that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-protocol.&lt;/strong&gt; OpenAI Chat Completions &lt;em&gt;and&lt;/em&gt; Responses &lt;em&gt;and&lt;/em&gt; Anthropic Messages, so all of Copilot's provider types work and Claude runs on its native protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key, many models,&lt;/strong&gt; with an account-scoped &lt;code&gt;/v1/models&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiable.&lt;/strong&gt; Fine with you benchmarking it and checking it isn't silently downgrading you (there are open-source tools for exactly this).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reachable where official billing isn't.&lt;/strong&gt; If your card keeps getting declined at an official checkout, a gateway that accepts other payment paths is the difference between shipping and not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;daoxe checks those boxes — OpenAI-compatible base URL (&lt;code&gt;https://daoxe.com/v1&lt;/code&gt;), native Anthropic Messages, one key across many models — and it's &lt;strong&gt;not available in mainland China&lt;/strong&gt;. But BYOK is the point: you're not locked in, so point Copilot at whatever endpoint you trust and can verify.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Copilot BYOK works in &lt;strong&gt;Chat, CLI, App, and SDK&lt;/strong&gt; — set a base URL + key.&lt;/li&gt;
&lt;li&gt;A multi-protocol gateway covers all three Copilot provider types (openai / responses / anthropic).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;exact model ids&lt;/strong&gt; from &lt;code&gt;/v1/models&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline completions don't change&lt;/strong&gt; — BYOK is for chat/agent only.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>ai</category>
      <category>vscode</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>One key for CrewAI, AutoGen, LlamaIndex and 8 more: the base_url trick for Python agents</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:11:15 +0000</pubDate>
      <link>https://dev.to/seven7763/one-key-for-crewai-autogen-llamaindex-and-8-more-the-baseurl-trick-for-python-agents-5a3g</link>
      <guid>https://dev.to/seven7763/one-key-for-crewai-autogen-llamaindex-and-8-more-the-baseurl-trick-for-python-agents-5a3g</guid>
      <description>&lt;p&gt;Here's a fact that quietly makes multi-model agent development a lot less painful: &lt;strong&gt;in 2026, virtually every Python agent framework natively supports pointing its underlying LLM at a custom OpenAI-compatible &lt;code&gt;base_url&lt;/code&gt;.&lt;/strong&gt; No new package, no fork, no framework change. You set one URL and one key, and the framework talks to whatever backend you choose.&lt;/p&gt;

&lt;p&gt;That means a single &lt;strong&gt;OpenAI-compatible gateway&lt;/strong&gt; — one key that fronts Claude, GPT, Gemini, DeepSeek, Kimi and more, with fallback and one bill — is a natural backend for &lt;em&gt;all&lt;/em&gt; of these frameworks at once. The orchestration stays with the framework; the models come from one endpoint.&lt;/p&gt;

&lt;p&gt;This post is the map: the exact one-liner for each framework, plus the &lt;strong&gt;one honest gotcha&lt;/strong&gt; each has, because those gotchas are what actually cost you an afternoon.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, one such gateway. But nothing below is daoxe-specific — every snippet works against &lt;em&gt;any&lt;/em&gt; OpenAI-compatible endpoint. Swap the base URL for your own; the frameworks don't care who's behind it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why this works at all
&lt;/h2&gt;

&lt;p&gt;Almost every framework builds on the OpenAI Chat Completions shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST {base_url}/chat/completions
Authorization: Bearer &amp;lt;key&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only two things that route this call to OpenAI are the &lt;strong&gt;base URL&lt;/strong&gt; and the &lt;strong&gt;key&lt;/strong&gt;. Change the base URL to a compatible gateway and the same orchestration code now runs against a different backend. Frameworks expose this under slightly different names — &lt;code&gt;base_url&lt;/code&gt;, &lt;code&gt;api_base&lt;/code&gt;, &lt;code&gt;api_base_url&lt;/code&gt; — but it's the same idea.&lt;/p&gt;

&lt;p&gt;Two universal rules before the snippets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model ids are account-scoped.&lt;/strong&gt; Don't copy an id from a blog post. List yours with &lt;code&gt;curl {base_url}/models -H "Authorization: Bearer $KEY"&lt;/code&gt; and use an exact id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys go in env vars,&lt;/strong&gt; never hardcoded. Every snippet below reads from the environment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The 11 frameworks, grouped by mechanism
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A) Frameworks with a direct &lt;code&gt;base_url&lt;/code&gt; parameter
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LangChain (+ LangGraph)&lt;/strong&gt; — the biggest ecosystem, so start here:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# key via env
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&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;span class="c1"&gt;# LangGraph: feed that llm straight into a prebuilt agent
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.prebuilt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_react_agent&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_react_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; &lt;code&gt;ChatOpenAI&lt;/code&gt; targets the official OpenAI spec; non-standard fields some proxies add aren't preserved. Fine for standard responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AutoGen (Microsoft AgentChat):&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AssistantAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_core.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelInfo&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_ext.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatCompletionClient&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;OpenAIChatCompletionClient&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ModelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_calling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structured_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_client&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; a non-OpenAI model id needs &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;base_url&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;model_info&lt;/code&gt; (capability flags). Omit &lt;code&gt;model_info&lt;/code&gt; and it errors. Set the flags to match the model behind your id.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;smolagents (Hugging Face):&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;smolagents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CodeAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenAIModel&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CodeAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; pre-1.9 the class was &lt;code&gt;OpenAIServerModel&lt;/code&gt; (same params). Uses &lt;code&gt;api_base&lt;/code&gt;, not &lt;code&gt;base_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Haystack (deepset):&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.components.generators.chat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatGenerator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Secret&lt;/span&gt;

&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIChatGenerator&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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;Secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_env_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAOXE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replies&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; the param is &lt;code&gt;api_base_url&lt;/code&gt; (note the extra word), and the key goes through &lt;code&gt;Secret.from_env_var(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agno (formerly phidata):&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.models.openai.like&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAILike&lt;/span&gt;
&lt;span class="nc"&gt;Agent&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="nc"&gt;OpenAILike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;print_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; use &lt;code&gt;OpenAILike&lt;/code&gt; (from &lt;code&gt;agno.models.openai.like&lt;/code&gt;), not the plain &lt;code&gt;OpenAIChat&lt;/code&gt;, for a third-party endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  B) Frameworks with an explicit provider object
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PydanticAI&lt;/strong&gt; — type-safe agents:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai.providers.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIProvider&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIChatModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIProvider&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; build a generic &lt;code&gt;OpenAIProvider&lt;/code&gt; and pass it to &lt;code&gt;OpenAIChatModel&lt;/code&gt; — this is the same pattern the docs use for any OpenAI-compatible provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LlamaIndex:&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.llms.openai_like&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAILike&lt;/span&gt;  &lt;span class="c1"&gt;# pip install llama-index-llms-openai-like
&lt;/span&gt;
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAILike&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;is_chat_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_function_calling_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; set &lt;strong&gt;&lt;code&gt;is_chat_model=True&lt;/code&gt;&lt;/strong&gt; or it calls the &lt;code&gt;/completions&lt;/code&gt; endpoint instead of &lt;code&gt;/chat/completions&lt;/code&gt;. Also set a real &lt;code&gt;context_window&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  C) Frameworks that route through LiteLLM (need the &lt;code&gt;openai/&lt;/code&gt; prefix)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CrewAI:&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLM&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Greeter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Greet briefly.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backstory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Concise.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A short greeting.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; keep the &lt;strong&gt;&lt;code&gt;openai/&lt;/code&gt; prefix&lt;/strong&gt; on the model id. Without it, CrewAI (via LiteLLM) may match a &lt;em&gt;native&lt;/em&gt; provider client for a familiar model name and ignore &lt;code&gt;base_url&lt;/code&gt; — you'll get a misleading "API key not valid". Use the API root (&lt;code&gt;.../v1&lt;/code&gt;), not &lt;code&gt;.../chat/completions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DSPy:&lt;/strong&gt;&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dspy&lt;/span&gt;
&lt;span class="n"&gt;lm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dspy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dspy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dspy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question -&amp;gt; answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; same &lt;code&gt;openai/&lt;/code&gt; prefix, and keep &lt;code&gt;/v1&lt;/code&gt; in &lt;code&gt;api_base&lt;/code&gt; with &lt;strong&gt;no trailing slash&lt;/strong&gt; — a trailing slash can double the path and 404.&lt;/p&gt;

&lt;h3&gt;
  
  
  D) Wrap the OpenAI client directly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Instructor&lt;/strong&gt; — structured output validated into Pydantic models:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;instructor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_openai&lt;/span&gt;&lt;span class="p"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://daoxe.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Out&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Gotcha:&lt;/em&gt; the default (tool-calling) mode works for tool-capable models; if a model lacks tool calling, pass &lt;code&gt;mode=instructor.Mode.JSON&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gotchas, in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Param name&lt;/th&gt;
&lt;th&gt;The one gotcha&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LangChain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;non-standard proxy fields not preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AutoGen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;non-OpenAI id needs &lt;code&gt;model_info&lt;/code&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smolagents&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;class renamed from &lt;code&gt;OpenAIServerModel&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haystack&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;key via &lt;code&gt;Secret.from_env_var&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agno&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;use &lt;code&gt;OpenAILike&lt;/code&gt;, not &lt;code&gt;OpenAIChat&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PydanticAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OpenAIProvider(base_url=)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;generic provider, not a vendor class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LlamaIndex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;is_chat_model=True&lt;/code&gt; or it hits &lt;code&gt;/completions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrewAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model must be &lt;code&gt;openai/&amp;lt;id&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSPy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;openai/&lt;/code&gt; prefix + &lt;strong&gt;no trailing slash&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instructor&lt;/td&gt;
&lt;td&gt;wrap &lt;code&gt;OpenAI(base_url=)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;use &lt;code&gt;Mode.JSON&lt;/code&gt; if no tool calling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why one endpoint beats juggling keys
&lt;/h2&gt;

&lt;p&gt;Multi-agent orchestration is where model sprawl hurts most: a planner on a strong model, workers on a cheap one, a critic on a third — that's three vendors, three keys, three bills, three failure modes. Pointing all of them at one OpenAI-compatible endpoint means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One key, one bill&lt;/strong&gt; across every framework and every agent role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model choice is a string,&lt;/strong&gt; not a new integration. Swap &lt;code&gt;MODEL_ID&lt;/code&gt; and the same agent runs on Claude, GPT, or Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're not locked to a framework.&lt;/strong&gt; The same key runs the same task across all 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because it's a standard endpoint, you can &lt;strong&gt;verify it&lt;/strong&gt; rather than trust it — run a probe against it and against the official API and compare (I wrote a separate piece on catching silent model swaps).&lt;/p&gt;

&lt;p&gt;daoxe is the endpoint I use for this: OpenAI-compatible at &lt;code&gt;https://daoxe.com/v1&lt;/code&gt;, plus native Anthropic Messages for Claude-native tools, one key across many models. It's &lt;strong&gt;not available in mainland China&lt;/strong&gt;. But the whole point of this article is that none of the code above is daoxe-specific — point it at any compatible endpoint you trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every framework here takes a custom OpenAI-compatible endpoint natively — it's a one-liner.&lt;/li&gt;
&lt;li&gt;Watch the param name (&lt;code&gt;base_url&lt;/code&gt; vs &lt;code&gt;api_base&lt;/code&gt; vs &lt;code&gt;api_base_url&lt;/code&gt;) and the per-framework gotcha.&lt;/li&gt;
&lt;li&gt;CrewAI/DSPy need the &lt;code&gt;openai/&lt;/code&gt; prefix; AutoGen needs &lt;code&gt;model_info&lt;/code&gt;; LlamaIndex needs &lt;code&gt;is_chat_model=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;One key, one bill, model choice as a string — and you can verify the backend instead of trusting it.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Your cheap LLM relay might be swapping the model. Here's how to catch it.</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:07:09 +0000</pubDate>
      <link>https://dev.to/seven7763/your-cheap-llm-relay-might-be-swapping-the-model-heres-how-to-catch-it-2a8c</link>
      <guid>https://dev.to/seven7763/your-cheap-llm-relay-might-be-swapping-the-model-heres-how-to-catch-it-2a8c</guid>
      <description>&lt;p&gt;If you buy model access through a cheap "GPT / Claude / DeepSeek" API relay, you have a trust problem nobody puts on the pricing page: &lt;strong&gt;some relays quietly swap in a smaller, quantized, or context-truncated model&lt;/strong&gt; and bill you for the flagship. The nasty part is the timing — it often works fine on day one and degrades weeks later, right after you've stopped checking.&lt;/p&gt;

&lt;p&gt;This article is the verification playbook I wish existed when I started evaluating gateways. It works against &lt;em&gt;any&lt;/em&gt; OpenAI- or Anthropic-compatible endpoint, and at the end there's a small open-source tool that automates the whole thing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure up front:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, one such gateway. I'm writing the verification method so you can test &lt;em&gt;any&lt;/em&gt; provider — including us. A verification tool is only useful if it will just as happily flag the people who wrote it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The one check that doesn't work
&lt;/h2&gt;

&lt;p&gt;The obvious move is to ask the endpoint: &lt;em&gt;"What model are you?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Don't bother. That answer comes from a system prompt or a fine-tune and is &lt;strong&gt;trivially spoofable in both directions&lt;/strong&gt;. A relay serving you a 7B model can print "I am GPT-4-class" all day; a genuine flagship can be told to call itself something else. Self-report is theatre.&lt;/p&gt;

&lt;p&gt;You have to test &lt;strong&gt;capability and behavior&lt;/strong&gt;, not what the model claims about itself. And you have to &lt;em&gt;measure&lt;/em&gt; — one request tells you nothing, because temperature, sampling, and routing all add noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four cheats you're actually guarding against
&lt;/h2&gt;

&lt;p&gt;When margins are thin, a dishonest relay can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Substitute the model.&lt;/strong&gt; You ask for a flagship; they serve a cheaper one and bill you for the flagship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serve heavy quantization.&lt;/strong&gt; Same model &lt;em&gt;name&lt;/em&gt;, aggressively quantized weights, visibly worse reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truncate your context.&lt;/strong&gt; They silently cap your context window to save tokens, so long-context tasks quietly fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fall back without telling you.&lt;/strong&gt; When the upstream is rate-limited, they route you to whatever's available and never surface it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these leaves a &lt;em&gt;behavioral&lt;/em&gt; fingerprint. Here's how to read them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five signals that actually catch it
&lt;/h2&gt;

&lt;p&gt;None of these individually proves anything. Together, they either reassure you or give you a reason to look closer.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tokenizer fingerprint
&lt;/h3&gt;

&lt;p&gt;Different model families use different tokenizers, and the tokenizer leaks through the &lt;code&gt;usage.prompt_tokens&lt;/code&gt; the server reports. Send a battery of crafted strings — digit runs, whitespace, CJK, emoji, code, URLs, mixed unicode — and record how many tokens each costs. To cancel the fixed per-request chat-template overhead, measure a &lt;strong&gt;delta&lt;/strong&gt;: &lt;code&gt;tokens(anchor + probe) − tokens(anchor)&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best case (reference-free):&lt;/strong&gt; diff that token vector against an endpoint you trust serving the &lt;em&gt;same&lt;/em&gt; model id. A different vector means a different tokenizer, which means a different model family — a &lt;strong&gt;high-confidence&lt;/strong&gt; signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single endpoint:&lt;/strong&gt; classify the vector against known tokenizer tables (e.g. OpenAI's &lt;code&gt;cl100k_base&lt;/code&gt; / &lt;code&gt;o200k_base&lt;/code&gt;). If the claimed family has no public reference (Claude, Gemini…), the honest answer is &lt;em&gt;inconclusive — use a diff&lt;/em&gt;, not a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Capability floor
&lt;/h3&gt;

&lt;p&gt;A handful of tasks with &lt;strong&gt;objectively checkable answers you already know&lt;/strong&gt;: multi-step arithmetic, string reversal, character counting, plus a strict-JSON format test. These are trivial for any full-tier model; a downgraded or heavily quantized substitute is more likely to trip. Weight &lt;em&gt;failure&lt;/em&gt; more than success — passing an easy task proves little, but a "flagship" that fails a floor task is worth surfacing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Long-context recall (the needle)
&lt;/h3&gt;

&lt;p&gt;Generate deterministic filler text, hide a unique passphrase in the middle, and ask the endpoint to read it back — across several context lengths. Because &lt;em&gt;you&lt;/em&gt; placed the needle, you always know the right answer (no external key needed). Recall that works at short context but breaks at longer context is the classic signature of &lt;strong&gt;silent context truncation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Stability &amp;amp; performance
&lt;/h3&gt;

&lt;p&gt;Repeat one fixed prompt N times at &lt;code&gt;temperature=0&lt;/code&gt; and look at output determinism, the stability of the server-reported &lt;code&gt;model&lt;/code&gt; field and &lt;code&gt;system_fingerprint&lt;/code&gt;, plus &lt;strong&gt;p50/p95 latency and error rate&lt;/strong&gt;. A server that reports different &lt;code&gt;model&lt;/code&gt; values or wildly different outputs for identical calls may be routing you across different backends.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Self-report (low weight, on purpose)
&lt;/h3&gt;

&lt;p&gt;Ask the model to name itself and compare to the claim — but cap its confidence at &lt;strong&gt;low&lt;/strong&gt;, because it's spoofable both ways. Include it only because its &lt;em&gt;absence&lt;/em&gt; would be conspicuous; never let it drive a verdict.&lt;/p&gt;

&lt;h3&gt;
  
  
  The design rules that make the signals trustworthy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;temperature=0&lt;/code&gt;, fixed &lt;code&gt;max_tokens&lt;/code&gt;, fixed system prompt&lt;/strong&gt; — remove randomness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure, don't eyeball&lt;/strong&gt; — repeat and record percentiles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff against a reference you trust&lt;/strong&gt; whenever you can. This is the single strongest move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run on a schedule.&lt;/strong&gt; Silent degradation is a time-series problem, not a launch-day one.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Do it by hand (the 60-second version)
&lt;/h2&gt;

&lt;p&gt;You can start without any tooling. Pin the parameters and fire the same probe at two endpoints:&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="c"&gt;# Your relay&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n%{time_total}s\n'&lt;/span&gt; https://your-relay.example/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$RELAY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"MODEL_ID","temperature":0,"max_tokens":64,
       "messages":[{"role":"user","content":"Reverse this exactly: abcdefghij. Then count the letters."}]}'&lt;/span&gt;

&lt;span class="c"&gt;# The official API, same model, as your reference&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n%{time_total}s\n'&lt;/span&gt; https://api.openai.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OFFICIAL_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"MODEL_ID","temperature":0,"max_tokens":64,
       "messages":[{"role":"user","content":"Reverse this exactly: abcdefghij. Then count the letters."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the &lt;code&gt;usage.prompt_tokens&lt;/code&gt;, the answer quality, and the latency. Do it a few times. Divergence is your first thread to pull.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automate it: an open-source probe
&lt;/h2&gt;

&lt;p&gt;Running that battery by hand every week is tedious, so there's a small open-source CLI, &lt;strong&gt;&lt;code&gt;llm-honesty-probe&lt;/code&gt;&lt;/strong&gt;, that does it for you. Design goals worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies.&lt;/strong&gt; Pure Python standard library — nothing to &lt;code&gt;pip install&lt;/code&gt;, nothing in a lockfile to audit. Clone it and read it before you run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your key is never printed, logged, or stored.&lt;/strong&gt; It's read &lt;em&gt;only&lt;/em&gt; from an environment variable (there's deliberately no &lt;code&gt;--key&lt;/code&gt; flag, so it can't land in shell history), and every line of output passes through a redaction layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-neutral.&lt;/strong&gt; No allow-list, no "preferred" endpoint, no telemetry. It treats the official API and any relay identically.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/seven7763/llm-honesty-probe
&lt;span class="nb"&gt;cd &lt;/span&gt;llm-honesty-probe
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="nt"&gt;--self-test&lt;/span&gt;        &lt;span class="c"&gt;# runs against a built-in mock; no key needed&lt;/span&gt;

&lt;span class="c"&gt;# Single endpoint: does it behave like the model it claims?&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-...your key..."&lt;/span&gt;        &lt;span class="c"&gt;# never appears in argv or history&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://any-provider.example/v1 &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; MODEL_ID

&lt;span class="c"&gt;# Differential (strongest): diff the relay against the official API&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY_OFFICIAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-...official..."&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://cheap-relay.example/v1 &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; MODEL_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compare-base-url&lt;/span&gt; https://api.openai.com/v1 &lt;span class="nt"&gt;--compare-model&lt;/span&gt; MODEL_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compare-api-key-env&lt;/span&gt; OPENAI_API_KEY_OFFICIAL

&lt;span class="c"&gt;# Claude / Anthropic-shaped endpoints:&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://any-provider.example &lt;span class="nt"&gt;--protocol&lt;/span&gt; anthropic &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; claude-sonnet-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output marks each signal &lt;code&gt;[OK]&lt;/code&gt; (consistent), &lt;code&gt;[!!]&lt;/code&gt; (suspicious), or &lt;code&gt;[--]&lt;/code&gt; (inconclusive), each with a confidence level. Use &lt;code&gt;--json --out report.json&lt;/code&gt; to diff results in cron/CI over time — which is the whole point, since degradation shows up as drift, not on launch day.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the limitations, seriously
&lt;/h2&gt;

&lt;p&gt;Overstating an honesty tool defeats its purpose. So, plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;These are signals, not proof.&lt;/strong&gt; Nothing here cryptographically establishes which weights a provider runs. A provider that mirrors the tokenizer &lt;em&gt;and&lt;/em&gt; matches capability &lt;em&gt;and&lt;/em&gt; holds context &lt;em&gt;and&lt;/em&gt; stays stable is, for practical purposes, giving you the model — but you can't prove intent from the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The probe set is small and known.&lt;/strong&gt; A provider that special-cases these exact probes could game a single-endpoint run. That's why the strongest mode is a &lt;strong&gt;differential diff you control&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Suspicious" has innocent explanations.&lt;/strong&gt; A smaller model can be the &lt;em&gt;correct&lt;/em&gt; answer if that's what you asked for; a short context limit can be the model's real limit; different infra changes latency. Use &lt;code&gt;--compare&lt;/code&gt; to disambiguate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;temperature=0&lt;/code&gt; isn't perfectly deterministic&lt;/strong&gt; on real hardware, so the determinism signal is intentionally low confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat a clean run as &lt;em&gt;reassuring&lt;/em&gt;, not a guarantee. Treat a flag as a &lt;em&gt;reason to look closer&lt;/em&gt;, not a conviction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "honest" looks like from a provider
&lt;/h2&gt;

&lt;p&gt;The reason I can publish this without flinching is that verifiability is the whole pitch. A gateway worth using should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmarkable.&lt;/strong&gt; Fine with you running this against it. (If a provider gets cagey when you ask to benchmark, that's your answer.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native on the protocols you use.&lt;/strong&gt; OpenAI Chat Completions &lt;em&gt;and&lt;/em&gt; Anthropic Messages, so Claude Code works properly — not just the OpenAI shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key, many models,&lt;/strong&gt; with an account-scoped &lt;code&gt;/v1/models&lt;/code&gt; so what you see is what you can call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent on cost and status&lt;/strong&gt; — itemized usage, nothing about billing hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;daoxe is built around exactly that: an OpenAI-compatible base URL (&lt;code&gt;https://daoxe.com/v1&lt;/code&gt;), native Anthropic Messages for Claude Code, one key across many models, and a design meant to be &lt;em&gt;verified rather than trusted&lt;/em&gt;. It's &lt;strong&gt;not available in mainland China&lt;/strong&gt;. Please don't take my word for any of it — &lt;a href="https://github.com/seven7763/llm-honesty-probe" rel="noopener noreferrer"&gt;point the probe at it&lt;/a&gt; and at whatever you use today, and compare. If daoxe ever fails these checks, that's a bug report we want.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Complementary tool: &lt;a href="https://github.com/seven7763/llm-gateway-benchmark" rel="noopener noreferrer"&gt;&lt;code&gt;llm-gateway-benchmark&lt;/code&gt;&lt;/a&gt; answers "is this endpoint fast and cheap?" (success rate, p50/p95, $/1M tokens). This one answers "is it actually the model it claims?" They're complementary, not competing.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't&lt;/strong&gt; ask the model what it is. &lt;strong&gt;Do&lt;/strong&gt; test capability and behavior.&lt;/li&gt;
&lt;li&gt;Five signals: tokenizer fingerprint, capability floor, long-context needle, stability, and (low-weight) self-report.&lt;/li&gt;
&lt;li&gt;Pin &lt;code&gt;temperature=0&lt;/code&gt;, measure percentiles, &lt;strong&gt;diff against a reference&lt;/strong&gt;, and &lt;strong&gt;re-run weekly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Signals, not proof — but signals are exactly what you're missing right now.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your cheap LLM relay might be swapping the model. Here's how to catch it.</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Sun, 19 Jul 2026 21:54:07 +0000</pubDate>
      <link>https://dev.to/seven7763/your-cheap-llm-relay-might-be-swapping-the-model-heres-how-to-catch-it-3lih</link>
      <guid>https://dev.to/seven7763/your-cheap-llm-relay-might-be-swapping-the-model-heres-how-to-catch-it-3lih</guid>
      <description>&lt;p&gt;If you buy model access through a cheap "GPT / Claude / DeepSeek" API relay, you have a trust problem nobody puts on the pricing page: &lt;strong&gt;some relays quietly swap in a smaller, quantized, or context-truncated model&lt;/strong&gt; and bill you for the flagship. The nasty part is the timing — it often works fine on day one and degrades weeks later, right after you've stopped checking.&lt;/p&gt;

&lt;p&gt;This article is the verification playbook I wish existed when I started evaluating gateways. It works against &lt;em&gt;any&lt;/em&gt; OpenAI- or Anthropic-compatible endpoint, and at the end there's a small open-source tool that automates the whole thing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure up front:&lt;/strong&gt; I work on &lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=en_launch" rel="noopener noreferrer"&gt;daoxe&lt;/a&gt;, one such gateway. I'm writing the verification method so you can test &lt;em&gt;any&lt;/em&gt; provider — including us. A verification tool is only useful if it will just as happily flag the people who wrote it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The one check that doesn't work
&lt;/h2&gt;

&lt;p&gt;The obvious move is to ask the endpoint: &lt;em&gt;"What model are you?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Don't bother. That answer comes from a system prompt or a fine-tune and is &lt;strong&gt;trivially spoofable in both directions&lt;/strong&gt;. A relay serving you a 7B model can print "I am GPT-4-class" all day; a genuine flagship can be told to call itself something else. Self-report is theatre.&lt;/p&gt;

&lt;p&gt;You have to test &lt;strong&gt;capability and behavior&lt;/strong&gt;, not what the model claims about itself. And you have to &lt;em&gt;measure&lt;/em&gt; — one request tells you nothing, because temperature, sampling, and routing all add noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four cheats you're actually guarding against
&lt;/h2&gt;

&lt;p&gt;When margins are thin, a dishonest relay can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Substitute the model.&lt;/strong&gt; You ask for a flagship; they serve a cheaper one and bill you for the flagship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serve heavy quantization.&lt;/strong&gt; Same model &lt;em&gt;name&lt;/em&gt;, aggressively quantized weights, visibly worse reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truncate your context.&lt;/strong&gt; They silently cap your context window to save tokens, so long-context tasks quietly fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fall back without telling you.&lt;/strong&gt; When the upstream is rate-limited, they route you to whatever's available and never surface it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these leaves a &lt;em&gt;behavioral&lt;/em&gt; fingerprint. Here's how to read them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five signals that actually catch it
&lt;/h2&gt;

&lt;p&gt;None of these individually proves anything. Together, they either reassure you or give you a reason to look closer.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tokenizer fingerprint
&lt;/h3&gt;

&lt;p&gt;Different model families use different tokenizers, and the tokenizer leaks through the &lt;code&gt;usage.prompt_tokens&lt;/code&gt; the server reports. Send a battery of crafted strings — digit runs, whitespace, CJK, emoji, code, URLs, mixed unicode — and record how many tokens each costs. To cancel the fixed per-request chat-template overhead, measure a &lt;strong&gt;delta&lt;/strong&gt;: &lt;code&gt;tokens(anchor + probe) − tokens(anchor)&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best case (reference-free):&lt;/strong&gt; diff that token vector against an endpoint you trust serving the &lt;em&gt;same&lt;/em&gt; model id. A different vector means a different tokenizer, which means a different model family — a &lt;strong&gt;high-confidence&lt;/strong&gt; signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single endpoint:&lt;/strong&gt; classify the vector against known tokenizer tables (e.g. OpenAI's &lt;code&gt;cl100k_base&lt;/code&gt; / &lt;code&gt;o200k_base&lt;/code&gt;). If the claimed family has no public reference (Claude, Gemini…), the honest answer is &lt;em&gt;inconclusive — use a diff&lt;/em&gt;, not a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Capability floor
&lt;/h3&gt;

&lt;p&gt;A handful of tasks with &lt;strong&gt;objectively checkable answers you already know&lt;/strong&gt;: multi-step arithmetic, string reversal, character counting, plus a strict-JSON format test. These are trivial for any full-tier model; a downgraded or heavily quantized substitute is more likely to trip. Weight &lt;em&gt;failure&lt;/em&gt; more than success — passing an easy task proves little, but a "flagship" that fails a floor task is worth surfacing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Long-context recall (the needle)
&lt;/h3&gt;

&lt;p&gt;Generate deterministic filler text, hide a unique passphrase in the middle, and ask the endpoint to read it back — across several context lengths. Because &lt;em&gt;you&lt;/em&gt; placed the needle, you always know the right answer (no external key needed). Recall that works at short context but breaks at longer context is the classic signature of &lt;strong&gt;silent context truncation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Stability &amp;amp; performance
&lt;/h3&gt;

&lt;p&gt;Repeat one fixed prompt N times at &lt;code&gt;temperature=0&lt;/code&gt; and look at output determinism, the stability of the server-reported &lt;code&gt;model&lt;/code&gt; field and &lt;code&gt;system_fingerprint&lt;/code&gt;, plus &lt;strong&gt;p50/p95 latency and error rate&lt;/strong&gt;. A server that reports different &lt;code&gt;model&lt;/code&gt; values or wildly different outputs for identical calls may be routing you across different backends.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Self-report (low weight, on purpose)
&lt;/h3&gt;

&lt;p&gt;Ask the model to name itself and compare to the claim — but cap its confidence at &lt;strong&gt;low&lt;/strong&gt;, because it's spoofable both ways. Include it only because its &lt;em&gt;absence&lt;/em&gt; would be conspicuous; never let it drive a verdict.&lt;/p&gt;

&lt;h3&gt;
  
  
  The design rules that make the signals trustworthy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;temperature=0&lt;/code&gt;, fixed &lt;code&gt;max_tokens&lt;/code&gt;, fixed system prompt&lt;/strong&gt; — remove randomness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure, don't eyeball&lt;/strong&gt; — repeat and record percentiles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff against a reference you trust&lt;/strong&gt; whenever you can. This is the single strongest move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run on a schedule.&lt;/strong&gt; Silent degradation is a time-series problem, not a launch-day one.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Do it by hand (the 60-second version)
&lt;/h2&gt;

&lt;p&gt;You can start without any tooling. Pin the parameters and fire the same probe at two endpoints:&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="c"&gt;# Your relay&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n%{time_total}s\n'&lt;/span&gt; https://your-relay.example/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$RELAY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"MODEL_ID","temperature":0,"max_tokens":64,
       "messages":[{"role":"user","content":"Reverse this exactly: abcdefghij. Then count the letters."}]}'&lt;/span&gt;

&lt;span class="c"&gt;# The official API, same model, as your reference&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n%{time_total}s\n'&lt;/span&gt; https://api.openai.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OFFICIAL_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"MODEL_ID","temperature":0,"max_tokens":64,
       "messages":[{"role":"user","content":"Reverse this exactly: abcdefghij. Then count the letters."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the &lt;code&gt;usage.prompt_tokens&lt;/code&gt;, the answer quality, and the latency. Do it a few times. Divergence is your first thread to pull.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automate it: an open-source probe
&lt;/h2&gt;

&lt;p&gt;Running that battery by hand every week is tedious, so there's a small open-source CLI, &lt;strong&gt;&lt;code&gt;llm-honesty-probe&lt;/code&gt;&lt;/strong&gt;, that does it for you. Design goals worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies.&lt;/strong&gt; Pure Python standard library — nothing to &lt;code&gt;pip install&lt;/code&gt;, nothing in a lockfile to audit. Clone it and read it before you run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your key is never printed, logged, or stored.&lt;/strong&gt; It's read &lt;em&gt;only&lt;/em&gt; from an environment variable (there's deliberately no &lt;code&gt;--key&lt;/code&gt; flag, so it can't land in shell history), and every line of output passes through a redaction layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-neutral.&lt;/strong&gt; No allow-list, no "preferred" endpoint, no telemetry. It treats the official API and any relay identically.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/seven7763/llm-honesty-probe
&lt;span class="nb"&gt;cd &lt;/span&gt;llm-honesty-probe
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="nt"&gt;--self-test&lt;/span&gt;        &lt;span class="c"&gt;# runs against a built-in mock; no key needed&lt;/span&gt;

&lt;span class="c"&gt;# Single endpoint: does it behave like the model it claims?&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-...your key..."&lt;/span&gt;        &lt;span class="c"&gt;# never appears in argv or history&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://any-provider.example/v1 &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; MODEL_ID

&lt;span class="c"&gt;# Differential (strongest): diff the relay against the official API&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY_OFFICIAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-...official..."&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://cheap-relay.example/v1 &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; MODEL_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compare-base-url&lt;/span&gt; https://api.openai.com/v1 &lt;span class="nt"&gt;--compare-model&lt;/span&gt; MODEL_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compare-api-key-env&lt;/span&gt; OPENAI_API_KEY_OFFICIAL

&lt;span class="c"&gt;# Claude / Anthropic-shaped endpoints:&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; llm_honesty_probe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://any-provider.example &lt;span class="nt"&gt;--protocol&lt;/span&gt; anthropic &lt;span class="nt"&gt;--claimed-model&lt;/span&gt; claude-sonnet-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output marks each signal &lt;code&gt;[OK]&lt;/code&gt; (consistent), &lt;code&gt;[!!]&lt;/code&gt; (suspicious), or &lt;code&gt;[--]&lt;/code&gt; (inconclusive), each with a confidence level. Use &lt;code&gt;--json --out report.json&lt;/code&gt; to diff results in cron/CI over time — which is the whole point, since degradation shows up as drift, not on launch day.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the limitations, seriously
&lt;/h2&gt;

&lt;p&gt;Overstating an honesty tool defeats its purpose. So, plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;These are signals, not proof.&lt;/strong&gt; Nothing here cryptographically establishes which weights a provider runs. A provider that mirrors the tokenizer &lt;em&gt;and&lt;/em&gt; matches capability &lt;em&gt;and&lt;/em&gt; holds context &lt;em&gt;and&lt;/em&gt; stays stable is, for practical purposes, giving you the model — but you can't prove intent from the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The probe set is small and known.&lt;/strong&gt; A provider that special-cases these exact probes could game a single-endpoint run. That's why the strongest mode is a &lt;strong&gt;differential diff you control&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Suspicious" has innocent explanations.&lt;/strong&gt; A smaller model can be the &lt;em&gt;correct&lt;/em&gt; answer if that's what you asked for; a short context limit can be the model's real limit; different infra changes latency. Use &lt;code&gt;--compare&lt;/code&gt; to disambiguate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;temperature=0&lt;/code&gt; isn't perfectly deterministic&lt;/strong&gt; on real hardware, so the determinism signal is intentionally low confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat a clean run as &lt;em&gt;reassuring&lt;/em&gt;, not a guarantee. Treat a flag as a &lt;em&gt;reason to look closer&lt;/em&gt;, not a conviction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "honest" looks like from a provider
&lt;/h2&gt;

&lt;p&gt;The reason I can publish this without flinching is that verifiability is the whole pitch. A gateway worth using should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmarkable.&lt;/strong&gt; Fine with you running this against it. (If a provider gets cagey when you ask to benchmark, that's your answer.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native on the protocols you use.&lt;/strong&gt; OpenAI Chat Completions &lt;em&gt;and&lt;/em&gt; Anthropic Messages, so Claude Code works properly — not just the OpenAI shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key, many models,&lt;/strong&gt; with an account-scoped &lt;code&gt;/v1/models&lt;/code&gt; so what you see is what you can call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent on cost and status&lt;/strong&gt; — itemized usage, nothing about billing hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;daoxe is built around exactly that: an OpenAI-compatible base URL (&lt;code&gt;https://daoxe.com/v1&lt;/code&gt;), native Anthropic Messages for Claude Code, one key across many models, and a design meant to be &lt;em&gt;verified rather than trusted&lt;/em&gt;. It's &lt;strong&gt;not available in mainland China&lt;/strong&gt;. Please don't take my word for any of it — &lt;a href="https://github.com/seven7763/llm-honesty-probe" rel="noopener noreferrer"&gt;point the probe at it&lt;/a&gt; and at whatever you use today, and compare. If daoxe ever fails these checks, that's a bug report we want.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Complementary tool: &lt;a href="https://github.com/seven7763/llm-gateway-benchmark" rel="noopener noreferrer"&gt;&lt;code&gt;llm-gateway-benchmark&lt;/code&gt;&lt;/a&gt; answers "is this endpoint fast and cheap?" (success rate, p50/p95, $/1M tokens). This one answers "is it actually the model it claims?" They're complementary, not competing.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't&lt;/strong&gt; ask the model what it is. &lt;strong&gt;Do&lt;/strong&gt; test capability and behavior.&lt;/li&gt;
&lt;li&gt;Five signals: tokenizer fingerprint, capability floor, long-context needle, stability, and (low-weight) self-report.&lt;/li&gt;
&lt;li&gt;Pin &lt;code&gt;temperature=0&lt;/code&gt;, measure percentiles, &lt;strong&gt;diff against a reference&lt;/strong&gt;, and &lt;strong&gt;re-run weekly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Signals, not proof — but signals are exactly what you're missing right now.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>Account-scoped /models is a feature: stop shipping static model lists</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Thu, 16 Jul 2026 16:16:59 +0000</pubDate>
      <link>https://dev.to/seven7763/account-scoped-models-is-a-feature-stop-shipping-static-model-lists-1epa</link>
      <guid>https://dev.to/seven7763/account-scoped-models-is-a-feature-stop-shipping-static-model-lists-1epa</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure and availability:&lt;/strong&gt; DaoXE is a multi-model multi-protocol API gateway we operate. Not available in mainland China. This article is for developers in regions allowed by the service terms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used to put model IDs in README tables.&lt;/p&gt;

&lt;p&gt;Then a teammate's key failed with &lt;code&gt;model_not_found&lt;/code&gt; while mine worked.&lt;/p&gt;

&lt;p&gt;Same base URL. Same client. Different &lt;strong&gt;account catalog&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is why I treat &lt;code&gt;/v1/models&lt;/code&gt; as live data, not documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Series
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/the-3-step-smoke-test-i-use-for-any-openai-compatible-api-311g"&gt;Smoke test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/one-gateway-three-protocols-chat-completions-responses-and-anthropic-messages-459k"&gt;Multiprotocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/how-i-configure-cline-continue-and-similar-tools-against-a-multi-protocol-gateway-44i1"&gt;Client setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/my-curl-smoke-test-passed-why-does-cline-continue-still-fail-52p4"&gt;curl OK, IDE fails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/claude-protocol-without-the-openai-only-mental-model-34kh"&gt;Claude protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/i-stopped-hardcoding-model-ids-modelsdev-v1models-changed-the-workflow-4na2"&gt;models.dev + /models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/a-10-minute-checklist-before-you-point-a-coding-agent-at-a-custom-gateway-2opk"&gt;Agent pre-flight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/openaiapikey-vs-daoxeapikey-which-env-name-your-client-actually-reads-2063"&gt;Env var names&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/the-base-url-shape-checklist-v1-trailing-slashes-and-double-paths-2idj"&gt;Base URL shape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post:&lt;/strong&gt; account-scoped models&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two catalogs people confuse
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Catalog&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public marketing / models.dev&lt;/td&gt;
&lt;td&gt;website, provider lists&lt;/td&gt;
&lt;td&gt;“what exists in the product world”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account &lt;code&gt;/v1/models&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /v1/models&lt;/code&gt; with &lt;strong&gt;your&lt;/strong&gt; key&lt;/td&gt;
&lt;td&gt;“what this key can call &lt;strong&gt;now&lt;/strong&gt;”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They overlap. They are not identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Blog says model &lt;code&gt;X&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You hardcode &lt;code&gt;X&lt;/code&gt; in Cline / Continue / SDK&lt;/li&gt;
&lt;li&gt;Key B never had &lt;code&gt;X&lt;/code&gt; (plan, region, entitlement, rotation)&lt;/li&gt;
&lt;li&gt;You debug “the gateway is broken”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gateway answered correctly: that account cannot use &lt;code&gt;X&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix workflow
&lt;/h2&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DAOXE_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://daoxe.com/v1"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/models"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy an &lt;strong&gt;exact&lt;/strong&gt; &lt;code&gt;id&lt;/code&gt; from the JSON&lt;/li&gt;
&lt;li&gt;Smoke one Chat Completions call with that id&lt;/li&gt;
&lt;li&gt;Paste &lt;strong&gt;that&lt;/strong&gt; id into the client&lt;/li&gt;
&lt;li&gt;Re-run &lt;code&gt;/models&lt;/code&gt; after plan changes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design implications for apps
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anti-pattern&lt;/th&gt;
&lt;th&gt;Prefer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ship a frozen model enum in client builds&lt;/td&gt;
&lt;td&gt;Refresh models at setup / login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share one screenshot of models with the team&lt;/td&gt;
&lt;td&gt;Each person lists with their key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treat models.dev as entitlement proof&lt;/td&gt;
&lt;td&gt;Treat it as discovery only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache model IDs forever&lt;/td&gt;
&lt;td&gt;Cache with TTL + refresh on 404/not_found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Soft CTA
&lt;/h2&gt;

&lt;p&gt;Product side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_models_scope" rel="noopener noreferrer"&gt;https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_models_scope&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Public notes: &lt;a href="https://github.com/seven7763/DaoXE-AI/blob/main/CLIENT_SETUP.md" rel="noopener noreferrer"&gt;CLIENT_SETUP.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comment with whether your client supports model refresh from &lt;code&gt;/models&lt;/code&gt;. No API keys in comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The base URL shape checklist: /v1, trailing slashes, and double paths</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:42:37 +0000</pubDate>
      <link>https://dev.to/seven7763/the-base-url-shape-checklist-v1-trailing-slashes-and-double-paths-2idj</link>
      <guid>https://dev.to/seven7763/the-base-url-shape-checklist-v1-trailing-slashes-and-double-paths-2idj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure and availability:&lt;/strong&gt; DaoXE is a multi-model multi-protocol API gateway we operate. Public base: &lt;code&gt;https://daoxe.com/v1&lt;/code&gt;. Not available in mainland China. This article is for developers in regions allowed by the service terms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most gateway failures I see are not model quality.&lt;/p&gt;

&lt;p&gt;They are &lt;strong&gt;path join&lt;/strong&gt; bugs.&lt;/p&gt;

&lt;p&gt;The host is reachable. The key works in curl. The client still hits 404.&lt;/p&gt;

&lt;p&gt;This post is the base URL checklist I run before blaming the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Series
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/the-3-step-smoke-test-i-use-for-any-openai-compatible-api-311g"&gt;Smoke test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/one-gateway-three-protocols-chat-completions-responses-and-anthropic-messages-459k"&gt;Multiprotocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/how-i-configure-cline-continue-and-similar-tools-against-a-multi-protocol-gateway-44i1"&gt;Client setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/my-curl-smoke-test-passed-why-does-cline-continue-still-fail-52p4"&gt;curl OK, IDE fails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/claude-protocol-without-the-openai-only-mental-model-34kh"&gt;Claude protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/i-stopped-hardcoding-model-ids-modelsdev-v1models-changed-the-workflow-4na2"&gt;models.dev + /models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/a-10-minute-checklist-before-you-point-a-coding-agent-at-a-custom-gateway-2opk"&gt;Agent pre-flight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/openaiapikey-vs-daoxeapikey-which-env-name-your-client-actually-reads-2063"&gt;Env var names&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post:&lt;/strong&gt; base URL shape&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What “OpenAI-compatible base” usually means
&lt;/h2&gt;

&lt;p&gt;Many clients expect a root like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://daoxe.com/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They then append:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/chat/completions
/models
/embeddings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the final path becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://daoxe.com/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already put &lt;code&gt;/chat/completions&lt;/code&gt; in the base, or you omit &lt;code&gt;/v1&lt;/code&gt; when the client does not add it, you get nonsense paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Final path symptom&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base missing &lt;code&gt;/v1&lt;/code&gt; when client expects full OpenAI base&lt;/td&gt;
&lt;td&gt;404 on &lt;code&gt;/chat/completions&lt;/code&gt; at host root&lt;/td&gt;
&lt;td&gt;use &lt;code&gt;https://daoxe.com/v1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base already ends with &lt;code&gt;/v1&lt;/code&gt; and client adds &lt;code&gt;/v1/...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/v1/chat/completions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drop one &lt;code&gt;/v1&lt;/code&gt; per client docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trailing slash quirks&lt;/td&gt;
&lt;td&gt;intermittent join bugs&lt;/td&gt;
&lt;td&gt;pick one style; retest with curl to the exact URL the client logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs host ≠ live API host&lt;/td&gt;
&lt;td&gt;wrong environment&lt;/td&gt;
&lt;td&gt;use the live API host from the product, not a blog screenshot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Verify with curl to the exact joined URL
&lt;/h2&gt;

&lt;p&gt;Do not only test “the host is up.”&lt;/p&gt;

&lt;p&gt;Test the &lt;strong&gt;same path the client will call&lt;/strong&gt;:&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DAOXE_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://daoxe.com/v1"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DAOXE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"paste_exact_id_from_models"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_MODEL&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;max_tokens&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 8,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;messages&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}]
  }"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/chat/completions"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that is green and the client is red, log the client’s request URL. Compare character-for-character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client-specific mental model
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client says&lt;/th&gt;
&lt;th&gt;Often means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;“OpenAI base URL”&lt;/td&gt;
&lt;td&gt;include &lt;code&gt;/v1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“API host”&lt;/td&gt;
&lt;td&gt;sometimes host only; client adds &lt;code&gt;/v1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“Custom provider”&lt;/td&gt;
&lt;td&gt;check whether models.dev key implies the base&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the client’s one line of docs. Do not assume every “OpenAI-compatible” UI joins paths the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft CTA
&lt;/h2&gt;

&lt;p&gt;Product side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_baseurl" rel="noopener noreferrer"&gt;https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_baseurl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Public notes: &lt;a href="https://github.com/seven7763/DaoXE-AI/blob/main/CLIENT_SETUP.md" rel="noopener noreferrer"&gt;CLIENT_SETUP.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comment with client name + the exact request URL from its logs (redact the key).&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>OPENAI_API_KEY vs DAOXE_API_KEY: which env name your client actually reads</title>
      <dc:creator>Seven</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:16:12 +0000</pubDate>
      <link>https://dev.to/seven7763/openaiapikey-vs-daoxeapikey-which-env-name-your-client-actually-reads-2063</link>
      <guid>https://dev.to/seven7763/openaiapikey-vs-daoxeapikey-which-env-name-your-client-actually-reads-2063</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure and availability:&lt;/strong&gt; DaoXE is a multi-model multi-protocol API gateway we operate. It is not available in mainland China. This article is for developers in regions allowed by the service terms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had a green curl.&lt;/p&gt;

&lt;p&gt;The agent still said “unauthorized.”&lt;/p&gt;

&lt;p&gt;The key was fine. The &lt;strong&gt;env name&lt;/strong&gt; was wrong for that client.&lt;/p&gt;

&lt;p&gt;This post is the short map I use when base URL works but the tool never picks up the secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Series
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/the-3-step-smoke-test-i-use-for-any-openai-compatible-api-311g"&gt;Smoke test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/one-gateway-three-protocols-chat-completions-responses-and-anthropic-messages-459k"&gt;Multiprotocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/how-i-configure-cline-continue-and-similar-tools-against-a-multi-protocol-gateway-44i1"&gt;Client setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/my-curl-smoke-test-passed-why-does-cline-continue-still-fail-52p4"&gt;curl OK, IDE fails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/claude-protocol-without-the-openai-only-mental-model-34kh"&gt;Claude protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/i-stopped-hardcoding-model-ids-modelsdev-v1models-changed-the-workflow-4na2"&gt;models.dev + /models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seven7763/a-10-minute-checklist-before-you-point-a-coding-agent-at-a-custom-gateway-2opk"&gt;Agent pre-flight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post:&lt;/strong&gt; env var names&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two different secrets
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vendor-shaped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;many OpenAI SDKs / tools default here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway-shaped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DAOXE_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;docs/catalog for DaoXE; some clients allow custom envar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic-shaped&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; / &lt;code&gt;x-api-key&lt;/code&gt; field&lt;/td&gt;
&lt;td&gt;Messages clients&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Curl does not care about the name:&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DAOXE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://daoxe.com/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;agent process&lt;/strong&gt; might only read &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; unless you configure a custom provider field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. OpenAI SDK with custom base
&lt;/h3&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;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_gateway_key"&lt;/span&gt;   &lt;span class="c"&gt;# SDK default name&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://daoxe.com/v1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some libraries use &lt;code&gt;OPENAI_API_BASE&lt;/code&gt; instead of &lt;code&gt;OPENAI_BASE_URL&lt;/code&gt;. Check the client docs once.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Gateway-native name (when the tool supports it)
&lt;/h3&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;DAOXE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_gateway_key"&lt;/span&gt;
&lt;span class="c"&gt;# base often hard-set to https://daoxe.com/v1 in UI or catalog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;models.dev lists DaoXE with env &lt;code&gt;DAOXE_API_KEY&lt;/code&gt; and &lt;code&gt;api=https://daoxe.com/v1&lt;/code&gt;. Catalog-driven tools may expect that exact name.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Anthropic Messages clients
&lt;/h3&gt;

&lt;p&gt;Bearer-only Chat Completions success does &lt;strong&gt;not&lt;/strong&gt; prove Messages works. Those clients often need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x-api-key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;anthropic-version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /v1/messages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Failure table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;curl 200, agent 401&lt;/td&gt;
&lt;td&gt;agent reads different env&lt;/td&gt;
&lt;td&gt;set the name that client documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;works in shell, fails in IDE&lt;/td&gt;
&lt;td&gt;IDE not inheriting shell env&lt;/td&gt;
&lt;td&gt;set key in IDE provider UI / launch env&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;works for OpenAI path, fails Claude path&lt;/td&gt;
&lt;td&gt;wrong protocol + auth style&lt;/td&gt;
&lt;td&gt;Messages headers + &lt;code&gt;/v1/messages&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;teammate fails with same README&lt;/td&gt;
&lt;td&gt;account-scoped models / different secret store&lt;/td&gt;
&lt;td&gt;each person lists &lt;code&gt;/v1/models&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Minimal verification order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;curl with explicit &lt;code&gt;-H "Authorization: Bearer …"&lt;/code&gt; (no env ambiguity)&lt;/li&gt;
&lt;li&gt;export the &lt;strong&gt;client’s&lt;/strong&gt; preferred key name&lt;/li&gt;
&lt;li&gt;restart the agent process so it reloads env&lt;/li&gt;
&lt;li&gt;one tiny Chat Completions call from that same process path&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Public setup notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/seven7763/DaoXE-AI/blob/main/CLIENT_SETUP.md" rel="noopener noreferrer"&gt;CLIENT_SETUP.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/seven7763/DaoXE-AI" rel="noopener noreferrer"&gt;DaoXE-AI examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Soft CTA
&lt;/h2&gt;

&lt;p&gt;If you want the product side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_env" rel="noopener noreferrer"&gt;https://daoxe.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=global_launch_env&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comment with client name + which env names it documents. Do not paste keys.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
