{"record":{"id":"abcc850d8cd5bb70","repo":"different-ai/openwork","slug":"billing-portal-failed-response-status","errorCode":null,"errorMessage":"Billing portal failed (${response.status}).","messagePattern":"Billing portal failed \\((.+?)\\)\\.","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx","lineNumber":316,"sourceCode":"    } catch (error) {\n      setStripeError(error instanceof Error ? error.message : \"Could not start seat billing checkout.\");\n    } finally {\n      setStripeActionBusy(null);\n    }\n  }\n\n  async function openStripePortal() {\n    if (!canManageBillingSettings) {\n      setStripeError(\"Only workspace owners and super-admins can open billing portals from Settings.\");\n      return;\n    }\n\n    setStripeError(null);\n    try {\n      await runReauthableAction(\"billing-portal\", async () => {\n        setStripeActionBusy(\"portal\");\n        const { response, payload } = await requestJson(\"/v1/billing/stripe/portal\", { method: \"POST\" }, 12000);\n        if (!response.ok) throw getRequestError(payload, response, `Billing portal failed (${response.status}).`);\n        const url = payload && typeof payload === \"object\" && \"url\" in payload && typeof payload.url === \"string\" ? payload.url : null;\n        if (!url) throw new Error(\"Billing portal response did not include a URL.\");\n        window.location.href = url;\n      });\n    } catch (error) {\n      setStripeError(error instanceof Error ? error.message : \"Could not open the billing portal.\");\n    } finally {\n      setStripeActionBusy(null);\n    }\n  }\n\n  const showPolar = polarBilling?.hasActivePlan === true && Boolean(polarBilling.portalUrl);\n  const stripePrice = stripeBilling ? formatMoneyMinor(stripeBilling.unitAmount, stripeBilling.currency) : null;\n  const seatBilling = stripeBilling?.seats;\n  const webBilling = stripeBilling?.web ?? null;\n  const seatPrice = seatBilling ? formatMoneyMinor(seatBilling.unitAmount, seatBilling.currency) : null;\n  const activeMemberCount = stripeBilling?.memberCount ?? 0;\n","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx#L298-L334","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n} catch (error) {\n  setStripeError(error instanceof Error ? error.message : \"Could not open the billing portal.\");\n}\n// after\n} catch (error) {\n  if (isReauthRequiredError(error)) { setStripeError(\"Session expired - sign in again.\"); return; }\n  setStripeError(error instanceof Error ? error.message : \"Could not open the billing portal.\");\n}","handlingStrategy":"try-catch","validationCode":"const status = await requestJson(\"/v1/billing/status\", {}, 10000);\nif (!status.response.ok) return; // org has no billing record; portal will 4xx","typeGuard":"function isReauth(error: unknown): error is ReauthRequiredError { return error instanceof ReauthRequiredError; }","tryCatchPattern":"try {\n  await runReauthableAction(\"billing-portal\", openStripePortal);\n} catch (error) {\n  if (isReauthRequiredError(error)) { promptReauth(); return; }\n  setStripeError(error instanceof Error ? error.message : \"Could not open the billing portal.\");\n}","preventionTips":["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"],"tags":["http","billing","stripe","network"],"backgroundTag":"stripe-portal-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}