{"record":{"id":"823dda98361ce3aa","repo":"different-ai/openwork","slug":"invalid-billing-payload","errorCode":"invalid_billing_payload","errorMessage":"Billing response was missing details.","messagePattern":"Billing response was missing details\\.","errorType":"error_code","errorClass":"DenApiError","httpStatus":500,"severity":"error","filePath":"apps/app/src/app/lib/den.ts","lineNumber":3522,"sourceCode":"    },\n\n    async getBillingStatus(options: { includePortal?: boolean; includeInvoices?: boolean } = {}): Promise<DenBillingSummary> {\n      const params = new URLSearchParams();\n      if (options.includePortal === false) {\n        params.set(\"excludePortal\", \"1\");\n      }\n      if (options.includeInvoices === false) {\n        params.set(\"excludeInvoices\", \"1\");\n      }\n\n      const path = params.size > 0 ? `/v1/workers/billing?${params.toString()}` : \"/v1/workers/billing\";\n      const payload = await requestJson<unknown>(baseUrls, path, {\n        method: \"GET\",\n        token,\n      });\n      const summary = getBillingSummary(payload);\n      if (!summary) {\n        throw new DenApiError(500, \"invalid_billing_payload\", \"Billing response was missing details.\");\n      }\n      return summary;\n    },\n\n    async updateSubscriptionCancellation(cancelAtPeriodEnd: boolean): Promise<{ subscription: DenBillingSubscription | null; billing: DenBillingSummary }> {\n      const payload = await requestJson<unknown>(baseUrls, \"/v1/workers/billing/subscription\", {\n        method: \"POST\",\n        token,\n        body: { cancelAtPeriodEnd },\n      });\n      const billing = getBillingSummary(payload);\n      if (!billing) {\n        throw new DenApiError(500, \"invalid_billing_payload\", \"Subscription update response was missing billing details.\");\n      }\n\n      return {\n        subscription: isRecord(payload) ? getBillingSubscription(payload.subscription) : null,\n        billing,","sourceCodeStart":3504,"sourceCodeEnd":3540,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/app/lib/den.ts#L3504-L3540","documentation":"DenApiError thrown by getBillingStatus in apps/app/src/app/lib/den.ts when GET /v1/workers/billing returned 2xx but getBillingSummary could not build a DenBillingSummary from the payload — the billing details (plan/subscription/invoice fields the parser requires) were missing or malformed. This prevents the UI from rendering a half-populated billing page.","triggerScenarios":"GET /v1/workers/billing (with optional excludePortal/excludeInvoices params) responds 200 with an empty object, an error envelope, a non-object body, or a billing object missing the fields getBillingSummary requires (e.g. billing not provisioned for the token's account).","commonSituations":"Calling billing endpoints with a token that has no worker/billing context; self-hosted Den without the billing module returning an empty body; excludePortal/excludeInvoices flags trimming fields the parser needs; proxy or gateway replacing JSON with a 200 HTML page; server/client schema drift.","solutions":["Log the raw GET /v1/workers/billing body and compare against getBillingSummary's required fields","Confirm the request includes a valid token and that billing is provisioned for that account on the Den server","Try without the excludePortal/excludeInvoices flags to see if the reduced payload is what fails the parser","Align server and client versions if the billing schema changed","Rule out proxies/gateways rewriting the response to HTML"],"exampleFix":"// before\nconst summary = getBillingSummary(payload);\nif (!summary) {\n  throw new DenApiError(500, \"invalid_billing_payload\", \"Billing response was missing details.\");\n}\n// after\ncaller-side:\ntry {\n  const billing = await client.getBillingStatus({ includePortal: true, includeInvoices: true });\n} catch (err) {\n  if (err instanceof DenApiError && err.code === \"invalid_billing_payload\") {\n    // show a 'billing unavailable' state instead of crashing\n  }\n}","handlingStrategy":"validation","validationCode":"// ensure the response looks like billing data before trusting it\nfunction looksLikeBilling(v: unknown): boolean {\n  return typeof v === \"object\" && v !== null && Object.keys(v).length > 0;\n}\n// also: request full payload first time (omit excludePortal/excludeInvoices)","typeGuard":"function isBillingSummary(v: unknown): v is DenBillingSummary {\n  return (\n    typeof v === \"object\" && v !== null &&\n    \"plan\" in v // narrow further to match getBillingSummary's required fields\n  );\n}","tryCatchPattern":"try {\n  const billing = await client.getBillingStatus();\n} catch (err) {\n  if (err instanceof DenApiError && err.code === \"invalid_billing_payload\") {\n    // render a billing-unavailable state; optionally retry once without exclude params\n  } else { throw err; }\n}","preventionTips":["Call getBillingStatus with default options (include portal and invoices) before using exclusion flags","Confirm billing is provisioned on your Den deployment before relying on billing UI","Check the token has access to the billing/worker context","Log raw /v1/workers/billing responses in dev to detect schema drift","Handle DenApiError code invalid_billing_payload with a graceful empty-billing state"],"tags":["billing","api-response","schema-mismatch","den-api"],"backgroundTag":"invalid-api-response-shape","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}