{"record":{"id":"60916851cbf3376f","repo":"langgenius/dify","slug":"recommended-app-not-found","errorCode":null,"errorMessage":"Recommended app not found","messagePattern":"Recommended app not found","errorType":"http","errorClass":"NotFound","httpStatus":404,"severity":"error","filePath":"api/controllers/console/explore/installed_app.py","lineNumber":205,"sourceCode":"                \"installed_apps\": installed_app_list,\n                \"has_more\": has_more,\n                \"next_cursor\": _encode_installed_app_cursor(next_cursor) if next_cursor else None,\n            },\n        )\n\n    @login_required\n    @account_initialization_required\n    @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","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/explore/installed_app.py#L187-L223","documentation":"HTTP 404 raised by POST /console/explore/installed-apps when no RecommendedApp row matches the submitted app_id. The explore store is a separate table from App; only apps explicitly published to RecommendedApp are installable through this endpoint. The check runs before the App entity is even loaded.","triggerScenarios":"POST /console/explore/installed-apps with a body {\"app_id\": \"<id>\"} where <id> is not present in the recommended_apps table. Happens with typos, with app ids scraped from a different environment, or with apps that were delisted from Explore after the client cached the id.","commonSituations":"Stale client cache pointing at a delisted app; cross-environment id (test id sent to prod); app exists in the App table but was never published to the Explore listing; admin removed the app from the recommended store.","solutions":["Verify the app_id is currently returned by GET /console/explore/apps (the Explore listing) before calling install.","If the id came from a cached/stored value, refresh it from the Explore list the user is currently viewing.","If you expect the app to be installable but it is not listed, have an admin re-add it to RecommendedApp via the Explore management surface.","Confirm the request hits the same environment (same DB) that listed the app originally."],"exampleFix":"// before: app_id from stale local storage\nawait post('/console/explore/installed-apps', { app_id: cachedId });\n\n// after: resolve id from the live Explore listing\nconst listing = await get('/console/explore/apps');\nconst target = listing.data.find(a => a.app_id === cachedId);\nif (!target) throw new UserError('App no longer available in Explore');\nawait post('/console/explore/installed-apps', { app_id: target.app_id });","handlingStrategy":"validation","validationCode":"// Resolve app_id from the live Explore listing before installing\nasync function getRecommendedAppId(desiredId) {\n  const res = await fetch('/console/explore/apps', { headers: authHeaders() });\n  const list = await res.json();\n  const found = (list.data || []).find(a => a.app_id === desiredId);\n  return found ? found.app_id : null; // null => do not call install\n}","typeGuard":"function isInstallableAppId(value, listing) {\n  return typeof value === 'string'\n    && /^[0-9a-fA-F-]{36}$/.test(value)\n    && listing.some(a => a.app_id === value);\n}","tryCatchPattern":"try {\n  await post('/console/explore/installed-apps', { app_id });\n} catch (e) {\n  if (e.status === 404 && /Recommended app not found/.test(e.message)) {\n    refreshExploreListing(); // app no longer recommended\n  } else throw e;\n}","preventionTips":["Always source the install app_id from a fresh GET /console/explore/apps response.","Invalidate cached Explore listings on app refresh, not on a long TTL.","Surface a clear 'app no longer available' state instead of retrying the same id."],"tags":["rest-api","explore","not-found","installed-app","dify"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}