Skip to main content
8 min read

Per-Branch AI Endpoints: Isolating Model Spend Across Prod, Preview, and CI

Per-Branch AI Endpoints: Isolating Model Spend Across Prod, Preview, and CI

AI spend is hard to see. In most setups the same gateway credential is used by production, every preview environment, CI, and whatever load test someone ran on Friday. All of that lands in one undifferentiated number. You cannot answer "what did that preview cost," you cannot cap a specific environment, and you find out a CI job went into a retry loop against an expensive model when the monthly invoice arrives, not when it happens.

The reason is that spend is attributed to a key, and the key is shared. Neon changes what is shared: each branch is its own deployment, and if you log usage to Postgres, that ledger lives on the branch too. So a preview or CI branch records its own spend in its own ledger, and none of it moves production's numbers. I tested it by running calls on a CI branch and watching production's ledger stay flat. The repo is at the end.

TL;DR

  • One shared gateway key means one undifferentiated bill: no per-environment attribution, no per-environment cap, and no early warning when a preview or CI job spends a lot.
  • On Neon, each branch is its own deployment (its own function endpoint), and the usage log you keep in Postgres lives on the branch. Calls on a branch record against the branch's ledger.
  • I tested it: two model calls on a ci-run branch raised the branch's token count while production's ledger stayed exactly where it was.
  • Copy-on-write means a branch inherits production's ledger snapshot at branch time; the isolation is in what happens after, new spend on a branch never touches production.

Prerequisites

  • A Neon project with the AI gateway enabled (us-east-2)
  • A usage table in Postgres (the demo logs every call), and branches for your environments

The shared-key problem

When production and every ephemeral environment authenticate with the same credential, the provider's dashboard shows you one line. That has real consequences:

  • No attribution. You cannot say what fraction of last month's tokens came from previews, from CI, or from real users.
  • No isolation. A preview running a batch job, or a CI test that loops, spends against the same budget production draws on, and can exhaust a rate limit everyone shares.
  • No early signal. The first time you learn a non-production environment burned money is the invoice.

Tagging requests helps a little, but it is bookkeeping bolted on after the fact, and it still shares one budget and one rate limit.

The Neon model: spend rides the branch

On Neon each branch is its own deployment with its own function URL, and because you log usage to Postgres and Postgres branches, the usage ledger is per branch too. A call made against a branch's function URL writes to that branch's usage_log, and that ledger is what makes spend attributable per environment: production's ledger is a different table on a different branch. The isolation demonstrated here is that per-branch ledger in Postgres, not a claim that Neon meters the gateway credential itself separately per branch. That distinction matters: the attribution you can rely on is the one you record yourself, in the branch's database.

The usage view is an ordinary query over that branch's log:

// GET /usage: tokens grouped by model, from THIS branch's log
const rows = await db
  .select({
    model: usageLog.model,
    calls: sql`count(*)::int`,
    totalTokens: sql`sum(${usageLog.totalTokens})::int`,
  })
  .from(usageLog)
  .groupBy(usageLog.model);

The proof: a CI branch spends, production does not move

I read production's usage, branched a ci-run environment, made two model calls against the branch, and read both ledgers.

The branch's gpt-5-nano total went from 25 to 71 as its two calls landed, while production stayed at 25. The CI run's spend was recorded against the CI branch and nowhere else, and deleting the branch takes its ledger with it.

Note

Because storage is copy-on-write, a new branch inherits production's ledger as it was at branch time (that is why the branch started at 25, not 0). The isolation is in the delta: everything spent on the branch after it is created stays on the branch, and nothing the branch does changes production's numbers. For clean per-run attribution, read the branch's growth, or keep CI branches short-lived so their ledger is just that run.

What this buys you

  • Attribution. Each environment's spend is a query against its own ledger, so "what did this preview cost" has an answer.
  • Containment. A runaway CI job or a preview load test spends against its branch, not production's budget or rate limit.
  • Cleanup. Delete the branch and its spend record goes with it; there is no separate accounting resource to prune.
  • Governance. Because each environment records against its own branch ledger, you can reason about and bound non-production usage separately from the real thing.

The repo

The gateway function with the per-branch usage log is here:

Wrapping up

Model spend is only invisible because it is attributed to a shared key. Move the usage ledger onto the branch and the picture inverts: every environment keeps its own record, a preview or CI run spends against itself, and production's numbers are unaffected by anything a branch does. You get per-environment attribution and containment for free, and cleanup is the same delete a branch that already tears down the rest of the preview.

Published: 2026-07-16|Last updated: 2026-07-16T09:00:00Z

Found an issue?

Also worth your time on this topic