{"record":{"id":"5515d219ae6ba57c","repo":"different-ai/openwork","slug":"desktop-policies-require-an-enterprise-plan","errorCode":null,"errorMessage":"Desktop policies require an Enterprise plan","messagePattern":"Desktop policies require an Enterprise plan","errorType":"exception","errorClass":"Error","httpStatus":402,"severity":"warning","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/desktop-policy-data.tsx","lineNumber":200,"sourceCode":"      setBusy(false);\n    }\n  }\n\n  useEffect(() => {\n    void reloadPolicies();\n  }, [orgId]);\n\n  return { definitions, desktopPolicies, busy, error, reloadPolicies };\n}\n\nexport async function createDesktopPolicy(input: DesktopPolicyPayload) {\n  const { response, payload } = await requestJson(\"/v1/desktop-policies\", {\n    method: \"POST\",\n    headers: { \"content-type\": \"application/json\" },\n    body: JSON.stringify(input),\n  }, 12000);\n  if (!response.ok) {\n    if (response.status === 402) throw new Error(DESKTOP_POLICY_ENTERPRISE_PLAN_ERROR);\n    throw getRequestError(payload, response, `Failed to create desktop policy (${response.status}).`);\n  }\n}\n\nexport async function updateDesktopPolicy(policyId: string, input: DesktopPolicyPayload) {\n  const { response, payload } = await requestJson(`/v1/desktop-policies/${encodeURIComponent(policyId)}`, {\n    method: \"PATCH\",\n    headers: { \"content-type\": \"application/json\" },\n    body: JSON.stringify(input),\n  }, 12000);\n  if (!response.ok) {\n    if (response.status === 402) throw new Error(DESKTOP_POLICY_ENTERPRISE_PLAN_ERROR);\n    throw getRequestError(payload, response, `Failed to update desktop policy (${response.status}).`);\n  }\n}\n\nexport async function deleteDesktopPolicy(policyId: string) {\n  const { response, payload } = await requestJson(`/v1/desktop-policies/${encodeURIComponent(policyId)}`, {","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/desktop-policy-data.tsx#L182-L218","documentation":"createDesktopPolicy POSTs a new policy to /v1/desktop-policies. When the Den server answers 402 Payment Required, the client deliberately maps it to the constant DESKTOP_POLICY_ENTERPRISE_PLAN_ERROR (\"Desktop policies require an Enterprise plan\") instead of the generic request-error fallback. This is a plan-gating signal: the org's subscription tier does not include desktop policy management.","triggerScenarios":"Calling createDesktopPolicy (via handleSave on the policy editor or ensureAdminExceptionPolicy) while the org is on a non-Enterprise plan and Den returns HTTP 402 for POST /v1/desktop-policies.","commonSituations":"A trial or free/Pro org tries to create its first desktop policy; billing lapsed and the subscription downgraded; a self-hosted instance whose license doesn't cover the desktop-policy feature.","solutions":["Upgrade the organization to an Enterprise plan in Den/billing, then retry creating the policy.","Verify the current subscription status on the org's billing page (a lapsed subscription can also return 402).","If self-hosting, apply the Enterprise license/entitlement to the Den instance.","If you believe the org is already Enterprise, confirm the correct org/workspace is selected — the 402 is scoped to the authenticated org."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!orgPlan.includes(\"enterprise\")) {\n  showUpgradePrompt(\"Desktop policies require an Enterprise plan\");\n  return; // skip createDesktopPolicy\n}","typeGuard":"function isPlanGatedError(e: unknown): e is Error {\n  return e instanceof Error && e.message === \"Desktop policies require an Enterprise plan\";\n}","tryCatchPattern":"try {\n  await createDesktopPolicy(input);\n} catch (e) {\n  if (isPlanGatedError(e)) showUpgradePrompt(e.message);\n  else showError(e instanceof Error ? e.message : \"Failed to create desktop policy.\");\n}","preventionTips":["Check the org's plan/entitlements before rendering the policy editor so the 402 path is rarely hit.","Cache the org plan in app state and refresh it after any billing change.","Surface an upgrade CTA instead of a generic error when the message matches the plan gate.","Verify subscription is active (not past-due) after billing operations."],"tags":["http","billing","plan-gating","api"],"backgroundTag":"payment-required-plan-gate","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}