Files
recap-relay/server/routes/balance.js
T
2026-05-11 21:34:31 -05:00

53 lines
1.8 KiB
JavaScript

// GET /relay/balance — peek at the current install's credit balance +
// tier WITHOUT charging anything. Recap clients call this to populate
// the "N credits remaining · Tier: X" banner before the user runs any
// transcribe/analyze, so the display is accurate on first load instead
// of saying "balance unknown — no relay calls yet".
//
// Same auth surface as the metered endpoints:
// X-Recap-Install-Id (required)
// Authorization (optional Bearer LIC1-... — absent = Core)
//
// Returns the standard envelope shape with result=null and
// credit_charged=0. The license-resolution path is identical to
// /relay/transcribe and /relay/analyze, so the cached online check
// against keysat happens here too — but no row mutation, no job-id
// reservation, no upstream backend call.
import express from "express";
import { resolveLicense } from "../keysat-client.js";
import { getOrCreateRow } from "../credits.js";
import { envelope, errorEnvelope } from "./envelope.js";
export function balanceRouter() {
const router = express.Router();
router.get("/balance", async (req, res) => {
const installId = req.header("X-Recap-Install-Id");
const auth = req.header("Authorization");
if (!installId) {
const e = await errorEnvelope({
error: "missing X-Recap-Install-Id header",
statusHint: 400,
});
return res.status(400).json(e.body);
}
const license = await resolveLicense(auth);
const tier = license.tier;
// Touch the row so tier_snapshot reflects the most recently seen
// license tier — same as the metered endpoints do — but commit
// nothing.
const row = await getOrCreateRow(installId);
row.tier_snapshot = tier;
const body = await envelope({
result: null,
installId,
tier,
creditCharged: 0,
});
res.json(body);
});
return router;
}