diff --git a/src/Makefile.am b/src/Makefile.am
index 6bed09fa71cdb95e48ea6765e093fe6be5167c30..973b7bc02d182f5d0b31fd31b22cb0151700078e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -82,6 +82,7 @@ libjami_la_SOURCES = \
 		call.cpp \
 		gittransport.cpp \
 		account.cpp \
+		account_config.cpp \
 		logger.cpp \
 		fileutils.cpp \
 		archiver.cpp \
diff --git a/src/account_config.cpp b/src/account_config.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5fee1015d4665f7024311e8aef3d7f32ec2d730a
--- /dev/null
+++ b/src/account_config.cpp
@@ -0,0 +1,177 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#include "account_config.h"
+#include "account_const.h"
+#include "account_schema.h"
+#include "yamlparser.h"
+#include "string_utils.h"
+#include "fileutils.h"
+
+#include <fmt/compile.h>
+
+namespace jami {
+
+constexpr const char* RINGTONE_PATH_KEY = "ringtonePath";
+constexpr const char* RINGTONE_ENABLED_KEY = "ringtoneEnabled";
+constexpr const char* VIDEO_ENABLED_KEY = "videoEnabled";
+constexpr const char* DISPLAY_NAME_KEY = "displayName";
+constexpr const char* ALIAS_KEY = "alias";
+constexpr const char* TYPE_KEY = "type";
+constexpr const char* AUTHENTICATION_USERNAME_KEY = "authenticationUsername";
+constexpr const char* USERNAME_KEY = "username";
+constexpr const char* PASSWORD_KEY = "password";
+constexpr const char* HOSTNAME_KEY = "hostname";
+constexpr const char* ACCOUNT_ENABLE_KEY = "enable";
+constexpr const char* ACCOUNT_AUTOANSWER_KEY = "autoAnswer";
+constexpr const char* ACCOUNT_READRECEIPT_KEY = "sendReadReceipt";
+constexpr const char* ACCOUNT_ISRENDEZVOUS_KEY = "rendezVous";
+constexpr const char* ACCOUNT_ACTIVE_CALL_LIMIT_KEY = "activeCallLimit";
+constexpr const char* MAILBOX_KEY = "mailbox";
+constexpr const char* USER_AGENT_KEY = "useragent";
+constexpr const char* HAS_CUSTOM_USER_AGENT_KEY = "hasCustomUserAgent";
+constexpr const char* UPNP_ENABLED_KEY = "upnpEnabled";
+constexpr const char* ACTIVE_CODEC_KEY = "activeCodecs";
+constexpr const char* DEFAULT_MODERATORS_KEY = "defaultModerators";
+constexpr const char* LOCAL_MODERATORS_ENABLED_KEY = "localModeratorsEnabled";
+constexpr const char* ALL_MODERATORS_ENABLED_KEY = "allModeratorsEnabled";
+constexpr const char* PROXY_PUSH_TOKEN_KEY = "proxyPushToken";
+constexpr const char* PROXY_PUSH_TOPIC_KEY = "proxyPushiOSTopic";
+
+using yaml_utils::parseValueOptional;
+
+void
+AccountConfig::serialize(YAML::Emitter& out) const
+{
+    out << YAML::Key << ACCOUNT_ENABLE_KEY << YAML::Value << enabled;
+    out << YAML::Key << TYPE_KEY << YAML::Value << type;
+    out << YAML::Key << ALIAS_KEY << YAML::Value << alias;
+    out << YAML::Key << HOSTNAME_KEY << YAML::Value << hostname;
+    out << YAML::Key << USERNAME_KEY << YAML::Value << username;
+    out << YAML::Key << ACTIVE_CODEC_KEY << YAML::Value << fmt::format(FMT_COMPILE("{}"), fmt::join(activeCodecs, "/"sv));
+    out << YAML::Key << MAILBOX_KEY << YAML::Value << mailbox;
+    out << YAML::Key << ACCOUNT_AUTOANSWER_KEY << YAML::Value << autoAnswerEnabled;
+    out << YAML::Key << ACCOUNT_READRECEIPT_KEY << YAML::Value << sendReadReceipt;
+    out << YAML::Key << ACCOUNT_ISRENDEZVOUS_KEY << YAML::Value << isRendezVous;
+    out << YAML::Key << ACCOUNT_ACTIVE_CALL_LIMIT_KEY << YAML::Value << activeCallLimit;
+    out << YAML::Key << RINGTONE_ENABLED_KEY << YAML::Value << ringtoneEnabled;
+    out << YAML::Key << RINGTONE_PATH_KEY << YAML::Value << ringtonePath;
+    out << YAML::Key << USER_AGENT_KEY << YAML::Value << customUserAgent;
+    out << YAML::Key << DISPLAY_NAME_KEY << YAML::Value << displayName;
+    out << YAML::Key << UPNP_ENABLED_KEY << YAML::Value << upnpEnabled;
+    out << YAML::Key << DEFAULT_MODERATORS_KEY << YAML::Value << defaultModerators;
+    out << YAML::Key << LOCAL_MODERATORS_ENABLED_KEY << YAML::Value << localModeratorsEnabled;
+    out << YAML::Key << ALL_MODERATORS_ENABLED_KEY << YAML::Value << allModeratorsEnabled;
+    out << YAML::Key << PROXY_PUSH_TOKEN_KEY << YAML::Value << deviceKey;
+    out << YAML::Key << PROXY_PUSH_TOPIC_KEY << YAML::Value << notificationTopic;
+    out << YAML::Key << VIDEO_ENABLED_KEY << YAML::Value << videoEnabled;
+}
+
+void
+AccountConfig::unserialize(const YAML::Node& node)
+{
+    parseValueOptional(node, ALIAS_KEY, alias);
+    //parseValueOptional(node, TYPE_KEY, type);
+    parseValueOptional(node, ACCOUNT_ENABLE_KEY, enabled);
+    parseValueOptional(node, HOSTNAME_KEY, hostname);
+    parseValueOptional(node, ACCOUNT_AUTOANSWER_KEY, autoAnswerEnabled);
+    parseValueOptional(node, ACCOUNT_READRECEIPT_KEY, sendReadReceipt);
+    parseValueOptional(node, ACCOUNT_ISRENDEZVOUS_KEY, isRendezVous);
+    parseValueOptional(node, ACCOUNT_ACTIVE_CALL_LIMIT_KEY, activeCallLimit);
+    parseValueOptional(node, MAILBOX_KEY, mailbox);
+
+    std::string codecs;
+    if (parseValueOptional(node, ACTIVE_CODEC_KEY, codecs))
+        activeCodecs = split_string_to_unsigned(codecs, '/');
+
+    parseValueOptional(node, DISPLAY_NAME_KEY, displayName);
+
+    parseValueOptional(node, USER_AGENT_KEY, customUserAgent);
+    parseValueOptional(node, RINGTONE_PATH_KEY, ringtonePath);
+    parseValueOptional(node, RINGTONE_ENABLED_KEY, ringtoneEnabled);
+    parseValueOptional(node, VIDEO_ENABLED_KEY, videoEnabled);
+
+    parseValueOptional(node, UPNP_ENABLED_KEY, upnpEnabled);
+
+    std::string defMod;
+    parseValueOptional(node, DEFAULT_MODERATORS_KEY, defMod);
+    defaultModerators = string_split_set(defMod);
+    parseValueOptional(node, LOCAL_MODERATORS_ENABLED_KEY, localModeratorsEnabled);
+    parseValueOptional(node, ALL_MODERATORS_ENABLED_KEY, allModeratorsEnabled);
+    parseValueOptional(node, PROXY_PUSH_TOKEN_KEY, deviceKey);
+    parseValueOptional(node, PROXY_PUSH_TOPIC_KEY, notificationTopic);
+}
+
+std::map<std::string, std::string>
+AccountConfig::toMap() const
+{
+    return {{Conf::CONFIG_ACCOUNT_ALIAS, alias},
+            {Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName},
+            {Conf::CONFIG_ACCOUNT_ENABLE, enabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_ACCOUNT_TYPE, type},
+            {Conf::CONFIG_ACCOUNT_USERNAME, username},
+            {Conf::CONFIG_ACCOUNT_HOSTNAME, hostname},
+            {Conf::CONFIG_ACCOUNT_MAILBOX, mailbox},
+            {Conf::CONFIG_ACCOUNT_USERAGENT, customUserAgent},
+            {Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_ACCOUNT_SENDREADRECEIPT, sendReadReceipt ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_ACCOUNT_ISRENDEZVOUS, isRendezVous ? TRUE_STR : FALSE_STR},
+            {libjami::Account::ConfProperties::ACTIVE_CALL_LIMIT, std::to_string(activeCallLimit)},
+            {Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_RINGTONE_PATH, ringtonePath},
+            {Conf::CONFIG_VIDEO_ENABLED, videoEnabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_UPNP_ENABLED, upnpEnabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_DEFAULT_MODERATORS, string_join(defaultModerators)},
+            {Conf::CONFIG_LOCAL_MODERATORS_ENABLED, localModeratorsEnabled ? TRUE_STR : FALSE_STR},
+            {Conf::CONFIG_ALL_MODERATORS_ENABLED, allModeratorsEnabled ? TRUE_STR : FALSE_STR}};
+}
+
+void
+AccountConfig::fromMap(const std::map<std::string, std::string>& details)
+{
+    parseString(details, Conf::CONFIG_ACCOUNT_ALIAS, alias);
+    parseString(details, Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName);
+    parseBool(details, Conf::CONFIG_ACCOUNT_ENABLE, enabled);
+    parseBool(details, Conf::CONFIG_VIDEO_ENABLED, videoEnabled);
+    parseString(details, Conf::CONFIG_ACCOUNT_HOSTNAME, hostname);
+    parseString(details, Conf::CONFIG_ACCOUNT_MAILBOX, mailbox);
+    parseBool(details, Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled);
+    parseBool(details, Conf::CONFIG_ACCOUNT_SENDREADRECEIPT, sendReadReceipt);
+    parseBool(details, Conf::CONFIG_ACCOUNT_ISRENDEZVOUS, isRendezVous);
+    parseInt(details, libjami::Account::ConfProperties::ACTIVE_CALL_LIMIT, activeCallLimit);
+    parseBool(details, Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled);
+    parseString(details, Conf::CONFIG_RINGTONE_PATH, ringtonePath);
+    parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, customUserAgent);
+    parseBool(details, Conf::CONFIG_UPNP_ENABLED, upnpEnabled);
+    std::string defMod;
+    parseString(details, Conf::CONFIG_DEFAULT_MODERATORS, defMod);
+    defaultModerators = string_split_set(defMod);
+    parseBool(details, Conf::CONFIG_LOCAL_MODERATORS_ENABLED, localModeratorsEnabled);
+    parseBool(details, Conf::CONFIG_ALL_MODERATORS_ENABLED, allModeratorsEnabled);
+}
+
+void
+parsePath(const std::map<std::string, std::string>& details,
+                   const char* key,
+                   std::string& s,
+                   const std::string& base)
+{
+    auto it = details.find(key);
+    if (it != details.end())
+        s = fileutils::getCleanPath(base, it->second);
+}
+
+}
diff --git a/src/account_config.h b/src/account_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..5b6c34e749ff06741a789101c1df238c5b65c3b1
--- /dev/null
+++ b/src/account_config.h
@@ -0,0 +1,159 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include "connectivity/sip_utils.h"
+#include "config/serializable.h"
+
+#include <string>
+#include <string_view>
+#include <utility>
+
+using namespace std::literals;
+
+namespace jami {
+constexpr const char* const DEFAULT_RINGTONE_PATH = "default.opus";
+
+struct AccountConfig: public Serializable {
+    AccountConfig(const std::string& type_, const std::string& id_, const std::string& path_ = {}): type(type_), id(id_), path(path_) {}
+
+    virtual void serialize(YAML::Emitter& out) const;
+    virtual void unserialize(const YAML::Node& node);
+
+    virtual std::map<std::string, std::string> toMap() const;
+    virtual void fromMap(const std::map<std::string, std::string>&);
+
+    /** Account type */
+    const std::string type;
+
+    /** Account id */
+    const std::string id;
+
+    /** Path where the configuration file is stored.
+     * Part of the context but not stored in the configuration
+     * Used to compute relative paths for configuraton fields */
+    const std::string path;
+
+    /** A user-defined name for this account */
+    std::string alias {};
+
+    std::string username {};
+
+    /** SIP hostname (SIP account) or DHT bootstrap nodes (Jami account) */
+    std::string hostname {};
+
+    /** True if the account is enabled. */
+    bool enabled {true};
+
+    /** If true, automatically answer calls to this account */
+    bool autoAnswerEnabled {false};
+
+    /** If true, send displayed status (and emit to the client) */
+    bool sendReadReceipt {true};
+
+    /** If true mix calls into a conference */
+    bool isRendezVous {false};
+
+    /**
+     * The number of concurrent calls for the account
+     * -1: Unlimited
+     *  0: Do not disturb
+     *  1: Single call
+     *  +: Multi line
+     */
+    int activeCallLimit {-1};
+
+    std::vector<unsigned> activeCodecs {};
+
+    /**
+     * Play ringtone when receiving a call
+     */
+    bool ringtoneEnabled {true};
+
+    /**
+     * Ringtone file used for this account
+     */
+    std::string ringtonePath {DEFAULT_RINGTONE_PATH};
+
+    /**
+     * Allows user to temporarily disable video calling
+     */
+    bool videoEnabled {true};
+
+    /**
+     * Display name when calling
+     */
+    std::string displayName {};
+
+    /**
+     * User-agent used for registration
+     */
+    std::string customUserAgent {};
+
+    /**
+     * Account mail box
+     */
+    std::string mailbox {};
+
+    /**
+     * UPnP IGD controller and the mutex to access it
+     */
+    bool upnpEnabled {true};
+
+    std::set<std::string> defaultModerators {};
+    bool localModeratorsEnabled {true};
+    bool allModeratorsEnabled {true};
+
+    /**
+     * Device push notification token.
+     */
+    std::string deviceKey {};
+
+    /**
+     * Push notification topic.
+     */
+    std::string notificationTopic {};
+};
+
+inline void parseString(const std::map<std::string, std::string>& details, const char* key, std::string& s)
+{
+    auto it = details.find(key);
+    if (it != details.end())
+        s = it->second;
+}
+
+inline void parseBool(const std::map<std::string, std::string>& details, const char* key, bool& s)
+{
+    auto it = details.find(key);
+    if (it != details.end())
+        s = it->second == TRUE_STR;
+}
+
+template<class T>
+inline void parseInt(const std::map<std::string, std::string>& details, const char* key, T& s)
+{
+    auto it = details.find(key);
+    if (it != details.end())
+        s = to_int<T>(it->second);
+}
+
+void
+parsePath(const std::map<std::string, std::string>& details,
+                   const char* key,
+                   std::string& s,
+                   const std::string& base);
+
+}