diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 0436b08398290cf93ba1c6ea431528a1cd1920f7..b1874a5688fb0156ea01d7f895ec6d8492814a3a 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -289,7 +289,7 @@
 
        <method name="changeAccountPassword" tp:name-for-bindings="changeAccountPassword">
            <tp:docstring>
-               Change the Ring account archive password.
+               Change the account archive password.
            </tp:docstring>
            <arg type="s" name="accountID" direction="in">
            </arg>
@@ -306,10 +306,10 @@
 
       <method name="lookupName" tp:name-for-bindings="lookupName">
            <tp:docstring>
-               Performs name lookup with RingNS protocol for the specified account (if any) or using the default nameserver.
+               Performs name lookup for the specified account (if any) or using the default nameserver.
            </tp:docstring>
            <arg type="s" name="accountID" direction="in">
-              The account to use. If empty, use the default RingNS server.
+              The account to use. If empty, use the default nameserver.
            </arg>
            <arg type="s" name="nameserverUri" direction="in">
               The name server URI to use, considered only if accountID is empty.
@@ -324,10 +324,10 @@
       </method>
       <method name="lookupAddress" tp:name-for-bindings="lookupAddress">
            <tp:docstring>
-               Performs address lookup with RingNS protocol for the specified account (if any) or using the default nameserver.
+               Performs address lookup for the specified account (if any) or using the default nameserver.
            </tp:docstring>
            <arg type="s" name="accountID" direction="in">
-              The account to use. If empty, use the default RingNS server.
+              The account to use. If empty, use the default nameserver.
            </arg>
            <arg type="s" name="nameserverUri" direction="in">
               The name server URI to use, considered only if accountID is empty.
@@ -416,6 +416,43 @@
            </arg>
        </signal>
 
+      <method name="searchUser" tp:name-for-bindings="searchUser">
+           <tp:docstring>
+               Performs name registration with RingNS protocol for the specified account.
+           </tp:docstring>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <arg type="s" name="query" direction="in">
+               <tp:docstring>
+                   Query to perform in the remote user directory. What properties are searched is server-dependant.
+               </tp:docstring>
+           </arg>
+           <arg type="b" name="success" direction="out">
+               <tp:docstring>
+                   True if the operation was initialized successfully and a corresponding signal will be triggered.
+               </tp:docstring>
+           </arg>
+      </method>
+       <signal name="userSearchEnded" tp:name-for-bindings="userSearchEnded">
+           <tp:docstring>
+               Notify clients when the registerName operation ended.
+           </tp:docstring>
+           <arg type="s" name="accountID">
+           </arg>
+           <arg type="i" name="status">
+           </arg>
+           <arg type="s" name="query">
+               <tp:docstring>
+                   The query which is answered by this signal
+               </tp:docstring>
+           </arg>
+           <arg type="aa{ss}" name="result" direction="out" tp:type="String_String_Map">
+               <tp:docstring>
+                   List of user results
+               </tp:docstring>
+           </arg>
+       </signal>
+
        <method name="testAccountICEInitialization" tp:name-for-bindings="testAccountICEInitialization">
            <tp:docstring>
                Test initializing an ICE transport with the current account configuration.
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 7869dd504746a639f7d0308b0bfe7e1d4355a19e..024ec38e57c489a3baaff51331ae5aaa5bb1ffd3 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -197,6 +197,7 @@ DBusClient::initLibrary(int flags)
         exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&DBusConfigurationManager::exportOnRingEnded, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&DBusConfigurationManager::knownDevicesChanged, confM, _1, _2 )),
         exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&DBusConfigurationManager::nameRegistrationEnded, confM, _1, _2, _3 )),
+        exportable_callback<ConfigurationSignal::UserSearchEnded>(bind(&DBusConfigurationManager::userSearchEnded, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&DBusConfigurationManager::registeredNameFound, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::DeviceRevocationEnded>(bind(&DBusConfigurationManager::deviceRevocationEnded, confM, _1, _2, _3)),
         exportable_callback<ConfigurationSignal::AccountAvatarReceived>(bind(&DBusConfigurationManager::accountAvatarReceived, confM, _1, _2)),
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index a8c511c1841e01fd9cf3b155debec3505321da23..3f072bc26ced576b234c84f5aefd885270c5bf5a 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -117,6 +117,12 @@ DBusConfigurationManager::registerName(const std::string& account, const std::st
     return DRing::registerName(account, password, name);
 }
 
+auto
+DBusConfigurationManager::searchUser(const std::string& account, const std::string& query) -> decltype(DRing::searchUser(account, query))
+{
+    return DRing::searchUser(account, query);
+}
+
 void
 DBusConfigurationManager::removeAccount(const std::string& accountID)
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 9321fa4d3ac69edc9f99ff8cf2652035e59fd85c..75a2b64a9b81dfe497eaf78d0a2dc5993bd6db7a 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -78,6 +78,7 @@ class DRING_PUBLIC DBusConfigurationManager :
         bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
         bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address);
         bool registerName(const std::string& account, const std::string& password, const std::string& name);
+        bool searchUser(const std::string& account, const std::string& query);
         void removeAccount(const std::string& accoundID);
         std::vector<std::string> getAccountList();
         void sendRegister(const std::string& accoundID, const bool& enable);
diff --git a/bin/jni/configurationmanager.i b/bin/jni/configurationmanager.i
index f19a7eb7a2a4ad8f600bbb3669cd86801c3efc56..608f9c4b91cfc86a2058d841aa7f718e437e8e8a 100644
--- a/bin/jni/configurationmanager.i
+++ b/bin/jni/configurationmanager.i
@@ -55,6 +55,7 @@ public:
 
     virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){}
     virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){}
+    virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){}
 
     virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){}
     virtual void deviceRevocationEnded(const std::string& /*accountId*/, const std::string& /*device*/, int /*status*/){}
@@ -101,6 +102,7 @@ bool changeAccountPassword(const std::string& accountID, const std::string& pass
 bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
 bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address);
 bool registerName(const std::string& account, const std::string& password, const std::string& name);
+bool searchUser(const std::string& account, const std::string& query);
 
 std::map<std::string, std::string> getTlsDefaultSettings();
 
@@ -265,6 +267,7 @@ public:
 
     virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){}
     virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){}
+    virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){}
 
     virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){}
     virtual void deviceRevocationEnded(const std::string& /*accountId*/, const std::string& /*device*/, int /*status*/){}
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 509339ffecad18e18067d382c0063a2a8c0fc28a..9336cd86cd012cb27513b685d1102ca4d82fb964 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -277,6 +277,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<ConfigurationSignal::GetDeviceName>(bind(&ConfigurationCallback::getDeviceName, confM, _1 )),
         exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&ConfigurationCallback::registeredNameFound, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&ConfigurationCallback::nameRegistrationEnded, confM, _1, _2, _3 )),
+        exportable_callback<ConfigurationSignal::UserSearchEnded>(bind(&ConfigurationCallback::userSearchEnded, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::MigrationEnded>(bind(&ConfigurationCallback::migrationEnded, confM, _1, _2)),
         exportable_callback<ConfigurationSignal::DeviceRevocationEnded>(bind(&ConfigurationCallback::deviceRevocationEnded, confM, _1, _2, _3)),
         exportable_callback<ConfigurationSignal::AccountAvatarReceived>(bind(&ConfigurationCallback::accountAvatarReceived, confM, _1, _2))
diff --git a/bin/nodejs/configurationmanager.i b/bin/nodejs/configurationmanager.i
index cc46b5838c6021b099f91aa3fe1ef8597f6b39d7..6ba71907f272875f14c13f898585cae4bd65cc90 100644
--- a/bin/nodejs/configurationmanager.i
+++ b/bin/nodejs/configurationmanager.i
@@ -50,6 +50,7 @@ public:
 
     virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){}
     virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){}
+    virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){}
 
     virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){}
     virtual void deviceRevocationEnded(const std::string& /*accountId*/, const std::string& /*device*/, int /*status*/){}
@@ -92,6 +93,7 @@ bool setMessageDisplayed(const std::string& accountID, const std::string& contac
 bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
 bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address);
 bool registerName(const std::string& account, const std::string& password, const std::string& name);
+bool searchUser(const std::string& account, const std::string& query);
 
 std::map<std::string, std::string> getTlsDefaultSettings();
 
@@ -247,6 +249,7 @@ public:
 
     virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){}
     virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){}
+    virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){}
 
     virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){}
     virtual void deviceRevocationEnded(const std::string& /*accountId*/, const std::string& /*device*/, int /*status*/){}
diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i
index ebe3a934cdc076ce5e1670ce17f70f525b8f3a62..449b460fd301f260e372d028cbe3fe8c27945dab 100644
--- a/bin/nodejs/nodejs_interface.i
+++ b/bin/nodejs/nodejs_interface.i
@@ -146,6 +146,7 @@ void init(const v8::Handle<v8::Value> &funcMap){
         exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&exportOnRingEnded, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&nameRegistrationEnded, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&registeredNameFound, _1, _2, _3, _4 )),
+        exportable_callback<ConfigurationSignal::UserSearchEnded>(bind(&ConfigurationCallback::userSearchEnded, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::VolatileDetailsChanged>(bind(&volatileDetailsChanged, _1, _2)),
         exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&knownDevicesChanged, _1, _2 )),
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&incomingAccountMessage, _1, _2, _3, _4 )),
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 382ca294658c143189c56c1cc66592a7dac77e59..dc8d00dcfc49e16daf138ad5629f8d1fb837861c 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -1034,6 +1034,14 @@ bool lookupAddress(const std::string& account, const std::string& nameserver, co
     return false;
 }
 
+bool searchUser(const std::string& account, const std::string& query)
+{
+    if (auto acc = jami::Manager::instance().getAccount<JamiAccount>(account)) {
+        return acc->searchUser(query);
+    }
+    return false;
+}
+
 bool registerName(const std::string& account, const std::string& password, const std::string& name)
 {
 #if HAVE_RINGNS
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index f3ed3752a79bc828d7e81677b97b905118ac2a0e..e4a11a80eec208785f65334d603886051a86189b 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -70,6 +70,7 @@ getSignalHandlers()
         exported_callback<DRing::ConfigurationSignal::KnownDevicesChanged>(),
         exported_callback<DRing::ConfigurationSignal::NameRegistrationEnded>(),
         exported_callback<DRing::ConfigurationSignal::RegisteredNameFound>(),
+        exported_callback<DRing::ConfigurationSignal::UserSearchEnded>(),
         exported_callback<DRing::ConfigurationSignal::MediaParametersChanged>(),
         exported_callback<DRing::ConfigurationSignal::MigrationEnded>(),
         exported_callback<DRing::ConfigurationSignal::DeviceRevocationEnded>(),
diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h
index 77410560246a12a022e45a9ebcff756df0ac16b0..2be0a906fc272268d379e4570eff0420c60cae20 100644
--- a/src/dring/configurationmanager_interface.h
+++ b/src/dring/configurationmanager_interface.h
@@ -70,6 +70,7 @@ DRING_PUBLIC bool isPasswordValid(const std::string& accountID, const std::strin
 DRING_PUBLIC bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
 DRING_PUBLIC bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address);
 DRING_PUBLIC bool registerName(const std::string& account, const std::string& password, const std::string& name);
+DRING_PUBLIC bool searchUser(const std::string& account, const std::string& query);
 
 DRING_PUBLIC void removeAccount(const std::string& accountID);
 DRING_PUBLIC void setAccountEnabled(const std::string& accountID, bool enable);
@@ -325,6 +326,10 @@ struct DRING_PUBLIC ConfigurationSignal {
                 constexpr static const char* name = "RegisteredNameFound";
                 using cb_type = void(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/);
         };
+        struct DRING_PUBLIC UserSearchEnded {
+                constexpr static const char* name = "UserSearchEnded";
+                using cb_type = void(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/);
+        };
         struct DRING_PUBLIC CertificatePinned {
                 constexpr static const char* name = "CertificatePinned";
                 using cb_type = void(const std::string& /*certId*/);
diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h
index d1e129c34e7c7cec26603e12121e1177066c5e74..4aa1e1822d3fe6514cb2d68e13b18d472191586e 100644
--- a/src/jamidht/account_manager.h
+++ b/src/jamidht/account_manager.h
@@ -212,10 +212,14 @@ public:
 
     // Name resolver
     using LookupCallback = NameDirectory::LookupCallback;
+    using SearchResult = NameDirectory::SearchResult;
+    using SearchCallback = NameDirectory::SearchCallback;
     using RegistrationCallback = NameDirectory::RegistrationCallback;
+    using SearchResponse = NameDirectory::Response;
 
     virtual void lookupUri(const std::string& name, const std::string& defaultServer, LookupCallback cb);
     virtual void lookupAddress(const std::string& address, LookupCallback cb);
+    virtual bool searchUser(const std::string& /*query*/, SearchCallback /*cb*/) { return false; }
     virtual void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) = 0;
 
 protected:
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index f006cfecb2f14bbc19c046eab6eddb222eee6f34..080d979acbdeaaf4e55fe03a670a4d50c07cdd81 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -1373,9 +1373,8 @@ JamiAccount::getVolatileAccountDetails() const
 void
 JamiAccount::lookupName(const std::string& name)
 {
-    auto acc = getAccountID();
     if (accountManager_)
-        accountManager_->lookupUri(name, nameServer_, [acc,name](const std::string& result, NameDirectory::Response response) {
+        accountManager_->lookupUri(name, nameServer_, [acc = getAccountID(),name](const std::string& result, NameDirectory::Response response) {
             emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, (int)response, result, name);
         });
 }
@@ -1408,6 +1407,16 @@ JamiAccount::registerName(const std::string& password, const std::string& name)
 }
 #endif
 
+bool
+JamiAccount::searchUser(const std::string& query)
+{
+    if (accountManager_)
+        return accountManager_->searchUser(query, [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result, jami::NameDirectory::Response response) {
+            jami::emitSignal<DRing::ConfigurationSignal::UserSearchEnded>(acc, (int)response, query, result);
+        });
+    return false;
+}
+
 void
 JamiAccount::checkPendingCall(const std::string& callId)
 {
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index e188c256329e97783c5935400ab2f11a64918a1a..474960cc61774285805980c02ca4862876f83eaf 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -346,6 +346,7 @@ public:
     void lookupAddress(const std::string& address);
     void registerName(const std::string& password, const std::string& name);
 #endif
+    bool searchUser(const std::string& nameQuery);
 
     ///
     /// Send a E2E connection request to a given peer for the given transfer id
diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h
index 186c771a399bda9c64f89aab78613d69862a751c..231039a72415680e5b51e136e860452cf50727fd 100644
--- a/src/jamidht/namedirectory.h
+++ b/src/jamidht/namedirectory.h
@@ -61,6 +61,8 @@ public:
                                         };
 
     using LookupCallback = std::function<void(const std::string& result, Response response)>;
+    using SearchResult = std::vector<std::map<std::string, std::string>>;
+    using SearchCallback = std::function<void(const SearchResult& result, Response response)>;
     using RegistrationCallback = std::function<void(RegistrationResponse response)>;
 
     NameDirectory(const std::string& serverUrl, std::shared_ptr<dht::Logger> l = {});
@@ -78,6 +80,7 @@ public:
 
     void lookupAddress(const std::string& addr, LookupCallback cb);
     void lookupName(const std::string& name, LookupCallback cb);
+    bool searchName(const std::string& /*name*/, SearchCallback /*cb*/) { return false; }
 
     void registerName(const std::string& addr, const std::string& name,
                       const std::string& owner, RegistrationCallback cb,
diff --git a/src/jamidht/server_account_manager.cpp b/src/jamidht/server_account_manager.cpp
index cc342e2116245548a4650b5ab440b38b50bda38c..cc913fbc5246be68adca07345107b7d134939186 100644
--- a/src/jamidht/server_account_manager.cpp
+++ b/src/jamidht/server_account_manager.cpp
@@ -359,4 +359,42 @@ ServerAccountManager::registerName(const std::string&, const std::string&, Regis
     cb(NameDirectory::RegistrationResponse::unsupported);
 }
 
+bool
+ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
+{
+    //TODO escape url query
+    const std::string url = managerHostname_ + PATH_SEARCH + "?queryString=" + query;
+    JAMI_WARN("[Search] Searching user %s at %s", query.c_str(), url.c_str());
+    sendDeviceRequest(std::make_shared<Request>(*Manager::instance().ioContext(), url, [cb, onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
+        onAsync([=] (AccountManager& accountManager) {
+            JAMI_DBG("[Search] Got request callback with status code=%u", response.status_code);
+            auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
+            if (response.status_code >= 200  && response.status_code < 300) {
+                try {
+                    Json::Value::ArrayIndex rcount = json.size();
+                    std::vector<std::map<std::string, std::string>> results(rcount);
+                    JAMI_WARN("[Search] Got server response: %s", response.body.c_str());
+                    for (Json::Value::ArrayIndex i=0; i<rcount; i++) {
+                        const auto& ruser = json[i];
+                        std::map<std::string, std::string> user;
+                        for (const auto& member : ruser.getMemberNames()) {
+                            user[member] = ruser[member].asString();
+                        }
+                        results.emplace_back(std::move(user));
+                    }
+                    if (cb)
+                        cb(results, SearchResponse::found);
+                }
+                catch (const std::exception& e) {
+                    JAMI_ERR("[Search] Error during search: %s", e.what());
+                }
+            }
+            else if (cb)
+                cb({}, SearchResponse::error);
+            this_.clearRequest(response.request);
+        });
+    }, logger_));
+    return true;
+}
+
 }
diff --git a/src/jamidht/server_account_manager.h b/src/jamidht/server_account_manager.h
index 60c419a9cf890c259a0981dacf99ca8da29a0035..f510cf4b1522208b45136031435d5fee8324d9fa 100644
--- a/src/jamidht/server_account_manager.h
+++ b/src/jamidht/server_account_manager.h
@@ -54,6 +54,7 @@ public:
 
     bool revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback cb) override;
 
+    bool searchUser(const std::string& query, SearchCallback cb) override;
     void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) override;
 
 private: