DEV Community

Cover image for How I Started Using OpenRouter Kimi Models (And What the API Bill Taught Me)
Hamimelon2026
Hamimelon2026

Posted on

How I Started Using OpenRouter Kimi Models (And What the API Bill Taught Me)

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.

That's the problem with routing through an aggregator like OpenRouter: it's genuinely convenient — one API key, one OpenAI-compatible endpoint, 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.

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.

Why I picked OpenRouter Kimi in the first place

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.

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.

What the pricing actually looks like

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 OpenRouter, roughly:

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

A side-by-side comparison of AI API pricing across different routing platforms

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.

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.

The real lesson: don't hardcode a single endpoint

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.

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.

// gateway.js — a minimal OpenAI-compatible request gateway with fallback

const PROVIDERS = [
  {
    name: "primary",
    baseURL: process.env.PRIMARY_BASE_URL,
    apiKey: process.env.PRIMARY_API_KEY,
    model: process.env.PRIMARY_MODEL, // e.g. "kimi-k2.6"
  },
  {
    name: "fallback",
    baseURL: process.env.FALLBACK_BASE_URL,
    apiKey: process.env.FALLBACK_API_KEY,
    model: process.env.FALLBACK_MODEL,
  },
];

function validateRequest(req) {
  if (!req.body || !req.body.messages) {
    throw new Error("Missing 'messages' in request body");
  }
  if (!Array.isArray(req.body.messages) || req.body.messages.length === 0) {
    throw new Error("'messages' must be a non-empty array");
  }
}

async function callProvider(provider, payload) {
  const response = await fetch(`${provider.baseURL}/chat/completions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${provider.apiKey}`,
    },
    body: JSON.stringify({ ...payload, model: provider.model }),
  });

  if (!response.ok) {
    throw new Error(`${provider.name} responded with ${response.status}`);
  }
  return response.json();
}

async function chatCompletion(req, res) {
  try {
    validateRequest(req);
  } catch (err) {
    return res.status(400).json({ error: err.message });
  }

  for (const provider of PROVIDERS) {
    try {
      const result = await callProvider(provider, req.body);
      return res.json({ ...result, _served_by: provider.name });
    } catch (err) {
      console.warn(`[gateway] ${provider.name} failed: ${err.message}`);
      // fall through to the next provider
    }
  }

  return res.status(502).json({ error: "All providers failed" });
}

module.exports = { chatCompletion };
Enter fullscreen mode Exit fullscreen mode

This is intentionally simple — no queueing, no retries with backoff, no streaming support yet. What it gave me immediately:

  • Request validation before anything goes out, so malformed payloads fail fast instead of burning a paid call.
  • A single place to swap models or providers by changing environment variables, not application code.
  • 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.

A request gateway routing between a primary and fallback AI API provider

Where RouteAI fit into this

While setting up that fallback slot, I tried RouteAI 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.

What I'd tell someone starting the same way

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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.

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.

Website: https://www.fastrouteai.com

Top comments (0)