diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index acbdd3d5f..9baf8940d 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -1,160 +1,177 @@
 # configsession -- the write API for the VyOS running config
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This library is free software; you can redistribute it and/or modify it under the terms of
 # the GNU Lesser General Public License as published by the Free Software Foundation;
 # either version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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 Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License along with this library;
 # if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 
 import os
 import re
 import sys
 import subprocess
 
 CLI_SHELL_API = '/bin/cli-shell-api'
 SET = '/opt/vyatta/sbin/my_set'
 DELETE = '/opt/vyatta/sbin/my_delete'
 COMMENT = '/opt/vyatta/sbin/my_comment'
 COMMIT = '/opt/vyatta/sbin/my_commit'
 DISCARD = '/opt/vyatta/sbin/my_discard'
 SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
 LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
+SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl']
+INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image']
+REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
 
 # Default "commit via" string
 APP = "vyos-http-api"
 
 # When started as a service rather than from a user shell,
 # the process lacks the VyOS-specific environment that comes
 # from bash configs, so we have to inject it
 # XXX: maybe it's better to do via a systemd environment file
 def inject_vyos_env(env):
     env['VYATTA_CFG_GROUP_NAME'] = 'vyattacfg'
     env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin'
+    env['VYATTA_PROCESS_CLIENT'] = 'gui2_rest'
+    env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api'
     env['vyatta_bindir']= '/opt/vyatta/bin'
     env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
     env['vyatta_configdir'] = '/opt/vyatta/config'
     env['vyatta_datadir'] = '/opt/vyatta/share'
     env['vyatta_datarootdir'] = '/opt/vyatta/share'
     env['vyatta_libdir'] = '/opt/vyatta/lib'
     env['vyatta_libexecdir'] = '/opt/vyatta/libexec'
     env['vyatta_op_templates'] = '/opt/vyatta/share/vyatta-op/templates'
     env['vyatta_prefix'] = '/opt/vyatta'
     env['vyatta_sbindir'] = '/opt/vyatta/sbin'
     env['vyatta_sysconfdir'] = '/opt/vyatta/etc'
     env['vyos_bin_dir'] = '/usr/bin'
     env['vyos_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
     env['vyos_completion_dir'] = '/usr/libexec/vyos/completion'
     env['vyos_configdir'] = '/opt/vyatta/config'
     env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode'
     env['vyos_datadir'] = '/opt/vyatta/share'
     env['vyos_datarootdir']= '/opt/vyatta/share'
     env['vyos_libdir'] = '/opt/vyatta/lib'
     env['vyos_libexec_dir'] = '/usr/libexec/vyos'
     env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode'
     env['vyos_op_templates'] = '/opt/vyatta/share/vyatta-op/templates'
     env['vyos_prefix'] = '/opt/vyatta'
     env['vyos_sbin_dir'] = '/usr/sbin'
     env['vyos_validators_dir'] = '/usr/libexec/vyos/validators'
 
     return env
 
 
 class ConfigSessionError(Exception):
     pass
 
 
 class ConfigSession(object):
     """
     The write API of VyOS.
     """
     def __init__(self, session_id, app=APP):
         """
          Creates a new config session.
 
          Args:
               session_id (str): Session identifier
               app (str): Application name, purely informational
 
         Note:
             The session identifier MUST be globally unique within the system.
             The best practice is to only have one ConfigSession object per process
             and used the PID for the session identifier.
         """
 
         env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)])
         self.__session_id = session_id
 
         # Extract actual variables from the chunk of shell it outputs
         # XXX: it's better to extend cli-shell-api to provide easily readable output
         env_list = re.findall(r'([A-Z_]+)=([^;\s]+)', env_str.decode())
 
         session_env = os.environ
         session_env = inject_vyos_env(session_env)
         for k, v in env_list:
             session_env[k] = v
 
         self.__session_env = session_env
         self.__session_env["COMMIT_VIA"] = app
 
         self.__run_command([CLI_SHELL_API, 'setupSession'])
 
     def __del__(self):
         try:
             output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip()
             if output:
                 print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr)
         except Exception as e:
             print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr)
 
     def __run_command(self, cmd_list):
         p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env)
         result = p.wait()
         output = p.stdout.read().decode()
         if result != 0:
             raise ConfigSessionError(output)
         return output
 
     def get_session_env(self):
         return self.__session_env
 
     def set(self, path, value=None):
         if not value:
             value = []
         else:
             value = [value]
         self.__run_command([SET] + path + value)
 
     def delete(self, path, value=None):
         if not value:
             value = []
         else:
             value = [value]
         self.__run_command([DELETE] + path + value)
 
     def comment(self, path, value=None):
         if not value:
             value = [""]
         else:
             value = [value]
         self.__run_command([COMMENT] + path + value)
 
     def commit(self):
         self.__run_command([COMMIT])
 
     def discard(self):
         self.__run_command([DISCARD])
 
     def show_config(self, path, format='raw'):
         config_data = self.__run_command(SHOW_CONFIG + path)
 
         if format == 'raw':
             return config_data
 
     def load_config(self, file_path):
         self.__run_command(LOAD_CONFIG + [file_path])
+
+    def save_config(self, file_path):
+        out = self.__run_command(SAVE_CONFIG + [file_path])
+        return out
+
+    def install_image(self, url):
+        out = self.__run_command(INSTALL_IMAGE + [url])
+        return out
+
+    def remove_image(self, name):
+        out = self.__run_command(REMOVE_IMAGE + [name])
+        return out
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index afab9be70..b9fdea1ac 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -1,247 +1,337 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 <http://www.gnu.org/licenses/>.
 #
 #
 
 import os
 import sys
 import grp
 import json
 import traceback
 import threading
 
 import vyos.config
 
 import bottle
 
+from functools import wraps
+
 from vyos.configsession import ConfigSession, ConfigSessionError
 from vyos.config import VyOSError
 
 
 DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf'
 
 CFG_GROUP = 'vyattacfg'
 
 app = bottle.default_app()
 
 # Giant lock!
 lock = threading.Lock()
 
 def load_server_config():
     with open(DEFAULT_CONFIG_FILE) as f:
         config = json.load(f)
     return config
 
 def check_auth(key_list, key):
     id = None
     for k in key_list:
         if k['key'] == key:
             id = k['id']
     return id
 
 def error(code, msg):
     bottle.response.status = code
     resp = {"success": False, "error": msg, "data": None}
     return json.dumps(resp)
 
 def success(data):
     resp = {"success": True, "data": data, "error": None}
     return json.dumps(resp)
 
+def auth_required(f):
+    @wraps(f)
+    def decorated_function(*args, **kwargs):
+        key = bottle.request.forms.get("key")
+        api_keys = app.config['vyos_keys']
+        id = check_auth(api_keys, key)
+        if not id:
+            return error(401, "Valid API key is required")
+        return f(*args, **kwargs)
+
+    return decorated_function
+
 @app.route('/configure', method='POST')
 def configure():
     session = app.config['vyos_session']
     config = app.config['vyos_config']
     api_keys = app.config['vyos_keys']
 
     key = bottle.request.forms.get("key")
     id = check_auth(api_keys, key)
     if not id:
         return error(401, "Valid API key is required")
 
     strict_field = bottle.request.forms.get("strict")
     if strict_field == "true":
         strict = True
     else:
         strict = False
 
     commands = bottle.request.forms.get("data")
     if not commands:
         return error(400, "Non-empty data field is required")
     else:
         try:
             commands = json.loads(commands)
         except Exception as e:
             return error(400, "Failed to parse JSON: {0}".format(e))
 
     # Allow users to pass just one command
     if not isinstance(commands, list):
         commands = [commands]
 
     # We don't want multiple people/apps to be able to commit at once,
     # or modify the shared session while someone else is doing the same,
     # so the lock is really global
     lock.acquire()
 
     status = 200
     error_msg = None
     try:
         for c in commands:
             # What we've got may not even be a dict
             if not isinstance(c, dict):
                 raise ConfigSessionError("Malformed command \"{0}\": any command must be a dict".format(json.dumps(c)))
 
             # Missing op or path is a show stopper
             if not ('op' in c):
                 raise ConfigSessionError("Malformed command \"{0}\": missing \"op\" field".format(json.dumps(c)))
             if not ('path' in c):
                 raise ConfigSessionError("Malformed command \"{0}\": missing \"path\" field".format(json.dumps(c)))
 
             # Missing value is fine, substitute for empty string
             if 'value' in c:
                 value = c['value']
             else:
                 value = ""
 
             op = c['op']
             path = c['path']
 
             if not path:
                 raise ConfigSessionError("Malformed command \"{0}\": empty path".format(json.dumps(c)))
 
             # Type checking
             if not isinstance(path, list):
                 raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list".format(json.dumps(c)))
 
             if not isinstance(value, str):
                 raise ConfigSessionError("Malformed command \"{0}\": \"value\" field must be a string".format(json.dumps(c)))
 
             # Account for the case when value field is present and set to null
             if not value:
                 value = ""
 
             # For vyos.configsessios calls that have no separate value arguments,
             # and for type checking too
             try:
                 cfg_path = " ".join(path + [value]).strip()
             except TypeError:
                 raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list of strings".format(json.dumps(c)))
 
             if op == 'set':
                 # XXX: it would be nice to do a strict check for "path already exists",
                 # but there's probably no way to do that
                 session.set(path, value=value)
             elif op == 'delete':
                 if strict and not config.exists(cfg_path):
                     raise ConfigSessionError("Cannot delete [{0}]: path/value does not exist".format(cfg_path))
                 session.delete(path, value=value)
             elif op == 'comment':
                 session.comment(path, value=value)
             else:
                 raise ConfigSessionError("\"{0}\" is not a valid operation".format(op))
         # end for
         session.commit()
         print("Configuration modified via HTTP API using key \"{0}\"".format(id))
     except ConfigSessionError as e:
         session.discard()
         status = 400
         if app.config['vyos_debug']:
             print(traceback.format_exc(), file=sys.stderr)
         error_msg = str(e)
     except Exception as e:
         session.discard()
         print(traceback.format_exc(), file=sys.stderr)
         status = 500
 
         # Don't give the details away to the outer world
         error_msg = "An internal error occured. Check the logs for details."
     finally:
         lock.release()
 
     if status != 200:
         return error(status, error_msg)
     else:
         return success(None)
 
 @app.route('/retrieve', method='POST')
 def get_value():
     config = app.config['vyos_config']
     session = app.config['vyos_session']
 
     api_keys = app.config['vyos_keys']
 
     key = bottle.request.forms.get("key")
     id = check_auth(api_keys, key)
     if not id:
         return error(401, "Valid API key is required")
 
     command = bottle.request.forms.get("data")
     command = json.loads(command)
 
     try:
         op = command['op']
         path = " ".join(command['path'])
     except KeyError:
         return error(400, "Missing required field. \"op\" and \"path\" fields are required")
 
     try:
         if op == 'returnValue':
             res = config.return_value(path)
         elif op == 'returnValues':
             res = config.return_values(path)
         elif op == 'exists':
             res = config.exists(path)
         elif op == 'showConfig':
             config_format = 'raw'
             if 'configFormat' in command:
                 config_format = command['configFormat']
 
             res = session.show_config(command['path'], format=config_format)
         else:
             return error(400, "\"{0}\" is not a valid operation".format(op))
     except VyOSError as e:
         return error(400, str(e))
     except Exception as e:
         print(traceback.format_exc(), file=sys.stderr)
         return error(500, "An internal error occured. Check the logs for details.")
 
     return success(res)
 
+@app.route('/config-file', method='POST')
+@auth_required
+def config_file_op():
+    config = app.config['vyos_config']
+    session = app.config['vyos_session']
+
+    command = bottle.request.forms.get("data")
+    command = json.loads(command)
+
+    try:
+        op = command['op']
+    except KeyError:
+        return error(400, "Missing required field \"op\"")
+
+    try:
+        if op == 'save':
+            try:
+                path = command['file']
+            except KeyError:
+                path = '/config/config.boot'
+            res = session.save_config(path)
+        elif op == 'load':
+            try:
+                path = command['file']
+            except KeyError:
+                return error(400, "Missing required field \"file\"")
+            res = session.load_config(path)
+            res = session.commit()
+        else:
+            return error(400, "\"{0}\" is not a valid operation".format(op))
+    except VyOSError as e:
+        return error(400, str(e))
+    except Exception as e:
+        print(traceback.format_exc(), file=sys.stderr)
+        return error(500, "An internal error occured. Check the logs for details.")
+
+    return success(res)
+
+@app.route('/image', method='POST')
+@auth_required
+def config_file_op():
+    config = app.config['vyos_config']
+    session = app.config['vyos_session']
+
+    command = bottle.request.forms.get("data")
+    command = json.loads(command)
+
+    try:
+        op = command['op']
+    except KeyError:
+        return error(400, "Missing required field \"op\"")
+
+    try:
+        if op == 'add':
+            try:
+                url = command['url']
+            except KeyError:
+                return error(400, "Missing required field \"url\"")
+            res = session.install_image(url)
+        elif op == 'delete':
+            try:
+                name = command['name']
+            except KeyError:
+                return error(400, "Missing required field \"name\"")
+            res = session.remove_image(name)
+        else:
+            return error(400, "\"{0}\" is not a valid operation".format(op))
+    except VyOSError as e:
+        return error(400, str(e))
+    except Exception as e:
+        print(traceback.format_exc(), file=sys.stderr)
+        return error(500, "An internal error occured. Check the logs for details.")
+
+    return success(res)
+
+
 if __name__ == '__main__':
     # systemd's user and group options don't work, do it by hand here,
     # else no one else will be able to commit
     cfg_group = grp.getgrnam(CFG_GROUP)
     os.setgid(cfg_group.gr_gid)
 
     # Need to set file permissions to 775 too so that every vyattacfg group member
     # has write access to the running config
     os.umask(0o002)
 
     try:
         server_config = load_server_config()
     except Exception as e:
         print("Failed to load the HTTP API server config: {0}".format(e))
 
     session = ConfigSession(os.getpid())
     env = session.get_session_env()
     config = vyos.config.Config(session_env=env)
 
     app.config['vyos_session'] = session
     app.config['vyos_config'] = config
     app.config['vyos_keys'] = server_config['api_keys']
     app.config['vyos_debug'] = server_config['debug']
 
     bottle.run(app, host=server_config["listen_address"], port=server_config["port"], debug=True)