diff --git a/.gitignore b/.gitignore
index 3f839901b0b8d82dd6c2011fccc6ff8b902c9166..55f90aa57e570f8f9c9ee55c3da281ec68347d4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ doc/Doxyfile
 !.vscode/tasks.json
 !.vscode/launch.json
 !.vscode/extensions.json
+*.code-workspace
 
 ### VisualStudioCode Patch ###
 # Ignore all local history of files
diff --git a/daemon b/daemon
index 597cde8d30814b5078e2ac8c8a0953dd471ec716..86d3bb664489077107e68b838e419f4cd6459859 160000
--- a/daemon
+++ b/daemon
@@ -1 +1 @@
-Subproject commit 597cde8d30814b5078e2ac8c8a0953dd471ec716
+Subproject commit 86d3bb664489077107e68b838e419f4cd6459859
diff --git a/src/app/accountadapter.cpp b/src/app/accountadapter.cpp
index 5d4daffaf8ae4ae8c439a6385002c68928f37ce0..13b2a301909ffe7d79873b4980ec416a89265631 100644
--- a/src/app/accountadapter.cpp
+++ b/src/app/accountadapter.cpp
@@ -22,8 +22,11 @@
 #include "systemtray.h"
 #include "lrcinstance.h"
 #include "accountlistmodel.h"
+#include "wizardviewstepmodel.h"
+#include "global.h"
+#include "api/account.h"
 
-#include <QtConcurrent/QtConcurrent>
+#include <QThreadPool>
 
 AccountAdapter::AccountAdapter(AppSettingsManager* settingsManager,
                                SystemTray* systemTray,
@@ -111,7 +114,10 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
         &lrcInstance_->accountModel(),
         &lrc::api::AccountModel::accountAdded,
         [this, registeredName, settings](const QString& accountId) {
-            lrcInstance_->accountModel().setAvatar(accountId, settings["avatar"].toString(), true,1);
+            lrcInstance_->accountModel().setAvatar(accountId,
+                                                   settings["avatar"].toString(),
+                                                   true,
+                                                   1);
             Utils::oneShotConnect(&lrcInstance_->accountModel(),
                                   &lrc::api::AccountModel::accountDetailsChanged,
                                   [this](const QString& accountId) {
@@ -159,8 +165,9 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
 
     connectFailure();
 
-    auto futureResult = QtConcurrent::run([this, settings] {
+    QThreadPool::globalInstance()->start([this, settings] {
         lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::JAMI,
+                                                      {},
                                                       settings["alias"].toString(),
                                                       settings["archivePath"].toString(),
                                                       settings["password"].toString(),
@@ -206,14 +213,14 @@ AccountAdapter::createSIPAccount(const QVariantMap& settings)
 
     connectFailure();
 
-    auto futureResult = QtConcurrent::run([this, settings] {
+    QThreadPool::globalInstance()->start([this, settings] {
         lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::SIP,
+                                                      {},
                                                       settings["alias"].toString(),
                                                       settings["archivePath"].toString(),
                                                       "",
                                                       "",
-                                                      settings["username"].toString(),
-                                                      {});
+                                                      settings["username"].toString());
     });
 }
 
@@ -250,7 +257,7 @@ AccountAdapter::createJAMSAccount(const QVariantMap& settings)
 
     connectFailure();
 
-    auto futureResult = QtConcurrent::run([this, settings] {
+    QThreadPool::globalInstance()->start([this, settings] {
         lrcInstance_->accountModel().connectToAccountManager(settings["username"].toString(),
                                                              settings["password"].toString(),
                                                              settings["manager"].toString());
@@ -293,7 +300,7 @@ AccountAdapter::setCurrAccDisplayName(const QString& text)
 void
 AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
 {
-    auto futureResult = QtConcurrent::run([this, source]() {
+    QThreadPool::globalInstance()->start([this, source]() {
         QPixmap image;
         if (!image.load(source)) {
             qWarning() << "Not a valid image file";
@@ -308,7 +315,7 @@ AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
 void
 AccountAdapter::setCurrentAccountAvatarBase64(const QString& data)
 {
-    auto futureResult = QtConcurrent::run([this, data]() {
+    QThreadPool::globalInstance()->start([this, data]() {
         auto accountId = lrcInstance_->get_currentAccountId();
         lrcInstance_->accountModel().setAvatar(accountId, data, true, 1);
     });
@@ -339,9 +346,73 @@ AccountAdapter::exportToFile(const QString& accountId,
 void
 AccountAdapter::setArchivePasswordAsync(const QString& accountID, const QString& password)
 {
-    auto futureResult = QtConcurrent::run([this, accountID, password] {
+    QThreadPool::globalInstance()->start([this, accountID, password] {
         auto config = lrcInstance_->accountModel().getAccountConfig(accountID);
         config.archivePassword = password;
         lrcInstance_->accountModel().setAccountConfig(accountID, config);
     });
 }
+
+void
+AccountAdapter::startImportAccount()
+{
+    auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
+    wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
+    wizardModel->set_deviceLinkDetails({});
+
+    // This will create an account with the ARCHIVE_URL configured to start the import process.
+    importAccountId_ = lrcInstance_->accountModel().createDeviceImportAccount();
+}
+
+void
+AccountAdapter::provideAccountAuthentication(const QString& password)
+{
+    if (importAccountId_.isEmpty()) {
+        qWarning() << "No import account to provide password to";
+        return;
+    }
+
+    auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
+    wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::IN_PROGRESS);
+
+    Utils::oneShotConnect(
+        &lrcInstance_->accountModel(),
+        &lrc::api::AccountModel::accountAdded,
+        [this](const QString& accountId) {
+            Q_EMIT lrcInstance_->accountListChanged();
+            Q_EMIT accountAdded(accountId,
+                                lrcInstance_->accountModel().getAccountList().indexOf(accountId));
+        },
+        this,
+        &AccountAdapter::accountCreationFailed);
+
+    connectFailure();
+
+    QThreadPool::globalInstance()->start([this, password] {
+        lrcInstance_->accountModel().provideAccountAuthentication(importAccountId_, password);
+    });
+}
+
+QString
+AccountAdapter::getImportErrorMessage(QVariantMap details)
+{
+    QString errorString = details.value("error").toString();
+    if (!errorString.isEmpty() && errorString != "none") {
+        auto error = lrc::api::account::mapLinkDeviceError(errorString.toStdString());
+        return lrc::api::account::getLinkDeviceString(error);
+    }
+
+    return "";
+}
+
+void
+AccountAdapter::cancelImportAccount()
+{
+    auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
+    wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
+    wizardModel->set_deviceLinkDetails({});
+
+    // Remove the account if it was created
+    lrcInstance_->accountModel().removeAccount(importAccountId_);
+    importAccountId_.clear();
+}
diff --git a/src/app/accountadapter.h b/src/app/accountadapter.h
index bd2f767506457d54ca500dfd86d1c71fef3cd5a6..1684537930e42e94531ac82480b90c8b2c1ad209 100644
--- a/src/app/accountadapter.h
+++ b/src/app/accountadapter.h
@@ -81,6 +81,13 @@ public:
                                          const bool& state);
     Q_INVOKABLE QStringList getDefaultModerators(const QString& accountId);
 
+    // New import account / link device functions
+    // import:  (note: Listen for: DeviceAuthStateChanged)
+    Q_INVOKABLE void startImportAccount();
+    Q_INVOKABLE void provideAccountAuthentication(const QString& password = {});
+    Q_INVOKABLE QString getImportErrorMessage(QVariantMap details);
+    Q_INVOKABLE void cancelImportAccount();
+
 Q_SIGNALS:
     // Trigger other components to reconnect account related signals.
     void accountStatusChanged(QString accountId);
@@ -98,6 +105,9 @@ private:
 
     QMetaObject::Connection registeredNameSavedConnection_;
 
+    // The account ID of the last used import account.
+    QString importAccountId_;
+
     AppSettingsManager* settingsManager_;
     SystemTray* systemTray_;
 };
diff --git a/src/app/avatarimageprovider.h b/src/app/avatarimageprovider.h
index 36e21787ff0ff8098cd5010593dc7758b8c59b4a..8e0f5d0067ebfde8c9bf98b4b9440fbcb4b49c1b 100644
--- a/src/app/avatarimageprovider.h
+++ b/src/app/avatarimageprovider.h
@@ -22,6 +22,7 @@
 #include "lrcinstance.h"
 
 #include <QImage>
+#include <QRegularExpression>
 
 class AsyncAvatarImageResponseRunnable : public AsyncImageResponseRunnable
 {
@@ -69,6 +70,16 @@ public:
             image = Utils::accountPhoto(lrcInstance_, imageId, requestedSize_);
         } else if (type == "contact") {
             image = Utils::contactPhoto(lrcInstance_, imageId, requestedSize_);
+        } else if (type == "temporaryAccount") {
+            // Check if imageId is a SHA-1 hash (jamiId or registered name)
+            static const QRegularExpression sha1Pattern("^[0-9a-fA-F]{40}$");
+            if (sha1Pattern.match(imageId).hasMatch()) {
+                // If we only have a jamiId use default avatar
+                image = Utils::fallbackAvatar("jami:" + imageId, QString(), requestedSize_);
+            } else {
+                // For registered usernames, use fallbackAvatar avatar with the name
+                image = Utils::fallbackAvatar(QString(), imageId, requestedSize_);
+            }
         } else {
             qWarning() << Q_FUNC_INFO << "Missing valid prefix in the image url";
             return;
diff --git a/src/app/commoncomponents/Avatar.qml b/src/app/commoncomponents/Avatar.qml
index 89728ded7995e155a59908b7112dbb9f7a46e6ba..270100d8455049586b82fa8e41c3b9e058eba092 100644
--- a/src/app/commoncomponents/Avatar.qml
+++ b/src/app/commoncomponents/Avatar.qml
@@ -28,7 +28,8 @@ Item {
     enum Mode {
         Account,
         Contact,
-        Conversation
+        Conversation,
+        TemporaryAccount
     }
     property int mode: Avatar.Mode.Account
     property alias sourceSize: image.sourceSize
@@ -45,6 +46,8 @@ Item {
             return 'contact';
         case Avatar.Mode.Conversation:
             return 'conversation';
+        case Avatar.Mode.TemporaryAccount:
+            return 'temporaryAccount';
         }
     }
 
diff --git a/src/app/net/jami/Constants/JamiStrings.qml b/src/app/net/jami/Constants/JamiStrings.qml
index f9ab49021f086d5282edd811a7e93e98bde68ef2..d848e849d4a74e297a8947434178217904338017 100644
--- a/src/app/net/jami/Constants/JamiStrings.qml
+++ b/src/app/net/jami/Constants/JamiStrings.qml
@@ -70,6 +70,21 @@ Item {
     property string transferThisCall: qsTr("Transfer this call")
     property string transferTo: qsTr("Transfer to")
 
+    // Device import/linking
+    property string scanToImportAccount: qsTr("Scan this QR code on your other device to proceed with importing your account.")
+    property string waitingForToken: qsTr("Please wait…")
+    property string scanQRCode: qsTr("Scan QR code")
+    property string connectingToDevice: qsTr("Action required.\nPlease confirm account on your old device.")
+    property string confirmAccountImport: qsTr("Authenticating device")
+    property string transferringAccount: qsTr("Transferring account…")
+    property string cantScanQRCode: qsTr("If you are unable to scan the QR code, enter this token on your other device to proceed.")
+    property string optionConfirm: qsTr("Confirm")
+    property string optionTryAgain: qsTr("Try again")
+    property string importFailed: qsTr("Import failed")
+    property string importFromAnotherAccount: qsTr("Import from another account")
+    property string connectToAccount: qsTr("Connect to account")
+    property string authenticationError: qsTr("An authentication error occurred. Please check credentials and try again.")
+
     // AccountMigrationDialog
     property string authenticationRequired: qsTr("Authentication required")
     property string migrationReason: qsTr("Your session has expired or been revoked on this device. Please enter your password.")
@@ -579,19 +594,8 @@ Item {
     // ImportFromDevicePage
     property string importButton: qsTr("Import")
     property string pin: qsTr("Enter the PIN code")
-    property string importFromDeviceDescription: qsTr("A PIN code is required to use an existing Jami account on this device.")
-    property string importStep1: qsTr("Step 1")
-    property string importStep2: qsTr("Step 2")
-    property string importStep3: qsTr("Step 3")
-    property string importStep4: qsTr("Step 4")
-    property string importStep1Desc: qsTr("Open the manage account tab in the settings of the previous device.")
-    property string importStep2Desc: qsTr("Select the account to link.")
-    property string importStep3Desc: qsTr("Select “Link new device.”")
-    property string importStep4Desc: qsTr("The PIN code will expire in 10 minutes.")
-    property string importPasswordDesc: qsTr("Fill if the account is password-encrypted.")
 
     // LinkDevicesDialog
-    property string pinTimerInfos: qsTr("The PIN code and the account password should be entered in the device within 10 minutes.")
     property string close: qsTr("Close")
     property string enterAccountPassword: qsTr("Enter account password")
     property string enterPasswordPinCode: qsTr("This account is password encrypted, enter the password to generate a PIN code.")
diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp
index 19357e4e5aba4b83bf628181fe12eb1fa1baacbd..de94474ed619e03b61f2f7f96e7f58f1d3eaa831 100644
--- a/src/app/qmlregister.cpp
+++ b/src/app/qmlregister.cpp
@@ -179,6 +179,12 @@ registerTypes(QQmlEngine* engine,
     QQmlEngine::setObjectOwnership(pluginStoreListModel, QQmlEngine::CppOwnership);
     REG_QML_SINGLETON<PluginStoreListModel>(REG_MODEL, "PluginStoreListModel", CREATE(pluginStoreListModel));
 
+    // WizardViewStepModel
+    auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, settingsManager, app);
+    qApp->setProperty("WizardViewStepModel", QVariant::fromValue(wizardViewStepModel));
+    QQmlEngine::setObjectOwnership(wizardViewStepModel, QQmlEngine::CppOwnership);
+    REG_QML_SINGLETON<WizardViewStepModel>(REG_MODEL, "WizardViewStepModel", CREATE(wizardViewStepModel));
+
     // Register app-level objects that are used by QML created objects.
     // These MUST be set prior to loading the initial QML file, in order to
     // be available to the QML adapter class factory creation methods.
@@ -205,7 +211,6 @@ registerTypes(QQmlEngine* engine,
     QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, TipsModel);
     QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, VideoDevices);
     QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentAccountToMigrate);
-    QML_REGISTERSINGLETON_TYPE(NS_MODELS, WizardViewStepModel);
     QML_REGISTERSINGLETON_TYPE(NS_HELPERS, ImageDownloader);
 
     // TODO: remove these
@@ -263,12 +268,12 @@ registerTypes(QQmlEngine* engine,
     // Enums
     QML_REGISTERUNCREATABLE(NS_ENUMS, Settings)
     QML_REGISTERUNCREATABLE(NS_ENUMS, NetworkManager)
-    QML_REGISTERUNCREATABLE(NS_ENUMS, WizardViewStepModel)
     QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceItemListModel)
     QML_REGISTERUNCREATABLE(NS_ENUMS, ModeratorListModel)
     QML_REGISTERUNCREATABLE(NS_ENUMS, VideoInputDeviceModel)
     QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatResolutionModel)
     QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatFpsModel)
+    QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceAuthStateEnum)
 
     engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance));
     engine->addImageProvider(QLatin1String("avatarimage"), new AvatarImageProvider(lrcInstance));
diff --git a/src/app/qrimageprovider.h b/src/app/qrimageprovider.h
index 72c9ab8ca42554a2c6d272ff4199a7067c177a71..c86172ea9ef19d6165844e04c93fa356a1eb02bb 100644
--- a/src/app/qrimageprovider.h
+++ b/src/app/qrimageprovider.h
@@ -18,7 +18,6 @@
 #pragma once
 
 #include "quickimageproviderbase.h"
-#include "accountlistmodel.h"
 
 #include <QPair>
 #include <QString>
diff --git a/src/app/settingsview/components/LinkDeviceDialog.qml b/src/app/settingsview/components/LinkDeviceDialog.qml
index 813a233501fd75b6ee05da1b6042f445c6dba690..7148d502014273046889e23c97a0a32d1640d608 100644
--- a/src/app/settingsview/components/LinkDeviceDialog.qml
+++ b/src/app/settingsview/components/LinkDeviceDialog.qml
@@ -42,7 +42,6 @@ BaseModalDialog {
             }
             stackedWidget.currentIndex = exportingSpinnerPage.pageIndex;
             spinnerMovie.playing = true;
-            timerForExport.restart();
         }
 
         function setExportPage(status, pin) {
@@ -69,25 +68,6 @@ BaseModalDialog {
             stackedWidget.height = exportingLayout.implicitHeight;
         }
 
-        Timer {
-            id: timerForExport
-
-            repeat: false
-            interval: 200
-
-            onTriggered: {
-                AccountAdapter.model.exportOnRing(LRCInstance.currentAccountId, passwordEdit.dynamicText);
-            }
-        }
-
-        Connections {
-            target: NameDirectory
-
-            function onExportOnRingEnded(status, pin) {
-                stackedWidget.setExportPage(status, pin);
-            }
-        }
-
         onVisibleChanged: {
             if (visible) {
                 if (CurrentAccount.hasArchivePassword) {
diff --git a/src/app/utils.cpp b/src/app/utils.cpp
index ad4f500cc13314426c4311ffda5ecc853710fe18..52cdb7866695160d6d2b5e7d8131b9e387901bcc 100644
--- a/src/app/utils.cpp
+++ b/src/app/utils.cpp
@@ -165,8 +165,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
 #endif
 
     if (desktopPath.isEmpty() || !(QFile::exists(desktopPath))) {
-        qDebug() << "Error while attempting to locate .desktop file at"
-                 << desktopPath;
+        qDebug() << "Error while attempting to locate .desktop file at" << desktopPath;
         return false;
     }
 
@@ -193,8 +192,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
             if (QDir().mkdir(autoStartDir)) {
                 qDebug() << "Created autostart directory:" << autoStartDir;
             } else {
-                qWarning() << "Error while creating autostart directory:"
-                           << autoStartDir;
+                qWarning() << "Error while creating autostart directory:" << autoStartDir;
                 return false;
             }
         }
@@ -283,7 +281,8 @@ Utils::CheckStartupLink(const std::wstring& wstrAppName)
 #else
     Q_UNUSED(wstrAppName)
     return (
-        !QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop").isEmpty());
+        !QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop")
+             .isEmpty());
 #endif
 }
 
@@ -616,14 +615,16 @@ Utils::getProjectCredits()
         return {};
     }
     QTextStream in(&projectCreditsFile);
-    return in.readAll().arg(
-        QObject::tr("We would like to thank our contributors, whose efforts over many years have made this software what it is."),
-        QObject::tr("Developers"),
-        QObject::tr("Media"),
-        QObject::tr("Community Management"),
-        QObject::tr("Special thanks to"),
-        QObject::tr("This is a list of people who have made a significant investment of time, with useful results, into Jami. Any such contributors who want to be added to the list should contact us.")
-      );
+    return in.readAll().arg(QObject::tr("We would like to thank our contributors, whose efforts "
+                                        "over many years have made this software what it is."),
+                            QObject::tr("Developers"),
+                            QObject::tr("Media"),
+                            QObject::tr("Community Management"),
+                            QObject::tr("Special thanks to"),
+                            QObject::tr(
+                                "This is a list of people who have made a significant investment "
+                                "of time, with useful results, into Jami. Any such contributors "
+                                "who want to be added to the list should contact us."));
 }
 
 inline QString
@@ -951,3 +952,13 @@ Utils::getTempSwarmAvatarPath()
     return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QDir::separator()
            + "tmpSwarmImage";
 }
+
+QVariantMap
+Utils::mapStringStringToVariantMap(const MapStringString& map)
+{
+    QVariantMap variantMap;
+    for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
+        variantMap.insert(it.key(), it.value());
+    }
+    return variantMap;
+}
diff --git a/src/app/utils.h b/src/app/utils.h
index ec0e6bc2d5e492dbe898cc83a29a46245ec31e3c..7124a1fdd15fad1ef330b9beb19c2696431f6ddd 100644
--- a/src/app/utils.h
+++ b/src/app/utils.h
@@ -120,4 +120,7 @@ QString generateUid();
 QString humanFileSize(qint64 fileSize);
 QString getDebugFilePath();
 
+// Convert a MapStringString to a QVariantMap
+QVariantMap mapStringStringToVariantMap(const MapStringString& map);
+
 } // namespace Utils
diff --git a/src/app/wizardview/WizardView.qml b/src/app/wizardview/WizardView.qml
index 151c198d0a33e9f6d7bd44c66e4186ca6b077d94..13da78fd069289a6689ca048d94c53fc7e611929 100644
--- a/src/app/wizardview/WizardView.qml
+++ b/src/app/wizardview/WizardView.qml
@@ -56,9 +56,11 @@ BaseView {
             case WizardViewStepModel.AccountCreationOption.CreateJamiAccount:
             case WizardViewStepModel.AccountCreationOption.CreateRendezVous:
             case WizardViewStepModel.AccountCreationOption.ImportFromBackup:
-            case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
                 AccountAdapter.createJamiAccount(WizardViewStepModel.accountCreationInfo);
                 break;
+            case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
+                AccountAdapter.startImportAccount();
+                break;
             case WizardViewStepModel.AccountCreationOption.ConnectToAccountManager:
                 AccountAdapter.createJAMSAccount(WizardViewStepModel.accountCreationInfo);
                 break;
diff --git a/src/app/wizardview/components/ImportFromDevicePage.qml b/src/app/wizardview/components/ImportFromDevicePage.qml
index 4d59ffc8930aefe3e7e87adf350d2b679b8c3955..b308ddf47337beb04ee261a427976bfe904dc4b9 100644
--- a/src/app/wizardview/components/ImportFromDevicePage.qml
+++ b/src/app/wizardview/components/ImportFromDevicePage.qml
@@ -17,9 +17,13 @@
 import QtQuick
 import QtQuick.Layouts
 import QtQuick.Controls
+import QtQuick.Dialogs
+import net.jami.Adapters 1.1
 import net.jami.Models 1.1
 import net.jami.Constants 1.1
+import net.jami.Enums 1.1
 import "../../commoncomponents"
+import "../../mainview/components"
 
 Rectangle {
     id: root
@@ -27,30 +31,99 @@ Rectangle {
     property string errorText: ""
     property int preferredHeight: importFromDevicePageColumnLayout.implicitHeight + 2 * JamiTheme.preferredMarginSize
 
-    signal showThisPage
+    // The token is used to generate the QR code and is also provided to the user as a backup if the QR
+    // code cannot be scanned. It is a URI using the scheme "jami-auth".
+    readonly property string tokenUri: WizardViewStepModel.deviceLinkDetails["token"] || ""
+
+    property string jamiId: ""
+
+    function isPasswordWrong() {
+        return WizardViewStepModel.deviceLinkDetails["auth_error"] !== undefined &&
+                WizardViewStepModel.deviceLinkDetails["auth_error"] !== "" &&
+                WizardViewStepModel.deviceLinkDetails["auth_error"] !== "none"
+    }
+
+    function requiresPassword() {
+        return WizardViewStepModel.deviceLinkDetails["auth_scheme"] === "password"
+    }
+
+    function requiresConfirmationBeforeClosing() {
+        const state = WizardViewStepModel.deviceAuthState
+        return state !== DeviceAuthStateEnum.INIT &&
+               state !== DeviceAuthStateEnum.DONE
+    }
 
-    function initializeOnShowUp() {
-        clearAllTextFields();
+    function isLoadingState() {
+        const state = WizardViewStepModel.deviceAuthState
+        return state === DeviceAuthStateEnum.INIT ||
+               state === DeviceAuthStateEnum.CONNECTING ||
+               state === DeviceAuthStateEnum.IN_PROGRESS
     }
 
+    signal showThisPage
+
     function clearAllTextFields() {
-        connectBtn.spinnerTriggered = false;
+        errorText = "";
     }
 
     function errorOccurred(errorMessage) {
         errorText = errorMessage;
-        connectBtn.spinnerTriggered = false;
+    }
+
+    MessageDialog {
+        id: confirmCloseDialog
+
+        text: JamiStrings.linkDeviceCloseWarningTitle
+        informativeText: JamiStrings.linkDeviceCloseWarningMessage
+        buttons: MessageDialog.Ok | MessageDialog.Cancel
+
+        onButtonClicked: function(button) {
+            if (button === MessageDialog.Ok) {
+                AccountAdapter.cancelImportAccount();
+                WizardViewStepModel.previousStep();
+            }
+        }
     }
 
     Connections {
         target: WizardViewStepModel
 
         function onMainStepChanged() {
-            if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.AccountCreation && WizardViewStepModel.accountCreationOption === WizardViewStepModel.AccountCreationOption.ImportFromDevice) {
+            if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.DeviceAuthorization) {
                 clearAllTextFields();
                 root.showThisPage();
             }
         }
+
+        function onDeviceAuthStateChanged() {
+            switch (WizardViewStepModel.deviceAuthState) {
+            case DeviceAuthStateEnum.TOKEN_AVAILABLE:
+                // Token is available and displayed as QR code
+                clearAllTextFields();
+                break;
+            case DeviceAuthStateEnum.CONNECTING:
+                // P2P connection being established
+                clearAllTextFields();
+                break;
+            case DeviceAuthStateEnum.AUTHENTICATING:
+                jamiId = WizardViewStepModel.deviceLinkDetails["peer_id"] || "";
+                if (jamiId.length > 0) {
+                    NameDirectory.lookupAddress(CurrentAccount.id, jamiId)
+                }
+                break;
+            case DeviceAuthStateEnum.IN_PROGRESS:
+                // Account archive is being transferred
+                clearAllTextFields();
+                break;
+            case DeviceAuthStateEnum.DONE:
+                // Final state - check for specific errors
+                const error = AccountAdapter.getImportErrorMessage(WizardViewStepModel.deviceLinkDetails);
+                if (error.length > 0) {
+                    errorOccurred(error)
+                }
+                break;
+            }
+        }
     }
 
     color: JamiTheme.secondaryBackgroundColor
@@ -65,184 +138,276 @@ Rectangle {
         width: Math.max(508, root.width - 100)
 
         Text {
-
-            text: JamiStrings.importAccountFromAnotherDevice
+            text: JamiStrings.importFromAnotherAccount
             Layout.alignment: Qt.AlignCenter
             Layout.topMargin: JamiTheme.preferredMarginSize
             Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
-            color: JamiTheme.textColor
 
+            color: JamiTheme.textColor
             font.pixelSize: JamiTheme.wizardViewTitleFontPixelSize
             wrapMode: Text.WordWrap
         }
 
         Text {
-
-            text: JamiStrings.importFromDeviceDescription
-            Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
-            Layout.topMargin: JamiTheme.wizardViewDescriptionMarginSize
-            Layout.alignment: Qt.AlignCenter
-            font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
-            font.weight: Font.Medium
-            color: JamiTheme.textColor
-            wrapMode: Text.WordWrap
+            Layout.alignment: Qt.AlignHCenter
+            Layout.maximumWidth: parent.width
             horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
+            font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
             lineHeight: JamiTheme.wizardViewTextLineHeight
+            text: {
+                switch (WizardViewStepModel.deviceAuthState) {
+                case DeviceAuthStateEnum.INIT:
+                    return JamiStrings.waitingForToken;
+                case DeviceAuthStateEnum.TOKEN_AVAILABLE:
+                    return JamiStrings.scanToImportAccount;
+                case DeviceAuthStateEnum.CONNECTING:
+                    return JamiStrings.connectingToDevice;
+                case DeviceAuthStateEnum.AUTHENTICATING:
+                    return JamiStrings.confirmAccountImport;
+                case DeviceAuthStateEnum.IN_PROGRESS:
+                    return JamiStrings.transferringAccount;
+                case DeviceAuthStateEnum.DONE:
+                    return errorText.length > 0 ? JamiStrings.importFailed : "";
+                default:
+                    return "";
+                }
+            }
+            wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+            color: JamiTheme.textColor
         }
 
-        Flow {
-            spacing: 30
+        // Confirmation form
+        ColumnLayout {
             Layout.alignment: Qt.AlignHCenter
-            Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
-            Layout.preferredWidth: Math.min(step1.width * 2 + spacing, root.width - JamiTheme.preferredMarginSize * 2)
-
-            InfoBox {
-                id: step1
-                icoSource: JamiResources.settings_24dp_svg
-                title: JamiStrings.importStep1
-                description: JamiStrings.importStep1Desc
-                icoColor: JamiTheme.buttonTintedBlue
-            }
-
-            InfoBox {
-                id: step2
-                icoSource: JamiResources.person_24dp_svg
-                title: JamiStrings.importStep2
-                description: JamiStrings.importStep2Desc
-                icoColor: JamiTheme.buttonTintedBlue
-            }
-
-            InfoBox {
-                id: step3
-                icoSource: JamiResources.finger_select_svg
-                title: JamiStrings.importStep3
-                description: JamiStrings.importStep3Desc
-                icoColor: JamiTheme.buttonTintedBlue
+            Layout.maximumWidth: Math.min(parent.width - 40, 400)
+            visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.AUTHENTICATING
+            spacing: JamiTheme.wizardViewPageLayoutSpacing
+
+            Text {
+                Layout.fillWidth: true
+                font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
+                lineHeight: JamiTheme.wizardViewTextLineHeight
+                text: JamiStrings.connectToAccount
+                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+                horizontalAlignment: Text.AlignHCenter
+                color: JamiTheme.textColor
+                font.bold: true
             }
 
-            InfoBox {
-                id: step4
-                icoSource: JamiResources.time_clock_svg
-                title: JamiStrings.importStep4
-                description: JamiStrings.importStep4Desc
-                icoColor: JamiTheme.buttonTintedBlue
+            // Account Widget (avatar + username + ID)
+            Rectangle {
+                id: accountContainer
+                Layout.alignment: Qt.AlignHCenter
+                implicitWidth: accountLayout.implicitWidth + 40
+                implicitHeight: accountLayout.implicitHeight + 40
+                radius: 8
+                color: JamiTheme.primaryBackgroundColor
+                border.width: 1
+                border.color: JamiTheme.tabbarBorderColor
+
+                RowLayout {
+                    id: accountLayout
+                    anchors {
+                        centerIn: parent
+                    }
+                    spacing: 20
+
+                    Avatar {
+                        id: accountAvatar
+                        showPresenceIndicator: false
+                        Layout.alignment: Qt.AlignVCenter
+                        Layout.preferredWidth: 48
+                        Layout.preferredHeight: 48
+                        mode: Avatar.Mode.TemporaryAccount
+                        imageId: name.text || jamiId
+                    }
+
+                    ColumnLayout {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
+                        Layout.alignment: Qt.AlignVCenter
+                        spacing: 4
+
+                        Text {
+                            id: name
+                            visible: text !== undefined && text !== ""
+
+                            Connections {
+                                id: registeredNameFoundConnection
+                                target: NameDirectory
+                                enabled: jamiId.length > 0
+
+                                function onRegisteredNameFound(status, address, registeredName, requestedName) {
+                                    if (address === jamiId && status === NameDirectory.LookupStatus.SUCCESS) {
+                                        name.text = registeredName;
+                                    }
+                                }
+                            }
+                        }
+                        Text {
+                            id: userId
+                            text: jamiId
+                        }
+                    }
+                }
             }
-        }
-
-        ModalTextEdit {
-            id: pinFromDevice
 
-            objectName: "pinFromDevice"
-
-            Layout.alignment: Qt.AlignCenter
-            Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
-            Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
+            // Password
+            PasswordTextEdit {
+                id: passwordField
 
-            focus: visible
+                Layout.fillWidth: true
+                Layout.leftMargin: 10
+                Layout.rightMargin: 10
+                Layout.topMargin: 10
+                Layout.bottomMargin: 10
+                visible: requiresPassword()
+                placeholderText: JamiStrings.enterPassword
+                echoMode: TextInput.Password
 
-            placeholderText: JamiStrings.pin
-            staticText: ""
+                onAccepted: confirmButton.clicked()
+            }
 
-            KeyNavigation.up: backButton
-            KeyNavigation.down: passwordFromDevice
-            KeyNavigation.tab: KeyNavigation.down
+            Text {
+                id: passwordErrorField
+                Layout.alignment: Qt.AlignHCenter
+                Layout.maximumWidth: parent.width - 40
+                visible: isPasswordWrong()
+                text: JamiStrings.authenticationError
+                font.pointSize: JamiTheme.tinyFontSize
+                color: JamiTheme.redColor
+                horizontalAlignment: Text.AlignHCenter
+                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+            }
 
-            onAccepted: passwordFromDevice.forceActiveFocus()
+            RowLayout {
+                Layout.alignment: Qt.AlignHCenter
+                spacing: 16
+                Layout.margins: 10
+
+                MaterialButton {
+                    id: confirmButton
+                    text: JamiStrings.optionConfirm
+                    primary: true
+                    enabled: !passwordField.visible || passwordField.dynamicText.length > 0
+                    onClicked: {
+                        AccountAdapter.provideAccountAuthentication(passwordField.visible ? passwordField.dynamicText : "");
+                    }
+                }
+            }
         }
 
-        Text {
-
-            Layout.alignment: Qt.AlignCenter
-            Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
-
-            color: JamiTheme.textColor
-            wrapMode: Text.WordWrap
-            text: JamiStrings.importPasswordDesc
-            font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
-            font.weight: Font.Medium
+        // Show busy indicator when waiting for token
+        BusyIndicator {
+            Layout.alignment: Qt.AlignHCenter
+            visible: isLoadingState()
+            Layout.preferredWidth: 50
+            Layout.preferredHeight: 50
+            running: visible
         }
 
-        PasswordTextEdit {
-            id: passwordFromDevice
-
-            objectName: "passwordFromDevice"
-            Layout.alignment: Qt.AlignCenter
-            Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
-            Layout.topMargin: JamiTheme.wizardViewMarginSize
-
-            placeholderText: JamiStrings.enterPassword
-
-            KeyNavigation.up: pinFromDevice
-            KeyNavigation.down: {
-                if (connectBtn.enabled)
-                    return connectBtn;
-                else if (connectBtn.spinnerTriggered)
-                    return passwordFromDevice;
-                return backButton;
+        // QR Code container with frame
+        Rectangle {
+            Layout.alignment: Qt.AlignHCenter
+            Layout.preferredWidth: qrLoader.Layout.preferredWidth + 40
+            Layout.preferredHeight: qrLoader.Layout.preferredHeight + 40
+            visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
+            color: JamiTheme.primaryBackgroundColor
+            radius: 8
+            border.width: 1
+            border.color: JamiTheme.tabbarBorderColor
+
+            Loader {
+                id: qrLoader
+                anchors.centerIn: parent
+                active: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
+                Layout.preferredWidth: Math.min(parent.parent.width - 60, 250)
+                Layout.preferredHeight: Layout.preferredWidth
+
+                sourceComponent: Image {
+                    width: qrLoader.Layout.preferredWidth
+                    height: qrLoader.Layout.preferredHeight
+                    smooth: false
+                    fillMode: Image.PreserveAspectFit
+                    source: "image://qrImage/raw_" + tokenUri
+                }
             }
-            KeyNavigation.tab: KeyNavigation.down
-
-            onAccepted: pinFromDevice.forceActiveFocus()
         }
 
-        SpinnerButton {
-            id: connectBtn
-
-            TextMetrics {
-                id: textSize
-                font.weight: Font.Bold
-                font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
-                text: connectBtn.normalText
+        // Token URI backup text
+        ColumnLayout {
+            Layout.alignment: Qt.AlignHCenter
+            visible: tokenUri !== ""
+            spacing: 8
+
+            Text {
+                Layout.alignment: Qt.AlignHCenter
+                Layout.maximumWidth: parent.parent.width - 40
+                horizontalAlignment: Text.AlignHCenter
+                text: JamiStrings.cantScanQRCode
+                font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
+                lineHeight: JamiTheme.wizardViewTextLineHeight
+                color: JamiTheme.textColor
+                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
             }
 
-            objectName: "importFromDevicePageConnectBtn"
-
-            Layout.alignment: Qt.AlignCenter
-            Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
-            Layout.bottomMargin: errorLabel.visible ? 0 : JamiTheme.wizardViewPageBackButtonMargins
-
-            preferredWidth: textSize.width + 2 * JamiTheme.buttontextWizzardPadding + 1
-            primary: true
-
-            spinnerTriggeredtext: JamiStrings.generatingAccount
-            normalText: JamiStrings.importButton
-
-            enabled: pinFromDevice.dynamicText.length !== 0 && !spinnerTriggered
-
-            KeyNavigation.tab: backButton
-            KeyNavigation.up: passwordFromDevice
-            KeyNavigation.down: backButton
-
-            onClicked: {
-                spinnerTriggered = true;
-                WizardViewStepModel.accountCreationInfo = JamiQmlUtils.setUpAccountCreationInputPara({
-                        "archivePin": pinFromDevice.dynamicText,
-                        "password": passwordFromDevice.dynamicText
-                    });
-                WizardViewStepModel.nextStep();
+            TextArea {
+                id: tokenUriTextArea
+                Layout.alignment: Qt.AlignHCenter
+                Layout.maximumWidth: parent.parent.width - 40
+                text: tokenUri
+                font.pointSize: JamiTheme.wizardViewDescriptionFontPixelSize
+                horizontalAlignment: Text.AlignHCenter
+                readOnly: true
+                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+                selectByMouse: true
+                background: Rectangle {
+                    color: JamiTheme.primaryBackgroundColor
+                    radius: 5
+                    border.width: 1
+                    border.color: JamiTheme.tabbarBorderColor
+                }
             }
         }
 
-        Label {
-            id: errorLabel
-
-            Layout.alignment: Qt.AlignCenter
-            Layout.bottomMargin: JamiTheme.wizardViewPageBackButtonMargins
-
-            visible: errorText.length !== 0
-
-            text: errorText
+        // Error view
+        ColumnLayout {
+            id: errorColumn
+            Layout.alignment: Qt.AlignHCenter
+            Layout.maximumWidth: parent.width - 40
+            visible: errorText !== ""
+            spacing: 16
+
+            Text {
+                Layout.alignment: Qt.AlignHCenter
+                Layout.maximumWidth: parent.width
+                text: errorText
+                color: JamiTheme.textColor
+                font.pointSize: JamiTheme.mediumFontSize
+                horizontalAlignment: Text.AlignHCenter
+                wrapMode: Text.WordWrap
+            }
 
-            font.pixelSize: JamiTheme.textEditError
-            color: JamiTheme.redColor
+            MaterialButton {
+                Layout.alignment: Qt.AlignHCenter
+                text: JamiStrings.optionTryAgain
+                toolTipText: JamiStrings.optionTryAgain
+                primary: true
+                onClicked: {
+                    AccountAdapter.cancelImportAccount();
+                    WizardViewStepModel.previousStep();
+                }
+            }
         }
     }
 
-    BackButton {
+    // Back button
+    JamiPushButton {
         id: backButton
+        QWKSetParentHitTestVisible {
+        }
 
         objectName: "importFromDevicePageBackButton"
 
@@ -250,12 +415,18 @@ Rectangle {
         anchors.top: parent.top
         anchors.margins: JamiTheme.wizardViewPageBackButtonMargins
 
-        visible: !connectBtn.spinnerTriggered
+        preferredSize: 36
+        imageContainerWidth: 20
+        source: JamiResources.ic_arrow_back_24dp_svg
 
-        KeyNavigation.tab: pinFromDevice
-        KeyNavigation.up: connectBtn.enabled ? connectBtn : passwordFromDevice
-        KeyNavigation.down: pinFromDevice
+        visible: WizardViewStepModel.deviceAuthState !== DeviceAuthStateEnum.IN_PROGRESS
 
-        onClicked: WizardViewStepModel.previousStep()
+        onClicked: {
+            if (requiresConfirmationBeforeClosing()) {
+                confirmCloseDialog.open();
+            } else {
+                WizardViewStepModel.previousStep();
+            }
+        }
     }
 }
diff --git a/src/app/wizardviewstepmodel.cpp b/src/app/wizardviewstepmodel.cpp
index 06567c02e904805015e245239868be4c05282b5f..6b19cd521118e913dd4d7466e8289868e3c0a882 100644
--- a/src/app/wizardviewstepmodel.cpp
+++ b/src/app/wizardviewstepmodel.cpp
@@ -19,6 +19,7 @@
 
 #include "appsettingsmanager.h"
 #include "lrcinstance.h"
+#include "global.h"
 
 #include "api/accountmodel.h"
 
@@ -46,17 +47,31 @@ WizardViewStepModel::WizardViewStepModel(LRCInstance* lrcInstance,
 
                 Q_EMIT accountIsReady(accountId);
             });
+
+    // Connect to account model signals to track import progress
+    connect(&lrcInstance_->accountModel(),
+            &AccountModel::deviceAuthStateChanged,
+            this,
+            [this](const QString& accountID, int state, const MapStringString& details) {
+                set_deviceLinkDetails(Utils::mapStringStringToVariantMap(details));
+                set_deviceAuthState(static_cast<lrc::api::account::DeviceAuthState>(state));
+            });
 }
 
 void
 WizardViewStepModel::startAccountCreationFlow(AccountCreationOption accountCreationOption)
 {
+    using namespace lrc::api::account;
     set_accountCreationOption(accountCreationOption);
-    if (accountCreationOption == AccountCreationOption::CreateJamiAccount
-        || accountCreationOption == AccountCreationOption::CreateRendezVous)
+    if (accountCreationOption == AccountCreationOption::ImportFromDevice) {
+        set_mainStep(MainSteps::DeviceAuthorization);
+        Q_EMIT createAccountRequested(accountCreationOption);
+    } else if (accountCreationOption == AccountCreationOption::CreateJamiAccount
+               || accountCreationOption == AccountCreationOption::CreateRendezVous) {
         set_mainStep(MainSteps::NameRegistration);
-    else
+    } else {
         set_mainStep(MainSteps::AccountCreation);
+    }
 }
 
 void
@@ -80,6 +95,10 @@ WizardViewStepModel::previousStep()
         reset();
         break;
     }
+    case MainSteps::DeviceAuthorization: {
+        reset();
+        break;
+    }
     }
 }
 
@@ -88,4 +107,6 @@ WizardViewStepModel::reset()
 {
     set_accountCreationOption(AccountCreationOption::None);
     set_mainStep(MainSteps::Initial);
+    set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
+    set_deviceLinkDetails({});
 }
diff --git a/src/app/wizardviewstepmodel.h b/src/app/wizardviewstepmodel.h
index ebf4ff0c22d53820ee8102d67cc3cddcade49d1e..084c470425eae74fece44cbebd05bd19efc36397 100644
--- a/src/app/wizardviewstepmodel.h
+++ b/src/app/wizardviewstepmodel.h
@@ -18,6 +18,7 @@
 #pragma once
 
 #include "qtutils.h"
+#include "api/account.h" // Include for DeviceAuthState
 
 #include <QObject>
 #include <QVariant>
@@ -29,6 +30,21 @@ class AccountAdapter;
 class LRCInstance;
 class AppSettingsManager;
 
+class DeviceAuthStateEnum : public QObject
+{
+    Q_OBJECT
+public:
+    enum State {
+        INIT = static_cast<int>(lrc::api::account::DeviceAuthState::INIT),
+        TOKEN_AVAILABLE = static_cast<int>(lrc::api::account::DeviceAuthState::TOKEN_AVAILABLE),
+        CONNECTING = static_cast<int>(lrc::api::account::DeviceAuthState::CONNECTING),
+        AUTHENTICATING = static_cast<int>(lrc::api::account::DeviceAuthState::AUTHENTICATING),
+        IN_PROGRESS = static_cast<int>(lrc::api::account::DeviceAuthState::IN_PROGRESS),
+        DONE = static_cast<int>(lrc::api::account::DeviceAuthState::DONE)
+    };
+    Q_ENUM(State)
+};
+
 class WizardViewStepModel : public QObject
 {
     Q_OBJECT
@@ -37,9 +53,10 @@ class WizardViewStepModel : public QObject
 
 public:
     enum class MainSteps {
-        Initial,          // Initial welcome step.
-        AccountCreation,  // General account creation step.
-        NameRegistration, // Name registration step : CreateJamiAccount, CreateRendezVous
+        Initial,            // Initial welcome step.
+        AccountCreation,    // General account creation step.
+        NameRegistration,   // Name registration step : CreateJamiAccount, CreateRendezVous
+        DeviceAuthorization // Add new step for device authorization.
     };
     Q_ENUM(MainSteps)
 
@@ -57,6 +74,8 @@ public:
     QML_PROPERTY(MainSteps, mainStep)
     QML_PROPERTY(AccountCreationOption, accountCreationOption)
     QML_PROPERTY(QVariantMap, accountCreationInfo)
+    QML_PROPERTY(lrc::api::account::DeviceAuthState, deviceAuthState)
+    QML_PROPERTY(QVariantMap, deviceLinkDetails)
 
 public:
     static WizardViewStepModel* create(QQmlEngine*, QJSEngine*)
diff --git a/src/libclient/accountmodel.cpp b/src/libclient/accountmodel.cpp
index a2826579197df7cab2accba3452c49c87f2c8009..3d0fdd511dbaa7f4ab90f92d7893d2ce8af474c9 100644
--- a/src/libclient/accountmodel.cpp
+++ b/src/libclient/accountmodel.cpp
@@ -117,12 +117,14 @@ public Q_SLOTS:
     void slotAccountStatusChanged(const QString& accountID, const api::account::Status status);
 
     /**
-     * Emit exportOnRingEnded.
+     * Emit deviceAuthStateChanged.
      * @param accountId
-     * @param status
-     * @param pin
+     * @param state
+     * @param details map
      */
-    void slotExportOnRingEnded(const QString& accountID, int status, const QString& pin);
+    void slotDeviceAuthStateChanged(const QString& accountID,
+                                    int state,
+                                    const MapStringString& details);
 
     /**
      * @param accountId
@@ -282,11 +284,12 @@ AccountModel::setAlias(const QString& accountId, const QString& alias, bool save
     accountInfo.profileInfo.alias = alias;
 
     if (save)
-        ConfigurationManager::instance().updateProfile(accountId,
-                                                       alias,
-                                                       "",
-                                                       "",
-                                                       5);// flag out of range to avoid updating avatar
+        ConfigurationManager::instance()
+            .updateProfile(accountId,
+                           alias,
+                           "",
+                           "",
+                           5); // flag out of range to avoid updating avatar
     Q_EMIT profileUpdated(accountId);
 }
 
@@ -323,9 +326,30 @@ AccountModel::exportToFile(const QString& accountId,
 }
 
 bool
-AccountModel::exportOnRing(const QString& accountId, const QString& password) const
+AccountModel::provideAccountAuthentication(const QString& accountId,
+                                           const QString& credentialsFromUser) const
+{
+    return ConfigurationManager::instance().provideAccountAuthentication(accountId,
+                                                                         credentialsFromUser,
+                                                                         "password");
+}
+
+int32_t
+AccountModel::addDevice(const QString& accountId, const QString& token) const
+{
+    return ConfigurationManager::instance().addDevice(accountId, token);
+}
+
+bool
+AccountModel::confirmAddDevice(const QString& accountId, uint32_t operationId) const
 {
-    return ConfigurationManager::instance().exportOnRing(accountId, password);
+    return ConfigurationManager::instance().confirmAddDevice(accountId, operationId);
+}
+
+bool
+AccountModel::cancelAddDevice(const QString& accountId, uint32_t operationId) const
+{
+    return ConfigurationManager::instance().cancelAddDevice(accountId, operationId);
 }
 
 void
@@ -403,9 +427,9 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
             this,
             &AccountModelPimpl::slotVolatileAccountDetailsChanged);
     connect(&callbacksHandler,
-            &CallbacksHandler::exportOnRingEnded,
-            this,
-            &AccountModelPimpl::slotExportOnRingEnded);
+            &CallbacksHandler::deviceAuthStateChanged,
+            &linked,
+            &AccountModel::deviceAuthStateChanged);
     connect(&callbacksHandler,
             &CallbacksHandler::nameRegistrationEnded,
             this,
@@ -594,23 +618,13 @@ AccountModelPimpl::slotVolatileAccountDetailsChanged(const QString& accountId,
 }
 
 void
-AccountModelPimpl::slotExportOnRingEnded(const QString& accountID, int status, const QString& pin)
+AccountModelPimpl::slotDeviceAuthStateChanged(const QString& accountId,
+                                              int state,
+                                              const MapStringString& details)
 {
-    account::ExportOnRingStatus convertedStatus = account::ExportOnRingStatus::INVALID;
-    switch (status) {
-    case 0:
-        convertedStatus = account::ExportOnRingStatus::SUCCESS;
-        break;
-    case 1:
-        convertedStatus = account::ExportOnRingStatus::WRONG_PASSWORD;
-        break;
-    case 2:
-        convertedStatus = account::ExportOnRingStatus::NETWORK_ERROR;
-        break;
-    default:
-        break;
-    }
-    Q_EMIT linked.exportOnRingEnded(accountID, convertedStatus, pin);
+    // implement business logic here
+    // can be bypassed with a signal to signal
+    Q_EMIT linked.deviceAuthStateChanged(accountId, state, details);
 }
 
 void
@@ -673,7 +687,11 @@ AccountModelPimpl::slotRegisteredNameFound(const QString& accountId,
     default:
         break;
     }
-    Q_EMIT linked.registeredNameFound(accountId, requestedName, convertedStatus, address, registeredName);
+    Q_EMIT linked.registeredNameFound(accountId,
+                                      requestedName,
+                                      convertedStatus,
+                                      address,
+                                      registeredName);
 }
 
 void
@@ -1041,32 +1059,47 @@ account::ConfProperties_t::toDetails() const
 
 QString
 AccountModel::createNewAccount(profile::Type type,
+                               const MapStringString& config,
                                const QString& displayName,
                                const QString& archivePath,
                                const QString& password,
                                const QString& pin,
-                               const QString& uri,
-                               const MapStringString& config)
+                               const QString& uri)
 {
+    // Get the template for the account type to prefill the details
     MapStringString details = type == profile::Type::SIP
                                   ? ConfigurationManager::instance().getAccountTemplate("SIP")
                                   : ConfigurationManager::instance().getAccountTemplate("RING");
-    using namespace libjami::Account;
-    details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
-    details[ConfProperties::DISPLAYNAME] = displayName;
-    details[ConfProperties::ALIAS] = displayName;
-    details[ConfProperties::UPNP_ENABLED] = "true";
-    details[ConfProperties::ARCHIVE_PASSWORD] = password;
-    details[ConfProperties::ARCHIVE_PIN] = pin;
-    details[ConfProperties::ARCHIVE_PATH] = archivePath;
-    if (type == profile::Type::SIP)
-        details[ConfProperties::USERNAME] = uri;
+
+    // Add the supplied config to the details
     if (!config.isEmpty()) {
         for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
             details[it.key()] = it.value();
         }
     }
 
+    using namespace libjami::Account;
+
+    // Add the rest of the details if we are not creating an ephemeral account for linking
+    // in which case the ARCHIVE_URL was set to "jami-auth" or the MANAGER_URI was set to
+    // the account manager URI in the case of a remote account manager connection
+    if (details[ConfProperties::ARCHIVE_URL].isEmpty()
+        && details[ConfProperties::MANAGER_URI].isEmpty()) {
+        details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
+        details[ConfProperties::DISPLAYNAME] = displayName;
+        details[ConfProperties::ALIAS] = displayName;
+        details[ConfProperties::UPNP_ENABLED] = "true";
+        details[ConfProperties::ARCHIVE_PASSWORD] = password;
+        details[ConfProperties::ARCHIVE_PIN] = pin;
+        details[ConfProperties::ARCHIVE_PATH] = archivePath;
+
+        // Override the username with the provided URI if it's a SIP account
+        if (type == profile::Type::SIP) {
+            details[ConfProperties::USERNAME] = uri;
+        }
+    }
+
+    // Actually add the account and return the account ID
     QString accountId = ConfigurationManager::instance().addAccount(details);
     return accountId;
 }
@@ -1077,20 +1110,24 @@ AccountModel::connectToAccountManager(const QString& username,
                                       const QString& serverUri,
                                       const MapStringString& config)
 {
-    MapStringString details = ConfigurationManager::instance().getAccountTemplate("RING");
+    MapStringString details = config;
     using namespace libjami::Account;
     details[ConfProperties::TYPE] = "RING";
     details[ConfProperties::MANAGER_URI] = serverUri;
     details[ConfProperties::MANAGER_USERNAME] = username;
     details[ConfProperties::ARCHIVE_PASSWORD] = password;
-    if (!config.isEmpty()) {
-        for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
-            details[it.key()] = it.value();
-        }
-    }
+    return createNewAccount(profile::Type::JAMI, details);
+}
 
-    QString accountId = ConfigurationManager::instance().addAccount(details);
-    return accountId;
+QString
+AccountModel::createDeviceImportAccount()
+{
+    // auto details = ConfigurationManager::instance().getAccountTemplate("RING");
+    MapStringString details;
+    using namespace libjami::Account;
+    details[ConfProperties::TYPE] = "RING";
+    details[ConfProperties::ARCHIVE_URL] = "jami-auth";
+    return createNewAccount(profile::Type::JAMI, details);
 }
 
 void
diff --git a/src/libclient/api/account.h b/src/libclient/api/account.h
index c2504c173e23a736848bd4af25b981e8cead849f..430921430ef101fc0e735bb91dc8dc41450b940c 100644
--- a/src/libclient/api/account.h
+++ b/src/libclient/api/account.h
@@ -188,9 +188,65 @@ struct ConfProperties_t
     MapStringString toDetails() const;
 };
 
-// Possible account export status
-enum class ExportOnRingStatus { SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2, INVALID };
-Q_ENUM_NS(ExportOnRingStatus)
+// The following statuses are used to track the status of
+// device-linking and account-import
+enum class DeviceAuthState {
+    INIT = 0,
+    TOKEN_AVAILABLE = 1,
+    CONNECTING = 2,
+    AUTHENTICATING = 3,
+    IN_PROGRESS = 4,
+    DONE = 5
+};
+Q_ENUM_NS(DeviceAuthState)
+
+enum class DeviceLinkError {
+    WRONG_PASSWORD, // auth_error, invalid_credentials
+    NETWORK,        // network
+    TIMEOUT,        // timeout
+    STATE,          // state
+    CANCELED,       // canceled
+    UNKNOWN         // fallback
+};
+
+Q_ENUM_NS(DeviceLinkError)
+
+inline DeviceLinkError
+mapLinkDeviceError(const std::string& error)
+{
+    if (error == "auth_error" || error == "invalid_credentials")
+        return DeviceLinkError::WRONG_PASSWORD;
+    if (error == "network")
+        return DeviceLinkError::NETWORK;
+    if (error == "timeout")
+        return DeviceLinkError::TIMEOUT;
+    if (error == "state")
+        return DeviceLinkError::STATE;
+    if (error == "canceled")
+        return DeviceLinkError::CANCELED;
+    return DeviceLinkError::UNKNOWN;
+}
+
+inline QString
+getLinkDeviceString(DeviceLinkError error)
+{
+    switch (error) {
+    case DeviceLinkError::WRONG_PASSWORD:
+        return QObject::tr(
+            "An authentication error occurred.\nPlease check credentials and try again.");
+    case DeviceLinkError::NETWORK:
+        return QObject::tr("A network error occurred.\nPlease verify your connection.");
+    case DeviceLinkError::TIMEOUT:
+        return QObject::tr("The operation has timed out.\nPlease try again.");
+    case DeviceLinkError::STATE:
+        return QObject::tr("An error occurred while exporting the account.\nPlease try again.");
+    case DeviceLinkError::CANCELED:
+        return QObject::tr("Operation was canceled.");
+    case DeviceLinkError::UNKNOWN:
+    default:
+        return QObject::tr("An unexpected error occurred.\nPlease try again.");
+    }
+}
 
 enum class RegisterNameStatus {
     SUCCESS = 0,
diff --git a/src/libclient/api/accountmodel.h b/src/libclient/api/accountmodel.h
index 25573cdf5ab69507a47d0aa541c2439213698081..6b55a85ce298778cf67799021bbf968358853db3 100644
--- a/src/libclient/api/accountmodel.h
+++ b/src/libclient/api/accountmodel.h
@@ -112,13 +112,37 @@ public:
     Q_INVOKABLE bool exportToFile(const QString& accountId,
                                   const QString& path,
                                   const QString& password = {}) const;
+
     /**
-     * Call exportOnRing from the daemon
+     * Provide authentication for an account
      * @param accountId
-     * @param password
+     * @param credentialsFromUser
+     * @return if the authentication is successful
+     */
+    Q_INVOKABLE bool provideAccountAuthentication(const QString& accountId,
+                                                  const QString& credentialsFromUser) const;
+
+    /**
+     * @param accountId
+     * @param uri
      * @return if the export is initialized
      */
-    Q_INVOKABLE bool exportOnRing(const QString& accountId, const QString& password) const;
+    Q_INVOKABLE int32_t addDevice(const QString& accountId, const QString& token) const;
+
+    /**
+     * Confirm the addition of a device
+     * @param accountId
+     * @param operationId
+     */
+    Q_INVOKABLE bool confirmAddDevice(const QString& accountId, uint32_t operationId) const;
+
+    /**
+     * Cancel the addition of a device
+     * @param accountId
+     * @param operationId
+     */
+    Q_INVOKABLE bool cancelAddDevice(const QString& accountId, uint32_t operationId) const;
+
     /**
      * Call removeAccount from the daemon
      * @param accountId to remove
@@ -141,7 +165,7 @@ public:
      * @param avatar
      * @throws out_of_range exception if account is not found
      */
-    void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag =0);
+    void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag = 0);
     /**
      * Change the alias of an account
      * @param accountId
@@ -159,18 +183,7 @@ public:
     Q_INVOKABLE bool registerName(const QString& accountId,
                                   const QString& password,
                                   const QString& username);
-    /**
-     * Connect to JAMS to retrieve the account
-     * @param username
-     * @param password
-     * @param serverUri
-     * @param config
-     * @return the account id
-     */
-    static QString connectToAccountManager(const QString& username,
-                                           const QString& password,
-                                           const QString& serverUri,
-                                           const MapStringString& config = MapStringString());
+
     /**
      * Create a new Ring or SIP account
      * @param type determine if the new account will be a Ring account or a SIP one
@@ -184,12 +197,32 @@ public:
      * @return the created account
      */
     static QString createNewAccount(profile::Type type,
+                                    const MapStringString& config = MapStringString(),
                                     const QString& displayName = "",
                                     const QString& archivePath = "",
                                     const QString& password = "",
                                     const QString& pin = "",
-                                    const QString& uri = "",
-                                    const MapStringString& config = MapStringString());
+                                    const QString& uri = "");
+
+    /**
+     * Connect to JAMS to retrieve the account
+     * @param username
+     * @param password
+     * @param serverUri
+     * @param config
+     * @return the account id
+     */
+    static QString connectToAccountManager(const QString& username,
+                                           const QString& password,
+                                           const QString& serverUri,
+                                           const MapStringString& config = MapStringString());
+
+    /**
+     * Create a simple ephemeral account from a device import
+     * @return the account id of the created account
+     */
+    static QString createDeviceImportAccount();
+
     /**
      * Set an account to the first position
      */
@@ -296,14 +329,24 @@ Q_SIGNALS:
     void profileUpdated(const QString& accountID);
 
     /**
-     * Connect this signal to know when an account is exported on the DHT
+     * Device authentication state has changed
      * @param accountID
-     * @param status
-     * @param pin
+     * @param state
+     * @param details map
+     */
+    void deviceAuthStateChanged(const QString& accountID, int state, const MapStringString& details);
+
+    /**
+     * Add device state has changed
+     * @param accountID
+     * @param operationId
+     * @param state
+     * @param details map
      */
-    void exportOnRingEnded(const QString& accountID,
-                           account::ExportOnRingStatus status,
-                           const QString& pin);
+    void addDeviceStateChanged(const QString& accountID,
+                               uint32_t operationId,
+                               int state,
+                               const MapStringString& details);
 
     /**
      * Name registration has ended
diff --git a/src/libclient/callbackshandler.cpp b/src/libclient/callbackshandler.cpp
index 3452f281249837eeb20a56a3367d8bf41aaa2efb..308e234e01cb3f7d11779441d785522701d0a91c 100644
--- a/src/libclient/callbackshandler.cpp
+++ b/src/libclient/callbackshandler.cpp
@@ -242,9 +242,9 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             Qt::QueuedConnection);
 
     connect(&ConfigurationManager::instance(),
-            &ConfigurationManagerInterface::exportOnRingEnded,
+            &ConfigurationManagerInterface::deviceAuthStateChanged,
             this,
-            &CallbacksHandler::slotExportOnRingEnded,
+            &CallbacksHandler::slotDeviceAuthStateChanged,
             Qt::QueuedConnection);
 
     connect(&ConfigurationManager::instance(),
@@ -546,7 +546,9 @@ CallbacksHandler::slotIncomingMessage(const QString& accountId,
 }
 
 void
-CallbacksHandler::slotConferenceCreated(const QString& accountId, const QString& convId, const QString& callId)
+CallbacksHandler::slotConferenceCreated(const QString& accountId,
+                                        const QString& convId,
+                                        const QString& callId)
 {
     Q_EMIT conferenceCreated(accountId, convId, callId);
 }
@@ -678,9 +680,11 @@ CallbacksHandler::slotDeviceRevokationEnded(const QString& accountId,
 }
 
 void
-CallbacksHandler::slotExportOnRingEnded(const QString& accountId, int status, const QString& pin)
+CallbacksHandler::slotDeviceAuthStateChanged(const QString& accountId,
+                                             int state,
+                                             const MapStringString& details)
 {
-    Q_EMIT exportOnRingEnded(accountId, status, pin);
+    Q_EMIT deviceAuthStateChanged(accountId, state, details);
 }
 
 void
diff --git a/src/libclient/callbackshandler.h b/src/libclient/callbackshandler.h
index 9f0f54786f772e9abf871867b728d38459b49083..428cf54ce6e20199c4ccac7ead6dd845922eb25d 100644
--- a/src/libclient/callbackshandler.h
+++ b/src/libclient/callbackshandler.h
@@ -171,7 +171,9 @@ Q_SIGNALS:
      * Connect this signal to know when a new conference is created
      * @param callId of the conference
      */
-    void conferenceCreated(const QString& accountId, const QString& conversationId, const QString& callId);
+    void conferenceCreated(const QString& accountId,
+                           const QString& conversationId,
+                           const QString& callId);
     void conferenceChanged(const QString& accountId, const QString& confId, const QString& state);
     /**
      * Connect this signal to know when a conference is removed
@@ -235,12 +237,12 @@ Q_SIGNALS:
                                 const QString& userPhoto);
 
     /**
-     * Emit exportOnRingEnded
+     * Device authentication state has changed
      * @param accountId
-     * @param status SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2
-     * @param pin
+     * @param state
+     * @param details map
      */
-    void exportOnRingEnded(const QString& accountId, int status, const QString& pin);
+    void deviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details);
 
     /**
      * Name registration has ended
@@ -504,7 +506,9 @@ private Q_SLOTS:
      * @param callId         of the conference
      * @param conversationId of the conference
      */
-    void slotConferenceCreated(const QString& accountId, const QString& conversationId, const QString& callId);
+    void slotConferenceCreated(const QString& accountId,
+                               const QString& conversationId,
+                               const QString& callId);
     /**
      * Emit conferenceRemove
      * @param accountId
@@ -574,12 +578,14 @@ private Q_SLOTS:
                                     const QString& userPhoto);
 
     /**
-     * Emit exportOnRingEnded
+     * Device authentication state has changed
      * @param accountId
-     * @param status SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2
-     * @param pin
+     * @param state
+     * @param details map
      */
-    void slotExportOnRingEnded(const QString& accountId, int status, const QString& pin);
+    void slotDeviceAuthStateChanged(const QString& accountId,
+                                    int state,
+                                    const MapStringString& details);
 
     /**
      * Emit nameRegistrationEnded
diff --git a/src/libclient/namedirectory.cpp b/src/libclient/namedirectory.cpp
index 15fb0d583085e85d71d5f168e3a2f5f650f9bfeb..3068a202933cceb83fcc8506168a193c00573ea6 100644
--- a/src/libclient/namedirectory.cpp
+++ b/src/libclient/namedirectory.cpp
@@ -36,11 +36,6 @@ NameDirectoryPrivate::NameDirectoryPrivate(NameDirectory* q)
             this,
             &NameDirectoryPrivate::slotRegisteredNameFound,
             Qt::QueuedConnection);
-    connect(&configurationManager,
-            &ConfigurationManagerInterface::exportOnRingEnded,
-            this,
-            &NameDirectoryPrivate::slotExportOnRingEnded,
-            Qt::QueuedConnection);
 }
 
 NameDirectory::NameDirectory()
@@ -100,16 +95,6 @@ NameDirectoryPrivate::slotRegisteredNameFound(const QString& accountId,
                                       requestedName);
 }
 
-// Export account has ended with pin generated
-void
-NameDirectoryPrivate::slotExportOnRingEnded(const QString& accountId, int status, const QString& pin)
-{
-    LC_DBG << "Export on ring ended for account: " << accountId << "status: " << status
-           << "PIN: " << pin;
-
-    Q_EMIT q_ptr->exportOnRingEnded(static_cast<NameDirectory::ExportOnRingStatus>(status), pin);
-}
-
 // Lookup a name
 bool
 NameDirectory::lookupName(const QString& accountId,
diff --git a/src/libclient/namedirectory.h b/src/libclient/namedirectory.h
index 008dc1979d99b3f92d1c371e24ea557c1e9ca808..9211c80fef1b849b635129214f161a12b7328ac3 100644
--- a/src/libclient/namedirectory.h
+++ b/src/libclient/namedirectory.h
@@ -40,15 +40,16 @@ public:
     enum class LookupStatus { SUCCESS = 0, INVALID_NAME = 1, NOT_FOUND = 2, ERROR = 3 };
     Q_ENUM(LookupStatus)
 
-    enum class ExportOnRingStatus { SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2, INVALID };
-    Q_ENUM(ExportOnRingStatus)
-
     // Singleton
     static NameDirectory& instance();
 
     // Lookup
-    Q_INVOKABLE bool lookupName(const QString& accountId, const QString& name, const QString& nameServiceURL = "") const;
-    Q_INVOKABLE bool lookupAddress(const QString& accountId, const QString& address, const QString& nameServiceURL = "") const;
+    Q_INVOKABLE bool lookupName(const QString& accountId,
+                                const QString& name,
+                                const QString& nameServiceURL = "") const;
+    Q_INVOKABLE bool lookupAddress(const QString& accountId,
+                                   const QString& address,
+                                   const QString& nameServiceURL = "") const;
 
 private:
     // Constructors & Destructors
@@ -70,8 +71,5 @@ Q_SIGNALS:
                              const QString& address,
                              const QString& registeredName,
                              const QString& requestedName);
-
-    // Export account has ended with pin generated
-    void exportOnRingEnded(NameDirectory::ExportOnRingStatus status, const QString& pin);
 };
 Q_DECLARE_METATYPE(NameDirectory*)
diff --git a/src/libclient/private/namedirectory_p.h b/src/libclient/private/namedirectory_p.h
index 89e49cd2d1efb243b4fd1c3c52a9f73e73821b98..ebc5204344a0e56139a7c4dc026f544b0b51a77a 100644
--- a/src/libclient/private/namedirectory_p.h
+++ b/src/libclient/private/namedirectory_p.h
@@ -37,5 +37,4 @@ public Q_SLOTS:
                                  int status,
                                  const QString& address,
                                  const QString& registeredName);
-    void slotExportOnRingEnded(const QString& accountId, int status, const QString& pin);
 };
diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h
index 467fed9ea8da3cf0bd878418031ad918c047bd4b..b34918f80e0e2159b56d7601d496a3ab51d53204 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -150,11 +150,23 @@ public:
                                                         QString(displayName.c_str()),
                                                         QString(userPhoto.c_str()));
                 }),
-            exportable_callback<ConfigurationSignal::ExportOnRingEnded>(
-                [this](const std::string& accountId, int status, const std::string& pin) {
-                    Q_EMIT this->exportOnRingEnded(QString(accountId.c_str()),
-                                                   status,
-                                                   QString(pin.c_str()));
+            exportable_callback<ConfigurationSignal::AddDeviceStateChanged>(
+                [this](const std::string& accountId,
+                       uint32_t operationId,
+                       int state,
+                       const std::map<std::string, std::string>& details) {
+                    Q_EMIT this->addDeviceStateChanged(QString(accountId.c_str()),
+                                                       operationId,
+                                                       state,
+                                                       convertMap(details));
+                }),
+            exportable_callback<ConfigurationSignal::DeviceAuthStateChanged>(
+                [this](const std::string& accountId,
+                       int state,
+                       const std::map<std::string, std::string>& details) {
+                    Q_EMIT this->deviceAuthStateChanged(QString(accountId.c_str()),
+                                                        state,
+                                                        convertMap(details));
                 }),
             exportable_callback<ConfigurationSignal::NameRegistrationEnded>(
                 [this](const std::string& accountId, int status, const std::string& name) {
@@ -431,9 +443,28 @@ public Q_SLOTS: // METHODS
                               path.toStdString());
     }
 
-    bool exportOnRing(const QString& accountId, const QString& password)
+    bool provideAccountAuthentication(const QString& accountId,
+                                      const QString& credentialsFromUser,
+                                      const QString scheme = "password")
+    {
+        return libjami::provideAccountAuthentication(accountId.toStdString(),
+                                                     credentialsFromUser.toStdString(),
+                                                     scheme.toStdString());
+    }
+
+    int32_t addDevice(const QString& accountId, const QString& token)
+    {
+        return libjami::addDevice(accountId.toStdString(), token.toStdString());
+    }
+
+    bool confirmAddDevice(const QString& accountId, uint32_t operationId)
+    {
+        return libjami::confirmAddDevice(accountId.toStdString(), operationId);
+    }
+
+    bool cancelAddDevice(const QString& accountId, uint32_t operationId)
     {
-        return libjami::exportOnRing(accountId.toStdString(), password.toStdString());
+        return libjami::cancelAddDevice(accountId.toStdString(), operationId);
     }
 
     bool exportToFile(const QString& accountId,
@@ -498,8 +529,7 @@ public Q_SLOTS: // METHODS
                                displayName.toStdString(),
                                avatarPath.toStdString(),
                                fileType.toStdString(),
-                               flag
-                               );
+                               flag);
     }
 
     QStringList getAccountList()
@@ -1197,7 +1227,11 @@ Q_SIGNALS: // SIGNALS
                                  const QString& certId,
                                  const QString& status);
     void knownDevicesChanged(const QString& accountId, const MapStringString& devices);
-    void exportOnRingEnded(const QString& accountId, int status, const QString& pin);
+    void addDeviceStateChanged(const QString& accountId,
+                               uint32_t operationId,
+                               int state,
+                               const MapStringString& details);
+    void deviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details);
     void incomingAccountMessage(const QString& accountId,
                                 const QString& from,
                                 const QString msgId,