diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp
index 7001b2390..561203bfd 100644
--- a/Telegram/SourceFiles/messenger.cpp
+++ b/Telegram/SourceFiles/messenger.cpp
@@ -844,6 +844,7 @@ bool Messenger::openLocalUrl(const QString &url) {
 			const auto callback = params.value("callback_url", QString());
 			const auto publicKey = params.value("public_key", QString());
 			const auto payload = params.value("payload", QString());
+			const auto errors = params.value("errors", QString());
 			if (const auto window = App::wnd()) {
 				if (const auto controller = window->controller()) {
 					controller->showPassportForm(Passport::FormRequest(
@@ -851,7 +852,8 @@ bool Messenger::openLocalUrl(const QString &url) {
 						scope,
 						callback,
 						publicKey,
-						payload));
+						payload,
+						errors));
 					return true;
 				}
 			}
diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style
index 9fa1d40ce..625a01ed7 100644
--- a/Telegram/SourceFiles/passport/passport.style
+++ b/Telegram/SourceFiles/passport/passport.style
@@ -172,6 +172,7 @@ passportUploadButton: InfoProfileButton {
 }
 passportUploadButtonPadding: margins(0px, 10px, 0px, 10px);
 passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px);
+passportUploadErrorPadding: margins(22px, 5px, 22px, 5px);
 passportDeleteButton: InfoProfileButton(passportUploadButton) {
 	textFg: attentionButtonFg;
 	textFgOver: attentionButtonFgOver;
diff --git a/Telegram/SourceFiles/passport/passport_encryption.cpp b/Telegram/SourceFiles/passport/passport_encryption.cpp
index 8dc17cade..a1e4b5383 100644
--- a/Telegram/SourceFiles/passport/passport_encryption.cpp
+++ b/Telegram/SourceFiles/passport/passport_encryption.cpp
@@ -238,6 +238,78 @@ std::map<QString, QString> DeserializeData(bytes::const_span bytes) {
 	return result;
 }
 
+std::vector<DataError> DeserializeErrors(bytes::const_span json) {
+	const auto serialized = QByteArray::fromRawData(
+		reinterpret_cast<const char*>(json.data()),
+		json.size());
+	auto error = QJsonParseError();
+	auto document = QJsonDocument::fromJson(serialized, &error);
+	if (error.error != QJsonParseError::NoError) {
+		LOG(("API Error: Could not deserialize errors JSON, error %1"
+			).arg(error.errorString()));
+		return {};
+	} else if (!document.isArray()) {
+		LOG(("API Error: Errors JSON root is not an array."));
+		return {};
+	}
+	auto array = document.array();
+	auto result = std::vector<DataError>();
+	for (const auto &error : array) {
+		if (!error.isObject()) {
+			LOG(("API Error: Not an object inside errors JSON."));
+			continue;
+		}
+		auto fields = error.toObject();
+		const auto typeIt = fields.constFind("type");
+		if (typeIt == fields.constEnd()) {
+			LOG(("API Error: type was not found in an error."));
+			continue;
+		} else if (!typeIt->isString()) {
+			LOG(("API Error: type was not a string in an error."));
+			continue;
+		}
+		const auto descriptionIt = fields.constFind("description");
+		if (descriptionIt == fields.constEnd()) {
+			LOG(("API Error: description was not found in an error."));
+			continue;
+		} else if (!typeIt->isString()) {
+			LOG(("API Error: description was not a string in an error."));
+			continue;
+		}
+		const auto targetIt = fields.constFind("target");
+		if (targetIt == fields.constEnd()) {
+			LOG(("API Error: target aws not found in an error."));
+			continue;
+		} else if (!targetIt->isString()) {
+			LOG(("API Error: target was not as string in an error."));
+			continue;
+		}
+		auto next = DataError();
+		next.type = typeIt->toString();
+		next.text = descriptionIt->toString();
+		const auto fieldIt = fields.constFind("field");
+		const auto fileHashIt = fields.constFind("file_hash");
+		if (fieldIt != fields.constEnd()) {
+			if (!fieldIt->isString()) {
+				LOG(("API Error: field was not a string in an error."));
+				continue;
+			}
+			next.key = fieldIt->toString();
+		} else if (fileHashIt != fields.constEnd()) {
+			if (!fileHashIt->isString()) {
+				LOG(("API Error: file_hash was not a string in an error."));
+				continue;
+			}
+			next.key = QByteArray::fromBase64(
+				fileHashIt->toString().toUtf8());
+		} else if (targetIt->toString() == "selfie") {
+			next.key = QByteArray();
+		}
+		result.push_back(std::move(next));
+	}
+	return result;
+}
+
 EncryptedData EncryptData(bytes::const_span bytes) {
 	return EncryptData(bytes, GenerateSecretBytes());
 }
diff --git a/Telegram/SourceFiles/passport/passport_encryption.h b/Telegram/SourceFiles/passport/passport_encryption.h
index ee758a029..71443ccde 100644
--- a/Telegram/SourceFiles/passport/passport_encryption.h
+++ b/Telegram/SourceFiles/passport/passport_encryption.h
@@ -23,6 +23,17 @@ bytes::vector DecryptSecureSecret(
 bytes::vector SerializeData(const std::map<QString, QString> &data);
 std::map<QString, QString> DeserializeData(bytes::const_span bytes);
 
+struct DataError {
+	// QByteArray - bad existing scan with such file_hash
+	// QString - bad data field value with such key
+	// base::none - additional scan required
+	base::optional_variant<QByteArray, QString> key;
+	QString type; // personal_details, passport, etc.
+	QString text;
+
+};
+std::vector<DataError> DeserializeErrors(bytes::const_span json);
+
 struct EncryptedData {
 	bytes::vector secret;
 	bytes::vector hash;
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index b17f5a1e2..8ae70592d 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -156,12 +156,14 @@ FormRequest::FormRequest(
 	const QString &scope,
 	const QString &callbackUrl,
 	const QString &publicKey,
-	const QString &payload)
+	const QString &payload,
+	const QString &errors)
 : botId(botId)
 , scope(scope)
 , callbackUrl(callbackUrl)
 , publicKey(publicKey)
-, payload(payload) {
+, payload(payload)
+, errors(errors) {
 }
 
 EditFile::EditFile(
@@ -247,8 +249,8 @@ auto FormController::prepareFinalData() -> FinalData {
 	auto hashes = QVector<MTPSecureValueHash>();
 	auto secureData = QJsonObject();
 	const auto addValueToJSON = [&](
-		const QString &key,
-		not_null<const Value*> value) {
+			const QString &key,
+			not_null<const Value*> value) {
 		auto object = QJsonObject();
 		if (!value->data.parsed.fields.empty()) {
 			object.insert("data", GetJSONFromMap({
@@ -279,8 +281,8 @@ auto FormController::prepareFinalData() -> FinalData {
 	};
 	const auto scopes = ComputeScopes(this);
 	for (const auto &scope : scopes) {
-		const auto ready = ComputeScopeRowReadyString(scope);
-		if (ready.isEmpty()) {
+		const auto row = ComputeScopeRow(scope);
+		if (row.ready.isEmpty() || !row.error.isEmpty()) {
 			errors.push_back(scope.fields);
 			continue;
 		}
@@ -489,6 +491,43 @@ void FormController::decryptValues() {
 	for (auto &[type, value] : _form.values) {
 		decryptValue(value);
 	}
+	fillErrors();
+}
+
+void FormController::fillErrors() {
+	const auto errors = _request.errors.toUtf8();
+	const auto list = DeserializeErrors(bytes::make_span(errors));
+	for (const auto &error : list) {
+		for (auto &[type, value] : _form.values) {
+			if (ValueCredentialsKey(type) != error.type) {
+				continue;
+			}
+			if (!error.key.has_value()) {
+				value.scanMissingError = error.text;
+			} else if (const auto key = base::get_if<QString>(&error.key)) {
+				value.data.parsed.fields[(*key)].error = error.text;
+			} else if (auto hash = base::get_if<QByteArray>(&error.key)) {
+				const auto check = [&](const File &file) {
+					return *hash == QByteArray::fromRawData(
+						reinterpret_cast<const char*>(file.hash.data()),
+						file.hash.size());
+				};
+				for (auto &scan : value.scans) {
+					if (check(scan)) {
+						scan.error = error.text;
+						break;
+					}
+				}
+				if (value.selfie) {
+					if (check(*value.selfie) || hash->isEmpty()) {
+						value.selfie->error = error.text;
+					}
+				}
+			}
+			break;
+		}
+
+	}
 }
 
 void FormController::decryptValue(Value &value) {
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h
index 09eb415e9..05b2df7d6 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.h
+++ b/Telegram/SourceFiles/passport/passport_form_controller.h
@@ -32,13 +32,15 @@ struct FormRequest {
 		const QString &scope,
 		const QString &callbackUrl,
 		const QString &publicKey,
-		const QString &payload);
+		const QString &payload,
+		const QString &errors);
 
 	UserId botId;
 	QString scope;
 	QString callbackUrl;
 	QString publicKey;
 	QString payload;
+	QString errors;
 
 };
 
@@ -317,6 +319,7 @@ private:
 	void decryptValue(Value &value);
 	bool validateValueSecrets(Value &value);
 	void resetValue(Value &value);
+	void fillErrors();
 
 	void loadFile(File &file);
 	void fileLoadDone(FileKey key, const QByteArray &bytes);
diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
index 83187c167..9ea0ba8d6 100644
--- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
@@ -188,14 +188,28 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
 	const auto addReadyError = [&](ScopeRow &&row) {
 		const auto ready = ComputeScopeRowReadyString(scope);
 		row.ready = ready;
-		// #TODO passport bot errors
-		//row.error = scope.fields->error.has_value()
-		//	? (!scope.fields->error->isEmpty()
-		//		? *scope.fields->error
-		//		: !ready.isEmpty()
-		//		? ready
-		//		: row.description)
-		//	: QString();
+		auto errors = QStringList();
+		const auto addValueErrors = [&](not_null<const Value*> value) {
+			for (const auto &scan : value->scans) {
+				if (!scan.error.isEmpty()) {
+					errors.push_back(scan.error);
+				}
+			}
+			if (value->selfie && !value->selfie->error.isEmpty()) {
+				errors.push_back(value->selfie->error);
+			}
+			if (!value->scanMissingError.isEmpty()) {
+				errors.push_back(value->scanMissingError);
+			}
+			for (const auto &[key, value] : value->data.parsed.fields) {
+				if (!value.error.isEmpty()) {
+					errors.push_back(value.error);
+				}
+			}
+		};
+		ranges::for_each(scope.documents, addValueErrors);
+		addValueErrors(scope.fields);
+		row.error = errors.join('\n');
 		return row;
 	};
 	switch (scope.type) {
diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
index ee468839b..f32011a72 100644
--- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
@@ -565,7 +565,7 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
 		&& (&file == &*_editDocument->selfieInEdit);
 	return {
 		FileKey{ file.fields.id, file.fields.dcId },
-		status,
+		!file.fields.error.isEmpty() ? file.fields.error : status,
 		file.fields.image,
 		file.deleted,
 		isSelfie,
@@ -871,6 +871,7 @@ void PanelController::editScope(int index, int documentIndex) {
 						_editDocument->type),
 					_editValue->data.parsedInEdit,
 					_editDocument->data.parsedInEdit,
+					_editDocument->scanMissingError,
 					valueFiles(*_editDocument),
 					(_editScope->selfieRequired
 						? valueSelfie(*_editDocument)
diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp
index 5fc447ec5..6db34120c 100644
--- a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp
@@ -930,10 +930,14 @@ int PanelDetailsRow::resizeGetHeight(int newWidth) {
 	const auto inputRight = padding.right();
 	const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0);
 	const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth);
-	return padding.top()
+	const auto result = padding.top()
 		+ innerHeight
 		+ (_error ? _error->height() : 0)
 		+ padding.bottom();
+	if (_error) {
+		_error->moveToLeft(inputLeft, result - _error->height());
+	}
+	return result;
 }
 
 void PanelDetailsRow::showError(const QString &error) {
@@ -959,12 +963,18 @@ void PanelDetailsRow::showError(const QString &error) {
 		} else {
 			_error->entity()->setText(error);
 		}
+		_error->heightValue(
+		) | rpl::start_with_next([=] {
+			resizeToWidth(width());
+		}, _error->lifetime());
 		_error->show(anim::type::normal);
-	} else if (_error) {
-		_error->hide(anim::type::normal);
 	}
 }
 
+bool PanelDetailsRow::errorShown() const {
+	return _errorShown;
+}
+
 void PanelDetailsRow::hideError() {
 	startErrorAnimation(false);
 	if (_error) {
diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.h b/Telegram/SourceFiles/passport/passport_panel_details_row.h
index 47fb6010b..7d206c12b 100644
--- a/Telegram/SourceFiles/passport/passport_panel_details_row.h
+++ b/Telegram/SourceFiles/passport/passport_panel_details_row.h
@@ -66,6 +66,7 @@ public:
 	virtual rpl::producer<QString> value() const = 0;
 	virtual QString valueCurrent() const = 0;
 	void showError(const QString &error);
+	bool errorShown() const;
 	void hideError();
 	void finishAnimating();
 
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
index 76adb22c2..2590a7b89 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
@@ -211,6 +211,7 @@ PanelEditDocument::PanelEditDocument(
 	Scheme scheme,
 	const ValueMap &data,
 	const ValueMap &scanData,
+	const QString &missingScansError,
 	std::vector<ScanInfo> &&files,
 	std::unique_ptr<ScanInfo> &&selfie)
 : _controller(controller)
@@ -222,7 +223,12 @@ PanelEditDocument::PanelEditDocument(
 		this,
 		langFactory(lng_passport_save_value),
 		st::passportPanelSaveValue) {
-	setupControls(data, &scanData, std::move(files), std::move(selfie));
+	setupControls(
+		data,
+		&scanData,
+		missingScansError,
+		std::move(files),
+		std::move(selfie));
 }
 
 PanelEditDocument::PanelEditDocument(
@@ -239,17 +245,19 @@ PanelEditDocument::PanelEditDocument(
 		this,
 		langFactory(lng_passport_save_value),
 		st::passportPanelSaveValue) {
-	setupControls(data, nullptr, {}, nullptr);
+	setupControls(data, nullptr, QString(), {}, nullptr);
 }
 
 void PanelEditDocument::setupControls(
 		const ValueMap &data,
 		const ValueMap *scanData,
+		const QString &missingScansError,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie) {
 	const auto inner = setupContent(
 		data,
 		scanData,
+		missingScansError,
 		std::move(files),
 		std::move(selfie));
 
@@ -267,6 +275,7 @@ void PanelEditDocument::setupControls(
 not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
 		const ValueMap &data,
 		const ValueMap *scanData,
+		const QString &missingScansError,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie) {
 	const auto inner = _scroll->setOwnedWidget(
@@ -282,6 +291,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
 				inner,
 				_controller,
 				_scheme.scansHeader,
+				missingScansError,
 				std::move(files),
 				std::move(selfie)));
 	} else {
@@ -401,13 +411,16 @@ bool PanelEditDocument::validate() {
 	auto first = QPointer<PanelDetailsRow>();
 	for (const auto [i, field] : base::reversed(_details)) {
 		const auto &row = _scheme.rows[i];
-		if (row.validate && !row.validate(field->valueCurrent())) {
+		if (field->errorShown()
+			|| (row.validate && !row.validate(field->valueCurrent()))) {
 			field->showError(QString());
 			first = field;
 		}
 	}
-	if (!first) {
-		return !error;
+	if (error) {
+		return false;
+	} else if (!first) {
+		return true;
 	}
 	const auto firsttop = first->mapToGlobal(QPoint(0, 0));
 	const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h
index a9c393ae8..e185feb70 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h
@@ -62,6 +62,7 @@ public:
 		Scheme scheme,
 		const ValueMap &data,
 		const ValueMap &scanData,
+		const QString &missingScansError,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie);
 	PanelEditDocument(
@@ -81,11 +82,13 @@ private:
 	void setupControls(
 		const ValueMap &data,
 		const ValueMap *scanData,
+		const QString &missingScansError,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie);
 	not_null<Ui::RpWidget*> setupContent(
 		const ValueMap &data,
 		const ValueMap *scanData,
+		const QString &missingScansError,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie);
 	void updateControlsGeometry();
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp
index b7945415f..74da8a8c3 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp
@@ -31,11 +31,13 @@ public:
 		const style::PassportScanRow &st,
 		const QString &name,
 		const QString &status,
-		bool deleted);
+		bool deleted,
+		bool error);
 
 	void setImage(const QImage &image);
 	void setStatus(const QString &status);
 	void setDeleted(bool deleted);
+	void setError(bool error);
 
 	rpl::producer<> deleteClicks() const {
 		return _delete->entity()->clicks();
@@ -57,6 +59,7 @@ private:
 	Text _status;
 	int _nameHeight = 0;
 	int _statusHeight = 0;
+	bool _error = false;
 	QImage _image;
 	object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _delete;
 	object_ptr<Ui::FadeWrapScaled<Ui::RoundButton>> _restore;
@@ -68,7 +71,8 @@ ScanButton::ScanButton(
 	const style::PassportScanRow &st,
 	const QString &name,
 	const QString &status,
-	bool deleted)
+	bool deleted,
+	bool error)
 : AbstractButton(parent)
 , _st(st)
 , _name(
@@ -79,6 +83,7 @@ ScanButton::ScanButton(
 	st::defaultTextStyle,
 	status,
 	Ui::NameTextOptions())
+, _error(error)
 , _delete(this, object_ptr<Ui::IconButton>(this, _st.remove))
 , _restore(
 	this,
@@ -109,6 +114,11 @@ void ScanButton::setDeleted(bool deleted) {
 	update();
 }
 
+void ScanButton::setError(bool error) {
+	_error = error;
+	update();
+}
+
 int ScanButton::resizeGetHeight(int newWidth) {
 	_nameHeight = st::semiboldFont->height;
 	_statusHeight = st::normalFont->height;
@@ -145,7 +155,8 @@ void ScanButton::paintEvent(QPaintEvent *e) {
 		_st.border,
 		_st.borderFg);
 
-	if (_restore->toggled()) {
+	const auto deleted = _restore->toggled();
+	if (deleted) {
 		p.setOpacity(st::passportScanDeletedOpacity);
 	}
 
@@ -173,7 +184,9 @@ void ScanButton::paintEvent(QPaintEvent *e) {
 		top + _st.nameTop,
 		availableWidth,
 		width());
-	p.setPen(st::windowSubTextFg);
+	p.setPen((_error && !deleted)
+		? st::boxTextFgError
+		: st::windowSubTextFg);
 	_status.drawLeftElided(
 		p,
 		left + _st.textLeft,
@@ -186,26 +199,56 @@ EditScans::EditScans(
 	QWidget *parent,
 	not_null<PanelController*> controller,
 	const QString &header,
+	const QString &errorMissing,
 	std::vector<ScanInfo> &&files,
 	std::unique_ptr<ScanInfo> &&selfie)
 : RpWidget(parent)
 , _controller(controller)
 , _files(std::move(files))
 , _selfie(std::move(selfie))
+, _initialCount(_files.size())
+, _errorMissing(errorMissing)
 , _content(this) {
 	setupContent(header);
 }
 
+bool EditScans::uploadedSomeMore() const {
+	const auto from = begin(_files) + _initialCount;
+	const auto till = end(_files);
+	return std::find_if(from, till, [](const ScanInfo &file) {
+		return !file.deleted;
+	}) != till;
+}
+
 base::optional<int> EditScans::validateGetErrorTop() {
-	const auto exists = ranges::find(
+	const auto exists = ranges::find_if(
 		_files,
-		false,
-		[](const ScanInfo &file) { return file.deleted; }) != end(_files);
-	if (!exists) {
+		[](const ScanInfo &file) { return !file.deleted; }) != end(_files);
+	const auto errorExists = ranges::find_if(
+		_files,
+		[](const ScanInfo &file) { return !file.error.isEmpty(); }
+	) != end(_files);
+
+	if (!exists
+		|| ((errorExists || _uploadMoreError) && !uploadedSomeMore())) {
 		toggleError(true);
 		return (_files.size() > 5) ? _upload->y() : _header->y();
 	}
-	if (_selfie && (!_selfie->key.id || _selfie->deleted)) {
+
+	const auto nonDeletedErrorIt = ranges::find_if(
+		_files,
+		[](const ScanInfo &file) {
+			return !file.error.isEmpty() && !file.deleted;
+		});
+	if (nonDeletedErrorIt != end(_files)) {
+		const auto index = (nonDeletedErrorIt - begin(_files));
+		toggleError(true);
+		return _rows[index]->y();
+	}
+	if (_selfie
+		&& (!_selfie->key.id
+			|| _selfie->deleted
+			|| !_selfie->error.isEmpty())) {
 		toggleSelfieError(true);
 		return _selfieHeader->y();
 	}
@@ -234,7 +277,18 @@ void EditScans::setupContent(const QString &header) {
 				st::passportFormHeader),
 			st::passportUploadHeaderPadding));
 	_header->toggle(!_files.empty(), anim::type::instant);
-
+	if (!_errorMissing.isEmpty()) {
+		_uploadMoreError = inner->add(
+			object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
+				inner,
+				object_ptr<Ui::FlatLabel>(
+					inner,
+					_errorMissing,
+					Ui::FlatLabel::InitType::Simple,
+					st::passportVerifyErrorLabel),
+				st::passportUploadErrorPadding));
+		_uploadMoreError->toggle(true, anim::type::instant);
+	}
 	_wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
 	for (const auto &scan : _files) {
 		pushScan(scan);
@@ -317,6 +371,7 @@ void EditScans::updateScan(ScanInfo &&info) {
 		button->setStatus(info.status);
 		button->setImage(info.thumb);
 		button->setDeleted(info.deleted);
+		button->setError(!info.error.isEmpty());
 	};
 	if (info.selfie) {
 		Assert(info.key.id != 0);
@@ -413,7 +468,8 @@ base::unique_qptr<Ui::SlideWrap<ScanButton>> EditScans::createScan(
 				st::passportScanRow,
 				name,
 				info.status,
-				info.deleted))));
+				info.deleted,
+				!info.error.isEmpty()))));
 	result->entity()->setImage(info.thumb);
 	return result;
 }
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h
index 6e29701d9..89cbdf3b8 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h
@@ -36,6 +36,7 @@ public:
 		QWidget *parent,
 		not_null<PanelController*> controller,
 		const QString &header,
+		const QString &errorMissing,
 		std::vector<ScanInfo> &&files,
 		std::unique_ptr<ScanInfo> &&selfie);
 
@@ -62,6 +63,7 @@ private:
 	void toggleError(bool shown);
 	void hideError();
 	void errorAnimationCallback();
+	bool uploadedSomeMore() const;
 
 	void toggleSelfieError(bool shown);
 	void hideSelfieError();
@@ -70,10 +72,13 @@ private:
 	not_null<PanelController*> _controller;
 	std::vector<ScanInfo> _files;
 	std::unique_ptr<ScanInfo> _selfie;
+	int _initialCount = 0;
+	QString _errorMissing;
 
 	object_ptr<Ui::VerticalLayout> _content;
 	QPointer<Ui::SlideWrap<BoxContentDivider>> _divider;
 	QPointer<Ui::SlideWrap<Ui::FlatLabel>> _header;
+	QPointer<Ui::SlideWrap<Ui::FlatLabel>> _uploadMoreError;
 	QPointer<Ui::VerticalLayout> _wrap;
 	std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> _rows;
 	QPointer<Info::Profile::Button> _upload;
diff --git a/Telegram/SourceFiles/passport/passport_panel_form.cpp b/Telegram/SourceFiles/passport/passport_panel_form.cpp
index f52dbad9a..83f4d9f4c 100644
--- a/Telegram/SourceFiles/passport/passport_panel_form.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_form.cpp
@@ -74,8 +74,13 @@ void PanelForm::Row::updateContent(
 	_description.setText(
 		st::defaultTextStyle,
 		description,
-		Ui::NameTextOptions());
-	_ready = ready;
+		TextParseOptions {
+			TextParseMultiline,
+			0,
+			0,
+			Qt::LayoutDirectionAuto
+		});
+	_ready = ready && !error;
 	if (_error != error) {
 		_error = error;
 		if (animated == anim::type::instant) {