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
- Re-fetch the integrations list via GET /data-source/integrates to obtain current binding ids for this tenant before patching.
- Confirm the binding_id UUID is copied exactly (no extra characters) and belongs to the current tenant.
- If the binding was deleted, re-authorize the data source to create a new binding and use the new id.
- 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
- Always patch using ids from a fresh GET /data-source/integrates response.
- Keep the tenant context header consistent with the binding owner.
- Disable UI controls for removed bindings after a list refresh.
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
- Dataset not found.
- Data source is not disabled.
- Data source is disabled.
- Credential not found.
- File not found.
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/68936e5bc05faff6.
Report an issue: GitHub.