langgenius/dify · error · NotFound

Data source binding not found.

Error message

Data source binding not found.

What it means

Flask NotFound (HTTP 404) raised at data_source.py:204 in DataSourceApi.patch (PATCH /data-source/integrates/<binding_id>/<action>). The lookup selects DataSourceOauthBinding by both id == binding_id AND tenant_id == current_tenant_id; if no row matches both, the binding 'does not exist' from this tenant's perspective and NotFound is raised before the enable/disable match runs.

Source

Thrown at api/controllers/console/datasets/data_source.py:204

        return dump_response(DataSourceIntegrateListResponse, {"data": integrate_data}), 200

    @setup_required
    @login_required
    @account_initialization_required
    @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
    @with_current_tenant_id
    @with_session
    def patch(
        self, session: Session, current_tenant_id: str, binding_id: UUID, action: Literal["enable", "disable"]
    ) -> tuple[dict[str, str], int]:
        binding_id_str = str(binding_id)
        data_source_binding = session.scalar(
            select(DataSourceOauthBinding).where(
                DataSourceOauthBinding.id == binding_id_str, DataSourceOauthBinding.tenant_id == current_tenant_id
            )
        )
        if data_source_binding is None:
            raise NotFound("Data source binding not found.")
        # enable binding
        match action:
            case "enable":
                if data_source_binding.disabled:
                    data_source_binding.disabled = False
                    data_source_binding.updated_at = naive_utc_now()
                else:
                    raise ValueError("Data source is not disabled.")
            # disable binding
            case "disable":
                if not data_source_binding.disabled:
                    data_source_binding.disabled = True
                    data_source_binding.updated_at = naive_utc_now()
                else:
                    raise ValueError("Data source is disabled.")
        return {"result": "success"}, 200

View on GitHub (pinned to ef8544b173)

Solutions

  1. Re-fetch the integrations list via GET /data-source/integrates to obtain current binding ids for this tenant before patching.
  2. Confirm the binding_id UUID is copied exactly (no extra characters) and belongs to the current tenant.
  3. If the binding was deleted, re-authorize the data source to create a new binding and use the new id.
  4. Ensure the request carries the correct tenant context header/session so current_tenant_id matches the binding's owner.
Defensive patterns

Strategy: validation

Validate before calling

// Fetch current bindings for the tenant and confirm the id before patching.
const bindings = await fetch('/console/api/data-source/integrates').then(r => r.json());
if (!bindings.data.some(b => b.id === bindingId)) {
  throw new Error('binding_id not in this tenant');
}

Type guard

function bindingBelongsToTenant(b: {id: string}, id: string): boolean { return b.id === id; }

Try / catch

try {
  await patchBinding(bindingId, action);
} catch (e) {
  if (e.status === 404 && /Data source binding not found/i.test(e.message)) { refreshBindings(); }
  else throw e;
}

Prevention

When it happens

Trigger: PATCH /console/api/data-source/integrates/<binding_id>/enable|disable where binding_id does not exist, belongs to a different tenant, or was deleted. The tenant-scoped query returns None.

Common situations: Stale UI showing a binding that was removed; cross-tenant access attempt; UUID typo; or the binding was disabled+hard-deleted by another admin. Tenant scoping means another tenant's binding id is indistinguishable from a nonexistent one.

Related errors


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