{"record":{"id":"03ba3cd8fdadfe12","repo":"langgenius/dify","slug":"you-can-t-install-a-non-public-app","errorCode":null,"errorMessage":"You can't install a non-public app","messagePattern":"You can't install a non-public app","errorType":"http","errorClass":"Forbidden","httpStatus":403,"severity":"error","filePath":"api/controllers/console/explore/installed_app.py","lineNumber":213,"sourceCode":"    @cloud_edition_billing_resource_check(\"apps\")\n    @console_ns.expect(console_ns.models[InstalledAppCreatePayload.__name__])\n    @console_ns.response(200, \"Success\", console_ns.models[SimpleMessageResponse.__name__])\n    @with_current_tenant_id\n    @model_validate(InstalledAppCreatePayload)\n    def post(self, req_data: InstalledAppCreatePayload, current_tenant_id: str):\n        recommended_app = db.session.scalar(\n            select(RecommendedApp).where(RecommendedApp.app_id == req_data.app_id).limit(1)\n        )\n        if recommended_app is None:\n            raise NotFound(\"Recommended app not found\")\n\n        app = db.session.get(App, req_data.app_id)\n\n        if app is None:\n            raise NotFound(\"App entity not found\")\n\n        if not app.is_public:\n            raise Forbidden(\"You can't install a non-public app\")\n\n        installed_app = db.session.scalar(\n            select(InstalledApp)\n            .where(and_(InstalledApp.app_id == req_data.app_id, InstalledApp.tenant_id == current_tenant_id))\n            .limit(1)\n        )\n\n        if installed_app is None:\n            # todo: position\n            recommended_app.install_count += 1\n\n            new_installed_app = InstalledApp(\n                app_id=req_data.app_id,\n                tenant_id=current_tenant_id,\n                app_owner_tenant_id=app.tenant_id,\n                is_pinned=False,\n                last_used_at=naive_utc_now(),\n            )","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/explore/installed_app.py#L195-L231","documentation":"HTTP 403 Forbidden raised in POST /console/explore/installed-apps when the App row exists but app.is_public is False. The platform enforces that only apps explicitly marked public can be installed by other tenants through Explore.","triggerScenarios":"POST /console/explore/installed-apps with an app_id whose apps.is_public column is False, even though a RecommendedApp row exists. Common right after the app owner toggles the app to private, or when the app was listed but its public flag was never set.","commonSituations":"App owner disabled public access after publishing to Explore; app is in draft/private mode; tenant settings changed; client is using an app id obtained out-of-band (not from the public Explore feed).","solutions":["Have the app owner re-enable the public/published state on the app (Settings > Site > enable public access).","Refresh the Explore listing on the client and only install apps that are still listed as public.","If you need a private app, use the workspace invite/tenant flow instead of the Explore install endpoint.","Verify no other tenant-admin toggled is_public off while you were installing."],"exampleFix":"# before: app is private, install attempt fails\n# owner action: in app Settings -> Site, turn on public access\n\n# after: confirm flag before calling install\napp = db.session.get(App, app_id)\nif not app.is_public:\n    raise Forbidden(\"You can't install a non-public app\")\n# owner must publish first; client should hide the install button","handlingStrategy":"validation","validationCode":"// Only show Install for apps the listing marks as public\nconst listing = await getExploreListing();\nconst installable = listing.data.filter(a => a.is_public !== false);\nif (!installable.some(a => a.app_id === targetId)) {\n  throw new UserError('App is not public; ask the owner to publish it');\n}","typeGuard":"function isPublicApp(app) {\n  return Boolean(app && app.is_public === true);\n}","tryCatchPattern":"try {\n  await post('/console/explore/installed-apps', { app_id });\n} catch (e) {\n  if (e.status === 403 && /non-public app/.test(e.message)) {\n    notify('The owner must make this app public before it can be installed.');\n  } else throw e;\n}","preventionTips":["Read is_public from the Explore listing and hide Install when false.","Owners: keep is_public in sync with RecommendedApp entries.","When an app is deliberately made private, expect clients to receive 403."],"tags":["rest-api","explore","forbidden","authorization","installed-app","dify"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}