langgenius/dify · error · Forbidden

You can't install a non-public app

Error message

You can't install a non-public app

What it means

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.

Source

Thrown at api/controllers/console/explore/installed_app.py:213

    @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 += 1

            new_installed_app = InstalledApp(
                app_id=req_data.app_id,
                tenant_id=current_tenant_id,
                app_owner_tenant_id=app.tenant_id,
                is_pinned=False,
                last_used_at=naive_utc_now(),
            )

View on GitHub (pinned to ef8544b173)

Solutions

  1. Have the app owner re-enable the public/published state on the app (Settings > Site > enable public access).
  2. Refresh the Explore listing on the client and only install apps that are still listed as public.
  3. If you need a private app, use the workspace invite/tenant flow instead of the Explore install endpoint.
  4. Verify no other tenant-admin toggled is_public off while you were installing.

Example fix

# before: app is private, install attempt fails
# owner action: in app Settings -> Site, turn on public access

# after: confirm flag before calling install
app = db.session.get(App, app_id)
if not app.is_public:
    raise Forbidden("You can't install a non-public app")
# owner must publish first; client should hide the install button
Defensive patterns

Strategy: validation

Validate before calling

// Only show Install for apps the listing marks as public
const listing = await getExploreListing();
const installable = listing.data.filter(a => a.is_public !== false);
if (!installable.some(a => a.app_id === targetId)) {
  throw new UserError('App is not public; ask the owner to publish it');
}

Type guard

function isPublicApp(app) {
  return Boolean(app && app.is_public === true);
}

Try / catch

try {
  await post('/console/explore/installed-apps', { app_id });
} catch (e) {
  if (e.status === 403 && /non-public app/.test(e.message)) {
    notify('The owner must make this app public before it can be installed.');
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/03ba3cd8fdadfe12. Report an issue: GitHub.