diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h
index 5634fdafad9c68226947c0d7fa08965b53db545d..d1e129c34e7c7cec26603e12121e1177066c5e74 100644
--- a/src/jamidht/account_manager.h
+++ b/src/jamidht/account_manager.h
@@ -121,7 +121,7 @@ public:
 
     virtual void syncDevices() = 0;
 
-    virtual bool isPasswordValid(const std::string& password) { return false; };
+    virtual bool isPasswordValid(const std::string& /*password*/) { return false; };
 
     dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const;
 
diff --git a/src/jamidht/namedirectory.cpp b/src/jamidht/namedirectory.cpp
index 965ce49ac187ed46a34b73fd22dc38d3bff7b2d6..6fa926b461e1a6aa82d30c0cb8b6df054af5ced7 100644
--- a/src/jamidht/namedirectory.cpp
+++ b/src/jamidht/namedirectory.cpp
@@ -50,6 +50,7 @@ namespace jami {
 constexpr const char* const QUERY_NAME {"/name/"};
 constexpr const char* const QUERY_ADDR {"/addr/"};
 constexpr const char* const CACHE_DIRECTORY {"namecache"};
+constexpr const char DEFAULT_SERVER_HOST[] = "https://ns.jami.net";
 
 const std::string  HEX_PREFIX = "0x";
 constexpr std::chrono::seconds SAVE_INTERVAL {5};
@@ -68,6 +69,9 @@ toLower(std::string& string)
     std::transform(string.begin(), string.end(), string.begin(), ::tolower);
 }
 
+NameDirectory&
+NameDirectory::instance() { return instance(DEFAULT_SERVER_HOST); }
+
 void
 NameDirectory::lookupUri(const std::string& uri, const std::string& default_server,
                          LookupCallback cb)
diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h
index 8b70b9132d4eef54c4c2bb1f377c7d5eb46941bc..186c771a399bda9c64f89aab78613d69862a751c 100644
--- a/src/jamidht/namedirectory.h
+++ b/src/jamidht/namedirectory.h
@@ -67,7 +67,11 @@ public:
     void load();
 
     static NameDirectory& instance(const std::string& serverUrl, std::shared_ptr<dht::Logger> l = {});
-    static NameDirectory& instance() { return instance(DEFAULT_SERVER_HOST); }
+    static NameDirectory& instance();
+
+    void setToken(std::string token) {
+        serverToken_ = std::move(token);
+    }
 
     static void lookupUri(const std::string& uri, const std::string& default_server,
                           LookupCallback cb);
@@ -83,9 +87,9 @@ private:
     NON_COPYABLE(NameDirectory);
     NameDirectory(NameDirectory&&) = delete;
     NameDirectory& operator=(NameDirectory&&) = delete;
-    constexpr static const char* const DEFAULT_SERVER_HOST = "https://ns.jami.net";
 
     std::string serverUrl_;
+    std::string serverToken_;
     std::string cachePath_;
 
     std::mutex cacheLock_ {};
diff --git a/src/jamidht/server_account_manager.cpp b/src/jamidht/server_account_manager.cpp
index f2b298c5fab59bc1a38c68358fb47560eece0e56..cc342e2116245548a4650b5ab440b38b50bda38c 100644
--- a/src/jamidht/server_account_manager.cpp
+++ b/src/jamidht/server_account_manager.cpp
@@ -32,11 +32,11 @@ namespace jami {
 
 using Request = dht::http::Request;
 
-static const std::string PATH_AUTH = "/api/auth";
+static const std::string PATH_LOGIN = "/api/login";
+static const std::string PATH_AUTH =  "/api/auth";
 static const std::string PATH_DEVICE = PATH_AUTH + "/device";
 static const std::string PATH_DEVICES = PATH_AUTH + "/devices";
-
-constexpr const char* const HTTPS_PROTO {"https"};
+static const std::string PATH_SEARCH = PATH_AUTH + "/directory/search";
 
 ServerAccountManager::ServerAccountManager(
     const std::string& path,
@@ -52,10 +52,8 @@ ServerAccountManager::ServerAccountManager(
 {};
 
 void
-ServerAccountManager::setHeaderFields(Request& request){
-    request.set_header_field(restinio::http_field_t::user_agent, "Jami");
-    request.set_header_field(restinio::http_field_t::accept, "application/json");
-    request.set_header_field(restinio::http_field_t::content_type, "application/json");
+ServerAccountManager::setAuthHeaderFields(Request& request) const {
+    request.set_header_field(restinio::http_field_t::authorization, "Bearer " + token_);
 }
 
 void
@@ -81,27 +79,19 @@ ServerAccountManager::initAuthentication(
     onChange_ = std::move(onChange);
     const std::string url = managerHostname_ + PATH_DEVICE;
     JAMI_WARN("[Auth] authentication with: %s to %s", ctx->credentials->username.c_str(), url.c_str());
-    auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
-    auto reqid = request->id();
-    request->set_method(restinio::http_method_post());
-    request->set_auth(ctx->credentials->username, ctx->credentials->password);
-    requests_[reqid] = request;
 
-    dht::ThreadPool::computation().run([onAsync = onAsync_, ctx, request, reqid]{
-        onAsync([ctx, request, reqid, onAsync](AccountManager& accountManager){
+    dht::ThreadPool::computation().run([onAsync = onAsync_, ctx, url]{
+        onAsync([=](AccountManager& accountManager){
             auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
+            Json::Value body;
             {
                 std::stringstream ss;
                 auto csr = ctx->request.get()->toString();
-                string_replace(csr, "\n", "\\n");
-                string_replace(csr, "\r", "\\r");
-                ss << "{\"csr\":\"" << csr  << "\", \"deviceName\":\"" << ctx->deviceName  << "\"}";
-                JAMI_WARN("[Auth] Sending request: %s", csr.c_str());
-                request->set_body(ss.str());
+                body["csr"] = csr;
+                body["deviceName"] = ctx->deviceName;
             }
-            this_.setHeaderFields(*request);
-            request->add_on_done_callback([reqid, request, ctx, onAsync]
-                                                (const dht::http::Response& response){
+            auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, body, [ctx, onAsync]
+                                                (Json::Value json, const dht::http::Response& response){
                 JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code);
                 if (response.status_code == 0)
                     ctx->onFailure(AuthError::SERVER_ERROR, "Can't connect to server");
@@ -112,22 +102,8 @@ ServerAccountManager::initAuthentication(
                 else {
                     do {
                         try {
-                            Json::Value json;
-                            std::string err;
-                            Json::CharReaderBuilder rbuilder;
-                            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                            if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
-                                JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
-                                ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse server response");
-                                break;
-                            }
                             JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
-
-                            auto certStr = json["certificateChain"].asString();
-                            string_replace(certStr, "\\n", "\n");
-                            string_replace(certStr, "\\r", "\r");
-                            auto cert = std::make_shared<dht::crypto::Certificate>(certStr);
-
+                            auto cert = std::make_shared<dht::crypto::Certificate>(json["certificateChain"].asString());
                             auto accountCert = cert->issuer;
                             if (not accountCert) {
                                 JAMI_ERR("[Auth] Can't parse certificate: no issuer");
@@ -136,18 +112,14 @@ ServerAccountManager::initAuthentication(
                             }
                             auto receipt = json["deviceReceipt"].asString();
                             Json::Value receiptJson;
-                            auto receiptReader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+                            std::string err;
+                            auto receiptReader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder{}.newCharReader());
                             if (!receiptReader->parse(receipt.data(), receipt.data() + receipt.size(), &receiptJson, &err)){
                                 JAMI_ERR("[Auth] Can't parse receipt from server: %s", err.c_str());
                                 ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse receipt from server");
                                 break;
                             }
-                            onAsync([reqid, ctx,
-                                        json=std::move(json),
-                                        receiptJson=std::move(receiptJson),
-                                        cert,
-                                        accountCert,
-                                        receipt=std::move(receipt)] (AccountManager& accountManager) mutable
+                            onAsync([=] (AccountManager& accountManager) mutable
                             {
                                 auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
                                 auto receiptSignature = base64::decode(json["receiptSignature"].asString());
@@ -195,42 +167,131 @@ ServerAccountManager::initAuthentication(
                         }
                     } while (false);
                 }
-                onAsync([reqid](AccountManager& accountManager){
+
+                onAsync([response](AccountManager& accountManager){
                     auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
-                    this_.requests_.erase(reqid);
+                    this_.clearRequest(response.request);
                 });
-            });
-            request->send();
+            }, this_.logger_);
+            request->set_auth(ctx->credentials->username, ctx->credentials->password);
+            this_.sendRequest(request);
         });
     });
 }
 
+void
+ServerAccountManager::authenticateDevice() {
+    const std::string url = managerHostname_ + PATH_LOGIN;
+    JAMI_WARN("[Auth] getting a device token: %s", url.c_str());
+    auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, Json::Value{Json::objectValue}, [onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
+        onAsync([=] (AccountManager& accountManager) {
+            auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
+            if (response.status_code >= 200 && response.status_code < 300) {
+                auto scopeStr = json["scope"].asString();
+                auto scope = scopeStr == "DEVICE" ? TokenScope::Device
+                          : (scopeStr == "USER"   ? TokenScope::User
+                                                  : TokenScope::None);
+                JAMI_WARN("[Auth] Got server response: %d %s", response.status_code, response.body.c_str());
+                this_.setToken(json["access_token"].asString(), scope);
+            } else {
+                this_.authFailed(TokenScope::Device, response.status_code);
+            }
+            this_.clearRequest(response.request);
+        });
+    }, logger_);
+    request->set_identity(info_->identity);
+    // request->set_certificate_authority(info_->identity.second->issuer->issuer);
+    sendRequest(request);
+}
+
+void
+ServerAccountManager::sendRequest(const std::shared_ptr<dht::http::Request>& request) {
+    request->set_header_field(restinio::http_field_t::user_agent, "Jami");
+    {
+        std::lock_guard<std::mutex> lock(requestLock_);
+        requests_.emplace(request);
+    }
+    request->send();
+}
+
+void
+ServerAccountManager::clearRequest(const std::weak_ptr<dht::http::Request>& request) {
+    if (auto req = request.lock()) {
+        std::lock_guard<std::mutex> lock(requestLock_);
+        requests_.erase(req);
+    }
+}
+
+void
+ServerAccountManager::authFailed(TokenScope scope, int code)
+{
+    RequestQueue requests;
+    {
+        std::lock_guard<std::mutex> lock(tokenLock_);
+        requests = std::move(getRequestQueue(scope));
+    }
+    JAMI_DBG("[Auth] Failed auth with scope %d, ending %zu pending requests", (int)scope, requests.size());
+    while (not requests.empty()) {
+        auto req = std::move(requests.front());
+        requests.pop();
+        req->terminate(code == 0 ? asio::error::not_connected : asio::error::access_denied);
+    }
+}
+
+void
+ServerAccountManager::setToken(std::string token, TokenScope scope)
+{
+    std::lock_guard<std::mutex> lock(tokenLock_);
+    token_ = std::move(token);
+    nameDir_.get().setToken(token_);
+    if (not token_.empty()) {
+        auto& reqQueue = getRequestQueue(scope);
+        JAMI_DBG("[Auth] Got token with scope %d, handling %zu pending requests", (int)scope, reqQueue.size());
+        while (not reqQueue.empty()) {
+            auto req = std::move(reqQueue.front());
+            reqQueue.pop();
+            setAuthHeaderFields(*req);
+            sendRequest(req);
+        }
+    }
+}
+
+void ServerAccountManager::sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req)
+{
+    std::lock_guard<std::mutex> lock(tokenLock_);
+    if (hasAuthorization(TokenScope::Device)) {
+        setAuthHeaderFields(*req);
+        sendRequest(req);
+    } else {
+        auto& rQueue = getRequestQueue(TokenScope::Device);
+        if (rQueue.empty())
+            authenticateDevice();
+        rQueue.emplace(req);
+    }
+}
+
+void ServerAccountManager::sendAccountRequest(const std::shared_ptr<dht::http::Request>& req)
+{
+    std::lock_guard<std::mutex> lock(tokenLock_);
+    if (hasAuthorization(TokenScope::User)) {
+        setAuthHeaderFields(*req);
+        sendRequest(req);
+    } else {
+        pendingAccountRequests_.emplace(req);
+    }
+}
+
 void
 ServerAccountManager::syncDevices()
 {
-    if (not creds_)
-        return;
-    const std::string url = managerHostname_ + PATH_DEVICES + "?username=" + creds_->username;
-    JAMI_WARN("[Auth] syncDevices with: %s to %s", creds_->username.c_str(), url.c_str());
-    auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
-    auto reqid = request->id();
-    request->set_method(restinio::http_method_get());
-    request->set_auth(creds_->username, creds_->password);
-    setHeaderFields(*request);
-    request->add_on_done_callback([reqid, onAsync = onAsync_] (const dht::http::Response& response){
-        onAsync([reqid, response] (AccountManager& accountManager) {
+    const std::string url = managerHostname_ + PATH_DEVICES;
+    JAMI_WARN("[Auth] syncDevices %s", url.c_str());
+    sendDeviceRequest(std::make_shared<Request>(*Manager::instance().ioContext(), url, [onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
+        onAsync([=] (AccountManager& accountManager) {
             JAMI_DBG("[Auth] 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) {
+            if (response.status_code >= 200 && response.status_code < 300) {
                 try {
-                    Json::Value json;
-                    std::string err;
-                    Json::CharReaderBuilder rbuilder;
-                    auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                    if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
-                        JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
-                        return;
-                    }
                     JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
                     if (not json.isArray()) {
                         JAMI_ERR("[Auth] Can't parse server response: not an array");
@@ -248,11 +309,9 @@ ServerAccountManager::syncDevices()
                     JAMI_ERR("Error when loading device list: %s", e.what());
                 }
             }
-            this_.requests_.erase(reqid);
+            this_.clearRequest(response.request);
         });
-    });
-    request->send();
-    requests_[reqid] = std::move(request);
+    }, logger_));
 }
 
 bool
@@ -265,27 +324,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin
     }
     const std::string url = managerHostname_ + PATH_DEVICE + "?deviceId=" + device;
     JAMI_WARN("[Revoke] Removing device of %s at %s", info_->username.c_str(), url.c_str());
-    auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
-    auto reqid = request->id();
-    request->set_method(restinio::http_method_delete());
-    request->set_auth(info_->username, password);
-    setHeaderFields(*request);
-    request->add_on_done_callback([reqid, cb, onAsync = onAsync_] (const dht::http::Response& response){
-        onAsync([reqid, cb, response] (AccountManager& accountManager) {
+    auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, [cb, onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
+        onAsync([=] (AccountManager& accountManager) {
             JAMI_DBG("[Revoke] 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) {
-                if (response.body.empty())
-                    return;
+            if (response.status_code >= 200 && response.status_code < 300) {
                 try {
-                    Json::Value json;
-                    std::string err;
-                    Json::CharReaderBuilder rbuilder;
-                    auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                    if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
-                        JAMI_ERR("[Revoke] Can't parse server response: %s", err.c_str());
-                    }
-                    JAMI_WARN("[Revoke] Got server response: %s", response.body.c_str());
+                    JAMI_WARN("[Revoke] Got server response");
                     if (json["errorDetails"].empty()){
                         if (cb)
                             cb(RevokeDeviceResult::SUCCESS);
@@ -298,12 +343,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin
             }
             else if (cb)
                 cb(RevokeDeviceResult::ERROR_NETWORK);
-            this_.requests_.erase(reqid);
+            this_.clearRequest(response.request);
         });
-    });
+    }, logger_);
+    request->set_method(restinio::http_method_delete());
+    request->set_auth(info_->username, password);
     JAMI_DBG("[Revoke] Sending revoke device '%s' to JAMS", device.c_str());
-    request->send();
-    requests_[reqid] = std::move(request);
+    sendRequest(request);
     return false;
 }
 
diff --git a/src/jamidht/server_account_manager.h b/src/jamidht/server_account_manager.h
index bbebf8ad30040903983f65b4db9d7f447eed8a61..60c419a9cf890c259a0981dacf99ca8da29a0035 100644
--- a/src/jamidht/server_account_manager.h
+++ b/src/jamidht/server_account_manager.h
@@ -20,6 +20,9 @@
 
 #include "account_manager.h"
 
+#include <queue>
+#include <set>
+
 namespace jami {
 
 class ServerAccountManager : public AccountManager {
@@ -64,10 +67,42 @@ private:
 
     const std::string managerHostname_;
     std::shared_ptr<dht::Logger> logger_;
-    std::map<unsigned int /*id*/, std::shared_ptr<dht::http::Request>> requests_;
+
+    std::mutex requestLock_;
+    std::set<std::shared_ptr<dht::http::Request>> requests_;
     std::unique_ptr<ServerAccountCredentials> creds_;
 
-    void setHeaderFields(dht::http::Request& request);
+    void sendRequest(const std::shared_ptr<dht::http::Request>& request);
+    void clearRequest(const std::weak_ptr<dht::http::Request>& request);
+
+    enum class TokenScope : unsigned {
+        None = 0,
+        Device,
+        User,
+        Admin
+    };
+    std::mutex tokenLock_;
+    TokenScope tokenScope_ {};
+    std::string token_ {};
+    using RequestQueue = std::queue<std::shared_ptr<dht::http::Request>>;
+    RequestQueue pendingDeviceRequests_;
+    RequestQueue pendingAccountRequests_;
+    RequestQueue& getRequestQueue(TokenScope scope) {
+        return scope == TokenScope::Device ? pendingDeviceRequests_ : pendingAccountRequests_;
+    }
+    bool hasAuthorization(TokenScope scope) const {
+        return not token_.empty() and tokenScope_ >= scope;
+    }
+    void setAuthHeaderFields(dht::http::Request& request) const;
+
+    void sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req);
+    void sendAccountRequest(const std::shared_ptr<dht::http::Request>& req);
+
+    void authenticateDevice();
+    void authenticateAccount();
+    void authFailed(TokenScope scope, int code);
+
+    void setToken(std::string token, TokenScope scope);
 };
 
 }