v0.2.2 balance peek endpoint

This commit is contained in:
local
2026-05-11 21:34:31 -05:00
parent c9f051cd07
commit 07fe14010c
4 changed files with 70 additions and 2 deletions
+2
View File
@@ -21,6 +21,7 @@ import {
import { transcribeRouter } from "./routes/transcribe.js"; import { transcribeRouter } from "./routes/transcribe.js";
import { analyzeRouter } from "./routes/analyze.js"; import { analyzeRouter } from "./routes/analyze.js";
import { healthRouter } from "./routes/health.js"; import { healthRouter } from "./routes/health.js";
import { balanceRouter } from "./routes/balance.js";
import { adminRouter } from "./routes/admin.js"; import { adminRouter } from "./routes/admin.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -46,6 +47,7 @@ setupAdminAuthRoutes(app);
// authenticates per-call via headers (X-Recap-Install-Id required, // authenticates per-call via headers (X-Recap-Install-Id required,
// Authorization optional). // Authorization optional).
app.use("/relay", healthRouter()); app.use("/relay", healthRouter());
app.use("/relay", balanceRouter());
app.use("/relay", transcribeRouter()); app.use("/relay", transcribeRouter());
app.use("/relay", analyzeRouter()); app.use("/relay", analyzeRouter());
+52
View File
@@ -0,0 +1,52 @@
// 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;
}
+3 -2
View File
@@ -2,8 +2,9 @@ import { VersionGraph } from '@start9labs/start-sdk'
import { v_0_1_0 } from './v0.1.0' import { v_0_1_0 } from './v0.1.0'
import { v_0_2_0 } from './v0.2.0' import { v_0_2_0 } from './v0.2.0'
import { v_0_2_1 } from './v0.2.1' import { v_0_2_1 } from './v0.2.1'
import { v_0_2_2 } from './v0.2.2'
export const versionGraph = VersionGraph.of({ export const versionGraph = VersionGraph.of({
current: v_0_2_1, current: v_0_2_2,
other: [v_0_2_0, v_0_1_0], other: [v_0_2_1, v_0_2_0, v_0_1_0],
}) })
+13
View File
@@ -0,0 +1,13 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_2 = VersionInfo.of({
version: '0.2.2:0',
releaseNotes: {
en_US:
'New GET /relay/balance endpoint returns credit balance + tier without charging. Recap clients call this on boot so the "N credits remaining" banner is populated before the user runs any transcribe/analyze.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})