different-ai/openwork · error · Error
Billing portal failed (${response.status}).
Error message
Billing portal failed (${response.status}). What it means
Thrown by openStripePortal when POST /v1/billing/stripe/portal returns a non-OK status. Like the other getRequestError sites, a 403 "reauth" payload is converted to ReauthRequiredError; otherwise the server message (or this status-annotated fallback) is thrown. It means the Stripe Customer Portal session could not be created server-side.
Source
Thrown at ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx:316
} catch (error) {
setStripeError(error instanceof Error ? error.message : "Could not start seat billing checkout.");
} finally {
setStripeActionBusy(null);
}
}
async function openStripePortal() {
if (!canManageBillingSettings) {
setStripeError("Only workspace owners and super-admins can open billing portals from Settings.");
return;
}
setStripeError(null);
try {
await runReauthableAction("billing-portal", async () => {
setStripeActionBusy("portal");
const { response, payload } = await requestJson("/v1/billing/stripe/portal", { method: "POST" }, 12000);
if (!response.ok) throw getRequestError(payload, response, `Billing portal failed (${response.status}).`);
const url = payload && typeof payload === "object" && "url" in payload && typeof payload.url === "string" ? payload.url : null;
if (!url) throw new Error("Billing portal response did not include a URL.");
window.location.href = url;
});
} catch (error) {
setStripeError(error instanceof Error ? error.message : "Could not open the billing portal.");
} finally {
setStripeActionBusy(null);
}
}
const showPolar = polarBilling?.hasActivePlan === true && Boolean(polarBilling.portalUrl);
const stripePrice = stripeBilling ? formatMoneyMinor(stripeBilling.unitAmount, stripeBilling.currency) : null;
const seatBilling = stripeBilling?.seats;
const webBilling = stripeBilling?.web ?? null;
const seatPrice = seatBilling ? formatMoneyMinor(seatBilling.unitAmount, seatBilling.currency) : null;
const activeMemberCount = stripeBilling?.memberCount ?? 0;
View on GitHub (pinned to 2b7df46e8a)
Solutions
- Read the surfaced server message to distinguish config vs auth failures.
- Handle reauth: retry after re-authenticating (runReauthableAction already wraps this).
- Enable the Stripe customer portal and its return URL in the Stripe dashboard for the connected account.
- Ensure the org has a Stripe customer record (portal requires one); subscribe first via checkout.
- Check Den server logs for Stripe API errors if 5xx.
Example fix
// before
} catch (error) {
setStripeError(error instanceof Error ? error.message : "Could not open the billing portal.");
}
// after
} catch (error) {
if (isReauthRequiredError(error)) { setStripeError("Session expired - sign in again."); return; }
setStripeError(error instanceof Error ? error.message : "Could not open the billing portal.");
} Defensive patterns
Strategy: try-catch
Validate before calling
const status = await requestJson("/v1/billing/status", {}, 10000);
if (!status.response.ok) return; // org has no billing record; portal will 4xx Type guard
function isReauth(error: unknown): error is ReauthRequiredError { return error instanceof ReauthRequiredError; } Try / catch
try {
await runReauthableAction("billing-portal", openStripePortal);
} catch (error) {
if (isReauthRequiredError(error)) { promptReauth(); return; }
setStripeError(error instanceof Error ? error.message : "Could not open the billing portal.");
} Prevention
- Only show the portal button once the org has a Stripe customer (billing status loaded)
- Enable the Stripe customer portal in the Stripe dashboard before shipping
- Keep sessions fresh; rely on runReauthableAction for reauth
- Log payload on failure to distinguish config vs auth causes
When it happens
Trigger: POST /v1/billing/stripe/portal fails: org lacks a Stripe customer ID (never subscribed), Stripe portal not configured/enabled in the Stripe dashboard, expired session (401), reauth required (403), or Stripe API error (5xx).
Common situations: Clicking 'Manage billing' on a self-hosted Den without Stripe portal links configured; org created but never subscribed; Stripe portal deactivated in the Stripe dashboard; stale admin session.
Related errors
- Seat checkout failed (${response.status}).
- Checkout failed (${response.status}).
- Failed to update inference settings (${response.status}).
- Failed to fetch latest-mac.yml (${response.status} ${respons
- Managed MCP outbound request exceeded the guarded redirect l
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/abcc850d8cd5bb70.
Report an issue: GitHub.