{"record":{"id":"e3adb13d11e9c658","repo":"different-ai/openwork","slug":"seat-billing-checkout-response-did-not-include-a-u","errorCode":null,"errorMessage":"Seat billing checkout response did not include a URL.","messagePattern":"Seat billing checkout response did not include a URL\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx","lineNumber":645,"sourceCode":"        ensureActiveOrganizationSelected();\n        const { response, payload } = await requestJson(\n          \"/v1/billing/stripe/checkout\",\n          {\n            method: \"POST\",\n            body: JSON.stringify({ type: \"seat\" }),\n          },\n          12000,\n        );\n\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Seat billing checkout failed (${response.status}).`);\n        }\n\n        const url = payload && typeof payload === \"object\" && \"url\" in payload && typeof payload.url === \"string\"\n          ? payload.url\n          : null;\n        if (!url) {\n          throw new Error(\"Seat billing checkout response did not include a URL.\");\n        }\n\n        window.location.href = url;\n      });\n    } finally {\n      setMutationBusy(null);\n    }\n  }\n\n  async function cancelInvitation(invitationId: string) {\n    if (!getCurrentAccess().canCancelInvitations) {\n      throw new Error(\"Only workspace admins can cancel invitations.\");\n    }\n\n    await runMutation(\"cancel-invitation\", async () => {\n      ensureActiveOrganizationSelected();\n      const { response, payload } = await requestJson(\n        `/v1/invitations/${encodeURIComponent(invitationId)}/cancel`,","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx#L627-L663","documentation":"After the seat-checkout API call completes, startSeatCheckout expects the response payload to contain a string url field for the checkout session redirect. If the payload is missing, not an object, or its url is absent/non-string, the provider throws \"Seat billing checkout response did not include a URL.\" rather than redirecting to an undefined location. This indicates a malformed or failed checkout-session response from the billing endpoint.","triggerScenarios":"The POST that creates the seat checkout session returns 2xx with an unexpected body — e.g. an error envelope, an empty object, or a payload shaped { checkoutUrl } instead of { url } — inside the runReauthableAction callback.","commonSituations":"Billing provider outage or misconfigured Stripe return path causing the server to return a success status without a session URL; API contract changed between server and web versions; a proxy/CDN stripping or rewriting the response body; reauth flow returning a different payload shape.","solutions":["Inspect the network response of the seat-checkout request to see the actual payload shape.","Retry the checkout; transient billing-provider issues can yield incomplete responses.","Verify server and web app versions match the /v1 seat checkout contract (payload.url string).","Report to the server team if the endpoint returns 2xx without url — the server should either return a URL or a non-2xx status."],"exampleFix":"// before\nconst url = payload && typeof payload === \"object\" && \"url\" in payload && typeof payload.url === \"string\"\n  ? payload.url : null;\nif (!url) throw new Error(\"Seat billing checkout response did not include a URL.\");\n\n// after (server side: fail loudly instead of 2xx-without-url)\nif (!checkoutSession?.url) {\n  return json({ error: \"checkout_session_not_created\" }, { status: 502 });\n}\nreturn json({ url: checkoutSession.url });","handlingStrategy":"type-guard","validationCode":"function hasCheckoutUrl(p: unknown): p is { url: string } {\n  return typeof p === \"object\" && p !== null && \"url\" in p && typeof (p as { url: unknown }).url === \"string\" && (p as { url: string }).url.length > 0;\n}","typeGuard":"function isCheckoutPayload(p: unknown): p is { url: string } {\n  return typeof p === \"object\" && p !== null && \"url\" in p && typeof (p as { url: unknown }).url === \"string\";\n}","tryCatchPattern":"try {\n  await startSeatCheckout();\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"did not include a URL\")) {\n    showNotice(\"Checkout could not be started. Please retry or contact support.\");\n  } else throw e;\n}","preventionTips":["Assert the /v1 seat checkout contract (payload.url) in server tests.","Treat 2xx-without-url as a server bug: return 502 with an error envelope instead.","Monitor billing-provider errors that produce empty checkout sessions.","Retry once on malformed checkout responses before surfacing to the user."],"tags":["billing","api-response","network"],"backgroundTag":"malformed-api-response","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}