export type ErrorDetail = Record<string, string | number | boolean | null | Record<string, string | null>>;
export class HttpError extends Error {
readonly status: number;
/** Machine-readable context for clients (e.g. which meter ran out); never secrets. */
detail: ErrorDetail | null;
constructor(status: number, message: string, detail: ErrorDetail | null = null) {
super(message);
this.name = "HttpError";
this.status = status;
this.detail = detail;
}
}
export const HTTP_PAYMENT_REQUIRED = 402;
export type PaymentReason = "plan_limit" | "spending_limit" | "paid_feature";
/**
* 402 Payment Required: the action is out of the account's budget. Clients show an upsell for `meter`
* (its price, Pro, prepaid credit) and link to the account's billing page.
*/
export function paymentRequired(
message: string,
detail: { meter: string; reason: PaymentReason; accountId?: string | null },
): HttpError {
return new HttpError(HTTP_PAYMENT_REQUIRED, message, {
code: "payment_required",
meter: detail.meter,
reason: detail.reason,
accountId: detail.accountId ?? null,
});
}
export function badRequest(message: string): HttpError {
return new HttpError(400, message);
}
export function unauthorized(message = "Authentication required"): HttpError {