Chapter 13 of 15 9 min intermediate

API Key Security

Why your key cannot ship in the build

Verified against a working setup on 2026-08-10

If you take one thing from this entire guide: an API key in a shipped game is a published API key. Not hard to find. Published. Someone will find it, and the bill is yours.

This chapter is short because the answer is short. It exists because the mistake is common, expensive, and completely avoidable.

Every place people hide it, and how it is found

Hiding placeHow it is extractedEffort
A string in a scriptstrings on the binarySeconds.
A SerializeFieldIt is in the scene or prefab assetSeconds.
A ScriptableObjectAsset bundles are readableMinutes.
Split across variablesIt is reassembled at runtimeMinutes.
XOR / base64 "encryption"The key to decrypt ships tooMinutes.
Behind IL2CPPDecompilers exist for thisAn afternoon.
A WebGL buildIt is plain text in the browserOpen DevTools.

The pattern is not that these are bad hiding places. It is that there is no good one. Anything the game can read at runtime, a player can read too, because the game runs on their machine.

Fetching the key from your server at startup does not help either. The game receives the key, so a player watching their own network traffic receives it too.

The fix: a proxy you control

Move the key to a machine the player does not own. The game talks to your server; your server talks to the API.

  1. 1
    The game sends the player line to your endpoint

    No key, no model name, no system prompt. Just the input and a session token.

  2. 2
    Your server adds the key and the prompt

    The key lives in an environment variable on the server. The system prompt lives there too — players cannot read or edit it.

  3. 3
    Your server calls the API and relays the reply

    This is the natural place for rate limits, abuse filtering, and cost logging.

A minimal proxy (Node)
javascript
app.post("/chat", async (req, res) => {
  // 1. Authenticate the player. Without this, your proxy is an
  //    open, free API for the entire internet.
  const player = await authenticate(req);
  if (!player) return res.status(401).end();

  // 2. Rate limit per player, not globally.
  if (await overQuota(player.id)) return res.status(429).end();

  // 3. Your key. Server-side environment variable, never in git.
  const upstream = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-haiku-4-5",
      max_tokens: 300,
      system: SYSTEM_PROMPT,              // yours, not the client's
      messages: [{ role: "user", content: String(req.body.line).slice(0, 500) }],
    }),
  });

  const data = await upstream.json();
  await recordUsage(player.id, data.usage);   // cost tracking

  // 4. Return only what the game needs.
  res.json({ text: data.content?.find(b => b.type === "text")?.text ?? "" });
});
An unauthenticated proxy is worse than a leaked key, because it looks fine. Anyone who finds the URL gets free model access billed to you, and there is no key to rotate — you have to take the endpoint down.

What the proxy buys you beyond safety

  • Change the model without shipping a patch. Swap Haiku for Sonnet server-side.
  • Change the prompt without shipping a patch. Tune your NPC's voice live.
  • Per-player rate limits, so one user cannot drain your budget.
  • Real cost data, because you see every request.
  • A kill switch. If costs spike, you can turn it off.

That second point is worth dwelling on. Prompts need iteration, and a prompt baked into a build is frozen until your next release. Keeping it server-side turns tuning into a config change.

If you only remember one checklist

  • The key lives in a server environment variable. Never in the project.
  • Add the key file pattern to .gitignore before writing any code.
  • The proxy authenticates every request.
  • The proxy rate limits per player.
  • The client sends input only — never the model, prompt, or key.
  • Set a spend alert on your API account today, not after launch.

What to take away

  • Anything shipped to a player is public, including your key.
  • No client-side obfuscation works. Do not spend time on it.
  • A proxy is the only real fix, and it is not much code.
  • An unauthenticated proxy is a different way to lose money.
  • Server-side prompts and models mean you can tune without a patch.