hcengineering/platform · error · BillingError
BillingError: server response text
Error message
BillingError: server response text
What it means
fetchSafe treats a completed HTTP response with a non-ok status as a BillingError carrying the raw response body text. Unlike NetworkError, the server was reached and returned an error status (4xx/5xx), so the message is whatever the billing server returned — often a JSON error body or plain-text message.
Source
Thrown at packages/billing-client/src/client.ts:123
const path = '/api/v1/ai/tokens'
const url = new URL(concatLink(this.endpoint, path))
const body = JSON.stringify(data)
await fetchSafe(url, { method: 'POST', headers: { ...this.headers }, body })
}
}
async function fetchSafe (url: string | URL, init?: RequestInit): Promise<Response> {
let response
try {
response = await fetch(url, init)
} catch (err: any) {
throw new NetworkError(`Network error ${err}`)
}
if (!response.ok) {
const text = await response.text()
throw new BillingError(text)
}
return response
}
View on GitHub (pinned to 63e28dc964)
Solutions
- Read the BillingError message (response text) — it usually contains the server's error JSON explaining the failure.
- For 401/403, refresh the billing token passed to getClient.
- For 400, validate the request payload sent to the failing method.
- For 5xx, retry with backoff and check billing service health/logs.
Example fix
// before
const stats = await client.response(workspace) // BillingError: {"message":"unauthorized"}
// after
try {
const stats = await client.response(workspace)
} catch (e) {
if (e instanceof BillingError && /unauthorized/i.test(e.message)) {
client = getClient(url, await refreshBillingToken())
} else throw e
} Defensive patterns
Strategy: try-catch
Type guard
function isBillingError(e: unknown): e is BillingError {
return e instanceof BillingError
} Try / catch
try {
return await client.response(ws)
} catch (e) {
if (isBillingError(e)) {
const body = e.message
if (/unauthorized|forbidden/i.test(body)) await refreshToken()
else if (/^4/.test(statusFrom(body))) throw new Error(`bad request: ${body}`)
else throw e // 5xx -> alert/monitor
}
throw e
} Prevention
- Catch BillingError separately from NetworkError — the server responded
- Parse/log the response text in the error; it explains the server-side cause
- Keep billing tokens fresh to avoid 401-driven BillingErrors
- Validate payloads client-side to avoid 400 responses
When it happens
Trigger: Any fetchSafe-based call (response, postLiveKitSessions, postLiveKitEgress, postAiTranscriptData, postAiTokensData) receiving 400/401/403/404/500 etc. from the billing service — e.g. invalid token, malformed payload, or server-side failure.
Common situations: Expired or invalid billing token (401); wrong request shape rejected with 400; billing backend crash or maintenance returning 5xx; hitting a proxy that returns HTML error pages.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to fetch deepgram requests ${res.status}: ${text}
- response.statusText
- Failed to fetch config
- rate-limit
- unknownError(response.statusText)
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/ab87339cea5edb2e.
Report an issue: GitHub.