<?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: Hamimelon2026</title>
    <description>The latest articles on DEV Community by Hamimelon2026 (@hamimelon2026_40bd96eff01).</description>
    <link>https://dev.to/hamimelon2026_40bd96eff01</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%2F4066681%2F03fdd0c8-e30a-406f-b9da-a12777422a63.png</url>
      <title>DEV Community: Hamimelon2026</title>
      <link>https://dev.to/hamimelon2026_40bd96eff01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamimelon2026_40bd96eff01"/>
    <language>en</language>
    <item>
      <title>Frames to Video AI: How I Turn a Storyboard Into Moving Shots Without Wasting Credits</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Mon, 21 Sep 2026 07:44:53 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/frames-to-video-ai-how-i-turn-a-storyboard-into-moving-shots-without-wasting-credits-360k</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/frames-to-video-ai-how-i-turn-a-storyboard-into-moving-shots-without-wasting-credits-360k</guid>
      <description>&lt;p&gt;I had six storyboard stills for a small product launch and one goal: get them moving. I went looking for a frames to video ai workflow and found plenty of demos, but nothing about the boring part that decides whether the result works: which frames to animate, in what order, and how to keep the shots consistent.&lt;/p&gt;

&lt;p&gt;So I built the workflow myself. This post covers what "frames to video" actually means, a free way to preview a sequence before you generate anything, and how I hand the best frames to a generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "frames to video AI" actually means
&lt;/h2&gt;

&lt;p&gt;Three different jobs share that name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One frame in, one clip out.&lt;/strong&gt; A still becomes a few seconds of motion. This is the image to video AI case, and it's what sits behind most "animate a photo" features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sequence of frames in, one video out.&lt;/strong&gt; Storyboard stills or keyframes that you want to turn into a scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frames pulled out of footage.&lt;/strong&gt; Existing clips sliced into stills you can pick from and animate again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most demos only show the first. Real projects, like an AI product video generator for a small brand or a moving preview of game concept art, need all three.&lt;/p&gt;

&lt;p&gt;The trouble is guesswork. Animate each frame separately and you won't know if the pacing works or if frame three is worth the effort until you've spent the time. So I stopped guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose frames that can actually move
&lt;/h2&gt;

&lt;p&gt;Not every good still makes a good clip. Before I animate anything, I check four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Room to move.&lt;/strong&gt; Leave space in the frame for a camera push or a gentle drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One clear subject.&lt;/strong&gt; Busy frames give a model too many things to move at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean key areas.&lt;/strong&gt; Keep text and tiny patterns out of the main area. They're the hardest details to keep stable once things move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matching light.&lt;/strong&gt; If the frames belong to one sequence, keep the light and color consistent across them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fail any of these and I fix the frame before I spend anything on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview the sequence before you animate anything
&lt;/h2&gt;

&lt;p&gt;Before generating a single clip, I stitch my stills into a rough animatic with ffmpeg. No AI, no cost. It tells me whether the order and pacing work, and which frames deserve motion.&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;# frames/ holds 01.jpg, 02.jpg, ... (any size)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1/2 &lt;span class="nt"&gt;-pattern_type&lt;/span&gt; glob &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'frames/*.jpg'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=yuv420p"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; 24 animatic.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each frame holds for two seconds. Change &lt;code&gt;1/2&lt;/code&gt; to hold frames longer or shorter, and the &lt;code&gt;pad&lt;/code&gt; filter letterboxes anything that isn't 16:9.&lt;/p&gt;

&lt;p&gt;Watch it once. Cut the frames that don't earn their place, reorder the rest, and mark the two or three that most need motion. That short list is what you take to the generator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F443zb3qsfpyx2x85p36b.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F443zb3qsfpyx2x85p36b.png" alt="From storyboard stills to animatic, frame picks, animation, and last-frame chaining" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I animate: frames to video AI in one workspace
&lt;/h2&gt;

&lt;p&gt;For the animating step, I used &lt;a href="https://vokoo.ai" rel="noopener noreferrer"&gt;VOKOO&lt;/a&gt;, a multi-model AI creation platform built around video. Its tagline is "Create more. Switch less," and that's how it felt. I dropped in one still, typed a short motion prompt, and had a clip to review before I finished my coffee.&lt;/p&gt;

&lt;p&gt;It's a web workspace rather than something I script, which suited this job. Here's what I actually used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frames in, motion out
&lt;/h3&gt;

&lt;p&gt;The AI video generator turns a prompt plus your first frame into a clip. Make a video before the idea gets cold.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate the frames you're missing
&lt;/h3&gt;

&lt;p&gt;Every storyboard has gaps. The AI image generator fills them: generate the image you need, then move it into motion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix weak frames first
&lt;/h3&gt;

&lt;p&gt;A soft frame gives a soft clip. The AI photo editor and image upscaler let me edit, refine, and make small or blurry images crisp and usable without leaving the flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch models, not tabs
&lt;/h3&gt;

&lt;p&gt;The AI agent lets me try different models without rebuilding my workflow. Same frame, same prompt, different model, then compare. One place to generate, edit, enhance, and animate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull frames out of footage you already have
&lt;/h2&gt;

&lt;p&gt;The third job on the list is the one people forget. If you have reference footage, ffmpeg can grab a frame at every scene change:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; frames_from_footage
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; reference.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"select='gt(scene,0.4)'"&lt;/span&gt; &lt;span class="nt"&gt;-fps_mode&lt;/span&gt; vfr &lt;span class="nt"&gt;-q&lt;/span&gt;:v 2 frames_from_footage/%03d.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lower &lt;code&gt;0.4&lt;/code&gt; to get more frames, raise it to get fewer. Then rank them so you don't have to squint at thumbnails:&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="c1"&gt;# sharpest.py - rank frames by edge strength, sharpest first
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageStat&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImageFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIND_EDGES&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;ImageStat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;var&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&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="nf"&gt;print&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="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;python sharpest.py frames_from_footage/*.jpg&lt;/code&gt;, take the top few, and run them through the workspace. You get moving versions of footage you already own. Only use footage you have the rights to.&lt;/p&gt;

&lt;p&gt;For continuity between shots, use the last frame of a finished clip as the first frame of the next one. &lt;code&gt;ffmpeg -sseof -0.1 -i clip_01.mp4 -frames:v 1 -update 1 last.jpg&lt;/code&gt; grabs it in one line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping frames to video AI runs cheap and repeatable
&lt;/h2&gt;

&lt;p&gt;My rule: draft at a lower spec, lock the frame, then render the final at full quality. The platform lets me pick quality and generation specs per stage and shows the estimated credit cost before I submit, so I never retry blind.&lt;/p&gt;

&lt;p&gt;Keep each motion prompt to one move per frame: "slow push-in," "gentle pan right," "subtle handheld drift." When a clip goes wrong, you'll know exactly which instruction to change.&lt;/p&gt;

&lt;p&gt;I also name every output after the frame that produced it, like &lt;code&gt;frame_03__model_a__push_in.mp4&lt;/code&gt;. Two weeks later, I can still tell which frame, model, and motion prompt made a clip I liked.&lt;/p&gt;

&lt;p&gt;If you'd like an LLM to write per-frame motion prompts from your storyboard notes, &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt; provides a cost-effective, OpenAI-compatible API gateway with multiple models, so setup stays simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this next
&lt;/h2&gt;

&lt;p&gt;Frames to video AI works best when you treat the frame as your unit of planning. Decide which frames matter before you generate, and the generating gets a lot cheaper. VOKOO handled the part I didn't want to script. Stop managing tools. Start making things.&lt;/p&gt;

&lt;p&gt;Here's a short test you can run this weekend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gather four to six stills and run the animatic command above.&lt;/li&gt;
&lt;li&gt;Cut and reorder until the pacing feels right.&lt;/li&gt;
&lt;li&gt;Animate your two most important frames on two different models.&lt;/li&gt;
&lt;li&gt;Chain the next shot from a last frame, and check the estimated cost before you scale up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want an easy AI video generator that keeps simple AI video creation simple and still leaves room to explore, try VOKOO at &lt;a href="https://vokoo.ai" rel="noopener noreferrer"&gt;https://vokoo.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>How I Simplified My AI Video Generation API Workflow With VOKOO</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Wed, 16 Sep 2026 08:33:38 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/how-i-simplified-my-ai-video-generation-api-workflow-with-vokoo-58g7</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/how-i-simplified-my-ai-video-generation-api-workflow-with-vokoo-58g7</guid>
      <description>&lt;p&gt;Three tabs, three API keys, one very tired afternoon&lt;/p&gt;

&lt;p&gt;Last month I needed a short product video for a side project. Nothing fancy — a few seconds of a still image coming to life. I figured this would take an hour, tops.&lt;/p&gt;

&lt;p&gt;It took most of a day.&lt;/p&gt;

&lt;p&gt;I started testing an AI video generation API from one provider, hit a rate limit, switched to another, rewrote half my request payload because the schema was different, then discovered the second model's pricing meant a handful of test runs had already eaten a meaningful chunk of my budget. By the time I had something worth showing, I had three browser tabs, two API keys, and zero confidence I was even using the "right" model for the job.&lt;/p&gt;

&lt;p&gt;If you've ever tried to build anything around video generation, this probably sounds familiar.&lt;/p&gt;

&lt;p&gt;The actual problem isn't the models&lt;/p&gt;

&lt;p&gt;Individually, most video and image models are fine. Some are faster, some are cheaper, some produce cleaner motion. The problem shows up the moment you try to combine them into an actual workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every provider has its own request format, auth pattern, and quirks.&lt;/li&gt;
&lt;li&gt;Pricing is rarely visible until after you've already burned credits on a test.&lt;/li&gt;
&lt;li&gt;There's no easy way to try model B without rewriting the integration you just built for model A.&lt;/li&gt;
&lt;li&gt;Image-to-video specifically requires stitching together generation, editing, and animation steps that were never designed to talk to each other.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a "your code is bad" problem. It's a "the tooling landscape is fragmented" problem. And fragmentation is expensive — not in dollars necessarily, but in the hours you spend gluing things together instead of shipping.&lt;/p&gt;

&lt;p&gt;What I ended up testing&lt;/p&gt;

&lt;p&gt;After the second failed integration attempt, I looked for something that treated "call a video model" as one job instead of five separate ones. That's when I started testing &lt;a href="https://vokoo.ai" rel="noopener noreferrer"&gt;VOKOO&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;VOKOO is a creation platform built around video generation, with multiple models available in one workspace instead of one model per integration. The pitch that got my attention: Create more. Switch less. — which, after my afternoon of tab-switching, sounded less like marketing copy and more like a personal callout.&lt;/p&gt;

&lt;p&gt;I spun up a quick test with a single prompt and had a video back to review in well under the time it took to write the request payload for my previous attempt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fesh62832oftie7tmanwq.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fesh62832oftie7tmanwq.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;br&gt;
What actually changed for me&lt;/p&gt;

&lt;p&gt;A few specific things made the difference, so I'll break them down the way I'd explain them to another developer:&lt;/p&gt;

&lt;p&gt;Turn a prompt into a video without rebuilding your integration each time. Instead of maintaining separate request logic per provider, the platform handles video generation as a single consistent workflow. You describe what you want, pick a model, and get output — no re-learning a new schema every time you want to compare results.&lt;/p&gt;

&lt;p&gt;Generate the image first, then pipe it into motion. A lot of real projects don't start with "generate a video." They start with a character, a product shot, or a scene that needs to move. Having image generation and video generation live in the same place means you're not exporting and re-uploading assets between two unrelated tools.&lt;/p&gt;

&lt;p&gt;Switch models, not SDKs. This was the big one for me. Comparing model output used to mean comparing integrations. Here, trying a different model for the same task doesn't mean touching your pipeline — it's closer to changing a parameter than rebuilding a client.&lt;/p&gt;

&lt;p&gt;One photo plus one voice track as your input. For anything involving a talking avatar or lip-synced output, the input requirement is refreshingly simple: an image and an audio track. No separate lip-sync service to bolt on afterward.&lt;/p&gt;

&lt;p&gt;Turn rough footage into something sharper. Enhancement is treated as part of the same pipeline rather than a separate export-and-reimport step, which matters if your source footage or first-pass generation isn't quite clean enough to ship.&lt;/p&gt;

&lt;p&gt;The bigger shift is less about any single feature and more about not having to manage five vendor relationships to finish one project. One workspace. Multiple models. Zero tool-hopping is the idea, and in practice it mostly held up.&lt;/p&gt;

&lt;p&gt;A basic API-style call, for reference&lt;/p&gt;

&lt;p&gt;If you're used to calling an OpenAI-compatible endpoint, the shape of a video generation request will feel familiar. Here's a simplified example showing the general pattern (placeholder key and model name — swap in real values from your own account):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.example-video-platform.com/v1/videos/generate"&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 YOUR_API_KEY"&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="s1"&gt;'{
    "model": "your-selected-model",
    "prompt": "a product photo slowly rotating on a clean background",
    "input_image": "https://yourcdn.com/product.jpg",
    "duration_seconds": 4,
    "resolution": "1080p"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical response returns a job ID you poll (or a webhook you listen on) until the render is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vid_8f2c1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimated_credits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact fields will differ depending on which model and platform you're calling — the point is the shape: send a prompt (and optionally a source image), get back a job you can track, and know the estimated cost before the render finishes rather than after.&lt;/p&gt;

&lt;p&gt;If you're building your own pipeline instead&lt;/p&gt;

&lt;p&gt;Not everyone wants a workspace UI — some of you just want to call models directly from code and build your own orchestration layer. If that's you, cost and API consistency become the real bottleneck, since juggling multiple providers' billing and auth is its own maintenance burden. For that case, a platform like &lt;a href="//fastrouteai.com"&gt;RouteAI&lt;/a&gt; offers a unified, OpenAI-compatible API gateway across multiple models at lower cost, which is useful if you're building the integration yourself rather than using a workspace. (fastrouteai.com)&lt;/p&gt;

&lt;p&gt;Where I landed&lt;/p&gt;

&lt;p&gt;I didn't need a custom pipeline for a one-off product video — I needed something that got out of my way. Testing a few models side by side without rewriting my integration each time was the actual unlock, more than any single feature.&lt;/p&gt;

&lt;p&gt;If you're in the middle of the same tab-juggling exercise I was in a few weeks ago, it's worth trying a single prompt through VOKOO before you spin up your third API key: &lt;a href="https://vokoo.ai" rel="noopener noreferrer"&gt;https://vokoo.ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Seedance Prompt Failures: What's Actually Causing the Jitter, Deformation, and Wrong References</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Fri, 11 Sep 2026 03:57:52 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/seedance-prompt-failures-whats-actually-causing-the-jitter-deformation-and-wrong-references-46mp</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/seedance-prompt-failures-whats-actually-causing-the-jitter-deformation-and-wrong-references-46mp</guid>
      <description>&lt;p&gt;Templates get you a working first prompt. They don't tell you why your second attempt came out jittery, why a reference image you provided got ignored in favor of one you didn't expect, or why a subject's hand briefly turned into something with the wrong number of fingers for two frames. Those failures have specific, identifiable causes, and once you can recognize the pattern, fixing them stops being trial and error. That's the part worth writing down instead of another copy-paste template list — there are already plenty of those.&lt;/p&gt;

&lt;p&gt;Jitter and artifacts: usually a camera-verb collision, not bad luck&lt;/p&gt;

&lt;p&gt;The single most common failure pattern I kept running into was visual jitter or subtle warping during motion — and it almost always traced back to the same root cause: stacking multiple, conflicting camera movement instructions in one shot. A prompt describing a handheld shake, an orbit, a zoom, and a whip pan all in the same few seconds isn't giving the model four compatible instructions — it's giving it four movements that can't all resolve into one coherent camera path, and the model's attempt to reconcile them is what shows up as artifacts.&lt;/p&gt;

&lt;p&gt;The fix is committing to one dominant camera movement per shot. If you genuinely need multiple distinct movements, that's a multi-shot prompt with explicit shot breaks, not one shot carrying all of them at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bad (stacked, conflicting):
Handheld tracking shot with a slow zoom and subtle orbit around the subject, quick whip pan at the end.

Better (one dominant move, split into shots):
Shot 1: Static medium shot, subject enters frame from the left.
Shot 2: Slow handheld push-in toward the subject's face as they turn.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk5cqtpbqonzlxi16u6ji.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk5cqtpbqonzlxi16u6ji.png" alt="Multiple conflicting camera movement instructions colliding into one unstable shot" width="799" height="336"&gt;&lt;/a&gt;&lt;br&gt;
Wrong reference driving the scene: an assignment problem, not a model problem&lt;/p&gt;

&lt;p&gt;If you supply more than one reference asset — an image for character appearance, another for environment, a video for camera motion — and don't explicitly say what each one is for, the model has to guess which reference controls which part of the output, and it doesn't always guess the way you expected. This shows up as a generated scene that pulls color palette from the wrong image, or motion that doesn't match the reference video you actually wanted followed.&lt;/p&gt;

&lt;p&gt;The fix is mechanical: label every reference's job explicitly rather than assuming order or context implies it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ambiguous:
[image1] [image2] [video1] — generate a scene with this character in this setting.

Explicit:
@image1 as character reference (face, outfit, hair)
@image2 as environment reference (lighting, color palette, set dressing)
@video1 as camera movement and pacing reference only — do not use its background
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line — explicitly telling the model what a reference should not drive — is worth using deliberately whenever a reference asset has one property you want and others you don't.&lt;/p&gt;

&lt;p&gt;Audio that silently does nothing&lt;/p&gt;

&lt;p&gt;I covered this one in more depth in an earlier piece on &lt;a href="https://dev.to/felixai/seedance-20-prompt-lessons-from-building-a-real-product-video-generator-59kj"&gt;Seedance 2.0&lt;/a&gt; specifically, but it's common enough to repeat here: audio submitted alone, or text plus audio with no visual reference at all, doesn't generate anything — every job needs a visual anchor (text-to-video, image-to-video, or a reference video), with audio layered on top of one of those rather than standing alone. If a job silently fails or produces nothing usable, checking whether you actually gave it a visual anchor is worth doing before assuming the prompt wording is the problem.&lt;/p&gt;

&lt;p&gt;Camera description and subject action, kept separate&lt;/p&gt;

&lt;p&gt;A subtler failure mode: writing camera movement and subject movement as one blended description, which makes it ambiguous which parts of the described motion belong to the camera versus the subject. "The camera follows the dancer as she spins toward the light" mixes a camera instruction (follows) with a subject instruction (spins) with a spatial detail (toward the light) in one clause. Separating them into distinct lines — what the camera does, then what the subject does — tends to produce cleaner motion, because the model isn't parsing one sentence to extract two different kinds of instructions.&lt;/p&gt;

&lt;p&gt;Vague adjective stacks instead of concrete action&lt;/p&gt;

&lt;p&gt;The other recurring pattern behind flat, generic-looking output: prompts loaded with mood adjectives (cinematic, dramatic, atmospheric, premium) but thin on what specifically happens. A model has a lot to infer from "cinematic dramatic lighting, premium atmosphere" and comparatively little to work with, versus a prompt naming a specific subject, a specific action, and a specific environment, with style descriptors added on top rather than substituted in for the concrete details. Style words are seasoning, not the actual recipe.&lt;/p&gt;

&lt;p&gt;Using negative instructions as a real prompting lever&lt;/p&gt;

&lt;p&gt;Beyond describing what you want, explicitly stating what shouldn't appear is worth using deliberately rather than as an afterthought — excluding duplicated limbs, unwanted on-screen text, background music bleeding in from a reference clip, or a reference's visual style leaking into parts of the scene it shouldn't touch. Treating exclusions as a first-class part of the prompt, not a fallback for when something already went wrong, catches a meaningful share of the failure modes above before you even get to a first generation.&lt;/p&gt;

&lt;p&gt;The actual debugging method: change one layer at a time&lt;/p&gt;

&lt;p&gt;When a generation comes back wrong, the instinct is to rewrite the whole prompt. The more effective approach is isolating a single variable per attempt — if framing is wrong but the action is right, adjust only the camera description and leave subject and action untouched; if motion feels unstable, adjust only the camera movement instruction. Changing multiple things at once after a failed generation makes it impossible to tell which change actually fixed anything, which means you learn nothing for the next prompt.&lt;/p&gt;

&lt;p&gt;This is straightforward enough to script if you're iterating through an API rather than a UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// prompt-variant-tester.js — holds everything constant except one variable per run,&lt;/span&gt;
&lt;span class="c1"&gt;// so a fix (or a non-fix) is actually attributable to a specific change&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;,&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;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/video/generations`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;job_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`Subject: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Action: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Environment: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Camera: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testCameraVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;,&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;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cameraOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;cameraOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;submitJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;,&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;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Submitted with camera="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" -&amp;gt; job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// poll each job_id separately, compare outputs side by side&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Everything held constant except the camera description across three runs&lt;/span&gt;
&lt;span class="nf"&gt;testCameraVariants&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;SEEDANCE_BASE_URL&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;SEEDANCE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seedance-2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a ceramic mug on a wooden table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;steam rising slowly from the mug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quiet kitchen, morning light through a window&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static medium shot, no movement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow push-in toward the mug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gentle handheld motion, subtle drift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running three controlled variants that differ only in the camera line, and comparing them side by side, tells you specifically whether the camera description was the actual source of a jitter problem — rather than rewriting the whole prompt and being unable to say which change did what.&lt;/p&gt;

&lt;p&gt;One version-awareness note before you commit to specific limits&lt;/p&gt;

&lt;p&gt;Reference asset limits, maximum duration, and supported input counts have changed between Seedance versions and continue to shift — treat any specific number you read (including counts I've mentioned in other pieces) as something to verify against the current documentation for the exact model version you're calling, rather than assuming it's carried over unchanged from an earlier release.&lt;/p&gt;

&lt;p&gt;Where this fits with actually calling the API&lt;/p&gt;

&lt;p&gt;None of the above requires a specific gateway — it's model behavior, not infrastructure. If you're already routing other model calls through one key and want Seedance access alongside them rather than a separate account, [RouteAI](Running three controlled variants that differ only in the camera line, and comparing them side by side, tells you specifically whether the camera description was the actual source of a jitter problem — rather than rewriting the whole prompt and being unable to say which change did what.&lt;/p&gt;

&lt;p&gt;One version-awareness note before you commit to specific limits&lt;/p&gt;

&lt;p&gt;Reference asset limits, maximum duration, and supported input counts have changed between Seedance versions and continue to shift — treat any specific number you read (including counts I've mentioned in other pieces) as something to verify against the current documentation for the exact model version you're calling, rather than assuming it's carried over unchanged from an earlier release.&lt;/p&gt;

&lt;p&gt;Where this fits with actually calling the API&lt;/p&gt;

&lt;p&gt;None of the above requires a specific gateway — it's model behavior, not infrastructure. If you're already routing other model calls through one key and want Seedance access alongside them rather than a separate account, &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt; lists Seedance in its catalog; that's a routing convenience, not something that changes any of the prompting behavior described here.&lt;/p&gt;

&lt;p&gt;TL;DR: Most Seedance prompt failures trace back to a handful of specific, recognizable causes — stacked conflicting camera verbs (jitter), unlabeled multi-reference assignment (wrong reference driving the scene), blended camera/subject descriptions, and vague adjective stacking instead of concrete action. Changing one variable per attempt instead of rewriting the whole prompt is what actually turns failures into fixes instead of another reroll.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;) lists Seedance in its catalog; that's a routing convenience, not something that changes any of the prompting behavior described here.&lt;/p&gt;

&lt;p&gt;TL;DR: Most Seedance prompt failures trace back to a handful of specific, recognizable causes — stacked conflicting camera verbs (jitter), unlabeled multi-reference assignment (wrong reference driving the scene), blended camera/subject descriptions, and vague adjective stacking instead of concrete action. Changing one variable per attempt instead of rewriting the whole prompt is what actually turns failures into fixes instead of another reroll.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>promptengineering</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>MiniMax API Pricing: What I Found After a Tracker Told Me It Was Free</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:24:50 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/minimax-api-pricing-what-i-found-after-a-tracker-told-me-it-was-free-1d26</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/minimax-api-pricing-what-i-found-after-a-tracker-told-me-it-was-free-1d26</guid>
      <description>&lt;p&gt;I was comparing models for a project and pulled up a pricing tracker site to check MiniMax M2.7's rate. It listed the model at $0.00 per million input tokens and $0.00 per million output tokens. For about ten seconds I genuinely considered whether MiniMax was running some kind of loss-leader promotion I hadn't heard about. It wasn't. That was a scraping error — a stale or broken data pull on the tracker's end, not a real price — and it's a good reminder that automated pricing aggregators are themselves just another layer that can be wrong, on top of the usual reseller-markup confusion.&lt;/p&gt;

&lt;p&gt;Once I went to the actual sources — MiniMax's own documentation and a cross-section of the platforms reselling access to the same models — the real picture turned out to be more interesting than "here's a number," mostly because of how consistent most of it is, with a few platforms that clearly aren't playing the same game as everyone else.&lt;/p&gt;

&lt;p&gt;The official rate card, direct from &lt;a href="https://platform.minimax.io/subscribe/token-plan?tab=api-enterprise" rel="noopener noreferrer"&gt;MiniMax&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For MiniMax-M3, the flagship model, standard-tier requests with up to 512K input tokens are billed at $0.30 per million input tokens and $1.20 per million output tokens. That number comes with a detail worth knowing: MiniMax's own pricing page lists a struck-through $0.60/$2.40 list price with a "permanent 50% off" applied to reach the $0.30/$1.20 rate actually charged. Structurally that's just the price — a permanent discount is a price — but it's worth knowing the discount framing exists if you're ever comparing screenshots of MiniMax pricing from different points in time and see the higher number somewhere.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuzwrn7suxjd1wvaa7v17.jpeg" 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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuzwrn7suxjd1wvaa7v17.jpeg" alt="Two different fast-response mechanisms depending on the model — a priority multiplier vs a separate high-speed model variant" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Cache reads are priced well below standard input — $0.06 per million tokens on M2.7, roughly a fifth of the standard rate — while cache writes cost more than standard input, at $0.375 per million. If your workload resends a large, mostly-static prefix on every call, that read discount matters more to your actual bill than the headline input rate.&lt;/p&gt;

&lt;p&gt;Two mechanisms exist for paying for faster responses, and they're not the same across models — worth checking which one applies before you flip a switch expecting the other behavior. M3 offers a service_tier: priority setting billed at 1.5x the standard rate for preferential request admission and more reliable latency under load. M2.7 instead ships as a separate -highspeed model variant, same weights, different routing, priced at 2x standard. Setting service_tier: priority on a model that actually uses the -highspeed variant mechanism won't get you what you're expecting.&lt;/p&gt;

&lt;p&gt;One more caveat from the docs worth flagging before you assume the full 1M-token context window bills at the same standard rate: inputs beyond 512K tokens sit in a separate, currently gated tier — limited availability, contact sales — rather than being automatically available at the standard rate. If your use case genuinely needs that upper range of the context window, that's worth confirming directly with MiniMax rather than assuming standard pricing extends all the way up.&lt;/p&gt;

&lt;p&gt;The reseller landscape: unusually consistent, with a few outliers&lt;/p&gt;

&lt;p&gt;Here's the part that actually surprised me. Pulling current per-million-token rates for MiniMax M3 and M2.7 across a dozen-plus resale platforms, most of them land on almost exactly the same number: $0.30 input / $1.20 output for M3, matching MiniMax's own standard rate almost to the cent, across platforms like novita, Together AI, tokenlab, fireworks, and ZenMux. A few — OpenRouter and DeepInfra among them — price M2.7 and M2.5 somewhat below that pack, closer to $0.15-0.25 input.&lt;/p&gt;

&lt;p&gt;That consistency is genuinely different from what you'll find comparing &lt;a href="https://dev.to/noah_bennett_85dfd9bed51e/kimi-k2-price-by-platform-why-the-same-model-costs-2-4x-depending-on-where-you-call-it-4o7n"&gt;Kimi K2 pricing&lt;/a&gt; across the same kind of platform list, where the spread is wide enough that platform choice meaningfully changes your bill. For MiniMax, most resellers appear to be passing through the model at close to cost rather than adding a meaningful markup, which flips the useful question: instead of "which platform is cheapest," it becomes "which platforms are charging noticeably more, and is there a reason."&lt;/p&gt;

&lt;p&gt;And there are real outliers worth naming, because paying 2-4x the standard rate without realizing it is an easy mistake if you're not checking: a few platforms in the set I reviewed price M3 as high as $0.60-1.11 input / $2.40-4.60 output — two to nearly four times the going rate everyone else converges on. That's not automatically a ripoff; it could reflect a different SLA, dedicated capacity, or bundled features. But it's the kind of gap worth confirming you're getting something for, rather than assuming all resellers are pricing the same model at the same rate just because most of them are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahbudanbf3gvgg4ge95k.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahbudanbf3gvgg4ge95k.png" alt="Most resale platforms converging on nearly the same price, with a few clear outliers" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A cost estimate that accounts for MiniMax's actual mechanics&lt;/p&gt;

&lt;p&gt;Generic per-token calculators miss the two things that actually move a MiniMax bill: the cache discount and the priority/highspeed multiplier, which work differently depending on the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// minimax-cost-estimate.js — accounts for cache discount and the priority/highspeed&lt;/span&gt;
&lt;span class="c1"&gt;// multiplier, which use different mechanisms depending on the model&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cacheRead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priorityMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m2.7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cacheRead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;highspeedMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cachedTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fastMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RATES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown model: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regularInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cachedTokens&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regularInput&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cacheRead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;outputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// M3: same model, priority service tier, 1.5x&lt;/span&gt;
    &lt;span class="c1"&gt;// M2.7: a distinct -highspeed model variant, 2x&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priorityMultiplier&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highspeedMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;outputCost&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;outputCost&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: same workload, standard vs fast, on each model&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cachedTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_500_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M3 standard:  $&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M3 priority:  $&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fastMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M2.7 standard:$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m2.7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M2.7 highspeed:$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m2.7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fastMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a heavily cached workload like this through the numbers makes the cache discount's actual impact obvious — it's a bigger lever on the final bill than deciding between standard and priority/highspeed service in most cases, simply because so much of a repeat-context workload's tokens fall under the cheaper cache rate rather than the standard input rate.&lt;/p&gt;

&lt;p&gt;What I'd actually check before committing&lt;/p&gt;

&lt;p&gt;Confirm you're pricing the model and tier you'll actually use — priority and highspeed aren't the same mechanism and aren't interchangeable by name. Check whether your workload reuses context heavily enough that the cache rate matters more than the headline input number. If you're evaluating a reseller platform, compare its number against the roughly $0.30/$1.20 standard most of the market converges on for M3, and if it's notably higher, find out why rather than assuming markup is universal. And don't trust a single pricing tracker's number without a source check — mine told me a paid API was free, which should have been the first sign to look elsewhere.&lt;/p&gt;

&lt;p&gt;Where a routing decision fits&lt;/p&gt;

&lt;p&gt;If you're already calling other model families through a gateway and would rather not open a separate MiniMax account just for this one model line, that's a routing question rather than a pricing one — &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt;, for instance, lists &lt;a href="https://dev.to/felixai/minimax-agent-mavis-what-the-hosted-product-actually-does-vs-building-your-own-on-the-api-1g2k"&gt;MiniMax's models&lt;/a&gt; in its catalog alongside DeepSeek, Qwen, Kimi, and GLM, which is worth checking against the direct-and-reseller numbers above the same way you'd check any other candidate, rather than assumed to be cheaper or pricier without confirming.&lt;/p&gt;

&lt;p&gt;TL;DR: MiniMax's official M3 rate is $0.30/$1.20 per million tokens (a permanently-discounted price from a $0.60/$2.40 list rate), with cache reads at roughly a fifth of standard input cost and two different fast-response mechanisms (M3's 1.5x priority tier vs M2.7's 2x highspeed variant) that aren't interchangeable. Most resellers price within a few cents of that official rate — a handful charge 2-4x more, which is worth noticing rather than assuming away.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>developers</category>
    </item>
    <item>
      <title>I Almost Built Against the Wrong Kimi AI Pricing — Here's What's Actually Free vs Metered</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Wed, 09 Sep 2026 03:35:17 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/i-almost-built-against-the-wrong-kimi-ai-pricing-heres-whats-actually-free-vs-metered-5f6j</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/i-almost-built-against-the-wrong-kimi-ai-pricing-heres-whats-actually-free-vs-metered-5f6j</guid>
      <description>&lt;p&gt;I'd been using Kimi's chat app for a few weeks — unlimited basic chat, no account friction, genuinely free — and when I decided to wire a small internal tool up to Kimi's API, I half-assumed the free part would carry over. It doesn't. The consumer app and the developer API are billed through completely separate systems, and finding that out mid-build cost me an afternoon of confused digging before I had an actual, current picture of what Kimi costs depending on which door you walk through.&lt;/p&gt;

&lt;p&gt;Worth saying up front: this specific confusion isn't just me being sloppy. When one research group tested how well AI search tools answered basic Kimi pricing questions, three out of four gave answers describing limits that didn't match Moonshot's actual system, and one carried over a pricing tier that doesn't exist, sourced from a listicle. Stale blog posts describing an old free-tier structure that Moonshot has since changed are apparently still getting cited months after they stopped being accurate. So if you've read a confident-sounding pricing summary somewhere recently, it's worth treating as a starting point to verify rather than a settled fact — including, at some point, this one.&lt;/p&gt;

&lt;p&gt;Two products, two separate bills&lt;/p&gt;

&lt;p&gt;The consumer Kimi app runs on a free-forever tier called Adagio: unlimited basic chat, file uploads, and web browsing at no cost, though heavier agentic features are capped even here. Above that sit four paid membership tiers — Moderato at $19/month, Allegretto at $39/month, Allegro at $99/month, and Vivace at $199/month — which mostly unlock more agent credits, Kimi Code usage, Deep Research sessions, and a larger context window rather than access to a fundamentally different model.&lt;/p&gt;

&lt;p&gt;None of that membership includes API access. The developer API is billed entirely separately, on a prepaid, pay-per-token basis, with no permanent free tier. You fund the account before you can call it — and the specific top-up amount and accepted payment methods appear to vary somewhat by account region, so it's worth confirming the current minimum on &lt;a href="https://platform.kimi.ai/docs/pricing/chat" rel="noopener noreferrer"&gt;Moonshot's own pricing page&lt;/a&gt; rather than trusting a specific number from any guide, including this one, since that's exactly the kind of detail that goes stale fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpgcwv0v1nk10ubfhqdv.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpgcwv0v1nk10ubfhqdv.png" alt="Two separate billing systems: a free consumer app and a metered developer API" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What the API actually costs right now&lt;/p&gt;

&lt;p&gt;Moonshot's current flagship, kimi-k3, is priced at $3 per million input tokens and $15 per million output tokens, with a $0.30 rate for cached input. The coding-tuned kimi-k2.7-code runs cheaper: $0.95 for uncached input, $0.19 for cached input, and $4 for output per million tokens.&lt;/p&gt;

&lt;p&gt;Worth knowing if you came into this expecting "the cheap option": K3's pricing is genuinely frontier-tier now, not budget-tier. Independent benchmarking has flagged it as priced above the median for its performance class, and it's also reportedly a fairly verbose model — since reasoning tokens get billed as output, that verbosity compounds the bill in a way a shorter, less chatty model wouldn't. Kimi's reputation as "the affordable one" is a couple of model generations out of date at this point; DeepSeek and Qwen occupy roughly the price position Kimi used to hold.&lt;/p&gt;

&lt;p&gt;For the fuller picture of how these same models get priced once resold through routing platforms — which sometimes undercuts the official rate and sometimes doesn't — that's its &lt;a href="https://dev.to/noah_bennett_85dfd9bed51e/kimi-k2-price-by-platform-why-the-same-model-costs-2-4x-depending-on-where-you-call-it-4o7n"&gt;own deep dive&lt;/a&gt; I've covered separately rather than repeating the full table here.&lt;/p&gt;

&lt;p&gt;The payment method problem nobody mentions upfront&lt;/p&gt;

&lt;p&gt;This is the part that actually surprised me most, and it's a genuinely practical detail: Moonshot's documentation describes WeChat Pay and Alipay as the funding methods for individual accounts, without clearly documenting card payment support, and notes that available options vary by account region. If you're outside a region where those payment rails are the default, or you simply don't have accounts set up for them, that's a real access barrier before you've written a single line of code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff79j60ak2ah7hztyp3hr.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff79j60ak2ah7hztyp3hr.png" alt="Payment method mismatch as an access barrier before pricing even matters" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workaround most people end up using is routing through a reseller platform that accepts standard card payment instead — OpenRouter is the one most commonly mentioned for this specifically, and gateways like &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt;, which also lists Kimi models, serve the same purpose: standard card billing, no separate account with Moonshot required, at the cost of an extra routing layer and that platform's own fee structure instead of Moonshot's direct rate. It's not automatically cheaper — sometimes it is, sometimes it isn't — but for anyone whose actual blocker is "I don't have WeChat Pay, and Moonshot's docs don't mention Visa," it solves the actual problem rather than the pricing one.&lt;/p&gt;

&lt;p&gt;A minimal sanity-check script before you commit&lt;/p&gt;

&lt;p&gt;Before funding an account on either side — consumer membership or API — this is the calculation that would have saved me the confused afternoon: figure out which one you actually need before paying for either.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// kimi-spend-check.js — a minimal reality check before funding an account&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;estimateApiCost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cachedTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k2.7-code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k2.7-code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.19&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown model: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regularInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cachedTokens&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regularInput&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: a moderate coding-agent session&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;estimated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;estimateApiCost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cachedTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="nx"&gt;_000&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;k2.7-code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Estimated cost: $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;estimated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;estimated&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This fits comfortably inside a low-tier API top-up — you likely don't need a consumer membership at all.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is real usage — confirm your top-up amount and payment method on Moonshot's current pricing page before committing.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of this isn't precision — it's forcing yourself to answer "am I actually building something that needs the &lt;a href="https://dev.to/_8242e3013b3a729b9bb98/getting-a-kimi-api-key-is-the-easy-part-heres-the-rate-limit-gotcha-that-actually-bites-4482"&gt;metered API&lt;/a&gt;, or was I trying to get consumer-app usage for free" before you fund either account. For me, the answer was that my internal tool needed maybe a few dollars a month of actual API usage, which meant the whole "which membership tier" question I'd been stuck on for an hour didn't apply to my situation at all — I needed API credits, not a subscription.&lt;/p&gt;

&lt;p&gt;The actual takeaway&lt;/p&gt;

&lt;p&gt;Kimi's chat app being genuinely free is real and not a bait-and-switch — the Adagio tier's basic chat, uploads, and browsing aren't secretly metered. What trips people up is assuming that generosity extends to the API, when it's a completely separate, prepaid, no-free-tier system with its own current rate card that's worth checking directly rather than trusting a cached search result or an older guide. If your actual blocker turns out to be payment methods rather than price, that's a different problem with a different fix than picking a cheaper model.&lt;/p&gt;

&lt;p&gt;TL;DR: Kimi's consumer chat app has a genuinely free Adagio tier, but the developer API is a completely separate, prepaid, no-free-tier system — currently $3/$15 per million tokens for the flagship K3 model, cheaper for K2.7-code — and payment method restrictions (WeChat Pay/Alipay, card support varies by region) are often the real barrier, not the price itself.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Tried 5 OpenRouter Alternatives So You Don't Have To</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:12:10 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/i-tried-5-openrouter-alternatives-so-you-dont-have-to-27km</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/i-tried-5-openrouter-alternatives-so-you-dont-have-to-27km</guid>
      <description>&lt;p&gt;I set myself a rule for this one: no comparison table copied from someone else's blog post. If I was going to write about &lt;a href="https://dev.to/felixai/why-developers-look-for-an-openrouter-alternative-and-what-actually-changes-when-you-switch-552d"&gt;OpenRouter alternatives&lt;/a&gt;, I'd actually wire each one up to a real script and see what broke.&lt;/p&gt;

&lt;p&gt;The project was small on purpose — a CLI tool that summarizes GitHub issues into a weekly digest, making maybe a few hundred model calls a week. Low enough stakes that I could afford to spend an evening swapping providers in and out without anything important depending on the outcome. Here's what actually happened with each one.&lt;/p&gt;

&lt;p&gt;The baseline: what I was already running&lt;/p&gt;

&lt;p&gt;My digest tool was already using OpenRouter with a plain fetch call — no SDK, just a base URL, an API key, and a model string. That's the part worth keeping in mind through this whole post: because most of these alternatives speak the same &lt;a href="https://dev.to/noah_bennett_85dfd9bed51e/the-real-value-of-an-openai-compatible-api-gateway-isnt-cost-its-reducing-blast-radius-1nhh"&gt;OpenAI-compatible&lt;/a&gt; /chat/completions shape, "trying" one mostly meant changing two environment variables, not rewriting the client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client.js — the one function every gateway below had to work with unmodified&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&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;GATEWAY_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chat/completions`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;GATEWAY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chatCompletion&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every gateway below got tested against this exact function. Anything that needed more than a .env change to work is called out explicitly, because that friction is itself useful information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Felv80veleo61zvwf773x.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Felv80veleo61zvwf773x.png" alt="A single client function tested unmodified against multiple gateway candidates" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LiteLLM (self-hosted)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://docs.litellm.ai/docs/proxy/docker_quick_start" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; is the one genuinely different entry on this list, because it isn't a hosted service you point at — it's an open-source proxy you run yourself. I spun it up locally with the provided Docker image and pointed my client's GATEWAY_BASE_URL at &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;. Setup took longer than every other option here, mostly because I had to configure provider API keys inside LiteLLM's own config file rather than just using one gateway key.&lt;/p&gt;

&lt;p&gt;What that buys you: the request never leaves infrastructure you control, which matters if compliance or data residency is the actual reason you're looking at alternatives. What it costs you: you're now responsible for keeping a proxy running, patched, and monitored. For my low-stakes CLI tool this was overkill. For a team with an actual compliance requirement, it's close to the point of the whole exercise.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Together AI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Together AI is a direct inference provider rather than a multi-vendor router — it hosts open models itself rather than reselling access to other labs' APIs. The client code above worked against it without modification once I had a key, which was expected since it's OpenAI-compatible too. The catch, and it's not really a flaw, is that it's not a like-for-like OpenRouter replacement: you get the models Together AI actually runs, not a single key covering dozens of providers. If you've already settled on an open-weight model family and don't need multi-provider flexibility, this removes a routing hop entirely. If you still want to compare models across labs from one key, it's the wrong tool for that specific job.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Requesty&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Requesty was the closest thing to a drop-in OpenRouter swap in terms of experience — same "one key, many models" pitch, same request shape, working against my unmodified client immediately. The meaningful difference I found reading through their docs while testing was the fee structure: a flat token-based markup rather than OpenRouter's credit-purchase-fee-plus-capped-BYOK model. Whether that's cheaper depends entirely on your usage pattern, which is exactly the kind of thing worth running your own numbers on rather than trusting either vendor's framing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RouteAI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt; also worked against the unmodified client on the first try — same OpenAI-compatible shape, key and base URL swap, nothing else to configure. It covers a different slice of the model landscape than some of the others here, with access to models like DeepSeek, Qwen, Kimi, GLM, and MiniMax, and bills per call with no monthly fee. For my digest tool specifically, which mostly needed a solid mid-tier model for summarization rather than a frontier reasoning model, this was one of the two gateways I actually left running after the evening was over — not because it's objectively "the best," but because it matched what this particular project needed without extra setup overhead.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloudflare AI Gateway&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.cloudflare.com/products/ai-gateway/" rel="noopener noreferrer"&gt;Cloudflare AI Gateway&lt;/a&gt; is a slightly different shape than the rest: it's designed to sit in front of your existing provider calls — including OpenRouter itself — adding caching, logging, and rate limiting rather than replacing the underlying model access entirely. I tested it by pointing my client at a Cloudflare-proxied endpoint in front of an existing OpenAI key. It's less "an OpenRouter alternative" in the strict sense and more "a layer you might put in front of any of these," which is worth knowing before you file it under the wrong category the way a couple of comparison lists do.&lt;/p&gt;

&lt;p&gt;What I actually kept running&lt;/p&gt;

&lt;p&gt;After the evening, my digest tool ended up split between two gateways: RouteAI for the routine weekly summarization calls, and my original OpenRouter setup left in place as a fallback for anything the primary model couldn't handle well. Nothing here was a dramatic verdict — the honest result of testing five alternatives to a small, low-volume tool is that several of them work fine, and the differences that matter (fee structure, self-hosting, model coverage) depend entirely on what you're actually building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa754olfmmnq1xcirm193.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa754olfmmnq1xcirm193.png" alt="Two gateways left running together after testing, one primary and one fallback" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I'd been building something with real compliance requirements, LiteLLM would have been the only serious option on this list. If I'd already committed to one open-weight model family, Together AI would have removed a layer I didn't need. For a small tool where "one key, reasonable pricing, no extra setup" was the whole requirement, RouteAI and Requesty were the two that just worked without me having to think about it further — which, for a project this size, was the actual bar.&lt;/p&gt;

&lt;p&gt;The part worth repeating&lt;/p&gt;

&lt;p&gt;None of this required a benchmark suite or a spreadsheet of features. Because the OpenAI-compatible shape is close to universal across this category, the real cost of testing an alternative is usually a .env file and twenty minutes, not a rewrite. If you're evaluating OpenRouter alternatives for your own project, that's the cheap experiment worth running before reading someone's ranked list — including this one.&lt;/p&gt;

&lt;p&gt;TL;DR: I swapped five OpenAI-compatible gateways into an existing small project with minimal code changes — LiteLLM needed real self-hosting setup, Together AI is a direct provider rather than a router, and Requesty and RouteAI worked as near drop-in OpenRouter replacements, with RouteAI ending up as the one I kept running for routine calls.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Started Using OpenRouter Kimi Models (And What the API Bill Taught Me)</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:31:14 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/how-i-started-using-openrouter-kimi-models-and-what-the-api-bill-taught-me-530p</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/how-i-started-using-openrouter-kimi-models-and-what-the-api-bill-taught-me-530p</guid>
      <description>&lt;p&gt;Three weeks into building a coding assistant side project, my OpenRouter usage dashboard showed a number I didn't expect. I'd been testing Kimi K2 models for a code-review bot, running maybe 40-50 requests a day during development, and the spend was already noticeably higher than my rough estimate. Nothing was broken. I just hadn't actually looked at what each model call cost until the invoice made me look.&lt;/p&gt;

&lt;p&gt;That's the problem with routing through an aggregator like OpenRouter: it's genuinely convenient — one API key, one &lt;a href="https://dev.to/noah_bennett_85dfd9bed51e/the-real-value-of-an-openai-compatible-api-gateway-isnt-cost-its-reducing-blast-radius-1nhh"&gt;OpenAI-compatible endpoint&lt;/a&gt;, dozens of models including Moonshot AI's Kimi K2 family — but the convenience can quietly hide the cost details you'd normally pay attention to if you were calling a single provider directly.&lt;/p&gt;

&lt;p&gt;This is the story of what I found when I actually sat down and compared numbers, and the small gateway script I built afterward so it wouldn't happen again.&lt;/p&gt;

&lt;p&gt;Why I picked OpenRouter Kimi in the first place&lt;/p&gt;

&lt;p&gt;I wanted to try Kimi K2 for a specific reason: it's strong at long-context code tasks, and my bot needed to review pull requests that sometimes ran over 100K tokens of diff and surrounding context. Signing up for Moonshot's own API directly meant a separate account, a separate key, and a separate billing relationship — not a big deal on its own, but I already had three other provider keys in my .env file for different experiments. OpenRouter Kimi access looked like the fastest way to just try the model without adding a fourth vendor relationship for a test that might not even pan out.&lt;/p&gt;

&lt;p&gt;That's a completely reasonable reason to reach for an aggregator, and for prototyping it worked fine. The trouble started once the prototype turned into something I actually wanted to keep running.&lt;/p&gt;

&lt;p&gt;What the pricing actually looks like&lt;/p&gt;

&lt;p&gt;Once the bill nudged me to pay attention, I pulled together the per-million-token pricing for the Kimi models I was actually calling. On &lt;a href="https://openrouter.ai/models" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;, roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kimi K2.5 (262K context): about $0.375 input / $2.025 output per 1M tokens&lt;/li&gt;
&lt;li&gt;Kimi K2.6 (262K context): about $0.68 input / $3.41 output per 1M tokens&lt;/li&gt;
&lt;li&gt;Kimi K2.7 Code (262K context): about $0.75 input / $3.50 output per 1M tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm8hqbe6f1aa1plx1g0r3.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm8hqbe6f1aa1plx1g0r3.png" alt="A side-by-side comparison of AI API pricing across different routing platforms" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of those numbers are outrageous in isolation. The problem was my usage pattern: code review means sending large diffs as input and getting back full explanations as output, so I was hitting the more expensive side of that ratio on almost every call, and I'd defaulted to the newer K2.7 Code variant without checking whether K2.5 was "good enough" for the job.&lt;/p&gt;

&lt;p&gt;Digging a little further, I noticed something else: the same Kimi models are resold through several different API aggregator platforms, and the per-token pricing isn't uniform. Some platforms priced the same Kimi K2.5 model noticeably lower on the input side, others were closer to OpenRouter's numbers but offered cheaper cached-input pricing (which matters a lot if you're re-sending the same system prompt or repo context on every call). None of this is a criticism of OpenRouter specifically — pricing differences across resale layers are just how this market works right now. But it meant that "OpenRouter Kimi" wasn't automatically the cheapest or most efficient way to reach the same underlying model.&lt;/p&gt;

&lt;p&gt;The real lesson: don't hardcode a single endpoint&lt;/p&gt;

&lt;p&gt;The deeper issue wasn't really about which platform was cheapest that week. It was that my code had the OpenRouter base URL and model string hardcoded directly into my request logic. If OpenRouter had an outage, or if I wanted to switch to a lower-cost route for the same model, I'd have to go edit application code and redeploy. That's a bad place to be for something as volatile as AI API pricing and availability currently are.&lt;/p&gt;

&lt;p&gt;So I rebuilt the request layer as a small middleware gateway instead: one internal interface, with the actual provider and model selection handled by configuration rather than scattered through the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gateway.js — a minimal OpenAI-compatible request gateway with fallback&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROVIDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;PRIMARY_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&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;PRIMARY_API_KEY&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="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;PRIMARY_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g. "kimi-k2.6"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;FALLBACK_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&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;FALLBACK_API_KEY&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="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;FALLBACK_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing 'messages' in request body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'messages' must be a non-empty array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;callProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chat/completions`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;payload&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="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; responded with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;PROVIDERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_served_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[gateway] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// fall through to the next provider&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;502&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All providers failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chatCompletion&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple — no queueing, no retries with backoff, no streaming support yet. What it gave me immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request validation before anything goes out, so malformed payloads fail fast instead of burning a paid call.&lt;/li&gt;
&lt;li&gt;A single place to swap models or providers by changing environment variables, not application code.&lt;/li&gt;
&lt;li&gt;Basic failover: if the primary route is down or rate-limited, the request tries the next one instead of just failing.
For my use case, the primary route stayed pointed at Kimi K2.6 through my usual aggregator, and I set the fallback to a second OpenAI-compatible endpoint so a single provider hiccup wouldn't take down the whole bot mid-review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadfybap6gup8syzdrfgi.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadfybap6gup8syzdrfgi.png" alt="A request gateway routing between a primary and fallback AI API provider" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where RouteAI fit into this&lt;/p&gt;

&lt;p&gt;While setting up that fallback slot, I tried &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;RouteAI&lt;/a&gt; as one of the endpoints, mainly because it exposes the same OpenAI-compatible /chat/completions shape, so it dropped into the gateway above without any extra adapter code — I only had to change the base URL and key. It's not a magic fix for the pricing question; it's still just another routing layer with its own per-model rates, and the same "check the actual numbers" advice from earlier in this post applies to it too. But having a second, drop-in-compatible option in the PROVIDERS array meant I wasn't stuck if my primary route had a bad day, which was really the whole point of the exercise.&lt;/p&gt;

&lt;p&gt;What I'd tell someone starting the same way&lt;/p&gt;

&lt;p&gt;If you're reaching for OpenRouter Kimi (or any aggregator) for the first time, a few things I wish I'd done from day one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check actual per-token pricing for the specific model variant you're calling, not just the model family. K2.5, K2.6, and K2.7 Code have different rates, and the difference compounds fast at scale.&lt;/li&gt;
&lt;li&gt;Separate input-heavy and output-heavy workloads mentally. Code review, summarization, and RAG-style tasks tend to be input-heavy; generation and long-form writing tend to be output-heavy. Pick the cheaper side of a model's pricing to optimize for based on your actual traffic.&lt;/li&gt;
&lt;li&gt;Don't hardcode a single base URL into your application logic. Even a two-line abstraction like the one above buys you the ability to switch providers without a redeploy.&lt;/li&gt;
&lt;li&gt;Watch cached-input pricing if you resend the same context repeatedly. Some platforms discount cached tokens heavily; if your prompts share a large static prefix (system instructions, repo context), this can matter more than the headline input rate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic advice — it's the same discipline people already apply to cloud infrastructure costs. AI API spend just hasn't caught up to that habit yet for a lot of us, myself included until that first invoice.&lt;/p&gt;

&lt;p&gt;TL;DR: I hardcoded OpenRouter Kimi K2 calls into a side project, got surprised by the actual per-token costs, and ended up building a tiny OpenAI-compatible gateway with validation and fallback so I could compare pricing and switch providers without touching application code.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.fastrouteai.com" rel="noopener noreferrer"&gt;https://www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a Fallback System for OpenRouter's Free API After It Silently Broke</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Tue, 01 Sep 2026 06:17:48 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/i-built-a-fallback-system-for-openrouters-free-api-after-it-silently-broke-3hdp</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/i-built-a-fallback-system-for-openrouters-free-api-after-it-silently-broke-3hdp</guid>
      <description>&lt;p&gt;The Script That Failed Without Warning&lt;/p&gt;

&lt;p&gt;My nightly batch job had been running fine for months on OpenRouter's free API. Then it started throwing 429 errors, and nothing in my code had changed. The actual cause: the specific :free model I'd hardcoded had quietly stopped being offered for free. No warning, no deprecation notice — it just started failing.&lt;/p&gt;

&lt;p&gt;Here's the fallback system I built afterward, and the mechanics of OpenRouter's free tier that would've saved me the debugging session if I'd understood them going in.&lt;/p&gt;

&lt;p&gt;How the Free Tier Actually Works&lt;/p&gt;

&lt;p&gt;Free models on OpenRouter carry a :free suffix and cost $0 per token, but the catalog rotates — providers add and pull free variants regularly, and a model that's free today isn't guaranteed to stay that way. Rate limits apply on top of that: 20 requests per minute on free models, and a daily cap that depends on your account history — 50 requests a day if you've never purchased credits, or 1,000 a day once you've bought at least $10 in credits at any point (that higher limit is permanent even if your balance drops back to zero later).&lt;/p&gt;

&lt;p&gt;None of that is hidden — it's in OpenRouter's own API documentation — but it's easy to build against a single hardcoded model ID and forget the catalog isn't static.&lt;/p&gt;

&lt;p&gt;Getting an &lt;a href="https://dev.to/felixai/setting-up-my-first-openrouter-ai-api-key-working-code-and-two-mistakes-i-made-4m65"&gt;OpenRouter API Key&lt;/a&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="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign up at openrouter.ai, no card required, and generate a key from the dashboard. The OpenRouter API is OpenAI-compatible, so your existing client library works with just a different base_url:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&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="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENROUTER_API_KEY&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://openrouter.ai/api/v1&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;The Broken Version&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_summary&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;some-provider/some-model:free&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works until the hardcoded model stops being free or gets removed from the catalog — at which point every call fails with no fallback, exactly what happened to me.&lt;/p&gt;

&lt;p&gt;The Fallback Version&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;time&lt;/span&gt;

&lt;span class="n"&gt;FREE_MODEL_FALLBACKS&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;provider-a/model-x:free&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;provider-b/model-y:free&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;provider-c/model-z:free&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_summary&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="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FREE_MODEL_FALLBACKS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries_per_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries_per_model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;429&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# brief pause on rate limit before moving on
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;All fallback models failed. Last error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last_error&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57fzjzqhfoa48qgo95pv.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57fzjzqhfoa48qgo95pv.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;br&gt;
If the first model in the list fails — rate limited, deprecated, or removed from the free catalog entirely — it moves to the next before giving up. This is a five-line change from the broken version, not a rewrite, and it turned a script that failed silently into one that degrades gracefully.&lt;/p&gt;

&lt;p&gt;Tracking Rate Limits Across Multiple Scripts&lt;/p&gt;

&lt;p&gt;The rate limit that actually caught me wasn't the daily cap — it was the 20-requests-per-minute limit, hit because I had two separate scripts calling the same key around the same time without either one tracking the other's usage. A simple shared counter fixes this if you're running more than one process against the same key:&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;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="n"&gt;request_timestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&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;rate_limited_call&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="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_per_minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# slight buffer under 20
&lt;/span&gt;    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;request_timestamps&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;request_timestamps&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="o"&gt;&amp;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;request_timestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_timestamps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_per_minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sleep_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;request_timestamps&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sleep_time&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;request_timestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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;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="n"&gt;model&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="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcu6guma4ayq8uzdcont.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcu6guma4ayq8uzdcont.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
What I'd Check Before Building on This&lt;br&gt;
Don't hardcode a single free model ID for anything you plan to run long-term — check &lt;a href="https://openrouter.ai/models" rel="noopener noreferrer"&gt;OpenRouter's current free-model&lt;/a&gt; list before shipping, since the roster shifts month to month&lt;br&gt;
Build the fallback list in from the start, not after your first silent failure — it's a small amount of code either way&lt;br&gt;
Track rate limits across every process sharing a key, not just within a single script — this is what actually broke my setup, not the daily cap&lt;br&gt;
Where This Left Me&lt;/p&gt;

&lt;p&gt;Once I had the fallback logic working reliably, I also tested the same workload through &lt;a href="//www.fastrouteai.com"&gt;RouteAI&lt;/a&gt;, mainly to compare pricing and model availability against what I'd gotten used to on OpenRouter's free tier — that's a decision worth making on your own project's volume and budget, not something either option deserves credit for by default. The fallback pattern above is useful regardless of which gateway or provider you're calling.&lt;/p&gt;

&lt;p&gt;TL;DR: OpenRouter's free API models rotate and carry real rate limits (20/min, 50-1000/day depending on account history) — hardcoding a single :free model ID will eventually break silently. Fallback code and rate-limit tracking above turn that into a graceful degradation instead of a 2am debugging session.&lt;/p&gt;

&lt;p&gt;Worth exploring if this is relevant to your stack: &lt;a href="http://www.fastrouteai.com" rel="noopener noreferrer"&gt;www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Tried GLM for the First Time. Here's the Minimal Setup That Actually Worked.</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Fri, 28 Aug 2026 02:45:55 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/i-tried-glm-for-the-first-time-heres-the-minimal-setup-that-actually-worked-579l</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/i-tried-glm-for-the-first-time-heres-the-minimal-setup-that-actually-worked-579l</guid>
      <description>&lt;p&gt;Everything I Found Assumed I Already Knew What GLM Was&lt;/p&gt;

&lt;p&gt;I kept seeing GLM mentioned alongside models I already knew — DeepSeek, Qwen — and every time I looked it up, the documentation jumped straight into advanced parameters without ever showing a plain, working first call. So here's the version of that first call I wish I'd found.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@hamimelon2026/the-glm-llm-family-has-five-years-of-history-most-people-discover-in-five-minutes-a09c8b3f4604?sharedUserId=hamimelon2026" rel="noopener noreferrer"&gt;GLM&lt;/a&gt; is a large language model family originally developed by a Tsinghua University research group, now maintained commercially as Z.ai. It's OpenAI-API-compatible, which meant I didn't need a new SDK — just a different base URL and model name.&lt;/p&gt;

&lt;p&gt;Getting an API Key&lt;/p&gt;

&lt;p&gt;Sign up on &lt;a href="https://bigmodel.cn/" rel="noopener noreferrer"&gt;Z.ai&lt;/a&gt;'s platform (or through BigModel, the mainland-facing endpoint) and generate a key from your account dashboard. Same basic flow as most other LLM providers at this point — nothing GLM-specific to worry about 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="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Minimal Working Call&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&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="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GLM_API_KEY&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://api.z.ai/api/paas/v4&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&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;glm-5.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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;Explain what a hash map is in two sentences.&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="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkw41royx5ces6q5tbmwt.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkw41royx5ces6q5tbmwt.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
That's the whole first call. If you've used any other OpenAI-compatible provider, this will look identical except for the base_url and model string.&lt;/p&gt;

&lt;p&gt;The One Thing That Actually Tripped Me Up&lt;/p&gt;

&lt;p&gt;The current GLM models default to an extended "thinking" mode — the model generates an internal reasoning trace before answering, which adds latency you might not expect for a simple prompt. On &lt;a href="https://dev.to/felixai/glm-53s-api-breaks-a-common-glm-52-pattern-heres-the-fix-4eg1"&gt;GLM-5.3&lt;/a&gt; specifically, this can't be fully turned off (older versions like 5.2 allowed a disabled setting; 5.3 replaced that with graduated effort levels instead). If you're just testing your first call and it feels slower than you expected, that's likely why — not a network issue, not a bug in your code.&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;glm-5.3&lt;/span&gt;&lt;span class="sh"&gt;"&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;Explain what a hash map is in two sentences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;extra_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thinking&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;type&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;enabled&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;effort&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;low&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa63l42tbcak3ok3ozsrb.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa63l42tbcak3ok3ozsrb.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
For a simple factual prompt like the one above, effort: low gets you close to the fastest response the model can give — worth setting explicitly rather than leaving it on the default if latency matters for what you're building.&lt;/p&gt;

&lt;p&gt;A Slightly More Realistic Example&lt;/p&gt;

&lt;p&gt;Most tutorials stop at a single hardcoded prompt, which doesn't tell you much about actually building something. Here's a small wrapper that takes a variable prompt and handles the basic case of an empty or malformed response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask_glm&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;effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;glm-5.3&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="n"&gt;extra_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thinking&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;type&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;enabled&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;effort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;effort&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No response content returned.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;Request failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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_glm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize the plot of a story about a lighthouse keeper in one sentence.&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;Nothing sophisticated — just enough structure to actually build on top of, rather than a single throwaway print() call.&lt;/p&gt;

&lt;p&gt;Where I Took This After the First Call&lt;/p&gt;

&lt;p&gt;Once I had this working, I wanted to see how GLM's output compared to a couple of models I was already using for a small side project, without setting up a completely separate client and auth flow for each one. I ended up testing GLM through &lt;a href="//www.fastrouteai.com"&gt;RouteAI&lt;/a&gt; alongside those other models — same request format shown above, just a different base_url and model name per test. That's a convenience note for anyone doing model comparisons, not a requirement for getting GLM working on its own; the code above runs fine against Z.ai's endpoint directly.&lt;/p&gt;

&lt;p&gt;If You're Setting This Up Yourself&lt;br&gt;
Start with the direct endpoint before adding anything else — get one working call before worrying about comparisons or routing&lt;br&gt;
If your first call feels slow, check your thinking/effort settings before assuming something's broken&lt;br&gt;
The OpenAI-compatible format means most of what you already know about calling other LLM APIs transfers directly — don't expect to relearn much&lt;/p&gt;

&lt;p&gt;TL;DR: GLM's API is OpenAI-compatible, so getting started is mostly a different base_url and model name. The main gotcha for first-timers is the default thinking mode adding unexpected latency — set an explicit effort level if speed matters for your prompt. Full minimal example above.&lt;/p&gt;

&lt;p&gt;Worth exploring if this is relevant to your stack: &lt;a href="http://www.fastrouteai.com" rel="noopener noreferrer"&gt;www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Ran the Same 40 Prompts Through Qwen2.5 and Qwen3. Here's the Script and Results.</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:38:53 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/i-ran-the-same-40-prompts-through-qwen25-and-qwen3-heres-the-script-and-results-5b0p</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/i-ran-the-same-40-prompts-through-qwen25-and-qwen3-heres-the-script-and-results-5b0p</guid>
      <description>&lt;p&gt;Why I Didn't Just Trust the Benchmarks&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qwen.ai/blog?id=qwen-image-3.0" rel="noopener noreferrer"&gt;Qwen3&lt;/a&gt;'s published benchmarks look like a clean win over Qwen2.5 — real gains on MMLU-Pro, MATH, and coding tasks, plus a much larger training set (roughly 36 trillion tokens versus Qwen2.5's 18 trillion) and support for far more languages. On paper, swapping in Qwen3 should have been an easy call for my project.&lt;/p&gt;

&lt;p&gt;I still wanted my own numbers, because a general benchmark tells you what a model can do on average, not what it does on the specific, sometimes-weird prompts your actual application receives. So I wrote a small script to run the same prompt set through both models and log the results side by side.&lt;/p&gt;

&lt;p&gt;The Eval Script&lt;/p&gt;

&lt;p&gt;Nothing fancy — this loops through a list of test prompts, calls both models with each one, and writes the outputs to a CSV for manual review.&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;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&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="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;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;YOUR_API_KEY&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://dashscope.aliyuncs.com/compatible-mode/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Qwen's own endpoint
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;test_prompts&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;Classify this support message: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;My payment failed twice this week.&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;A user says their invoice total doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t match what they were quoted. What category does this fall under, and what follow-up question would you ask?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... add your own prompts here
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;models_to_compare&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;qwen2.5-72b-instruct&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;qwen3-235b-a22b&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;qwen3-235b-a22b-instruct&lt;/span&gt;&lt;span class="sh"&gt;"&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;run_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
                &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;prompt&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# basic pacing to avoid rate limits
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models_to_compare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen_comparison.csv&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;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&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;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14ees7enizm8nnavsran.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14ees7enizm8nnavsran.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
I deliberately kept temperature=0 to reduce randomness between runs — you want the comparison to reflect model differences, not sampling noise.&lt;/p&gt;

&lt;p&gt;What I Actually Found&lt;/p&gt;

&lt;p&gt;Out of 40 prompts specific to my ticket-classification use case, plain Qwen2.5-72B-Instruct and Qwen3-235B-A22B (the default, hybrid-thinking variant) landed close to each other overall — Qwen3 won clearly on the more ambiguous, multi-step prompts, but on short, unambiguous ones, Qwen2.5's answers came back faster and were occasionally more directly usable without extra parsing.&lt;/p&gt;

&lt;p&gt;The bigger difference showed up when I added qwen3-235b-a22b-instruct — the non-thinking Instruct variant Qwen released separately — as a third comparison point. It matched Qwen2.5's speed on simple prompts while still outperforming it on the harder ones, which lines up with something I found afterward while reading about the initial Qwen3 release: early evaluations of the original hybrid-thinking Qwen3 models showed them underperforming Qwen2.5 on some agentic and instruction-following benchmarks, which is part of why the dedicated Instruct and Thinking variants exist as a separate release.&lt;/p&gt;

&lt;p&gt;That's a specific, checkable claim for your own use case, not a general one — the point isn't "Qwen3-Instruct is better," it's that "Qwen3" isn't one model, and comparing against the wrong variant will give you a misleading result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7j3xembq3yh9z6kce38.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7j3xembq3yh9z6kce38.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
A Few Things Worth Watching in Your Own Comparison&lt;br&gt;
Pin temperature=0 unless your task actually benefits from sampling variance — otherwise you're comparing noise, not models&lt;br&gt;
Test the actual variant you'd deploy, not just whichever one your first pip install example happens to reference — Qwen3's hybrid, Instruct, and Thinking variants behave differently enough that lumping them together will skew your conclusion&lt;br&gt;
Weight your prompt set toward your real traffic distribution — if 90% of your inputs are simple, a benchmark heavy on hard reasoning tasks won't tell you much about your actual cost/latency tradeoff&lt;br&gt;
Where I Took This Next&lt;/p&gt;

&lt;p&gt;Once I had this script working against Qwen's own endpoint, I wanted to run the same comparison against a couple of other providers, mostly out of curiosity about whether my results were Qwen-specific or held more generally. I ended up routing the same script through &lt;a href="//www.fastrouteai.com"&gt;RouteAI&lt;/a&gt; instead of maintaining separate client configs per provider — the eval loop above didn't change at all, just the base_url and the model names in the list. That's a convenience thing, not a result — the comparison numbers above came from testing directly against Qwen's models.&lt;/p&gt;

&lt;p&gt;TL;DR: Qwen3's benchmark gains are real, but "Qwen3" isn't a single model — the hybrid-thinking default, Instruct, and Thinking variants perform differently enough that comparing the wrong one against Qwen2.5 will mislead you. Full eval script above; test on your own prompts before deciding.&lt;/p&gt;

&lt;p&gt;Here's the tool I referenced in this post: &lt;a href="http://www.fastrouteai.com" rel="noopener noreferrer"&gt;www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Use DeepSeek's API: A Working Python Example From My First Side Project</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Tue, 25 Aug 2026 06:41:21 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/how-to-use-deepseeks-api-a-working-python-example-from-my-first-side-project-3928</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/how-to-use-deepseeks-api-a-working-python-example-from-my-first-side-project-3928</guid>
      <description>&lt;p&gt;The Tutorial I Wish I'd Had&lt;/p&gt;

&lt;p&gt;I spent about forty minutes stuck on a 401 Unauthorized error the first time I tried to use DeepSeek's API. The fix took ten seconds once I found it. The forty minutes was me not knowing where to look.&lt;/p&gt;

&lt;p&gt;I was building a small side project — a script that summarizes long PDFs into short study notes, mostly for going through research papers faster. DeepSeek came up as a cheap, capable option, and I figured I'd document the actual steps I took, errors included, since most "getting started" guides skip the part where things don't work the first time.&lt;/p&gt;

&lt;p&gt;Step 1: Get an API Key&lt;/p&gt;

&lt;p&gt;Sign up at DeepSeek's platform and generate an &lt;a href="https://api-docs.deepseek.com/zh-cn/" rel="noopener noreferrer"&gt;API key&lt;/a&gt; from your account dashboard. Keep it somewhere you won't accidentally commit to git — I use a .env file and python-dotenv, which is where my forty minutes actually went. (I'd put the key in the wrong environment variable name. Double-check this before you assume the API itself is broken.)&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="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, openai — DeepSeek's API is OpenAI-compatible, so you use the same SDK, just pointed at a different base URL.&lt;/p&gt;

&lt;p&gt;Step 2: Your First Request&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&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="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&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_API_KEY&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://api.deepseek.com&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&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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;system&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;You are a helpful assistant that summarizes text concisely.&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize this in three bullet points: [your text here]&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="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft8qarueny3khfhnv3xgy.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft8qarueny3khfhnv3xgy.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;br&gt;
That's the whole thing for a basic call. If you're coming from OpenAI's own API, this will look almost identical — that's intentional, and it's why the SDK doesn't need to change.&lt;/p&gt;

&lt;p&gt;Step 3: Handling Longer Documents&lt;/p&gt;

&lt;p&gt;For my actual use case (summarizing PDFs), the text usually blows past a reasonable single-prompt length. I ended up chunking the document and summarizing each piece, then doing a final pass to combine them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize_chunk&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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;system&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;Summarize the following text in 2-3 sentences.&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;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&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;partial_summaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;summarize_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partial_summaries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;final_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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;system&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;Combine these partial summaries into one coherent summary.&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;combined&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&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;final_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple — no retry logic, no async, nothing fancy. It's the version that got my prototype working, not the version I'd ship to production.&lt;/p&gt;

&lt;p&gt;What I'd Add Before Using This for Real&lt;br&gt;
Retries with backoff for rate limit errors (429s happen, especially at volume)&lt;br&gt;
Token counting before sending, so you're not surprised by a request that's too long&lt;br&gt;
Error handling around the response object, since response.choices[0].message.content will throw if the request failed silently&lt;br&gt;
The Part After "It Works"&lt;/p&gt;

&lt;p&gt;Once this was running, the next question I had was whether a different model would handle long documents better or cost less for my volume. Answering that with DeepSeek's SDK directly would've meant repeating steps 1–3 above for each new provider — new base URL, new auth pattern, occasionally a slightly different response shape to handle.&lt;/p&gt;

&lt;p&gt;I ended up routing requests through &lt;a href="//www.fastrouteai.com"&gt;RouteAI&lt;/a&gt; instead, which uses the same OpenAI-compatible format shown above — same client.chat.completions.create() call, just a different base_url and model name. That let me test the same chunking logic against a couple of other models without rewriting the functions above. Worth being clear about what this does and doesn't solve: it saved me integration time, not token cost — pricing still depends entirely on which model you pick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5aj8t4wctrpwxbiydena.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5aj8t4wctrpwxbiydena.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If You're Just Getting Started&lt;/p&gt;

&lt;p&gt;Get the basic request working first, with your own API key, before adding anything else. Everything above builds on that one call. The chunking function is specific to my use case (long documents) — if you're doing something simpler, like a chatbot or single-turn queries, you may not need it at all.&lt;/p&gt;

&lt;p&gt;TL;DR: DeepSeek's API is OpenAI-compatible, so getting started is mostly pip install openai, swap the base_url, and use your DeepSeek key. Full working example above, including a basic document-chunking pattern for long text.&lt;/p&gt;

&lt;p&gt;Linking the tool mentioned above: &lt;a href="http://www.fastrouteai.com" rel="noopener noreferrer"&gt;www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stop Comparing LLM APIs by Price Per Token. Compare Them by Price Per Solved Task</title>
      <dc:creator>Hamimelon2026</dc:creator>
      <pubDate>Thu, 20 Aug 2026 09:07:02 +0000</pubDate>
      <link>https://dev.to/hamimelon2026_40bd96eff01/stop-comparing-llm-apis-by-price-per-token-compare-them-by-price-per-solved-task-8ni</link>
      <guid>https://dev.to/hamimelon2026_40bd96eff01/stop-comparing-llm-apis-by-price-per-token-compare-them-by-price-per-solved-task-8ni</guid>
      <description>&lt;p&gt;Model A costs half as much per token as Model B. Model A also needs almost twice the tokens to actually solve the task correctly. Guess which one was cheaper by the time the invoice came.&lt;/p&gt;

&lt;p&gt;This is the gap nobody accounts for when they search "cheapest LLM API" and sort a comparison table by the input/output price columns. The table isn't wrong. It's just answering a narrower question than the one people think they're asking.&lt;/p&gt;

&lt;p&gt;The Metric Everyone Optimizes, and What It Leaves Out&lt;/p&gt;

&lt;p&gt;Price-per-token is the number every pricing page leads with, and it's the number every comparison article sorts by. It's also, on its own, an incomplete predictor of what a task will actually cost — because price-per-token tells you nothing about how many tokens a model needs to get you a usable answer.&lt;/p&gt;

&lt;p&gt;Two models can have identical per-token pricing and produce wildly different real-world costs, because token count isn't fixed by the task — it's a function of the model's behavior on that task. A model that's verbose, that pads its answers with unnecessary preamble and repetition, that needs a longer, more carefully engineered prompt to produce reliable output, or that has a higher rate of producing an answer you have to retry — all of that shows up as more tokens consumed per successfully completed task, regardless of how cheap each individual token is.&lt;/p&gt;

&lt;p&gt;"Cheapest LLM API" answered by price-per-token is answering "which model has the lowest sticker price." What most people actually want answered is closer to "which model gets me a correct, usable result for the least money" — and those are only the same question if every model needs exactly the same number of tokens to get there, which is almost never true.&lt;/p&gt;

&lt;p&gt;A Concrete Way to See the Gap&lt;/p&gt;

&lt;p&gt;Picture two models, both priced at output tokens, one at $1 per million and one at $2 per million — the first one looks twice as cheap on the pricing page. Now run the same task through both: a structured data extraction job, say, pulling five fields out of unstructured text.&lt;/p&gt;

&lt;p&gt;The cheaper-per-token model produces a verbose response — some preamble explaining what it's about to do, the extracted data, then a summary restating what it just extracted — averaging 400 output tokens per task. The pricier-per-token model returns exactly the structured output requested, no preamble, no restatement — averaging 90 output tokens per task.&lt;/p&gt;

&lt;p&gt;Run the actual math: the "cheap" model costs $0.0004 per task. The "expensive" model costs $0.00018 per task. The model with double the sticker price ends up costing under half as much per completed task, purely because of how differently the two models behave on the same prompt.&lt;/p&gt;

&lt;p&gt;This isn't a hypothetical edge case — verbosity, formatting habits, and instruction-following precision vary meaningfully across models, and none of that variance shows up in a price-per-token comparison table.&lt;/p&gt;

&lt;p&gt;Why This Gets Worse With Retries and &lt;a href="https://dev.to/felixai/i-wasted-3-days-on-bad-deepseek-prompts-heres-what-actually-works-4ihk"&gt;Prompt&lt;/a&gt; Length&lt;/p&gt;

&lt;p&gt;Two more factors compound the gap, and both get ignored by a pure per-token comparison:&lt;/p&gt;

&lt;p&gt;Retry rate. If a model produces an unusable or malformed response some percentage of the time — invalid JSON, a hallucinated field, an instruction it didn't follow — every retry is additional tokens spent on a task that hasn't actually been solved yet. A model with a meaningfully higher retry rate can lose a per-token price advantage entirely once you account for the wasted attempts.&lt;/p&gt;

&lt;p&gt;Prompt engineering overhead. Some models need a longer, more explicit system prompt to reliably follow formatting instructions — extra input tokens paid on every single call, forever, to compensate for something a different model does correctly with a shorter prompt. This cost is easy to miss because it gets baked into your prompt template once and then becomes invisible, but it's still being paid on every request.&lt;/p&gt;

&lt;p&gt;Both of these push the real cost further from the number on the pricing page, and both push in the same direction: toward the per-token price being a weaker predictor of actual cost than it appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51i3zsh1mnq6yv7pxzxt.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51i3zsh1mnq6yv7pxzxt.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to Actually Measure the Metric That Matters&lt;/p&gt;

&lt;p&gt;The fix isn't complicated, it's just a different thing to measure: price per successfully completed task, calculated as (input tokens + output tokens + retry overhead) × price per token, averaged across a representative sample of your actual use case — not a generic benchmark, your specific task.&lt;/p&gt;

&lt;p&gt;A rough way to approximate this without heavy tooling:&lt;/p&gt;

&lt;p&gt;Pick 15-20 representative examples of your actual task.&lt;br&gt;
Run them against each model candidate, recording total tokens consumed (including any retries needed to get a usable result).&lt;br&gt;
Multiply by each model's per-token pricing.&lt;br&gt;
Compare the resulting per-task cost, not the per-token rate.&lt;/p&gt;

&lt;p&gt;This takes an afternoon, not a research project, and it's the only version of "cheapest" that actually predicts your bill. It also tends to produce genuinely surprising results — the model with the higher sticker price wins this comparison more often than pure price-per-token rankings would suggest, precisely because verbosity and retry rate are real, common differentiators that the pricing page doesn't capture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kreh0l8mb1seroyp2tq.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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kreh0l8mb1seroyp2tq.png" alt=" " width="799" height="411"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/felixai/cheap-ai-is-a-moving-target-heres-the-framework-for-evaluating-it-correctly-c5c"&gt;&lt;br&gt;
Where This Connects to a Bigger Pattern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is also a decent argument for why testing multiple models against your actual task matters more than picking one off a price comparison chart and committing — since the real cost differences here only show up empirically, per task, not on a spec sheet. Standardized, OpenAI-compatible access across multiple providers makes that kind of task-level comparison meaningfully easier to run, since testing model B doesn't require a separate integration from testing model A. RouteAI is one example of infrastructure built around that specific need — one interface across DeepSeek, Qwen, Kimi, GLM, and other models, which makes running the kind of per-task cost comparison described above a matter of switching a model parameter rather than rebuilding an integration for each candidate.&lt;/p&gt;

&lt;p&gt;The Actual Takeaway&lt;/p&gt;

&lt;p&gt;"Cheapest LLM API" measured by price-per-token is a real, useful number — it's just not the number that determines your actual bill. The number that determines your actual bill is price-per-completed-task, and the only way to know it is to actually run your specific task against your specific candidates and count.&lt;/p&gt;

&lt;p&gt;The pricing page will tell you the price. It won't tell you the cost. Those turn out to be different things more often than the sorted comparison table would have you believe.&lt;/p&gt;

&lt;p&gt;TL;DR: Comparing LLM APIs by price-per-token misses how many tokens each model actually needs to complete a task — verbosity, retry rates, and prompt-engineering overhead can make a "cheaper" model cost more per solved task than a pricier one. The metric that actually predicts your bill is price-per-completed-task: run 15-20 representative examples through your candidate models, count total tokens including retries, and multiply by each model's rate. Standardized access across multiple providers (like a gateway such as &lt;a href="//www.fastrouteai.com"&gt;RouteAI&lt;/a&gt;) makes running this comparison easier, since testing a new model becomes a config change instead of a new integration.&lt;/p&gt;

&lt;p&gt;Here's the tool I referenced in this post: &lt;a href="http://www.fastrouteai.com" rel="noopener noreferrer"&gt;www.fastrouteai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
