{"record":{"id":"b23cfc3a1d3ebf3b","repo":"langflow-ai/langflow","slug":"metadata-key-key-is-reserved-for-ingestion-int","errorCode":null,"errorMessage":"Metadata key '{key}' is reserved for ingestion-internal use.","messagePattern":"Metadata key '(.+?)' is reserved for ingestion-internal use\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":88,"sourceCode":"    safely once validation passes). Raises :class:`HTTPException` with a 422\n    status on any violation so FastAPI surfaces an inline error.\n    \"\"\"\n    if not isinstance(metadata, dict):\n        msg = \"Metadata must be a JSON object.\"\n        raise HTTPException(status_code=422, detail=msg)\n    if len(metadata) > KB_METADATA_MAX_KEYS:\n        msg = f\"Metadata exceeds the {KB_METADATA_MAX_KEYS} key limit.\"\n        raise HTTPException(status_code=422, detail=msg)\n    for key, value in metadata.items():\n        if not isinstance(key, str) or not _is_valid_key(key):\n            msg = (\n                f\"Metadata key {key!r} is invalid: must be 1-{KB_METADATA_MAX_KEY_LENGTH} \"\n                \"lowercase alphanumeric or underscore characters.\"\n            )\n            raise HTTPException(status_code=422, detail=msg)\n        if key in KB_METADATA_RESERVED_KEYS:\n            msg = f\"Metadata key '{key}' is reserved for ingestion-internal use.\"\n            raise HTTPException(status_code=422, detail=msg)\n        _validate_value(key, value)\n    return metadata\n\n\ndef parse_user_metadata(raw: str | None) -> dict[str, Any]:\n    \"\"\"Decode + validate the ``metadata`` form field. Empty/None → ``{}``.\"\"\"\n    if not raw:\n        return {}\n    try:\n        decoded = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = f\"Metadata is not valid JSON: {exc.msg}\"\n        raise HTTPException(status_code=422, detail=msg) from exc\n    return validate_user_metadata(decoded)\n\n\ndef parse_per_file_metadata(raw: str | None) -> dict[str, dict[str, Any]]:\n    \"\"\"Decode + validate the ``per_file_metadata`` form field.","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L70-L106","documentation":"Eight metadata keys are reserved for the ingestion pipeline itself (KB_METADATA_RESERVED_KEYS: source, file_name, chunk_index, total_chunks, ingested_at, job_id, source_type, source_metadata). If a user-supplied metadata dict uses any of them it is rejected with 422, because a user value would silently overwrite the ingestion-internal tag and break the chunks-browser filters keyed off them.","triggerScenarios":"metadata or per_file_metadata contains e.g. {\"source\": \"cms\"} or {\"file_name\": \"renamed.pdf\"}. The reserved set is a frozenset in kb_constants.py; matching is exact and case-sensitive (so 'Source' instead fails the key-charset rule with error 153).","commonSituations":"Natural naming collisions — pipelines almost always want 'source' or 'file_name' as a user field; porting existing document metadata schemas; per-file overrides trying to rename the displayed filename.","solutions":["Rename your key to a non-reserved equivalent: 'origin', 'upstream_source', 'display_name', 'original_filename', etc. (still snake_case <=32).","To derive per-file values, use per_file_metadata with non-reserved keys — the pipeline's own file_name is populated automatically.","Keep a client-side denylist mirroring KB_METADATA_RESERVED_KEYS and strip/rename collisions before submit.","If you truly need to override ingestion tags, that is not supported at the API boundary — file a feature request instead of working around it."],"exampleFix":"# before\nmetadata = {\"source\": \"crm\", \"file_name\": f}  # 422 reserved\n\n# after\nmetadata = {\"origin\": \"crm\", \"original_filename\": f}","handlingStrategy":"validation","validationCode":"from langflow.utils.kb_constants import KB_METADATA_RESERVED_KEYS\n\ndef rename_reserved(meta: dict, mapping: dict) -> dict:\n    return {mapping.get(k, k): v for k, v in meta.items() if k not in KB_METADATA_RESERVED_KEYS or k in mapping}","typeGuard":"def free_of_reserved_keys(meta) -> bool:\n    return not (set(meta) & KB_METADATA_RESERVED_KEYS)","tryCatchPattern":"try:\n    validate_user_metadata(meta)\nexcept HTTPException as e:\n    if e.status_code == 422 and 'reserved for ingestion-internal use' in e.detail:\n        meta = rename_reserved(meta, {'source': 'origin', 'file_name': 'original_filename'})\n        validate_user_metadata(meta)\n    else:\n        raise","preventionTips":["Mirror KB_METADATA_RESERVED_KEYS as a client-side denylist.","Rename 'source'->'origin', 'file_name'->'original_filename' by convention.","Remember the pipeline populates those tags itself — you never need to set them."],"tags":["knowledge-base","metadata","validation","reserved-words","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}