{"record":{"id":"c1959763fdc2dbaf","repo":"different-ai/openwork","slug":"billing-response-was-incomplete","errorCode":null,"errorMessage":"Billing response was incomplete.","messagePattern":"Billing response was incomplete\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx","lineNumber":204,"sourceCode":"  );\n  const canManageBillingSettings = access.canManageSettings;\n\n  async function refreshStripeBilling(quiet = false) {\n    const expectedOrgId = activeOrgId;\n    if (!expectedOrgId) return null;\n    const requestId = billingRequestIdRef.current + 1;\n    billingRequestIdRef.current = requestId;\n    setStripeBusy(true);\n    if (!quiet) setStripeError(null);\n    try {\n      const { response, payload } = await requestJson(\n        \"/v1/billing\",\n        { method: \"GET\", headers: { [ORG_SCOPE_HEADER]: expectedOrgId } },\n        12000,\n      );\n      if (!response.ok) throw new Error(getErrorMessage(payload, `Billing lookup failed (${response.status}).`));\n      const parsed = parseStripeBilling(payload);\n      if (!parsed) throw new Error(\"Billing response was incomplete.\");\n      if (currentOrgIdRef.current !== expectedOrgId || billingRequestIdRef.current !== requestId) return null;\n      setStripeBillingValue(parsed);\n      setStripeBillingOrgId(expectedOrgId);\n      setPolarBilling(parsePolarBilling(payload));\n      return parsed;\n    } catch (error) {\n      if (!quiet && currentOrgIdRef.current === expectedOrgId && billingRequestIdRef.current === requestId) {\n        setStripeError(error instanceof Error ? error.message : \"Could not load billing details.\");\n      }\n      return null;\n    } finally {\n      if (currentOrgIdRef.current === expectedOrgId && billingRequestIdRef.current === requestId) setStripeBusy(false);\n    }\n  }\n\n  useEffect(() => {\n    if (!sessionHydrated || !user) return;\n    void refreshStripeBilling(false);","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx#L186-L222","documentation":"refreshStripeBilling fetches /v1/billing and passes the payload to parseStripeBilling; the error is thrown when the parser returns null, i.e. the response body is JSON but lacks the required Stripe billing fields (e.g. subscription/customer data). The API returned 200 OK but the payload shape was not a complete billing record, so the dashboard refuses to render partial data.","triggerScenarios":"GET /v1/billing returns 200 with a body that parseStripeBilling cannot validate: missing subscription object, wrong field types, an empty object, or the server returning a stubbed/degraded payload during Stripe outage or misconfiguration on the Den server.","commonSituations":"Den server not fully configured with Stripe keys so the endpoint returns a partial payload; API version drift between server and web app changing the billing response schema; a proxy or gateway returning a valid-JSON but non-billing body (e.g. an error envelope with status 200).","solutions":["Inspect the actual /v1/billing response body and compare against what parseStripeBilling expects; log the payload before parsing to spot missing fields.","Verify the Den server's Stripe configuration (API keys, webhook setup) is complete so billing data is fully populated.","Re-run refreshStripeBilling — stale org switches are guarded by currentOrgIdRef/billingRequestIdRef, but a transient partial response can be retried.","Upgrade the web app and Den server together so the billing schema versions match."],"exampleFix":"// before\nconst parsed = parseStripeBilling(payload);\nif (!parsed) throw new Error(\"Billing response was incomplete.\");\n// after\nconst parsed = parseStripeBilling(payload);\nif (!parsed) {\n  console.warn(\"billing payload missing expected fields\", payload);\n  throw new Error(`Billing response was incomplete: ${JSON.stringify(Object.keys(payload ?? {}))}`);\n}","handlingStrategy":"type-guard","validationCode":"function looksLikeStripeBilling(p: unknown): boolean {\n  return typeof p === \"object\" && p !== null && \"subscription\" in p;\n}\n// call before setStripeBillingValue: if (!looksLikeStripeBilling(payload)) return;","typeGuard":"function isStripeBilling(p: unknown): p is StripeBilling {\n  return typeof p === \"object\" && p !== null && \"subscription\" in p && \"customer\" in p;\n}","tryCatchPattern":"try {\n  const parsed = await refreshStripeBilling(orgId);\n  if (parsed === null) return; // stale request, ignore\n} catch (error) {\n  const msg = error instanceof Error ? error.message : \"Billing lookup failed\";\n  setStripeError(msg);\n}","preventionTips":["Log the raw /v1/billing payload when parsing fails to speed diagnosis","Keep client parseStripeBilling and the server billing handler in the same package/types","Add a server-side contract test asserting the billing endpoint returns all fields parseStripeBilling requires","Retry transient failures with backoff before surfacing the error to the user"],"tags":["api-response","validation","billing","stripe"],"backgroundTag":"incomplete-api-response","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}