langgenius/dify · error · NotFound
Recommended app not found
Error message
Recommended app not found
What it means
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.
Source
Thrown at api/controllers/console/explore/installed_app.py:205
"installed_apps": installed_app_list,
"has_more": has_more,
"next_cursor": _encode_installed_app_cursor(next_cursor) if next_cursor else None,
},
)
@login_required
@account_initialization_required
@cloud_edition_billing_resource_check("apps")
@console_ns.expect(console_ns.models[InstalledAppCreatePayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SimpleMessageResponse.__name__])
@with_current_tenant_id
@model_validate(InstalledAppCreatePayload)
def post(self, req_data: InstalledAppCreatePayload, current_tenant_id: str):
recommended_app = db.session.scalar(
select(RecommendedApp).where(RecommendedApp.app_id == req_data.app_id).limit(1)
)
if recommended_app is None:
raise NotFound("Recommended app not found")
app = db.session.get(App, req_data.app_id)
if app is None:
raise NotFound("App entity not found")
if not app.is_public:
raise Forbidden("You can't install a non-public app")
installed_app = db.session.scalar(
select(InstalledApp)
.where(and_(InstalledApp.app_id == req_data.app_id, InstalledApp.tenant_id == current_tenant_id))
.limit(1)
)
if installed_app is None:
# todo: position
recommended_app.install_count += 1View on GitHub (pinned to ef8544b173)
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.
Example fix
// before: app_id from stale local storage
await post('/console/explore/installed-apps', { app_id: cachedId });
// after: resolve id from the live Explore listing
const listing = await get('/console/explore/apps');
const target = listing.data.find(a => a.app_id === cachedId);
if (!target) throw new UserError('App no longer available in Explore');
await post('/console/explore/installed-apps', { app_id: target.app_id }); Defensive patterns
Strategy: validation
Validate before calling
// Resolve app_id from the live Explore listing before installing
async function getRecommendedAppId(desiredId) {
const res = await fetch('/console/explore/apps', { headers: authHeaders() });
const list = await res.json();
const found = (list.data || []).find(a => a.app_id === desiredId);
return found ? found.app_id : null; // null => do not call install
} Type guard
function isInstallableAppId(value, listing) {
return typeof value === 'string'
&& /^[0-9a-fA-F-]{36}$/.test(value)
&& listing.some(a => a.app_id === value);
} Try / catch
try {
await post('/console/explore/installed-apps', { app_id });
} catch (e) {
if (e.status === 404 && /Recommended app not found/.test(e.message)) {
refreshExploreListing(); // app no longer recommended
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Installed app not found
- You can't install a non-public app
- You can't uninstall an app owned by the current tenant
- Conversation Not Exists.
- First Message Not Exists.
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/60916851cbf3376f.
Report an issue: GitHub.