<?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: ModelPlane</title>
    <description>The latest articles on DEV Community by ModelPlane (@modelplane).</description>
    <link>https://dev.to/modelplane</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%2F4066690%2Fcbd71875-aaa2-4b68-b0c8-eb62500acc04.png</url>
      <title>DEV Community: ModelPlane</title>
      <link>https://dev.to/modelplane</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/modelplane"/>
    <language>en</language>
    <item>
      <title>Bring Your Own Keys, Safely: The BYOK Security Model Behind ModelPlane</title>
      <dc:creator>ModelPlane</dc:creator>
      <pubDate>Fri, 07 Aug 2026 03:43:30 +0000</pubDate>
      <link>https://dev.to/modelplane/bring-your-own-keys-safely-the-byok-security-model-behind-modelplane-1hf7</link>
      <guid>https://dev.to/modelplane/bring-your-own-keys-safely-the-byok-security-model-behind-modelplane-1hf7</guid>
      <description>&lt;h1&gt;
  
  
  Bring Your Own Keys, Safely: The BYOK Security Model Behind ModelPlane
&lt;/h1&gt;

&lt;p&gt;If you're building on top of LLMs, you've already made the security decision that matters most: you're not training your own models. You're renting intelligence from someone else's API. The question is how you manage the credentials that unlock that intelligence.&lt;/p&gt;

&lt;p&gt;Most teams hardcode a provider key into a service, or worse, share one key across an entire organization. When that key leaks—and it will—you're not just out a few dollars. You're exposed to prompt injection attacks, data exfiltration, and a billing nightmare that takes weeks to untangle.&lt;/p&gt;

&lt;p&gt;The solution isn't to stop using LLMs. It's to route your traffic through a gateway that treats credentials as first-class, tenant-isolated secrets. That's the BYOK (Bring Your Own Key) model at the heart of ModelPlane. It's not just about convenience—it's about building a security boundary between your application and the providers you depend on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrong Unit of Integration
&lt;/h2&gt;

&lt;p&gt;Before we talk about keys, let's talk about what you're actually integrating with. Most teams think in terms of providers: "We use OpenAI," or "We're on Anthropic now." That's the wrong mental model.&lt;/p&gt;

&lt;p&gt;The provider is the wrong unit of integration. Your application shouldn't care whether a request is answered by &lt;code&gt;gpt-4o&lt;/code&gt;, &lt;code&gt;claude-3-5-sonnet&lt;/code&gt;, or &lt;code&gt;deepseek-v3&lt;/code&gt;. It should care about the &lt;em&gt;capability&lt;/em&gt;: a fast chat model, a reasoning model, a cheap batch model. That capability is what we call a &lt;strong&gt;model group&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A model group is a name you control—like &lt;code&gt;prod-chat&lt;/code&gt;—that maps to one or more provider backends with a routing strategy. When your app sends a request to &lt;code&gt;model="prod-chat"&lt;/code&gt;, ModelPlane decides which backend answers, based on fallback rules, load-balancing weights, or conditional logic.&lt;/p&gt;

&lt;p&gt;This abstraction changes the security conversation. Instead of managing N provider keys across M services, you manage one gateway key per environment, and the gateway manages the provider keys for you. The keys become infrastructure, not application logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Keys, Two Jobs
&lt;/h2&gt;

&lt;p&gt;The first thing to understand about ModelPlane's security model is that there are two entirely different types of credentials in play. Confusing them is the root of most LLM gateway security failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gateway API Key (&lt;code&gt;gw-*&lt;/code&gt;)&lt;/strong&gt;: This is what your application sends to ModelPlane. It starts with &lt;code&gt;gw-&lt;/code&gt; and is a bearer token that authenticates your tenant. It's how we know which workspace is making the request, which model groups it can access, and which billing account to charge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend Credential&lt;/strong&gt;: This is the actual provider API key—your OpenAI key, your Anthropic key, your DeepSeek key. You upload these to ModelPlane via the Backends page or API, and we store them encrypted.&lt;/p&gt;

&lt;p&gt;The critical rule: &lt;strong&gt;the gateway key is for authentication only. It is never forwarded upstream.&lt;/strong&gt; When your request hits our edge, the &lt;code&gt;Authorization&lt;/code&gt; header carrying &lt;code&gt;gw-*&lt;/code&gt; is stripped before the request is routed to any provider. Forwarding it would be a bug—and it's a bug we've designed against at the middleware level.&lt;/p&gt;

&lt;p&gt;This separation means a leaked gateway key gives an attacker access to &lt;em&gt;your&lt;/em&gt; ModelPlane usage, not to your underlying provider accounts. They could burn your credits, but they can't exfiltrate your OpenAI or Anthropic keys. That's a meaningful reduction in blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Backend Credentials Are Stored
&lt;/h2&gt;

&lt;p&gt;When you upload a provider key to ModelPlane, it doesn't go into a database table where a SQL injection or a rogue admin could read it. It goes into Cloudflare Workers KV, encrypted.&lt;/p&gt;

&lt;p&gt;The encryption model is worth understanding because it's the difference between "we encrypt your data" and "we encrypt your data in a way that actually protects you."&lt;/p&gt;

&lt;p&gt;Each credential is stored under a key that includes your user ID: &lt;code&gt;cred:{userId}:{credId}&lt;/code&gt;. The payload is encrypted with AES-GCM, using a root key from a Worker secret. But here's the important part: that root key isn't used directly. It's fed through HKDF with your user ID as the salt, deriving a per-user encryption key.&lt;/p&gt;

&lt;p&gt;This means two things. First, your credentials are encrypted at rest with a key that's unique to you. Second, when the gateway needs to use a credential, it decrypts &lt;em&gt;only&lt;/em&gt; the keys belonging to the current tenant. Cross-tenant access—even by accident—is rejected at the hydration layer.&lt;/p&gt;

&lt;p&gt;We also never cache decrypted credentials. The routing cache may hold model-group metadata, but the API keys themselves are only decrypted in memory for the duration of a request, then discarded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Your Threat Model
&lt;/h2&gt;

&lt;p&gt;Let's be concrete about what this architecture protects against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: A gateway key leaks.&lt;/strong&gt; An attacker gets a &lt;code&gt;gw-*&lt;/code&gt; token from a compromised service. They can make requests through your model groups, spending your credits. But they cannot extract your provider keys, and they cannot access your other tenants' data. You revoke the key, and the attack surface closes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: A database backup leaks.&lt;/strong&gt; Supabase, Workers KV, S3—any of these could theoretically be compromised. But the provider keys in KV are AES-GCM encrypted with per-user keys. The gateway key hashes in Supabase are SHA-256, which means the plaintext is unrecoverable. A backup leak is a nuisance, not a catastrophe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: An insider threat.&lt;/strong&gt; A ModelPlane employee with database access still can't read your provider keys. The encryption keys are in Worker secrets, not in the database. This is the BYOK promise: you're not trusting us with your keys, you're trusting us with &lt;em&gt;encrypted&lt;/em&gt; keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Setup
&lt;/h2&gt;

&lt;p&gt;Here's what this looks like in practice. You sign up for ModelPlane, add your first backend, and point your OpenAI-compatible client at our endpoint.&lt;/p&gt;

&lt;p&gt;First, add a backend credential. You can do this through the portal or via the API:&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 https://modelplane.dev/api/backends &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$GATEWAY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "provider": "openai",
    "apiKey": "sk-your-openai-key",
    "options": { "models": ["gpt-4o", "gpt-4o-mini"] }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you a &lt;code&gt;backendId&lt;/code&gt;. You reference that ID in a model group, which defines the routing strategy. Then your application only ever sees the gateway key.&lt;/p&gt;

&lt;p&gt;Here's a complete Python example using the standard &lt;code&gt;openai&lt;/code&gt; client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;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;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://modelplane.dev/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gw-your-gateway-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# never a provider key
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# "prod-chat" is a model group, not a provider model ID
&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;prod-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.&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;Explain BYOK security in one paragraph.&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;Notice what's missing: there's no provider key in this code. There's no &lt;code&gt;sk-&lt;/code&gt; or &lt;code&gt;anthropic-&lt;/code&gt; token. If this file leaks to GitHub, the attacker gets a gateway key that can be revoked in seconds, not a provider key that gives them direct access to your OpenAI account.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gateway Key Lifecycle
&lt;/h2&gt;

&lt;p&gt;Gateway keys are designed to be ephemeral and revocable. When you create one via &lt;code&gt;POST /api/keys&lt;/code&gt;, the plaintext is returned exactly once. After that, only the SHA-256 hash is stored in Supabase. There's no "forgot my key" recovery—you create a new one and rotate.&lt;/p&gt;

&lt;p&gt;This is a feature, not a bug. It means a key that's been exposed in logs, in a leaked &lt;code&gt;.env&lt;/code&gt; file, or in a commit history is worthless to anyone who finds it later. You can't look up the plaintext, and neither can we.&lt;/p&gt;

&lt;p&gt;ModelPlane also distinguishes between &lt;strong&gt;Private&lt;/strong&gt; and &lt;strong&gt;Shared&lt;/strong&gt; keys. Private keys belong to an individual member and are scoped to their permissions. Shared keys are created by org owners or admins and can be used across a workspace. This lets you give a CI/CD pipeline its own key, then revoke it without affecting your developers' keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Teams
&lt;/h2&gt;

&lt;p&gt;If you're a solo developer, the security model matters because it's one less thing to worry about. If you're an engineering lead at a company with 50 engineers, it's a compliance requirement.&lt;/p&gt;

&lt;p&gt;The multi-tenant design means each workspace gets its own isolated credential store. Your team's keys are encrypted with your workspace's derived keys. Another team on the same ModelPlane instance—even another workspace in your own organization—cannot access them.&lt;/p&gt;

&lt;p&gt;This is the difference between a gateway that's bolted onto your stack and one that's designed for multi-tenancy from the ground up. The tenancy model (User/Workspace/Billing Account) is baked into every resource: API keys, backends, model groups, usage logs. When you audit who has access to what, the answer is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Tradeoffs
&lt;/h2&gt;

&lt;p&gt;No security model is perfect, and we're not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;First, BYOK means you're responsible for your provider keys. If you upload a key to ModelPlane and then leak it elsewhere, that's on you. We encrypt it at rest, but we can't protect you from your own &lt;code&gt;.env&lt;/code&gt; file hygiene.&lt;/p&gt;

&lt;p&gt;Second, there's a trust boundary you're accepting: you're trusting ModelPlane to handle your keys correctly. We've designed the system so that a compromise of our infrastructure doesn't expose your plaintext keys, but you're still relying on our encryption implementation being correct.&lt;/p&gt;

&lt;p&gt;Third, the latency/accuracy tradeoff in billing means usage accounting is eventually consistent. That's a billing concern, not a security one, but it's worth knowing that the balance gate is a pre-request snapshot, and deductions happen asynchronously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The provider key is the crown jewel of your LLM infrastructure. It's the credential that can read your prompts, generate your responses, and spend your money. Treating it like a regular API key—hardcoded in services, shared across teams, stored in plaintext—is a risk that will eventually materialize.&lt;/p&gt;

&lt;p&gt;ModelPlane's BYOK model gives you a clean separation: gateway keys for your applications, encrypted backend credentials for your providers, and a routing layer that never confuses the two. The gateway key is auth; the backend key is access. Keeping those separate is the foundation of a secure LLM stack.&lt;/p&gt;

&lt;p&gt;Start free with $5 in credits—no card required. Add your first backend, create a model group, and see how the security model holds up under real traffic. Your provider keys will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of our series on building production-grade LLM infrastructure. Here's what we've covered and what's coming:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One endpoint, every model&lt;/strong&gt; — why we built ModelPlane and the core abstractions of model routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing strategies, explained&lt;/strong&gt; — fallback, load-balance, and conditional routing patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model groups&lt;/strong&gt; — the one abstraction that decouples your app from providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring your own keys, safely&lt;/strong&gt; — the BYOK security model (you are here)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High availability for LLM apps&lt;/strong&gt; — a fallback playbook for provider outages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop paying twice&lt;/strong&gt; — route your coding-plan quota into production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price-aware routing&lt;/strong&gt; — cut your LLM bill without changing models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credits, usage &amp;amp; billing, explained&lt;/strong&gt; — one transparent bill across all providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1600+ models, one API&lt;/strong&gt; — the ModelPlane provider catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One gateway, two regions&lt;/strong&gt; — routing to global and China models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ModelPlane for teams&lt;/strong&gt; — orgs, workspaces &amp;amp; shared keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meet the assistant&lt;/strong&gt; — an AI helper that lives in your gateway dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inside the ModelPlane routing engine&lt;/strong&gt; — how a single request flows from auth to upstream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One thinking parameter, every model&lt;/strong&gt; — unified reasoning across providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The system prompt belongs at the router&lt;/strong&gt; — per-model-group system-prompt injection&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Credits, usage &amp; billing, explained</title>
      <dc:creator>ModelPlane</dc:creator>
      <pubDate>Fri, 07 Aug 2026 03:43:29 +0000</pubDate>
      <link>https://dev.to/modelplane/credits-usage-billing-explained-1266</link>
      <guid>https://dev.to/modelplane/credits-usage-billing-explained-1266</guid>
      <description>&lt;h1&gt;
  
  
  Credits, usage &amp;amp; billing, explained
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;The problem with LLM usage tracking isn't that it's hard to measure tokens — it's that every provider gives you a different dashboard, and none of them tell you what you actually spent through your application.&lt;/strong&gt; When you route through a gateway, usage becomes a property of your architecture, not a per-provider afterthought. ModelPlane gives you one surface for usage across all your model groups and providers, so you can answer "what did this feature cost?" without stitching together five vendor consoles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The multi-dashboard problem
&lt;/h2&gt;

&lt;p&gt;If you've built an AI feature in production, you know the ritual. You have a script, a spreadsheet, or a half-remembered URL for each provider's usage page. OpenAI shows you one number. Anthropic shows you another. DeepSeek shows you a third. And none of them map to the requests your application actually made, because your application doesn't call providers directly — it calls a model group.&lt;/p&gt;

&lt;p&gt;The mismatch is the core issue. Provider dashboards are organized by &lt;em&gt;provider account&lt;/em&gt;. Your costs are organized by &lt;em&gt;feature&lt;/em&gt;, &lt;em&gt;customer&lt;/em&gt;, or &lt;em&gt;environment&lt;/em&gt;. When you hard-code &lt;code&gt;model="gpt-4o"&lt;/code&gt; in your code, you've coupled your application's cost structure to a single vendor's accounting. You can't see which of your features is burning credits, which customer tier is expensive to serve, or whether your fallback routing is quietly sending traffic to a premium model you didn't intend.&lt;/p&gt;

&lt;p&gt;This is why the abstraction matters. When &lt;code&gt;request.model&lt;/code&gt; is a name you control — a model group like &lt;code&gt;prod-chat&lt;/code&gt; — usage tracking becomes something you can actually act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  One surface for usage across model groups
&lt;/h2&gt;

&lt;p&gt;ModelPlane's billing model is built around a single concept: &lt;strong&gt;credits&lt;/strong&gt;. One credit is equivalent to one US dollar. Every request through the gateway is accounted for in credits, regardless of which provider answered it. The cost formula is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost = (inputTokens × inputUnitPrice + outputTokens × outputUnitPrice) / 1_000_000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pricing for each model lives in the provider catalog, so the gateway can compute the cost of every request without you configuring per-model rates. This means your usage logs are not just token counts — they're actual spend, normalized across every provider.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;usage_logs&lt;/code&gt; table is the heart of this. Each entry records the request's dimensions: which API key made the call, which model group it went through, and which underlying model actually answered. That last point is critical. With fallback routing, the model that answers isn't always the one you asked for. Your logs show you the ground truth.&lt;/p&gt;

&lt;p&gt;This gives you three views of your spend that provider dashboards can't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;By model group.&lt;/strong&gt; What does &lt;code&gt;prod-chat&lt;/code&gt; cost this month? What about &lt;code&gt;customer-support&lt;/code&gt; or &lt;code&gt;internal-coding&lt;/code&gt;? Each group is a line item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By API key.&lt;/strong&gt; Which application or service is consuming the most? Private keys per developer, shared keys per environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By model.&lt;/strong&gt; Even within a group, you can see the split — how much traffic went to the fast tier versus the premium fallback.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The balance gate: what happens before a request runs
&lt;/h2&gt;

&lt;p&gt;ModelPlane's billing is designed to be low-latency and simple. Before a request runs, the gateway checks your credit balance. If you're out of credits, you get a &lt;code&gt;402 insufficient_credits&lt;/code&gt; response — fast, clear, and before any upstream call is made. This is the pre-request balance gate.&lt;/p&gt;

&lt;p&gt;This design is a deliberate tradeoff. The gateway doesn't do a strong-consistency reservation of funds before every request. That would add latency to every single call. Instead, it takes a snapshot of your balance, gates the request, and then accounts for the cost &lt;em&gt;after&lt;/em&gt; the request completes, asynchronously.&lt;/p&gt;

&lt;p&gt;The result is that billing is fast enough to sit on the hot path of every inference call. The tradeoff is that the balance you see can be slightly stale — up to 300 seconds, in some cases, because the balance may be served from a cache. For almost all use cases, this is the right trade. You don't want your LLM gateway to be the slow part of your stack, and you don't want it to reject requests because of a race condition in accounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after: async accounting
&lt;/h2&gt;

&lt;p&gt;Once the request completes, the gateway calculates the cost and deducts it from your balance. This is a fire-and-forget operation — it doesn't block the response to your application. The response you get is the model's output; the accounting happens in the background.&lt;/p&gt;

&lt;p&gt;This design is simple and fast, but it's worth being honest about the edge cases. The async deduction has no retry mechanism in its current implementation. If the background task fails, or the Worker running it is evicted, the usage row and the deduction could be silently lost. This is a known reliability gap, documented in the project's internal PRD. For most teams, this is acceptable — the cost of a few lost usage rows is far less than the cost of adding strong consistency to every request. But if you're building billing infrastructure on top of usage logs, you should know the data is eventually consistent, not exactly-once.&lt;/p&gt;

&lt;p&gt;The system also handles the accounting atomically at the database level. The &lt;code&gt;consume_credits&lt;/code&gt; RPC is atomic and allows a brief negative balance, which prevents a burst of concurrent requests from being rejected due to a race between the balance check and the deduction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plans and pricing: what you actually pay
&lt;/h2&gt;

&lt;p&gt;ModelPlane's pricing is designed to be transparent. There are three tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt;: You get $5 in credits on signup. No card required. This is enough to build and test a real feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic&lt;/strong&gt;: $10 per month, which includes $15 in credits. You can top up with additional credits as needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited&lt;/strong&gt;: A special plan for high-volume internal use cases, where the credit gate is bypassed entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of the plan, there's a flat gateway fee of approximately $0.02 per 1 million tokens. This is the cost of routing — the abstraction, the fallback, the unified billing. It's a rounding error compared to the cost of the tokens themselves, but it's what pays for the gateway to exist.&lt;/p&gt;

&lt;p&gt;The key number to understand is the &lt;strong&gt;included credit&lt;/strong&gt;. On the Basic plan, you pay $10 and get $15 in credits. That means the effective cost of the gateway is negative — you're getting more credit than you pay for, and the gateway fee comes out of the included credit. The math works because the gateway fee is so small relative to model costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing it in action
&lt;/h2&gt;

&lt;p&gt;Here's what it looks like to point your OpenAI client at ModelPlane and see usage flow into one dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;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;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://modelplane.dev/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gw-your-gateway-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# "prod-chat" is a model group, not a provider model id.
# It routes across your configured backends with your chosen strategy.
&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;prod-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;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 week&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s support tickets.&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;That's it. Your application code doesn't know or care which provider answered. It doesn't know whether the request went to a coding-plan quota or a pay-per-token endpoint. It just knows it called &lt;code&gt;prod-chat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After this request runs, you can query the usage API to see exactly what happened:&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;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer gw-your-gateway-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://modelplane.dev/api/usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response shows you the request's token count, the cost in credits, the model group, the API key, and the actual model that served it. This is the data you need to answer the questions that matter: "What does this feature cost per user?" "Is my fallback routing sending too much traffic to the expensive model?" "Which customer tier is burning through credits?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest tradeoff: latency vs. accuracy
&lt;/h2&gt;

&lt;p&gt;Every billing system makes a tradeoff between accuracy and latency. ModelPlane's choice is explicit: &lt;strong&gt;low latency wins&lt;/strong&gt;. The pre-request balance check is a snapshot, not a reservation. The post-request deduction is asynchronous, not synchronous. This means your requests are never slowed down by billing logic, and your gateway can handle high concurrency without becoming a bottleneck.&lt;/p&gt;

&lt;p&gt;The cost of this choice is that your balance and usage data are eventually consistent. You might see a request in your logs a few seconds after it completes. Your balance might be slightly stale for a few minutes. And in rare failure cases, a usage row might be lost entirely.&lt;/p&gt;

&lt;p&gt;For most teams, this is the right trade. If you're building a customer-facing billing system on top of your LLM usage, you should build your own reconciliation layer. But if you want to see what your AI features cost, and you want to catch a runaway model group before it burns through your credits, this is more than sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats provider dashboards
&lt;/h2&gt;

&lt;p&gt;The multi-dashboard problem isn't just inconvenient — it's a blind spot. When you can't see usage by model group, you can't see which features are profitable. When you can't see usage by API key, you can't see which developer or service is consuming the most. When you can't see the actual model that answered, you can't tell if your fallback strategy is working as intended.&lt;/p&gt;

&lt;p&gt;ModelPlane gives you the dimensions that matter for running an AI product. The abstraction of the model group — the same abstraction that decouples your code from providers — also decouples your accounting from providers. Your costs are organized the same way your architecture is organized.&lt;/p&gt;

&lt;p&gt;This is the point. The provider is the wrong unit of integration for your code, and it's the wrong unit of integration for your billing. ModelPlane makes the model group the unit of both. One endpoint, one dashboard, one bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;LLM usage tracking doesn't have to be a nightmare of spreadsheets and vendor consoles. When you route through a gateway, usage becomes a first-class property of your application. ModelPlane gives you one surface to see it all: credits, spend, and usage across every model group and provider.&lt;/p&gt;

&lt;p&gt;The system is fast because it's simple. The balance gate is a snapshot, the deduction is async, and the accounting is eventually consistent. That's the right trade for a gateway that sits on every inference call.&lt;/p&gt;

&lt;p&gt;If you're tired of stitching together provider dashboards, point your OpenAI client at ModelPlane and see your usage in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See your usage in one dashboard — &lt;a href="https://modelplane.dev" rel="noopener noreferrer"&gt;start free with $5 in credits, no card required&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of a series on building production-grade LLM applications with ModelPlane.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ModelPlane series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/one-endpoint-every-model/" rel="noopener noreferrer"&gt;One endpoint, every model: why we built ModelPlane&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/routing-strategies-explained/" rel="noopener noreferrer"&gt;Routing strategies, explained: fallback, load-balance, conditional&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/model-groups-explained/" rel="noopener noreferrer"&gt;Model groups: the one abstraction that decouples your app from providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/byok-credential-security/" rel="noopener noreferrer"&gt;Bring your own keys, safely&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/llm-high-availability-playbook/" rel="noopener noreferrer"&gt;High availability for LLM apps: a fallback playbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/coding-plan-routing/" rel="noopener noreferrer"&gt;Stop paying twice: route your coding-plan quota into production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/price-aware-routing/" rel="noopener noreferrer"&gt;Price-aware routing: cut your LLM bill without changing models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credits, usage &amp;amp; billing, explained&lt;/strong&gt; ← you are here&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/provider-catalog/" rel="noopener noreferrer"&gt;1600+ models, one API: the ModelPlane provider catalog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/global-and-china-routing/" rel="noopener noreferrer"&gt;One gateway, two regions: routing to global and China models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/orgs-workspaces/" rel="noopener noreferrer"&gt;ModelPlane for teams: orgs, workspaces &amp;amp; shared keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/in-product-assistant/" rel="noopener noreferrer"&gt;Meet the assistant: an AI helper that lives in your gateway dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/inside-the-routing-engine/" rel="noopener noreferrer"&gt;Inside the ModelPlane routing engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/unified-thinking-parameter/" rel="noopener noreferrer"&gt;One thinking parameter, every model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelplane.dev/blog/system-prompt-injection/" rel="noopener noreferrer"&gt;The system prompt belongs at the router, not in your app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>billing</category>
      <category>product</category>
    </item>
  </channel>
</rss>
