diff --git a/src/pylorax/api/__init__.py b/src/pylorax/api/__init__.py
new file mode 100644
index 00000000..5ee1ec8a
--- /dev/null
+++ b/src/pylorax/api/__init__.py
@@ -0,0 +1,21 @@
+#
+# lorax-composer API server
+#
+# Copyright (C) 2017  Red Hat, 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 2 of the License, 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 .
+from pylorax.api.crossdomain import crossdomain
+
+__all__ = ["crossdomain"]
+
diff --git a/src/pylorax/api/crossdomain.py b/src/pylorax/api/crossdomain.py
new file mode 100644
index 00000000..d2da96ff
--- /dev/null
+++ b/src/pylorax/api/crossdomain.py
@@ -0,0 +1,63 @@
+#
+# Copyright (C) 2017  Red Hat, 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 2 of the License, 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 .
+#
+
+# crossdomain decorator from - http://flask.pocoo.org/snippets/56/
+from datetime import timedelta
+from flask import make_response, request, current_app
+from functools import update_wrapper
+
+
+def crossdomain(origin, methods=None, headers=None,
+                max_age=21600, attach_to_all=True,
+                automatic_options=True):
+    if methods is not None:
+        methods = ', '.join(sorted(x.upper() for x in methods))
+    if headers is not None and not isinstance(headers, basestring):
+        headers = ', '.join(x.upper() for x in headers)
+    if not isinstance(origin, list):
+        origin = [origin]
+    if isinstance(max_age, timedelta):
+        max_age = max_age.total_seconds()
+
+    def get_methods():
+        if methods is not None:
+            return methods
+
+        options_resp = current_app.make_default_options_response()
+        return options_resp.headers['allow']
+
+    def decorator(f):
+        def wrapped_function(*args, **kwargs):
+            if automatic_options and request.method == 'OPTIONS':
+                resp = current_app.make_default_options_response()
+            else:
+                resp = make_response(f(*args, **kwargs))
+            if not attach_to_all and request.method != 'OPTIONS':
+                return resp
+
+            h = resp.headers
+
+            h.extend([("Access-Control-Allow-Origin", orig) for orig in origin])
+            h['Access-Control-Allow-Methods'] = get_methods()
+            h['Access-Control-Max-Age'] = str(max_age)
+            if headers is not None:
+                h['Access-Control-Allow-Headers'] = headers
+            return resp
+
+        f.provide_automatic_options = False
+        return update_wrapper(wrapped_function, f)
+    return decorator
diff --git a/src/pylorax/api/server.py b/src/pylorax/api/server.py
new file mode 100644
index 00000000..5e1d2a8a
--- /dev/null
+++ b/src/pylorax/api/server.py
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2017  Red Hat, 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 2 of the License, 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 .
+#
+
+from flask import Flask, jsonify
+
+from pylorax.api import crossdomain
+from pylorax.api.v0 import v0_api
+
+server = Flask(__name__)
+
+__all__ = ["server"]
+
+@server.route('/')
+@crossdomain(origin="*")
+def hello_world():
+    return 'Hello, World!'
+
+v0_api(server)
diff --git a/src/pylorax/api/v0.py b/src/pylorax/api/v0.py
new file mode 100644
index 00000000..b8a8e7b5
--- /dev/null
+++ b/src/pylorax/api/v0.py
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2017  Red Hat, 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 2 of the License, 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 .
+#
+from flask import jsonify
+
+# Use pykickstart to calculate disk image size
+from pykickstart.parser import KickstartParser
+from pykickstart.version import makeVersion, RHEL7
+
+from pylorax.api import crossdomain
+from pylorax.creator import DRACUT_DEFAULT, mount_boot_part_over_root
+from pylorax.creator import make_appliance, make_image, make_livecd, make_live_images
+from pylorax.creator import make_runtime, make_squashfs
+from pylorax.imgutils import copytree
+from pylorax.imgutils import Mount, PartitionMount, umount
+from pylorax.installer import InstallError
+from pylorax.sysutils import joinpaths
+
+
+# no-virt mode doesn't need libvirt, so make it optional
+try:
+    import libvirt
+except ImportError:
+    libvirt = None
+
+def v0_api(api):
+    """ Setup v0 of the API server"""
+    @api.route("/api/v0/status")
+    @crossdomain(origin="*")
+    def v0_status():
+        return jsonify(build="devel", api="0", db_version="0", schema_version="0", db_supported=False)
diff --git a/src/sbin/lorax b/src/sbin/lorax
index b60c7ce7..0984e927 100755
--- a/src/sbin/lorax
+++ b/src/sbin/lorax
@@ -1,5 +1,4 @@
 #!/usr/bin/python
-
 #
 # lorax
 #
diff --git a/src/sbin/lorax-composer b/src/sbin/lorax-composer
new file mode 100755
index 00000000..87c438c3
--- /dev/null
+++ b/src/sbin/lorax-composer
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+#
+# lorax-composer
+#
+# Copyright (C) 2017  Red Hat, 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 2 of the License, 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 .
+#
+import logging
+log = logging.getLogger("lorax-composer")
+program_log = logging.getLogger("program")
+pylorax_log = logging.getLogger("pylorax")
+
+import argparse
+import os
+import shutil
+import sys
+import tempfile
+from gevent.wsgi import WSGIServer
+
+from pylorax import vernum
+from pylorax.api.server import server
+
+VERSION = "{0}-{1}".format(os.path.basename(sys.argv[0]), vernum)
+
+def get_parser():
+    """ Return the ArgumentParser for lorax-composer"""
+
+    parser = argparse.ArgumentParser( description="Composer API Server",
+                                      fromfile_prefix_chars="@" )
+
+    parser.add_argument("--host", default="127.0.0.1", metavar="HOST",
+                        help="Host or IP to bind to (127.0.0.1)")
+    parser.add_argument("--port", default=4000, metavar="PORT",
+                        help="Port to bind to (4000)")
+    parser.add_argument("--log", dest="logfile", default="/var/log/lorax-composer/composer.log", metavar="LOG",
+                        help="Path to logfile (/var/log/lorax-composer/composer.log)")
+    parser.add_argument("--mockfiles", default="/var/tmp/bdcs-mockfiles/", metavar="MOCKFILES",
+                        help="Path to JSON files used for /api/mock/ paths (/var/tmp/bdcs-mockfiles/)")
+    parser.add_argument("--bdcs", default="/mddb/cs.repo", metavar="BDCS",
+                        help="Path to the content store directory (/mddb/cs.repo)")
+    parser.add_argument("-V", action="store_true", dest="showver",
+                        help="show program's version number and exit")
+    parser.add_argument("DB", metavar="DATABASE",
+                        help="Path to the BDCS sqlite database")
+    parser.add_argument("RECIPES", metavar="RECIPES",
+                        help="Path to the recipes")
+
+    return parser
+
+
+def setup_logging(logfile):
+    # Setup logging to console and to logfile
+    log.setLevel(logging.DEBUG)
+    pylorax_log.setLevel(logging.DEBUG)
+
+    sh = logging.StreamHandler()
+    sh.setLevel(logging.INFO)
+    fmt = logging.Formatter("%(asctime)s: %(message)s")
+    sh.setFormatter(fmt)
+    log.addHandler(sh)
+    pylorax_log.addHandler(sh)
+
+    fh = logging.FileHandler(filename=logfile, mode="w")
+    fh.setLevel(logging.DEBUG)
+    fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
+    fh.setFormatter(fmt)
+    log.addHandler(fh)
+    pylorax_log.addHandler(fh)
+
+    # External program output log
+    program_log.setLevel(logging.DEBUG)
+    logfile = os.path.abspath(os.path.dirname(logfile))+"/program.log"
+    fh = logging.FileHandler(filename=logfile, mode="w")
+    fh.setLevel(logging.DEBUG)
+    program_log.addHandler(fh)
+
+
+if __name__ == '__main__':
+    # parse the arguments
+    opts = get_parser().parse_args()
+
+    if opts.showver:
+        print(VERSION)
+        sys.exit(0)
+
+    errors = []
+    if not os.path.exists(opts.DB):
+        errors.append("Database, %s, doesn't exist." % opts.DB)
+    if not os.path.exists(opts.RECIPES):
+        errors.append("Recipe directory, %s, is missing." % opts.RECIPES)
+
+    for e in errors:
+        print("ERROR: " + e)
+    if errors:
+        sys.exit(1)
+
+    setup_logging(opts.logfile)
+    http_server = WSGIServer((opts.host, opts.port), server)
+    # The server writes directly to a file object, so point to our log directory
+    server_logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/server.log"
+    server_log = open(server_logfile, "w")
+    http_server.log = server_log
+    http_server.serve_forever()