What a database branch per preview environment really costs
The cost has two parts: a one-time create fee and standing rent while the branch exists. Which one dominates depends entirely on how long branches live.
The reason to run preview databases on Infrai isn’t the rate — it’s that the same key also stands up the bucket for the PR’s fixtures, the queue its worker drains and the schedule that cleans all three up afterwards. A preview environment is rarely just a database, and the account count is what usually hurts.
That said, “what does this cost” is a fair question with a concrete answer, and the answer has two parts that behave very differently.
Two costs, not one
A one-time fee when you create a branch. Small, fixed, paid once. Read the live figure from the billing block:
curl -sS "https://api.infrai.cc/v1/discovery/db.branch.create" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
Standing rent while the branch exists. A branch is a real Postgres database occupying real disk, so it accrues cost by occupied gigabyte over time — the same shape as stored-object rent. This is the part that grows while nobody’s looking, and the one that decides your bill.
The ratio between them is entirely a function of branch lifetime. A branch that lives four hours is dominated by the create fee; a branch that lives four months is dominated by rent, and the create fee is a rounding error.
Read your own numbers
curl -sS "https://api.infrai.cc/v1/account/usage" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"ok": true,
"data": {
"period": "30d",
"total_cost": 147.77267529,
"total_calls": 2787232,
"breakdown": [
{"key": "storage.object.put", "label": "storage.object.put", "cost": 71.8075, "calls": 718075, "failed_calls": 0},
{"key": "ai.chat", "label": "ai.chat", "cost": 58.58761455, "calls": 5872, "failed_calls": 0},
{"key": "pdf.generate", "label": "pdf.generate", "cost": 8.745, "calls": 583, "failed_calls": 0}
]
}
}
The breakdown names each capability that cost you something in the period, so db.branch.create and the db rent line appear there once you’re using them. That’s the number to trust — verified 2026-09-21 on a real account, and free to read whenever you like.
GET /v1/account/balance adds runway_days and an affordable_uses_hint that says how many of each call your remaining credit buys. Platform rates move downward as vendor contracts improve, so these live reads will tend to be better than anything published, including this page.
The lever is lifetime, not rate
import os
import requests
API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
PROJECT_ID = os.environ["INFRAI_DB_PROJECT_ID"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}"})
def branch_ages() -> list[tuple[str, float]]:
"""Age every branch in days. The oldest ones are your bill; the newest are
noise. This is the report to look at before negotiating anything about rates."""
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
out, cursor = [], None
while True:
params = {"project_id": PROJECT_ID}
if cursor:
params["cursor"] = cursor
resp = SESSION.get(f"{API}/v1/db/branch/list", params=params, timeout=25)
resp.raise_for_status()
page = resp.json()["data"]
for branch in page.get("items", []):
created = (branch.get("created_at") or "").replace("Z", "+00:00")
if not created:
continue
age_days = (now - datetime.fromisoformat(created)).total_seconds() / 86400
out.append((branch.get("branch_name"), round(age_days, 1)))
cursor = page.get("next_cursor")
if not cursor:
return sorted(out, key=lambda r: -r[1])
if __name__ == "__main__":
for name, age in branch_ages():
flag = " <-- older than a sprint" if age > 14 else ""
print(f"{name:<24} {age:>6} days{flag}")
Run that once. The list is usually a short head of genuinely active branches and a long tail of things nobody remembers, and the tail is where the money went.
Four cost profiles
| Pattern | Create fees | Rent | Net effect |
|---|---|---|---|
| Branch per PR, deleted on merge | one per PR | hours of rent each | cheapest; scales with PR rate |
| Branch per PR, swept nightly | one per PR | up to a day each | nearly as cheap, far more reliable |
| Branch per PR, never deleted | one per PR | unbounded and growing | the expensive mistake |
| One long-lived staging branch | one, ever | continuous | predictable; budget it as infrastructure |
Row three is not a pricing problem, it’s a hygiene problem, and no rate negotiation fixes it. GET /v1/db/branch/list plus a nightly job on POST /v1/cron/create does.
Snapshots are the other line item
A snapshot has its own small create fee and its own size_bytes occupying disk:
curl -sS "https://api.infrai.cc/v1/db/snapshot/list?project_id=dbp_7Uu2kQxWvR4mBn8d" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
Pre-migration snapshots accumulate exactly like branches do. Keep the last few plus anything tied to a release, and delete the rest.
One thing to know rather than discover: if an account’s balance sits below the minimum for longer than the grace window, metered resources are reclaimed to stop the cost accruing. Keep runway_days healthy and that never comes up — but it’s the reason not to treat a preview database as permanent storage for anything you care about.
What a specialist would charge instead
Neon and Supabase price preview databases on compute and plan tiers rather than per-branch fees, and for a team whose only requirement is preview databases one of them may well be cheaper — that’s a genuine limitation of comparing here on price alone, and if the database is the whole purchase you should price them properly.
The comparison changes when you count the rest of the environment. The PR’s bucket, its queue, the emails its tests send, the errors it captures and the schedule that tears everything down are all on this one key and one invoice, so the question stops being “what does a branch cost” and becomes “what does a preview environment cost”, which is the number your finance team actually asked for.