different-ai/openwork · error · Error

Seat checkout response did not include a URL.

Error message

Seat checkout response did not include a URL.

What it means

startSeatCheckout POSTs {type:"seat"} to /v1/billing/stripe/checkout expecting a JSON body with a string 'url' field for the Stripe-hosted checkout session. The error is thrown when the endpoint responds 200 OK but the payload contains no usable url, so the redirect (window.location.href = url) cannot proceed.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx:295

  async function startSeatCheckout() {
    if (!canManageBillingSettings) {
      setStripeError("Admins can start seat checkout from Members. Owners and super-admins manage Billing settings here.");
      return;
    }

    setStripeError(null);
    try {
      await runReauthableAction("seat-checkout", async () => {
        setStripeActionBusy("seat-checkout");
        const { response, payload } = await requestJson(
          "/v1/billing/stripe/checkout",
          { method: "POST", body: JSON.stringify({ type: "seat" }) },
          12000,
        );
        if (!response.ok) throw getRequestError(payload, response, `Seat checkout failed (${response.status}).`);
        const url = payload && typeof payload === "object" && "url" in payload && typeof payload.url === "string" ? payload.url : null;
        if (!url) throw new Error("Seat checkout response did not include a URL.");
        window.location.href = url;
      });
    } 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 () => {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Log the checkout response payload to confirm what the server actually returned for 'url'.
  2. Fix the server handler to return 4xx/5xx with getRequestError-compatible detail instead of a 200 without a URL.
  3. Verify the server can create Stripe checkout sessions (valid API keys, price configured for seat billing).
  4. Check client and server share the same response contract ({url: string}); update parsing if the field moved (e.g. payload.session.url).

Example fix

// before
const url = payload && typeof payload === "object" && "url" in payload && typeof payload.url === "string" ? payload.url : null;
if (!url) throw new Error("Seat checkout response did not include a URL.");
// after
const url = isCheckoutUrlResponse(payload) ? payload.url : null;
if (!url) throw new Error(`Seat checkout response did not include a URL (keys: ${Object.keys(payload ?? {}).join(",")}).`);
Defensive patterns

Strategy: type-guard

Validate before calling

function hasUrl(p: unknown): p is { url: string } {
  return typeof p === "object" && p !== null && "url" in p && typeof (p as { url: unknown }).url === "string";
}
// before redirecting: if (!hasUrl(payload)) throw ...

Type guard

function isCheckoutUrlResponse(p: unknown): p is { url: string } {
  return typeof p === "object" && p !== null && "url" in p && typeof (p as { url: unknown }).url === "string" && (p as { url: string }).url.startsWith("https://");
}

Try / catch

try {
  await startSeatCheckout();
} catch (error) {
  setStripeError(error instanceof Error ? error.message : "Could not start seat billing checkout.");
}

Prevention

When it happens

Trigger: POST /v1/billing/stripe/checkout returns 200 with a body like {}, {url: null}, or {url: 123}; the server-side Stripe checkout session creation silently failed but still returned success.

Common situations: Stripe publishable/secret keys misconfigured on the Den server; checkout session creation returning a session without a URL; response schema change (url nested under data); org has no Stripe customer yet and server returns an empty success.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/c18624e3378e9012. Report an issue: GitHub.