Merge branch 'develop'
diff --git a/.gitignore b/.gitignore
index 2a8b5f2..bbb2fb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,14 @@
 api-gateway-config/target/*
 api-gateway-config/conf.d/includes/resolvers.conf
 *.iml
+.DS_STORE
+lua_modules
+
+# Webstorm files
+.idea
+
+# No temporary vim files
+*~
+*.swp
+*.swo
+
diff --git a/Dockerfile b/Dockerfile
index bc03031..69f9723 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,37 +11,7 @@
     && apk add gcc tar libtool zlib jemalloc jemalloc-dev perl \ 
     make musl-dev openssl-dev pcre-dev g++ zlib-dev curl python \
     perl-test-longstring perl-list-moreutils perl-http-message \
-    geoip-dev
-
-ENV ZMQ_VERSION 4.0.5
-ENV CZMQ_VERSION 2.2.0
-
-# Installing throttling dependencies
-RUN echo " ... adding throttling support with ZMQ and CZMQ" \
-         && curl -L https://github.com/zeromq/zeromq4-x/archive/v${ZMQ_VERSION}.tar.gz -o /tmp/zeromq.tar.gz \
-         && cd /tmp/ \
-         && tar -xf /tmp/zeromq.tar.gz \
-         && cd /tmp/zeromq*/ \
-         && apk add automake autoconf \
-         && ./autogen.sh \
-         && ./configure --prefix=/usr \
-                        --sysconfdir=/etc \
-                        --mandir=/usr/share/man \
-                        --infodir=/usr/share/info \
-         && make && make install \
-         && curl -L https://github.com/zeromq/czmq/archive/v${CZMQ_VERSION}.tar.gz -o /tmp/czmq.tar.gz \
-         && cd /tmp/ \
-         && tar -xf /tmp/czmq.tar.gz \
-         && cd /tmp/czmq*/ \
-         && ./autogen.sh \
-         && ./configure --prefix=/usr \
-                        --sysconfdir=/etc \
-                        --mandir=/usr/share/man \
-                        --infodir=/usr/share/info \
-         && make && make install \
-         && apk del automake autoconf \
-         && rm -rf /tmp/zeromq* && rm -rf /tmp/czmq* \
-         && rm -rf /var/cache/apk/*
+    geoip-dev nodejs
 
 # openresty build
 ENV OPENRESTY_VERSION=1.9.7.3 \
@@ -183,174 +153,16 @@
     && $INSTALL lib/resty/*.lua ${LUA_LIB_DIR}/resty/ \
     && rm -rf /tmp/api-gateway
 
-ENV CONFIG_SUPERVISOR_VERSION 1.0.0
-ENV GOPATH /usr/lib/go/bin
-ENV GOBIN  /usr/lib/go/bin
-ENV PATH   $PATH:/usr/lib/go/bin
-RUN echo " ... installing api-gateway-config-supervisor  ... " \
-    && echo "http://dl-4.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
-    && apk update \
-    && apk add gcc make git 'go<1.7' \
+ENV NETURL_LUA_VERSION 0.9-1
+RUN echo " ... installing neturl.lua ... " \
     && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-config-supervisor/archive/${CONFIG_SUPERVISOR_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-config-supervisor-${CONFIG_SUPERVISOR_VERSION}.tar.gz \
-    && cd /tmp/api-gateway \
-    && tar -xf /tmp/api-gateway/api-gateway-config-supervisor-${CONFIG_SUPERVISOR_VERSION}.tar.gz \
-    && mkdir -p /tmp/go \
-    && mv /tmp/api-gateway/api-gateway-config-supervisor-${CONFIG_SUPERVISOR_VERSION}/* /tmp/go \
-    && cd /tmp/go \
-    && make setup \
-    && mkdir -p /tmp/go/Godeps/_workspace \
-    && ln -s /tmp/go/vendor /tmp/go/Godeps/_workspace/src \
-    && mkdir -p /tmp/go-src/src/github.com/adobe-apiplatform \
-    && ln -s /tmp/go /tmp/go-src/src/github.com/adobe-apiplatform/api-gateway-config-supervisor \
-    && GOPATH=/tmp/go/vendor:/tmp/go-src CGO_ENABLED=0 GOOS=linux /usr/lib/go/bin/godep  go build -ldflags "-s" -a -installsuffix cgo -o api-gateway-config-supervisor ./ \
-    && mv /tmp/go/api-gateway-config-supervisor /usr/local/sbin/ \
-
-    && echo "installing rclone sync ... skipped due to https://github.com/ncw/rclone/issues/663 ... " \
-    # && go get github.com/ncw/rclone \
-    # && mv /usr/lib/go/bin/rclone /usr/local/sbin/ \
-
-    && echo " cleaning up ... " \
-    && rm -rf /usr/lib/go/bin/src \
-    && rm -rf /tmp/go \
-    && rm -rf /tmp/go-src \
-    && rm -rf /usr/lib/go/bin/pkg/ \
-    && rm -rf /usr/lib/go/bin/godep \
-    && apk del make git go gcc \
-    && rm -rf /var/cache/apk/*
-
-RUN echo " ... installing aws-cli ..." \
-    && apk update \
-    && apk add python \
-    && apk add py-pip \
-    && pip install --upgrade pip \
-    && pip install awscli
-
-ENV HMAC_LUA_VERSION 1.0.0
-RUN echo " ... installing api-gateway-hmac ..." \
-    && apk update \
-    && apk add make \
-    && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-hmac/archive/${HMAC_LUA_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-hmac-${HMAC_LUA_VERSION}.tar.gz \
-    && tar -xf /tmp/api-gateway/api-gateway-hmac-${HMAC_LUA_VERSION}.tar.gz -C /tmp/api-gateway/ \
-    && cd /tmp/api-gateway/api-gateway-hmac-${HMAC_LUA_VERSION} \
-    && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-    && make test \
-    && make install \
-            LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
-            INSTALL=${_prefix}/api-gateway/bin/resty-install \
+    && curl -k -L https://github.com/golgote/neturl/archive/${NETURL_LUA_VERSION}.tar.gz -o /tmp/api-gateway/neturl.lua-${NETURL_LUA_VERSION}.tar.gz \
+    && tar -xf /tmp/api-gateway/neturl.lua-${NETURL_LUA_VERSION}.tar.gz -C /tmp/api-gateway/ \
+    && export LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
+    && cd /tmp/api-gateway/neturl-${NETURL_LUA_VERSION} \
+    && cp lib/net/url.lua ${LUA_LIB_DIR} \
     && rm -rf /tmp/api-gateway
 
-ENV CACHE_MANAGER_VERSION 1.0.1
-RUN echo " ... installing api-gateway-cachemanager..." \
-    && apk update \
-    && apk add make \
-    && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-cachemanager/archive/${CACHE_MANAGER_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-cachemanager-${CACHE_MANAGER_VERSION}.tar.gz \
-    && tar -xf /tmp/api-gateway/api-gateway-cachemanager-${CACHE_MANAGER_VERSION}.tar.gz -C /tmp/api-gateway/ \
-    && cd /tmp/api-gateway/api-gateway-cachemanager-${CACHE_MANAGER_VERSION} \
-    && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-    && apk update && apk add redis \
-    && REDIS_SERVER=/usr/bin/redis-server make test \
-    && make install \
-            LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
-            INSTALL=${_prefix}/api-gateway/bin/resty-install \
-    && apk del redis \
-    && rm -rf /var/cache/apk/* \
-    && rm -rf /tmp/api-gateway
-
-ENV AWS_VERSION 1.7.1
-RUN echo " ... installing api-gateway-aws ..." \
-    && apk update \
-    && apk add make \
-    && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-aws/archive/${AWS_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-aws-${AWS_VERSION}.tar.gz \
-    && tar -xf /tmp/api-gateway/api-gateway-aws-${AWS_VERSION}.tar.gz -C /tmp/api-gateway/ \
-    && cd /tmp/api-gateway/api-gateway-aws-${AWS_VERSION} \
-    && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-    && make test \
-    && make install \
-            LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
-            INSTALL=${_prefix}/api-gateway/bin/resty-install \
-    && rm -rf /var/cache/apk/* \
-    && rm -rf /tmp/api-gateway
-
-ENV REQUEST_VALIDATION_VERSION 1.1.1
-RUN echo " ... installing api-gateway-request-validation ..." \
-    && apk update \
-    && apk add make \
-    && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-request-validation/archive/${REQUEST_VALIDATION_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-request-validation-${REQUEST_VALIDATION_VERSION}.tar.gz \
-    && tar -xf /tmp/api-gateway/api-gateway-request-validation-${REQUEST_VALIDATION_VERSION}.tar.gz -C /tmp/api-gateway/ \
-    && cd /tmp/api-gateway/api-gateway-request-validation-${REQUEST_VALIDATION_VERSION} \
-    && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-    && apk update && apk add redis \
-    && REDIS_SERVER=/usr/bin/redis-server make test \
-    && make install \
-            LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
-            INSTALL=${_prefix}/api-gateway/bin/resty-install \
-    && apk del redis \
-    && rm -rf /var/cache/apk/* \
-    && rm -rf /tmp/api-gateway
-
-ENV ASYNC_LOGGER_VERSION 1.0.1
-RUN echo " ... installing api-gateway-async-logger ..." \
-    && apk update \
-    && apk add make \
-    && mkdir -p /tmp/api-gateway \
-    && curl -k -L https://github.com/adobe-apiplatform/api-gateway-async-logger/archive/${ASYNC_LOGGER_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-async-logger-${ASYNC_LOGGER_VERSION}.tar.gz \
-    && tar -xf /tmp/api-gateway/api-gateway-async-logger-${ASYNC_LOGGER_VERSION}.tar.gz -C /tmp/api-gateway/ \
-    && cd /tmp/api-gateway/api-gateway-async-logger-${ASYNC_LOGGER_VERSION} \
-    && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-    && make test \
-    && make install \
-            LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
-            INSTALL=${_prefix}/api-gateway/bin/resty-install \
-    && rm -rf /var/cache/apk/* \
-    && rm -rf /tmp/api-gateway
-
-ENV ZMQ_ADAPTOR_VERSION 0.1.1
-RUN echo " ... installing api-gateway-zmq-adaptor" \
-         && curl -L https://github.com/adobe-apiplatform/api-gateway-zmq-adaptor/archive/${ZMQ_ADAPTOR_VERSION}.tar.gz -o /tmp/api-gateway-zmq-adaptor-${ZMQ_ADAPTOR_VERSION} \
-         && apk update \
-         && apk add check-dev g++ gcc \
-         && cd /tmp/ \
-         && tar -xf /tmp/api-gateway-zmq-adaptor-${ZMQ_ADAPTOR_VERSION} \
-         && cd /tmp/api-gateway-zmq-adaptor-* \
-         && make test \
-         && PREFIX=/usr/local/sbin make install \
-         && rm -rf /tmp/api-gateway-zmq-adaptor-* \
-         && apk del check-dev g++ gcc \
-         && rm -rf /var/cache/apk/*
-
-ENV ZMQ_LOGGER_VERSION 1.0.0
-RUN echo " ... installing api-gateway-zmq-logger ..." \
-        && mkdir -p /tmp/api-gateway \
-        && curl -L https://github.com/adobe-apiplatform/api-gateway-zmq-logger/archive/${ZMQ_LOGGER_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-zmq-logger-${ZMQ_LOGGER_VERSION}.tar.gz \
-        && tar -xf /tmp/api-gateway/api-gateway-zmq-logger-${ZMQ_LOGGER_VERSION}.tar.gz -C /tmp/api-gateway/ \
-        && cd /tmp/api-gateway/api-gateway-zmq-logger-${ZMQ_LOGGER_VERSION} \
-        && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-        && make test \
-        && make install \
-             LUA_LIB_DIR=/usr/local/api-gateway/lualib \
-             INSTALL=/usr/local/api-gateway/bin/resty-install \
-        && rm -rf /tmp/api-gateway
-
-ENV REQUEST_TRACKING_VERSION 1.0.1
-RUN echo " ... installing api-gateway-request-tracking ..." \
-        && mkdir -p /tmp/api-gateway \
-        && curl -L https://github.com/adobe-apiplatform/api-gateway-request-tracking/archive/${REQUEST_TRACKING_VERSION}.tar.gz -o /tmp/api-gateway/api-gateway-request-tracking-${REQUEST_TRACKING_VERSION}.tar.gz \
-        && tar -xf /tmp/api-gateway/api-gateway-request-tracking-${REQUEST_TRACKING_VERSION}.tar.gz -C /tmp/api-gateway/ \
-        && cd /tmp/api-gateway/api-gateway-request-tracking-${REQUEST_TRACKING_VERSION} \
-        && cp -r /usr/local/test-nginx-${TEST_NGINX_VERSION}/* ./test/resources/test-nginx/ \
-        && apk update && apk add redis \
-        && REDIS_SERVER=/usr/bin/redis-server make test \
-        && make install \
-             LUA_LIB_DIR=/usr/local/api-gateway/lualib \
-             INSTALL=/usr/local/api-gateway/bin/resty-install \
-        && apk del redis \
-        && rm -rf /tmp/api-gateway
-
 RUN \
     curl -L -k -s -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \
     && apk update \
@@ -367,5 +179,7 @@
     && addgroup -S nginx-api-gateway
 ONBUILD COPY api-gateway-config /etc/api-gateway
 
+EXPOSE 80 8080 8423 9000
+
 
 ENTRYPOINT ["/etc/init-container.sh"]
diff --git a/Makefile b/Makefile
index 7c16900..8f8f827 100644
--- a/Makefile
+++ b/Makefile
@@ -2,15 +2,26 @@
 DOCKER_REGISTRY ?= ''
 
 docker:
-	docker build -t adobeapiplatform/apigateway .
+	docker build -t apicgw/apigateway .
 
 .PHONY: docker-ssh
 docker-ssh:
-	docker run -ti --entrypoint='bash' adobeapiplatform/apigateway:latest
+	docker run -ti --entrypoint='bash' apicgw/apigateway:latest
+
+.PHONY: test-build
+test-build:
+	cd api-gateway-config/tests; ./install-deps.sh
+
+.PHONY: test-run
+test-run:
+	cd api-gateway-config/tests; ./run-tests.sh
 
 .PHONY: docker-run
 docker-run:
-	docker run --rm --name="apigateway" -p 80:80 -p 5000:5000 adobeapiplatform/apigateway:latest ${DOCKER_ARGS}
+	docker run --rm --name="apigateway" -p 80:80 -p ${PUBLIC_MANAGEDURL_PORT}:8080 -p 9000:9000 \
+		-e PUBLIC_MANAGEDURL_HOST=${PUBLIC_MANAGEDURL_HOST} -e PUBLIC_MANAGEDURL_PORT=${PUBLIC_MANAGEDURL_PORT} \
+		-e REDIS_HOST=${REDIS_HOST} -e REDIS_PORT=${REDIS_PORT} -e REDIS_PASS=${REDIS_PASS} \
+		apicgw/apigateway:latest
 
 .PHONY: docker-debug
 docker-debug:
@@ -22,7 +33,7 @@
 			-p 80:80 -p 5000:5000 \
 			-e "LOG_LEVEL=info" -e "DEBUG=true" \
 			-v ${HOME}/tmp/apiplatform/apigateway/api-gateway-config/:/etc/api-gateway \
-			adobeapiplatform/apigateway:latest ${DOCKER_ARGS}
+			apicgw/apigateway:latest ${DOCKER_ARGS}
 
 .PHONY: docker-reload
 docker-reload:
@@ -39,19 +50,8 @@
 	docker stop apigateway
 	docker rm apigateway
 
-.PHONY: docker-compose
-docker-compose:
-	#Volumes directories must be under your Users directory
-	mkdir -p ${HOME}/tmp/apiplatform/apigateway
-	rm -rf ${HOME}/tmp/apiplatform/apigateway/api-gateway-config
-	cp -r `pwd`/api-gateway-config ${HOME}/tmp/apiplatform/apigateway/
-	sed -i '' 's/127\.0\.0\.1/redis\.docker/' ${HOME}/tmp/apiplatform/apigateway/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf
-	# clone api-gateway-redis block
-	sed -e '/api-gateway-redis/,/}/!d' ${HOME}/tmp/apiplatform/apigateway/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf | sed 's/-redis/-redis-replica/' >> ${HOME}/tmp/apiplatform/apigateway/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf
-	docker-compose up
-
 .PHONY: docker-push
 docker-push:
-	docker tag -f adobeapiplatform/apigateway $(DOCKER_REGISTRY)/adobeapiplatform/apigateway:$(DOCKER_TAG)
-	docker push $(DOCKER_REGISTRY)/adobeapiplatform/apigateway:$(DOCKER_TAG)
+	docker tag -f apicgw/apigateway $(DOCKER_REGISTRY)/apicgw/apigateway:$(DOCKER_TAG)
+	docker push $(DOCKER_REGISTRY)/apicgw/apigateway:$(DOCKER_TAG)
 
diff --git a/README.md b/README.md
index 00f6275..4fe1392 100644
--- a/README.md
+++ b/README.md
@@ -1,272 +1,73 @@
- apigateway
+apigateway
 =============
 A performant API Gateway based on Openresty and NGINX.
 
-Table of Contents
-=================
+Project status
+---------------
+This project is currently considered pre-alpha stage, and should not be used in production. Large swaths of code or APIs may change without notice.
 
-* [Status](#status)
-* [Quick start](#quick-start)
-* [What's inside](#whats-inside)
-* [Performance](#performance)
+
+## Table of Contents
+
+* [Quick Start](#quick-start)
+* [Routes](#routes)
 * [Developer Guide](#developer-guide)
-
-Status
-======
-
-The current project is considered production ready.
+  * [Running locally](#running-locally)
+  * [Testing](#testing)
 
 
-Quick start
-===========
+## Quick Start
 
 ```
-docker run --name="apigateway" \
-            -p 80:80 \
-            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
-            -e "LOG_LEVEL=info" \
-            adobeapiplatform/apigateway:latest
+docker run -p 80:80 -p <managedurl_port>:8080 -p 9000:9000 \
+            -e PUBLIC_MANAGEDURL_HOST=<managedurl_host> \
+            -e PUBLIC_MANAGEDURL_PORT=<managedurl_port> \
+            -e REDIS_HOST=<redis_host> \
+            -e REDIS_PORT=<redis_port> \
+            -e REDIS_PASS=<redis_pass> \
+            openwhisk/apigateway:latest
 ```
 
-This command starts an API Gateway that automatically discovers the services running in Marathon.
-The discovered services are exposed on individual VHosts as you can see in the [config file](https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/conf.d/marathon_apis.conf#L36).
+This command starts an API Gateway that subscribes to the Redis instance with the specified host and port. The `REDIS_PASS` variable is optional and is required only when redis needs authentication. 
 
-#### Accessing a Marathon app locally
+On startup, the API Gateway looks for pre-existing resources in redis, whose keys are defined as `resources:<namespace>:<resource>`, and creates nginx conf files associated with those resources. Then, it listens for any resource key changes in redis and updates nginx conf files appropriately. These conf files are stored in the running docker container at `/etc/api-gateway/managed_confs/<namespace>/<resource>.conf`.
 
-For example, if you have an application named `hello-world` you can access it on its VHost in 2 ways:
-
- 1. Edit `/etc/hosts` and add `<docker_host_ip> hello-world.api.localhost` then browse to `http://hello-world.api.localhost`
- 2. Sending the Host header in a curl command: `curl -H "Host:hello-world.api.localhost" http://<docker_host_ip>`
-
-The [discovery script](https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/marathon-service-discovery.sh) is provided as an example for a quick-start and it can be replaced with your favourite discovery mechanism.
-The script updates a [configuration file](https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf) containing all the NGINX upstreams that are used in the [config file](https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/conf.d/marathon_apis.conf#L36).
-
-#### Accessing a Marathon app remotely
-
-All you need to do is to create a DNS entry like `*.api.example.com` or `*.gw.example.com` and have it resolve to the nodes running the Gateway.
-
-Assuming there is an application deployed in Marathon called `hello-world` it can be accessed at the URL: `http://hello-world.api.example.com`
-The Gateway is automatically proxying the request to the `hello-world` application in Marathon.
-
-If you call `http://my-custom-app.api.example.com` the Gateway will proxy to a Marathon app named `my-custom-app`.
-If the application is not deployed in Marathon a 5xx error is returned back to the client. This behaviour is ofcourse configurable.
-
-#### Running with a different configuration folder
-
-One way to simplify the configuration of the Gateway is to copy the `api-gateway-config` folder in S3, having all nodes syncing from that location.
-For the moment only `AWS S3` is supported but the plan is to add support for other clouds too.
-
-In order to work with S3 the Gateway needs to know the bucket. The command is similar to the one above with an extra environment variable named `REMOTE_CONFIG`
-```
-docker run --name="apigateway" \
-            -p 80:80 \
-            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
-            -e "REMOTE_CONFIG=s3://api-gateway-config-bucket" \
-            -e "LOG_LEVEL=info" \
-            adobeapiplatform/apigateway:latest
-```
-
-`s3://api-gateway-config-bucket` should contain something similar to what's in the [api-gateway-config](https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/) folder.
-For any customizations it would be good to start from there.
-There are only 2 files that won't be synchronised:
-* `/etc/api-gateway/conf.d/includes/resolvers.conf` . Read bellow for more info
-* `https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf` which is automatically generated by the Marathon Discovery script.
-
-By default the access to the S3 bucket is done using IAM roles, but AWS Credentials can also be specified through environment variables:
-```
-docker run --name="apigateway" \
-            -p 80:80 \
-            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
-            -e "REMOTE_CONFIG=s3://api-gateway-config-bucket" \
-            -e "AWS_ACCESS_KEY_ID=--change-me--" \
-            -e "AWS_SECRET_ACCESS_KEY=--change-me--" \
-            -e "LOG_LEVEL=info" \
-            adobeapiplatform/apigateway:latest
-```
-By default the remote config is checked for changes every `10s` and it's configurable through the `REMOTE_CONFIG_SYNC_INTERVAL` env variable.
-There are a few things to take into consideration when changing this value:
-* How often the configs really change
-* Cost. Some tools may make 1 API request per file to compare it. I.e. in S3 72 config files checked every `10s` costs `$7.46` but when checked every `30s` it's only `$2.4`, times number of GW nodes.
-* Average time for an API Request. When reloading the GW the existing NGINX processes handling active connections are kept in the background until the request completes. So reloading the Gateway too fast may have the side effect of keeping too many processes running at the same time. This may, or may not be a problem but it's good to be aware of it.
-
-#### Resolvers
-While starting up this container automatically creates the `/etc/api-gateway/conf.d/includes/resolvers.conf` config file using `/etc/resolv.conf` as the source.
-To learn more about the `resolver` directive in NGINX see the [docs](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver).
-
-#### Running the API Gateway outside of Marathon and Mesos
-Besides the discovery part which is dependent on Marathon at the  moment, the API Gateway can run on its own as well. The Marathon service discovery is activated with the ` -e "MARATHON_HOST=http://<marathon_host>:<port>/"`.
-
-What's inside
-=============
+## Routes
+See [here](doc/routes.md) for the management interface for creating tenants/APIs. For detailed API policy definitions, see [here](doc/policies.md).
 
 
-|   Module     |   Version  |  Details     |
-|--------------|------------|--------------|
-| [Openresty](https://github.com/openresty/) | 1.9.7.3 | Installed in `/usr/local/sbin/api-gateway` |
-| [Openresty](https://github.com/openresty/) compiled `--with-debug` | 1.9.7.3 |  Installed in `/usr/local/sbin/api-gateway-debug` which enables [debugging log](http://nginx.org/en/docs/debugging_log.html) |
-| [Test Nginx](https://github.com/openresty/test-nginx) | [0.24](https://github.com/openresty/test-nginx/releases/tag/v0.24) | Useful for executing integration tests from the container. <br/> It's installed in `/usr/local/test-nginx-0.24/`. <br/> It's also used during Docker build to execute `make test` on lua modules.  |
-| [PCRE](https://sourceforge.net/projects/pcre/) | [8.37](https://sourceforge.net/projects/pcre/files/pcre/8.37/) | Enables PCRE JIT support | 
-| [NAXSI](https://github.com/nbs-system/naxsi) | [0.53-2](https://github.com/nbs-system/naxsi/releases/tag/0.53-2) |  NAXSI is an open-source, high performance, low rules maintenance WAF for NGINX |
-| [ZeroMQ](http://download.zeromq.org/) | [4.0.5](http://zeromq.org/area:download) |  ZeroMQ |
-| [CZMQ](http://download.zeromq.org/) | [2.2.0](http://czmq.zeromq.org/page:get-the-software) |  CZMQ - High-level C Binding for ZeroMQ |
+## Developer Guide
 
-
-##### Modules for API Management and Logging
-
-|   Module     |   Version  |  Description |
-|--------------|------------|--------------|
-| [api-gateway-config-supervisor](https://github.com/adobe-apiplatform/api-gateway-config-supervisor) | [1.0.0](https://github.com/adobe-apiplatform/api-gateway-config-supervisor/releases/tag/1.0.0) | Syncs config files from Amazon S3 reloading the gateway with the updates |
-| [api-gateway-cachemanager](https://github.com/adobe-apiplatform/api-gateway-cachemanager) | [1.0.1](https://github.com/adobe-apiplatform/api-gateway-cachemanager/releases/tag/1.0.1) | Lua library for managing multiple cache stores |
-| [api-gateway-hmac](https://github.com/adobe-apiplatform/api-gateway-hmac) | [1.0.0](https://github.com/adobe-apiplatform/api-gateway-hmac/releases/tag/1.0.0) | HMAC support for Lua with multiple algorithms, via OpenSSL and FFI |
-| [api-gateway-aws](https://github.com/adobe-apiplatform/api-gateway-aws) | [1.7.1](https://github.com/adobe-apiplatform/api-gateway-aws/releases/tag/1.7.1) | AWS SDK for Nginx in Lua |
-| [api-gateway-request-validation](https://github.com/adobe-apiplatform/api-gateway-request-validation) | [1.1.1](https://github.com/adobe-apiplatform/api-gateway-request-validation/releases/tag/1.1.1) | API Request Validation framework  |
-| [api-gateway-async-logger](https://github.com/adobe-apiplatform/api-gateway-async-logger) | [1.0.1](https://github.com/adobe-apiplatform/api-gateway-async-logger/releases/tag/1.0.1) | Performant async logger |
-| [api-gateway-zmq-logger](https://github.com/adobe-apiplatform/api-gateway-zmq-logger) | [1.0.0](https://github.com/adobe-apiplatform/api-gateway-zmq-logger/releases/tag/1.0.0) | Lua logger for ZMQ with FFI and CZMQ |
-| [api-gateway-request-tracking](https://github.com/adobe-apiplatform/api-gateway-request-tracking) | [1.0.1](https://github.com/adobe-apiplatform/api-gateway-request-tracking/releases/tag/1.0.1) | Usage and Tracking Handler for the API Gateway |
-
-##### Other Lua Modules
-
-|   Module     |   Version  |  Description |
-|--------------|------------|--------------|
-| [lua-resty-http](https://github.com/pintsized/lua-resty-http) | [v0.07](https://github.com/pintsized/lua-resty-http/releases/tag/v0.07) | Lua HTTP client cosocket driver for OpenResty / ngx_lua |
-| [lua-resty-iputils](https://github.com/hamishforbes/lua-resty-iputils) | [v0.2.0](https://github.com/hamishforbes/lua-resty-iputils/releases/tag/v0.2.0) | Utility functions for working with IP addresses in Openresty |
-
-Performance
-===========
-
-The following performance tests results have been obtained on a virtual machine with 8 CPU cores and 4GB Memory.
-
-The API Gateway container has been started with 4 CPU cores and `net=host` :
-```bash
-docker run --cpuset-cpus=0-3 --net=host --name="apigateway" -e "LOG_LEVEL=notice" adobeapiplatform/apigateway:latest
-```
-
-WRK test has been started with 4 CPU cores and `net=host`:
-```bash
-docker run --cpuset-cpus=4-7 --net=host williamyeh/wrk:4.0.1 -t4 -c1000 -d30s http://<docker_host_ip>/health-check
-Running 30s test @ http://192.168.75.158/health-check
-  4 threads and 1000 connections
-  Thread Stats   Avg      Stdev     Max   +/- Stdev
-    Latency    30.38ms   73.80ms   1.16s    90.90%
-    Req/Sec    35.26k    11.98k   83.70k    68.72%
-  4214013 requests in 30.06s, 1.28GB read
-
-Requests/sec: 140165.09
-
-Transfer/sec:     43.57MB
-```
-
-Developer guide
-================
+### Running locally
 
  To build the docker image locally use:
  ```
   make docker
  ```
 
- To SSH into the newly built image use ( note that this is not the running image):
+ To Run the Docker image
  ```
-  make docker-ssh
+  make docker-run PUBLIC_MANAGEDURL_HOST=<mangedurl_host> PUBLIC_MANAGEDURL_PORT=<managedurl_port> \
+    REDIS_HOST=<redis_host> REDIS_PORT=<redis_port> REDIS_PASS=<redis_pass>
  ```
-
-#### Running and Stopping the Docker image
- ```
-  make docker-run
- ```
+ 
  The main API Gateway process is exposed to port `80`. To test that the Gateway works see its `health-check`:
  ```
   $ curl http://<docker_host_ip>/health-check
     API-Platform is running!
  ```
- If you're up for a quick performance test, you can play with Apache Benchmark via Docker:
+ 
+### Testing
 
+ Unit tests can be found in the `api-gateway-config/tests/spec` directory.
+
+ First install the necessary dependencies:
  ```
-  docker run jordi/ab ab -k -n 200000 -c 500 http://<docker_host_ip>/health-check
+  make test-build
  ```
-
- To run docker mounting the local `api-gateway-config` directory into `/etc/api-gateway/` issue:
-
- ```bash
- $ make docker-debug
+ Then, run the unit tests:
  ```
- In debug mode the docker container starts a special `api-gateway` compiled `--with-debug` providing very detailed debugging information.
-When started with `-e "LOG_LEVEL=info"` the output is quite verbose.
-To learn more about this option visit [NGINX docs](http://nginx.org/en/docs/debugging_log.html).
-
- When done stop the image:
+  make test-run
  ```
- make docker-stop
- ```
-
-### Enabling API KEY Management through Redis
-
-This command starts two docker containers: redis and gateway
- ```
- make docker-compose
- ```
-
-### SSH into the running image
-
-```
-make docker-attach
-```
-
-#### Running the container in Mesos with Marathon
-
-Make an HTTP POST on `http://<marathon-host>/v2/apps` with the following payload.
-For optimal performance leave the `network` on `HOST` mode. To learn more about the network modes visit the Docker [documentation](https://docs.docker.com/articles/networking/#how-docker-networks-a-container).
-
-```javascript
-{
-  "id": "api-gateway",
-  "container": {
-    "type": "DOCKER",
-    "docker": {
-      "image": "adobeapiplatform/apigateway:latest",
-      "forcePullImage": true,
-      "network": "HOST"
-    }
-  },
-  "cpus": 4,
-  "mem": 4096.0,
-  "env": {
-    "MARATHON_HOST": "http://<marathon_host>:<marathon_port>"
-  },
-  "constraints": [  [ "hostname","UNIQUE" ]  ],
-  "ports": [ 80 ],
-  "healthChecks": [
-    {
-      "protocol": "HTTP",
-      "portIndex": 0,
-      "path": "/health-check",
-      "gracePeriodSeconds": 3,
-      "intervalSeconds": 10,
-      "timeoutSeconds": 10
-    }
-  ],
-  "instances": 1
-}
-```
-
-To run the Gateway only on specific nodes marked with `slave_public` you can add the property bellow to the main JSON object:
-```
-"acceptedResourceRoles": [ "slave_public" ]
-```
-
-##### Auto-discover and register Marathon tasks in the Gateway
-
-To enable auto-discovery in a Mesos with Marathon framework define the following Environment Variables:
-```
-MARATHON_URL=http://<marathon-url-1>
-MARATHON_TASKS=ws-.* ( NOT USED NOW. TBD IF THERE'S A NEED TO FILTER OUT SOME TASKS )
-```
-
-So the Docker command is now :
-```
-docker run --name="apigateway" \
-            -p 8080:80 \
-            -e "MARATHON_HOST=http://<marathon_host>:<port>/" \
-            -e "LOG_LEVEL=info" \
-            adobeapiplatform/apigateway:latest
-```
+ This will output the results of the tests as well as generate a code coverage report.
 
diff --git a/api-gateway-config/api-gateway.conf b/api-gateway-config/api-gateway.conf
index b33c6e5..42a4391 100644
--- a/api-gateway-config/api-gateway.conf
+++ b/api-gateway-config/api-gateway.conf
@@ -20,10 +20,17 @@
 # * DEALINGS IN THE SOFTWARE.
 # *
 # */
-user                 nginx-api-gateway;
+#user                 nginx-api-gateway;
+user                 root;
 worker_processes     auto;
 worker_rlimit_nofile 524288; # 100000
 
+env REDIS_HOST;
+env REDIS_PORT;
+env REDIS_PASS;
+env PUBLIC_MANAGEDURL_HOST;
+env PUBLIC_MANAGEDURL_PORT;
+
 events {
     use epoll;
     # the actual number of simultaneous connections cannot exceed the current limit on the maximum number of open files,
@@ -37,8 +44,6 @@
 
 http {
     default_type  text/plain;
-
-    include /etc/api-gateway/environment.conf.d/api-gateway-env.http.conf;
     # include all APIs being proxied
     include /etc/api-gateway/conf.d/*.conf;
     include /etc/api-gateway/generated-conf.d/*.conf;
diff --git a/api-gateway-config/conf.d/api_gateway_logging.conf b/api-gateway-config/conf.d/api_gateway_logging.conf
index 0189f87..9ce1c04 100644
--- a/api-gateway-config/conf.d/api_gateway_logging.conf
+++ b/api-gateway-config/conf.d/api_gateway_logging.conf
@@ -25,8 +25,8 @@
                   '"$http_user_agent" "$http_x_forwarded_for"';
 
 
-log_format  platform  '$remote_addr - $remote_user [$time_local] request="$request" api_key="$api_key" '
+log_format  platform  '$remote_addr - $remote_user [$time_local] request="$request" '
                       'status=$status bbs=$body_bytes_sent rl=$request_length rt=$request_time hr="$http_referer" '
                       'ua="$http_user_agent" xfwdf="$http_x_forwarded_for" '
                       'upadd="$upstream_addr" upstat=$upstream_status uprt="$upstream_response_time" '
-                      'sid="$service_id" sname="$service_name" reqid=$requestId';
\ No newline at end of file
+                      'reqid=$requestId';
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/default.conf b/api-gateway-config/conf.d/default.conf
index 45468e7..0a1a045 100644
--- a/api-gateway-config/conf.d/default.conf
+++ b/api-gateway-config/conf.d/default.conf
@@ -51,7 +51,4 @@
 
     # default locations exposed on the default VHost and port
     include /etc/api-gateway/conf.d/includes/*.conf;
-
-    # include environment variables
-    include /etc/api-gateway/environment.conf.d/api-gateway-env-vars.server.conf;
-}
\ No newline at end of file
+}
diff --git a/api-gateway-config/conf.d/includes/api_key_service.conf b/api-gateway-config/conf.d/includes/api_key_service.conf
deleted file mode 100644
index 83ac736..0000000
--- a/api-gateway-config/conf.d/includes/api_key_service.conf
+++ /dev/null
@@ -1,128 +0,0 @@
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-# This is a sample file containing a basic API for Managing API-KEYs with Redis
-
-# sample query: curl -i http://localhost/cache/redis_query?KEYS%20*cachedkey*
-# Sample query to list all keys;
-# curl http://localhost/cache/redis_query?KEYS%20*cachedkey* | grep cachedkey | awk -F ":" '{printf "%+ 32s %+ 20s \n",$2,$3}' | sort
-location /cache/redis_query {
-    allow 127.0.0.1;
-    deny all;
-    set_unescape_uri $query $query_string;
-    redis2_raw_query '$query\r\n';
-    redis2_pass api-gateway-redis;
-}
-
-location ~ /cache/api_key/set {
-    internal;
-
-    limit_except POST OPTIONS {
-        deny all;
-    }
-
-    set $key $arg_key;
-    set $key_secret $arg_secret;
-    set $realm $arg_realm;
-    set $service_id $arg_service_id;
-    set $service_name $arg_service_name;
-    set $consumer_org_name $arg_consumer_org_name;
-    set $app_name $arg_app_name;
-    set $plan_name $arg_plan_name;
-
-    set_if_empty $key_secret '-';
-    set_if_empty $realm sandbox;
-    set_if_empty $service_id _undefined_;
-    set_if_empty $service_name _undefined_;
-    set_if_empty $consumer_org_name _undefined_;
-    set_if_empty $app_name _undefined_;
-    set_if_empty $plan_name _undefined_;
-
-    # this should be backward compatible with the initial api
-    set $metadata '{
-           "key":"$key",
-           "key_secret":"$key_secret",
-           "realm":"$realm",
-           "service_id":"$service_id",
-           "service_name":"$service_name",
-           "consumer_org_name":"$consumer_org_name",
-           "app_name":"$app_name",
-           "plan_name":"$plan_name"
-           }';
-
-    default_type application/json;
-
-    content_by_lua '
-       local BaseValidator = require "api-gateway.validation.validator"
-       local validator = BaseValidator:new()
-       validator:setKeyInRedis("cachedkey:" .. ngx.var.key .. ":" .. ngx.var.service_id, "metadata", nil, ngx.var.metadata)
-       ngx.say(ngx.var.metadata)
-    ';
-}
-
-location ~ /cache/api_key/del {
-    internal;
-
-    limit_except DELETE {
-        deny all;
-    }
-
-    set $key $arg_key;
-    set $service_id $arg_service_id;
-
-    set $redis_cmd "DEL cachedkey:$key:$service_id";
-
-    # limit_except OPTIONS
-    proxy_pass http://127.0.0.1:9191/cache/redis_query?$redis_cmd;
-}
-
-location ~ /cache/api_key/get {
-    internal;
-
-    limit_except GET OPTIONS {
-        deny all;
-    }
-
-    set $api_key $arg_key;
-    set $service_id $arg_service_id;
-
-    content_by_lua 'ngx.apiGateway.validation.validateApiKey()';
-}
-
-# pure REST API URI where POST goes to /set, GET to /get, DELETE to /del through internal redirect
-location = /cache/api_key {
-    uninitialized_variable_warn off;
-    # allow 127.0.0.1;
-    # deny all;
-
-    if ($request_method = POST) {
-        rewrite ^/cache/api_key(.*)$ /cache/api_key/set$1 last;
-    }
-
-    if ($request_method = DELETE) {
-        rewrite ^/cache/api_key(.*)$ /cache/api_key/del$1 last;
-    }
-
-    if ($request_method ~* ^(GET|OPTIONS)$ ) {
-        rewrite ^/cache/api_key(.*)$ /cache/api_key/get$1 last;
-    }
-}
diff --git a/api-gateway-config/conf.d/includes/default_validators.conf b/api-gateway-config/conf.d/includes/default_validators.conf
deleted file mode 100644
index b153c45..0000000
--- a/api-gateway-config/conf.d/includes/default_validators.conf
+++ /dev/null
@@ -1,112 +0,0 @@
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-# the following variable are set by request_validator.lua
-set $validate_request_status 'na';
-set $validate_request_response_body 'na';
-set $validate_request_response_time -1;
-# where to redirect to send an error response
-set $validation_error_page '@handle_gateway_validation_error';
-
-# the next vars are automatically set from the api_key values
-# here they are initialized just so that they can be set later on by api_key_validator.lua
-set $publisher_org_name TBD;
-set $realm TBD;
-# service_id should be set by each individual service
-# set $service_id TBD;
-set $key_secret TBD;
-set $service_name TBD;
-set $consumer_org_name TBD;
-set $app_name TBD;
-set $plan_name TBD;
-set $service_env 'sandbox';
-
-#
-# default request validation impl
-#
-location /validate-request {
-    internal;
-    content_by_lua 'ngx.apiGateway.validation.defaultValidateRequestImpl()';
-}
-
-#
-# default api-key validator impl
-#
-location /validate_api_key {
-    internal;
-    content_by_lua 'ngx.apiGateway.validation.validateApiKey()';
-}
-
-location /validate_hmac_signature {
-    internal;
-    content_by_lua 'ngx.apiGateway.validation.validateHmacSignature()';
-}
-
-#
-# default OAuth Token validator impl along with the nginx variables it sets
-#
-set $oauth_token_scope '';
-set $oauth_token_client_id '';
-set $oauth_token_user_id '';
-location /validate_oauth_token {
-    internal;
-    content_by_lua 'ngx.apiGateway.validation.validateOAuthToken()';
-}
-
-#
-# default user Profile validator impl along with the nginx variables it sets
-#
-set $user_email '';
-set $user_country_code '';
-set $user_region '';
-set $user_name '';
-location /validate_user_profile {
-    internal;
-    content_by_lua 'ngx.apiGateway.validation.validateUserProfile()';
-}
-
-#
-# default validator for service plans that looks for Blocking rules to see if they match the requst
-#
-location /validate_service_plan {
-    internal;
-    content_by_lua 'ngx.apiGateway.tracking.validateServicePlan()';
-}
-
-# Error handler for validation errors
-# NOTE: this endpoint assumes that $validate_request_status and $validate_request_response_body is set before
-location @handle_gateway_validation_error {
-    internal;
-    content_by_lua '
-        local ErrorDecorator = require "api-gateway.validation.validatorsHandlerErrorDecorator"
-        local decorator = ErrorDecorator:new()
-        decorator:setUserDefinedResponsesFromJson(ngx.var.validator_custom_error_responses)
-        decorator:decorateResponse(ngx.var.validate_request_status, ngx.var.validate_request_response_body)
-    ';
-
-    # capture usage data
-    log_by_lua '
-        if ( ngx.apiGateway.metrics ~= nil ) then
-            ngx.apiGateway.metrics.captureUsageData()
-        end
-    ';
-}
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/includes/protected_locations.conf b/api-gateway-config/conf.d/includes/protected_locations.conf
deleted file mode 100644
index 3e23be3..0000000
--- a/api-gateway-config/conf.d/includes/protected_locations.conf
+++ /dev/null
@@ -1,46 +0,0 @@
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-location /protected-with-api-key {
-    set $api_key $http_x_api_key;
-    set_if_empty $api_key $arg_api_key;
-
-    # ----------------------------------
-    # add X-Request-Id header
-    # ----------------------------------
-    set $requestId $http_x_request_id;
-    set_secure_random_alphanum $requestId_random 32;
-    set_if_empty $requestId $requestId_random;
-    # add_header X-Request-Id $requestId;
-    proxy_set_header X-Request-Id  $requestId;
-
-    # TBD
-}
-
-location /protected-with-oauth-token {
-    # TBD
-}
-
-location /protected-with-api-key-and-token {
-    # TBD
-}
-
diff --git a/api-gateway-config/conf.d/marathon_apis.conf b/api-gateway-config/conf.d/managed_endpoints.conf
similarity index 66%
rename from api-gateway-config/conf.d/marathon_apis.conf
rename to api-gateway-config/conf.d/managed_endpoints.conf
index c203c8d..3aee3c4 100644
--- a/api-gateway-config/conf.d/marathon_apis.conf
+++ b/api-gateway-config/conf.d/managed_endpoints.conf
@@ -1,5 +1,5 @@
 #/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
+# * Copyright (c) 2016 IBM. All rights reserved.
 # *
 # * Permission is hereby granted, free of charge, to any person obtaining a
 # * copy of this software and associated documentation files (the "Software"),
@@ -21,19 +21,8 @@
 # *
 # */
 
-#
-# NOTE: THIS CONFIG FILE ASSUMES ALL HOST NAMES ARE IN FORMAT  <marathon_app_name>.api.any.domain
-#
-
-#import the list of upstreams
-include /etc/api-gateway/environment.conf.d/api-gateway-upstreams.http.conf;
-
 server {
-    listen 80;
-#    listen 8080;
-
-    # listenes on <marathon_app_name>.api.any.domain with the assumption that the marathon_app_name has been defined in marathon. TBD what to do when the app is not found
-    server_name ~^(?<marathon_app_name>.[^\.]+)\.(api|gw)\.(?<domain>.+);
+    listen 8080 default_server;
 
     server_tokens off;
 
@@ -47,14 +36,12 @@
 
     include /etc/api-gateway/conf.d/commons/common-headers.conf;
     include /etc/api-gateway/conf.d/includes/resolvers.conf;
-    include /etc/api-gateway/conf.d/includes/default_validators.conf;
 
     # Log locations with service name
     access_log /var/log/api-gateway/access.log platform;
-    error_log /var/log/api-gateway/marathon_error.log debug;
+    error_log /var/log/api-gateway/gateway_error.log debug;
 
-    # include environment variables
-    include /etc/api-gateway/environment.conf.d/api-gateway-env-vars.server.conf;
+    # include environment variable
 
     error_page 500 501 502 503 504 /50x.html;
 
@@ -64,46 +51,13 @@
         return 500 '{"code":$status, "message":"Oops. Something went wrong. Check your URI and try again."}\n';
     }
 
-    set $publisher_org_name 'generic';
-    set $consumer_org_name 'generic-consumer';
-    set $app_name 'generic-app';
-
-    location / {
-        # ----------------------------------
-        # add X-Request-Id header
-        # ----------------------------------
-        set $requestId $http_x_request_id;
-        set_secure_random_alphanum $requestId_random 32;
-        set_if_empty $requestId $requestId_random;
-        # add_header X-Request-Id $requestId;
-        proxy_set_header X-Request-Id  $requestId;
-
-        proxy_connect_timeout 10s;  # timeout for establishing a connection with a proxied server
-
-        proxy_read_timeout 10s;     # Defines a timeout for reading a response from the proxied server.
-                                    # The timeout is set only between two successive read operations,
-                                    # not for the transmission of the whole response.
-
-        proxy_send_timeout 10s;     # Sets a timeout for transmitting a request to the proxied server.
-                                    # The timeout is set only between two successive write operations,
-                                    # not for the transmission of the whole request.
-        keepalive_timeout 10s;      # timeout during which a keep-alive client connection will stay open on the server side
-        proxy_buffering off;        # enables or disables buffering of responses from the proxied server.
-        proxy_http_version 1.1;     # Version 1.1 is recommended for use with keepalive connections.
-        proxy_set_header Connection "";
-
-        set $service_id $marathon_app_name;
-        set $service_name $marathon_app_name;
-
-        proxy_pass http://$marathon_app_name$request_uri;
-
-        # capture usage data
-        log_by_lua '
-            if ( ngx.apiGateway.metrics ~= nil ) then
-                ngx.apiGateway.metrics.captureUsageData()
-            end
+    location /api {
+        content_by_lua '
+            ngx.say("You have hit the api gateway managed endpoints.")
         ';
     }
+
+    include /etc/api-gateway/managed_confs/*/*.conf;
 }
 
 
@@ -195,6 +149,4 @@
 #            end
 #        ';
 #    }
-#}
-
-
+#}
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/management_apis.conf b/api-gateway-config/conf.d/management_apis.conf
new file mode 100644
index 0000000..eccdcb1
--- /dev/null
+++ b/api-gateway-config/conf.d/management_apis.conf
@@ -0,0 +1,103 @@
+#/*
+# * Copyright (c) 2016 IBM. All rights reserved.
+# *
+# * Permission is hereby granted, free of charge, to any person obtaining a
+# * copy of this software and associated documentation files (the "Software"),
+# * to deal in the Software without restriction, including without limitation
+# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# * and/or sell copies of the Software, and to permit persons to whom the
+# * Software is furnished to do so, subject to the following conditions:
+# *
+# * The above copyright notice and this permission notice shall be included in
+# * all copies or substantial portions of the Software.
+# *
+# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# * DEALINGS IN THE SOFTWARE.
+# *
+# */
+
+server {
+    listen 9000;
+    server_name management_gw;
+
+    include /etc/api-gateway/conf.d/commons/common-headers.conf;
+    include /etc/api-gateway/conf.d/includes/resolvers.conf;
+
+    # Log locations with service name
+    lua_socket_log_errors off;
+    access_log /var/log/api-gateway/access.log platform;
+    error_log /var/log/api-gateway/mgmt_error.log debug;
+
+    location /v1/apis {
+        access_by_lua_block {
+            local mgmt = require("management")
+            local requestMethod = ngx.req.get_method()
+            if requestMethod == "GET" then
+                mgmt.getAPIs()
+            elseif requestMethod == "PUT" then
+                mgmt.addAPI()
+            elseif requestMethod == "POST" then
+                mgmt.addAPI()
+            elseif requestMethod == "DELETE" then
+                mgmt.deleteAPI()
+            else
+                ngx.status = 400
+                ngx.say("Invalid verb")
+            end
+        }
+    }
+
+    location /v1/tenants {
+        access_by_lua_block {
+            local mgmt = require("management")
+            local requestMethod = ngx.req.get_method()
+            if requestMethod == "GET" then
+                mgmt.getTenants()
+            elseif requestMethod == "PUT" then
+                mgmt.addTenant()
+            elseif requestMethod == "POST" then
+                mgmt.addTenant()
+            elseif requestMethod == "DELETE" then
+                mgmt.deleteTenant()
+            else
+                ngx.status = 400
+                ngx.say("Invalid verb")
+            end
+        }
+    }
+
+    location /subscriptions {
+         access_by_lua_block {
+             local mgmt = require("management")
+             local requestMethod = ngx.req.get_method()
+             if requestMethod == "PUT" then
+                 mgmt.addSubscription()
+             elseif requestMethod == "DELETE" then
+                 mgmt.deleteSubscription()
+             else
+                 ngx.status = 400
+                 ngx.say("Invalid verb")
+             end
+
+         }
+     }
+
+    location /subscribe {
+        access_by_lua_block {
+            local mgmt = require("management")
+            mgmt.subscribe()
+        }
+    }
+
+    location /unsubscribe {
+        access_by_lua_block {
+            local mgmt = require("management")
+            mgmt.unsubscribe()
+        }
+    }
+}
diff --git a/api-gateway-config/conf.d/tracking_api.conf b/api-gateway-config/conf.d/tracking_api.conf
deleted file mode 100644
index 7fe450d..0000000
--- a/api-gateway-config/conf.d/tracking_api.conf
+++ /dev/null
@@ -1,73 +0,0 @@
-# VHost exposing an API for Gateway Tracking Service ( GTS ) to control the Gateway
-# This VHost should listen on a PORT that is never exposed externally
-# This VHost should use a server_name matching the hostname
-
-server {
-  listen 5000 default_server;
-
-  #
-  # Sample message to start tracking requests that match with the given format
-  #        {
-  #          "id": 222,
-  #          "domain" : "cc-eco;cceco-consumer;*",
-  #          "format": "$publisher_org_name;$consumer_org_name;$api_key",
-  #          "expire_at_utc": 1408065588,
-  #          "action" : "track",
-  #          "data" : 0
-  #        }
-  #
-  location /tracking {
-    access_log /var/log/api-gateway/access.log;
-    limit_except POST {
-      deny  all;
-    }
-    content_by_lua 'ngx.apiGateway.tracking.POST_HANDLER()';
-  }
-
-  #
-  # location returning all TRACKing rules
-  #
-  location /tracking/track {
-    access_log /var/log/api-gateway/access.log;
-    limit_except GET {
-      deny  all;
-    }
-    default_type 'application/json';
-    content_by_lua 'ngx.apiGateway.tracking.GET_HANDLER("TRACK")';
-  }
-
-  #
-  # location retuning all BLOCKing rules
-  #
-  location /tracking/block {
-    access_log /var/log/api-gateway/access.log;
-    limit_except GET {
-      deny  all;
-    }
-    default_type 'application/json';
-    content_by_lua 'ngx.apiGateway.tracking.GET_HANDLER("BLOCK")';
-  }
-
-  #
-  # location returning all DELAYing rules
-  #
-  location /tracking/delay {
-    access_log /var/log/api-gateway/access.log;
-    limit_except GET {
-      deny  all;
-    }
-    default_type 'application/json';
-    content_by_lua 'ngx.apiGateway.tracking.GET_HANDLER("DELAY")';
-  }
-
-  #
-  # location returning all REWRITE rules
-  #
-  location /tracking/rewrite {
-    limit_except GET {
-      deny  all;
-    }
-    default_type 'application/json';
-    content_by_lua 'ngx.apiGateway.tracking.GET_HANDLER("REWRITE")';
-  }
-}
\ No newline at end of file
diff --git a/api-gateway-config/environment.conf.d/api-gateway-env-vars.server.conf b/api-gateway-config/environment.conf.d/api-gateway-env-vars.server.conf
deleted file mode 100644
index cba2157..0000000
--- a/api-gateway-config/environment.conf.d/api-gateway-env-vars.server.conf
+++ /dev/null
@@ -1,27 +0,0 @@
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-
-# NOTE: TO BE INCLUDED FOR EACH VHOST
-# Use this file to overwrite any nginx variables based on the environment
-
-# set $my_var my_value;
\ No newline at end of file
diff --git a/api-gateway-config/environment.conf.d/api-gateway-env.http.conf b/api-gateway-config/environment.conf.d/api-gateway-env.http.conf
deleted file mode 100644
index f18e4da..0000000
--- a/api-gateway-config/environment.conf.d/api-gateway-env.http.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-
-# TO BE INCLUDED ON HTTP BLOCK
-more_set_headers 'Server: api-gateway/1.9.3.1';
\ No newline at end of file
diff --git a/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf b/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf
deleted file mode 100644
index 6f95f56..0000000
--- a/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-# placeholder
-#    - this file is automatically created by marathon-service-discovery.sh
-#      when docker images is started with -e "MARATHON_HOST=<marathon_host>"
-upstream api-gateway-redis {
-    server 127.0.0.1:6379;
-}
diff --git a/api-gateway-config/marathon-service-discovery.sh b/api-gateway-config/marathon-service-discovery.sh
deleted file mode 100755
index 93254a2..0000000
--- a/api-gateway-config/marathon-service-discovery.sh
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/bin/sh
-#/*
-# * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
-# *
-# * Permission is hereby granted, free of charge, to any person obtaining a
-# * copy of this software and associated documentation files (the "Software"),
-# * to deal in the Software without restriction, including without limitation
-# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# * and/or sell copies of the Software, and to permit persons to whom the
-# * Software is furnished to do so, subject to the following conditions:
-# *
-# * The above copyright notice and this permission notice shall be included in
-# * all copies or substantial portions of the Software.
-# *
-# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# * DEALINGS IN THE SOFTWARE.
-# *
-# */
-
-#
-#  Overview:
-#    It reads the list of tasks from Marathon and it dynamically generates the Nginx configuration with the upstreams.
-#
-#    In order to gather the information the script needs to know where to find Marathon
-#       so it looks for the $MARATHON_HOST environment variable.
-#
-
-TMP_FILE=/tmp/api-gateway-upstreams.http.conf
-UPSTREAM_FILE=/etc/api-gateway/environment.conf.d/api-gateway-upstreams.http.conf
-marathon_host=$(echo $MARATHON_HOST)
-
-do_log() {
-        local _MSG=$1
-        echo "`date +'%Y/%m/%d %H:%M:%S'` - marathon-service-discovery: ${_MSG}"
-}
-
-fatal_error() {
-        local _MSG=$1
-        do_log "ERROR: ${_MSG}"
-        exit 255
-}
-
-info_log() {
-        local _MSG=$1
-        do_log "${_MSG}"
-}
-
-# 1. create the new upstream config
-# NOTE: for the moment when tasks expose multiple ports, only the first one is exposed through nginx
-curl -s ${marathon_host}/v2/tasks -H "Accept:text/plain" | awk 'NF>2' | grep -v :0 | awk '!seen[$1]++' | awk ' {s=""; for (f=3; f<=NF; f++) s = s  "\n server " $f " fail_timeout=10s;" ; print "upstream " $1 " {"  s  "\n keepalive 16;\n}" }'  > ${TMP_FILE}
-# 1.1. check redis upstreams
-#
-# ASSUMPTION:  there is a redis app named "api-gateway-redis" deployed in marathon and optionally another app named "api-gateway-redis-replica"
-#
-redis_master=$(cat ${TMP_FILE} | grep api-gateway-redis | wc -l)
-redis_replica=$(cat ${TMP_FILE} | grep api-gateway-redis-replica | wc -l)
-#      if api-gateway-redis upstream exists but api-gateway-redis-replica does not, then create the replica
-if [ ${redis_master} -gt 0 ] && [ ${redis_replica} -eq 0 ]; then
-    # clone api-gateway-redis block
-    sed -e '/api-gateway-redis/,/}/!d' ${TMP_FILE} | sed 's/-redis/-redis-replica/' >> ${TMP_FILE}
-fi
-
-if [ ${redis_master} -eq 0 ]; then
-    echo "upstream api-gateway-redis { server 127.0.0.1:6379; }" >> ${TMP_FILE}
-fi
-
-# 2 check for changes
-cmp -s ${TMP_FILE} ${UPSTREAM_FILE}
-changed_upstreams=$?
-if [[ \( ${changed_upstreams} -gt 0 \) ]]; then
-    cp ${TMP_FILE} ${UPSTREAM_FILE}
-fi
diff --git a/api-gateway-config/scripts/lua/api_gateway_init.lua b/api-gateway-config/scripts/lua/api_gateway_init.lua
index 5893851..a5f46b3 100644
--- a/api-gateway-config/scripts/lua/api_gateway_init.lua
+++ b/api-gateway-config/scripts/lua/api_gateway_init.lua
@@ -76,9 +76,9 @@
     parentObject.metrics = require "metrics.factory"
 end
 
-initValidationFactory(_M)
-initZMQLogger(_M)
-initTrackingFactory(_M)
+--initValidationFactory(_M)
+--initZMQLogger(_M)
+--initTrackingFactory(_M)
 initMetricsFactory(_M)
 -- TODO: test health-check with the new version of Openresty
 -- initRedisHealthCheck()
diff --git a/api-gateway-config/scripts/lua/lib/filemgmt.lua b/api-gateway-config/scripts/lua/lib/filemgmt.lua
new file mode 100644
index 0000000..ca1d793
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/filemgmt.lua
@@ -0,0 +1,94 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module filemgmt
+-- Creates the Nginx Conf files
+-- @author Alex Song (songs), David Green (greend)
+
+local utils = require "lib/utils"
+local cjson = require "cjson"
+local request = require "lib/request"
+
+local _M = {}
+
+--- Create/overwrite Nginx Conf file for given resource
+-- @param baseConfDir the base directory for storing conf files for managed resources
+-- @param tenant the namespace for the resource
+-- @param gatewayPath the gateway path of the resource
+-- @param resourceObj object containing different operations/policies for the resource
+-- @return fileLocation location of created/updated conf file
+function _M.createResourceConf(baseConfDir, tenant, gatewayPath, resourceObj)
+  local decoded = cjson.decode(resourceObj)
+  resourceObj = utils.serializeTable(decoded)
+  local prefix = utils.concatStrings({
+    "\tinclude /etc/api-gateway/conf.d/commons/common-headers.conf;\n",
+    "\tset $upstream https://172.17.0.1;\n",
+    "\tset $tenant ", tenant, ";\n",
+    "\tset $backendUrl '';\n",
+    "\tset $gatewayPath '", ngx.unescape_uri(gatewayPath), "';\n"
+  })
+  if decoded.apiId ~= nil then
+    prefix = utils.concatStrings({prefix, "\tset $apiId ", decoded.apiId, ";\n"})
+  end
+  -- Add CORS headers
+  prefix = utils.concatStrings({prefix, "\tadd_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS';\n"})
+  -- Set resource headers and mapping by calling routing.processCall()
+  local outgoingResource = utils.concatStrings({
+    "\taccess_by_lua_block {\n",
+    "\t\tlocal routing = require \"routing\"\n",
+    "\t\trouting.processCall(", resourceObj, ")\n",
+    "\t}\n",
+    "\tproxy_pass $upstream;\n"
+  })
+  -- Add to endpoint conf file
+  os.execute(utils.concatStrings({"mkdir -p ", baseConfDir, tenant}))
+  local fileLocation = utils.concatStrings({baseConfDir, tenant, "/", gatewayPath, ".conf"})
+  local file, err = io.open(fileLocation, "w")
+  if not file then
+    request.err(500, utils.concatStrings({"Error adding to endpoint conf file: ", err}))
+  end
+  local updatedPath = ngx.unescape_uri(gatewayPath):gsub("%{(%w*)%}", utils.convertTemplatedPathParam)
+  local location = utils.concatStrings({
+    "location ~ ^/api/", tenant, "/", updatedPath, "(\\b) {\n",
+    prefix,
+    outgoingResource,
+    "}\n"
+  })
+  file:write(location)
+  file:close()
+  -- reload nginx to refresh conf files
+  os.execute("/usr/local/sbin/nginx -s reload")
+  return fileLocation
+end
+
+--- Delete Ngx conf file for given resource
+-- @param baseConfDir the base directory for storing conf files for managed resources
+-- @param tenant the namespace for the resource
+-- @param gatewayPath the gateway path of the resource
+-- @return fileLocation location of deleted conf file
+function _M.deleteResourceConf(baseConfDir, tenant, gatewayPath)
+  local fileLocation = utils.concatStrings({baseConfDir, tenant, "/", gatewayPath, ".conf"})
+  os.execute(utils.concatStrings({"rm -f ", fileLocation}))
+  -- reload nginx to refresh conf files
+  os.execute("/usr/local/sbin/nginx -s reload")
+  return fileLocation
+end
+
+return _M
diff --git a/api-gateway-config/scripts/lua/lib/logger.lua b/api-gateway-config/scripts/lua/lib/logger.lua
new file mode 100644
index 0000000..c1e5680
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/logger.lua
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module logger
+-- Module to handle logging in a single place
+-- @author Cody Walker (cmwalker), Alex Song (songs)
+
+local _M = {}
+
+--- Handle error stream
+-- @param s String to write to error stream
+function _M.err(s)
+  ngx.log(ngx.ERR, s)
+end
+
+--- Handle debug stream to stdout
+-- @param s String to write to debug stream
+function _M.debug(s)
+  os.execute("echo \"" .. s .. "\"")
+end
+
+return _M
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
new file mode 100644
index 0000000..62b2322
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -0,0 +1,419 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module
+--
+-- @author Alex Song (songs)
+
+local cjson = require "cjson"
+local filemgmt = require "lib/filemgmt"
+local utils = require "lib/utils"
+local logger = require "lib/logger"
+local request = require "lib/request"
+
+local REDIS_FIELD = "resources"
+local BASE_CONF_DIR = "/etc/api-gateway/managed_confs/"
+
+local _M = {}
+
+----------------------------
+-- Initialization/Cleanup --
+----------------------------
+
+--- Initialize and connect to Redis
+-- @param host redis host
+-- @param port redis port
+-- @param password redis password (nil if no password)
+-- @param timeout redis timeout in milliseconds
+function _M.init(host, port, password, timeout)
+  local redis = require "resty.redis"
+  local red   = redis:new()
+  red:set_timeout(timeout)
+  -- Connect to Redis server
+  local retryCount = 4
+  local connect, err = red:connect(host, port)
+  while not connect and retryCount > 0 do
+    local msg = utils.concatStrings({"Failed to conect to redis at ", host, ":", port, ". Retrying ", retryCount, " more times."})
+    if retryCount == 1 then
+      msg = utils.concatStrings({msg:sub(1, -3), "."})
+    end
+    logger.debug(msg)
+    retryCount = retryCount - 1
+    os.execute("sleep 1")
+    connect, err = red:connect(host, port)
+  end
+  if not connect then
+    request.err(500, utils.concatStrings({"Failed to connect to redis: ", err}))  
+  end
+  -- Authenticate with Redis
+  if password ~= nil and password ~= "" then
+    local res, err = red:auth(password)
+    if not res then
+      request.err(500, utils.concatStrings({"Failed to authenticate: ", err}))  
+    end
+  end
+  return red
+end
+
+--- Add current redis connection in the ngx_lua cosocket connection pool
+-- @param red Redis client instance
+function _M.close(red)
+  -- put it into the connection pool of size 100, with 10 seconds max idle time
+  local ok, err = red:set_keepalive(10000, 100)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to set keepalive: ", err}))  
+  end
+end
+
+---------------------------
+----------- APIs ----------
+---------------------------
+
+--- Add API to redis
+-- @param red Redis client instance
+-- @param id id of API
+-- @param apiObj the api to add
+function _M.addAPI(red, id, apiObj)
+  local ok, err = red:hset("apis", id, apiObj)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to save the API: ", err}))
+  end
+end
+
+--- Get all APIs from redis
+-- @param red Redis client instance
+function _M.getAllAPIs(red)
+  local res, err = red:hgetall("apis")
+  if not res then
+    request.err(500, utils.concatStrings({"Failed to retrieve APIs: ", err}))
+  end
+  return res
+end
+
+--- Get a single API from redis given its id
+-- @param red Redis client instance
+-- @param id id of API to get
+function _M.getAPI(red, id)
+  local api, err = red:hget("apis", id)
+  if not api then
+    request.err(500, utils.concatStrings({"Failed to retrieve the API: ", err}))
+  end
+  if api == ngx.null then
+    return nil
+  end
+  return cjson.decode(api)
+end
+
+--- Delete an API from redis given its id
+-- @param red Redis client instance
+-- @param id id of API to delete
+function _M.deleteAPI(red, id)
+  local ok, err = red:hdel("apis", id)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to delete the API: ", err}))
+  end
+end
+
+-----------------------------
+--------- Resources ---------
+-----------------------------
+
+--- Generate Redis object for resource
+-- @param ops list of operations for a given resource
+-- @param apiId resource api id (nil if no api)
+function _M.generateResourceObj(ops, apiId)
+  local resourceObj = {
+    operations = {}
+  }
+  for op, v in pairs(ops) do
+    op = op:upper()
+    resourceObj.operations[op] = {
+      backendUrl = v.backendUrl,
+      backendMethod = v.backendMethod
+    }
+    if v.policies then
+      resourceObj.operations[op].policies = v.policies
+    end
+    if v.security then
+      resourceObj.operations[op].security = v.security
+    end
+  end
+  if apiId then
+    resourceObj.apiId = apiId
+  end
+  return cjson.encode(resourceObj)
+end
+
+--- Create/update resource in redis
+-- @param red redis client instance
+-- @param key redis resource key
+-- @param field redis resource field
+-- @param resourceObj redis object containing operations for resource
+function _M.createResource(red, key, field, resourceObj)
+  -- Add/update resource to redis
+  local ok, err = red:hset(key, field, resourceObj)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to save the resource: ", err}))
+  end
+end
+
+--- Get resource in redis
+-- @param red redis client instance
+-- @param key redis resource key
+-- @param field redis resource field
+-- @return resourceObj redis object containing operations for resource
+function _M.getResource(red, key, field)
+  local resourceObj, err = red:hget(key, field)
+  if not resourceObj then
+    request.err(500, utils.concatStrings({"Failed to retrieve the resource: ", err}))
+  end
+  -- return nil if resource doesn't exist
+  if resourceObj == ngx.null then
+    return nil
+  end
+
+  return resourceObj
+end
+
+--- Delete resource in redis
+-- @param red redis client instance
+-- @param key redis resource key
+-- @param field redis resource field
+function _M.deleteResource(red, key, field)
+  local resourceObj, err = red:hget(key, field)
+  if not resourceObj then
+    request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
+  end
+  if resourceObj == ngx.null then
+    request.err(404, "Resource doesn't exist.")
+  end
+  local ok, err = red:del(key)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
+  else
+    return ok
+  end
+end
+
+-----------------------------
+---------- Tenants ----------
+-----------------------------
+
+--- Add tenant to redis
+-- @param red Redis client instance
+-- @param id id of tenant
+-- @param tenantObj the tenant to add
+function _M.addTenant(red, id, tenantObj)
+  local ok, err = red:hset("tenants", id, tenantObj)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to add the tenant: ", err}))
+  end
+end
+
+--- Get all tenants from redis
+-- @param red Redis client instance
+function _M.getAllTenants(red)
+  local res, err = red:hgetall("tenants")
+  if not res then
+    request.err(500, utils.concatStrings({"Failed to retrieve tenants: ", err}))
+  end
+  return res
+end
+
+--- Get a single tenant from redis given its id
+-- @param red Redis client instance
+-- @param id id of tenant to get
+function _M.getTenant(red, id)
+  local tenant, err = red:hget("tenants", id)
+  if not tenant then
+    request.err(500, utils.concatStrings({"Failed to retrieve the tenant: ", err}))
+  end
+  if tenant == ngx.null then
+    return nil
+  end
+  return cjson.decode(tenant)
+end
+
+--- Delete an tenant from redis given its id
+-- @param red Redis client instance
+-- @param id id of tenant to delete
+function _M.deleteTenant(red, id)
+  local ok, err = red:hdel("tenants", id)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to delete the tenant: ", err}))
+  end
+end
+
+-----------------------------
+--- API Key Subscriptions ---
+-----------------------------
+
+--- Create/update subscription/apikey in redis
+-- @param red redis client instance
+-- @param key redis subscription key to create
+function _M.createSubscription(red, key)
+  -- Add/update a subscription key to redis
+  local ok, err = red:set(key, '')
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to add the subscription key", err}))
+  end
+end
+
+--- Delete subscription/apikey int redis
+-- @param red redis client instance
+-- @param key redis subscription key to delete
+function _M.deleteSubscription(red, key)
+  local subscription, err = red:get(key)
+  if not subscription then
+    request.err(500, utils.concatStrings({"Failed to delete the subscription key: ", err}))
+  end
+  if subscription == ngx.null then
+    request.err(404, "Subscription doesn't exist.")
+  end
+  local ok, err = red:del(key)
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to delete the subscription key: ", err}))
+  end
+end
+
+-----------------------------------
+------- Pub/Sub with Redis --------
+-----------------------------------
+
+--- Subscribe to redis
+-- @param redisSubClient the redis client that is listening for the redis key changes
+-- @param redisGetClient the redis client that gets the changed resource to update the conf file
+function _M.subscribe(redisSubClient, redisGetClient)
+  -- create conf files for existing resources in redis
+  syncWithRedis(redisGetClient, ngx)
+  -- enable keyspace notifications
+  local ok, err = redisGetClient:config("set", "notify-keyspace-events", "KEA")
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed setting notify-keyspace-events: ", err}))
+  end
+  ok, err = redisSubClient:psubscribe("__keyspace@0__:resources:*:*")
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to subscribe to redis: ", err}))
+  end
+  ngx.say("\nSubscribed to redis and listening for key changes...")
+  ngx.flush(true)
+  subscribe(redisSubClient, redisGetClient, ngx)
+  ngx.exit(ngx.status)
+end
+
+--- Sync with redis on startup and create conf files for resources that are already in redis
+-- @param red redis client instance
+function syncWithRedis(red)
+  logger.debug("\nCreating nginx conf files for existing resources...")
+  local redisKeys, err = red:keys("*")
+  if not redisKeys then
+    request.err(500, util.concatStrings({"Failed to sync with Redis: ", err}))
+  end
+  -- Find all redis keys with "resources:*:*"
+  local resourcesExist = false
+  for k, redisKey in pairs(redisKeys) do
+    local index = 1
+    local tenant = ""
+    local gatewayPath = ""
+    for word in string.gmatch(redisKey, '([^:]+)') do
+      if index == 1 then
+        if word ~= "resources" then
+          break
+        else
+          resourcesExist = true
+          index = index + 1
+        end
+      else
+        if index == 2 then
+          tenant = word
+        elseif index == 3 then
+          gatewayPath = word
+          -- Create new conf file
+          local resourceObj = _M.getResource(red, redisKey, REDIS_FIELD)
+          local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
+          logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
+        end
+        index = index + 1
+      end
+    end
+  end
+  if resourcesExist == false then
+    logger.debug("No existing resources.")
+  end
+end
+
+--- Subscribe helper method
+-- Starts a while loop that listens for key changes in redis
+-- @param redisSubClient the redis client that is listening for the redis key changes
+-- @param redisGetClient the redis client that gets the changed resource to update the conf file
+function subscribe(redisSubClient, redisGetClient)
+  while true do
+    local res, err = redisSubClient:read_reply()
+    if not res then
+      if err ~= "timeout" then
+        ngx.say("Read reply error: ", err)
+        ngx.exit(ngx.status)
+      end
+    else
+      local index = 1
+      local redisKey = ""
+      local tenant = ""
+      local gatewayPath = ""
+      for word in string.gmatch(res[3], '([^:]+)') do
+        if index == 2 then
+          redisKey = utils.concatStrings({redisKey, word, ":"})
+        elseif index == 3 then
+          tenant = word
+          redisKey = utils.concatStrings({redisKey, tenant, ":"})
+        elseif index == 4 then
+          gatewayPath = word
+          redisKey = utils.concatStrings({redisKey, gatewayPath})
+        end
+        index = index + 1
+      end
+      local resourceObj = _M.getResource(redisGetClient, redisKey, REDIS_FIELD)
+      if resourceObj == nil then
+        local fileLocation = filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath))
+        logger.debug(utils.concatStrings({"Redis key deleted: ", redisKey}))
+        logger.debug(utils.concatStrings({"Deleted file: ", fileLocation}))
+      else
+        local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
+        logger.debug(utils.concatStrings({"Redis key updated: ", redisKey}))
+        logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
+      end
+    end
+  end
+  ngx.exit(ngx.status)
+end
+
+
+--- Unsubscribe from redis
+-- @param red redis client instance
+function _M.unsubscribe(red)
+  local ok, err = red:unsubscribe("__keyspace@0__:resources:*:*")
+  if not ok then
+    request.err(500, utils.concatStrings({"Failed to unsubscribe to redis: ", err}))
+  end
+  _M.close(red, ngx)
+  ngx.say("Unsubscribed from redis")
+  ngx.exit(ngx.status)
+end
+
+return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/lib/request.lua b/api-gateway-config/scripts/lua/lib/request.lua
new file mode 100644
index 0000000..0ef5999
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/request.lua
@@ -0,0 +1,50 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module
+--
+-- @author Alex Song (songs)
+
+local utils = require "lib/utils"
+
+local _Request = {}
+
+--- Error function to call when request is malformed
+-- @param code error code
+-- @param msg error message
+function err(code, msg)
+  ngx.status = code
+  ngx.print(utils.concatStrings({"Error: ", msg}))
+  ngx.exit(ngx.status)
+end
+
+--- Function to call when request is successful
+-- @param code status code
+-- @param obj object to return
+function success(code, obj)
+  ngx.status = code
+  ngx.print(obj)
+  ngx.exit(ngx.status)
+end
+
+_Request.err = err
+_Request.success = success
+
+return _Request
diff --git a/api-gateway-config/scripts/lua/lib/resty/limit/req.lua b/api-gateway-config/scripts/lua/lib/resty/limit/req.lua
new file mode 100644
index 0000000..eeec626
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/resty/limit/req.lua
@@ -0,0 +1,173 @@
+--  Copyright (C) 2014 Monkey Zhang (timebug), UPYUN Inc.
+--  All rights reserved.
+--
+--   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--   Redistributions of source code must retain the above copyright notice,
+--   this list of conditions and the following disclaimer.
+--
+--   Redistributions in binary form must reproduce the above copyright notice,
+--   this list of conditions and the following disclaimer in the documentation
+--   and/or other materials provided with the distribution.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+local redis = require 'lib/redis'
+
+local floor = math.floor
+local tonumber = tonumber
+
+
+local _M = { _VERSION = "0.01", OK = 1, BUSY = 2, FORBIDDEN = 3 }
+
+
+local redis_limit_req_script_sha
+local redis_limit_req_script = [==[
+local key = KEYS[1]
+local rate = tonumber(KEYS[2])
+local now, interval = tonumber(KEYS[3]), tonumber(KEYS[4])
+
+local excess, last, forbidden = 0, 0, 0
+
+local res = redis.pcall('GET', key)
+if type(res) == "table" and res.err then
+    return {err=res.err}
+end
+
+if res and type(res) == "string" then
+    local v = cjson.decode(res)
+    if v and #v > 2 then
+        excess, last, forbidden = v[1], v[2], v[3]
+    end
+
+    if forbidden == 1 then
+        return {3, excess} -- FORBIDDEN
+    end
+
+    local ms = math.abs(now - last)
+    excess = excess - rate * ms / 1000 + 1000
+
+    if excess < 0 then
+        excess = 0
+    end
+
+    if excess > 0 then
+        if interval > 0 then
+            local res = redis.pcall('SET', key,
+                                    cjson.encode({excess, now, 1}))
+            if type(res) == "table" and res.err then
+                return {err=res.err}
+            end
+
+            local res = redis.pcall('EXPIRE', key, interval)
+            if type(res) == "table" and res.err then
+                return {err=res.err}
+            end
+        end
+
+        return {2, excess} -- BUSY
+    end
+end
+
+local res = redis.pcall('SET', key, cjson.encode({excess, now, 0}))
+if type(res) == "table" and res.err then
+    return {err=res.err}
+end
+
+local res = redis.pcall('EXPIRE', key, 60)
+if type(res) == "table" and res.err then
+    return {err=res.err}
+end
+
+return {1, excess}
+]==]
+
+
+local function redis_lookup(conn, zone, key, rate, duration)
+    local red = conn
+
+    if not redis_limit_req_script_sha then
+        local res, err = red:script("LOAD", redis_limit_req_script)
+        if not res then
+            return nil, err
+        end
+
+        ngx.log(ngx.NOTICE, "load redis limit req script")
+
+        redis_limit_req_script_sha = res
+    end
+
+    local now = ngx.now() * 1000
+    local res, err = red:evalsha(redis_limit_req_script_sha, 4,
+                                 zone .. ":" .. key, rate, now, duration)
+    if not res then
+        redis_limit_req_script_sha = nil
+        return nil, err
+    end
+
+    -- put it into the connection pool of size 100,
+    -- with 10 seconds max idle timeout
+    local ok, err = red:set_keepalive(10000, 100)
+    if not ok then
+        ngx.log(ngx.WARN, "failed to set keepalive: ", err)
+    end
+
+    return res
+end
+
+
+function _M.limit(cfg)
+    if not cfg.conn then
+        local rds = cfg.rds or {}
+        rds.timeout = rds.timeout or 1000
+        rds.host = rds.host or "127.0.0.1"
+        rds.port = rds.port or 6379
+        rds.pass = rds.pass or nil
+
+        cfg.conn = redis.init(rds.host, rds.port, rds.pass, rds.timeout)
+    end
+
+    local conn = cfg.conn
+    local zone = cfg.zone or "limit_req"
+    local key = cfg.key or ngx.var.remote_addr
+    local rate = cfg.rate or "1r/s"
+    local interval = cfg.interval or 0
+    local log_level = cfg.log_level or ngx.NOTICE
+
+    local scale = 1
+    local len = #rate
+
+    if len > 3 and rate:sub(len - 2) == "r/s" then
+        scale = 1
+        rate = rate:sub(1, len - 3)
+    elseif len > 3 and rate:sub(len - 2) == "r/m" then
+        scale = 60
+        rate = rate:sub(1, len - 3)
+    end
+
+    rate = floor((tonumber(rate) or 1) * 1000 / scale)
+
+    local res, err = redis_lookup(conn, zone, key, rate, interval)
+    if res and (res[1] == _M.BUSY or res[1] == _M.FORBIDDEN) then
+        if res[1] == _M.BUSY then
+            ngx.log(log_level, 'limiting requests, excess ' ..
+                        res[2]/1000 .. ' by zone "' .. zone .. '"')
+        end
+        return
+    end
+
+    if not res and err then
+        ngx.log(ngx.WARN, "redis lookup err: ", err)
+    end
+
+    return _M.OK
+end
+
+
+return _M
diff --git a/api-gateway-config/scripts/lua/lib/utils.lua b/api-gateway-config/scripts/lua/lib/utils.lua
new file mode 100644
index 0000000..cdaec4e
--- /dev/null
+++ b/api-gateway-config/scripts/lua/lib/utils.lua
@@ -0,0 +1,93 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module utils
+-- Holds the common supporting functions in one file to be referenced else where
+-- @author Alex Song (songs), Cody Walker (cmwalker), David Green (greend)
+
+local _Utils = {}
+
+--- Concatenate a list of strings into a single string. This is more efficient than concatenating
+-- strings together with "..", which creates a new string every time
+-- @param list List of strings to concatenate
+-- @return concatenated string
+function concatStrings(list)
+  local t = {}
+  for k,v in ipairs(list) do
+    t[#t+1] = tostring(v)
+  end
+  return table.concat(t)
+end
+
+--- Serializes a lua table, returning a string representation of the table.
+-- Recursively calls itself it
+-- Useful for saving a lua table to a file, as if not serialized it will save as "Table x35252"
+-- @param t The lua table
+-- @return String representing the serialized lua table
+function serializeTable(t)
+  local first = true
+  local tt = { '{' }
+  for k, v in pairs(t) do
+    if first == false then
+      tt[#tt+1] = ', '
+    else
+      first = false
+    end
+    if type(k) == 'string' then
+      tt[#tt+1] = concatStrings({tostring(k), ' = '})
+    end
+    if type(v) == 'table' then
+      tt[#tt+1] = serializeTable(v)
+    elseif type(v) == 'string' then
+      tt[#tt+1] = concatStrings({'"', tostring(v), '"'})
+    else
+      tt[#tt+1] = tostring(v)
+    end
+  end
+  tt[#tt+1] = '}'
+  return table.concat(tt)
+end
+
+--- Concatenate the path param name into string variable to be replaced by the path param value
+-- at time of being called by the user
+-- @param m where m is the string "{pathParam}"
+-- @return concatenated string of (?<path_pathParam>(\\w+))
+function convertTemplatedPathParam(m)
+  local x = m:gsub("{", ""):gsub("}", "")
+  return concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"})
+end
+
+--- Generate random uuid
+function uuid()
+  local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
+  math.randomseed(os.clock())
+  return string.gsub(template, '[xy]', function (c)
+    local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
+    return string.format('%x', v)
+  end)
+end
+
+_Utils.concatStrings = concatStrings
+_Utils.serializeTable = serializeTable
+_Utils.convertTemplatedPathParam = convertTemplatedPathParam
+_Utils.uuid = uuid
+
+return _Utils
+
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
new file mode 100644
index 0000000..a09ff51
--- /dev/null
+++ b/api-gateway-config/scripts/lua/management.lua
@@ -0,0 +1,624 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module management
+-- Defines and exposes a lightweight API management to create and remove resources in the running API Gateway
+-- @author Alex Song (songs)
+
+local cjson = require "cjson"
+local redis = require "lib/redis"
+local filemgmt = require "lib/filemgmt"
+local utils = require "lib/utils"
+local logger = require "lib/logger"
+local request = require "lib/request"
+local MANAGEDURL_HOST = os.getenv("PUBLIC_MANAGEDURL_HOST")
+MANAGEDURL_HOST = (MANAGEDURL_HOST ~= nil and MANAGEDURL_HOST ~= '') and MANAGEDURL_HOST or "0.0.0.0"
+local MANAGEDURL_PORT = os.getenv("PUBLIC_MANAGEDURL_PORT")
+MANAGEDURL_PORT = (MANAGEDURL_PORT ~= nil and MANAGEDURL_PORT ~= '') and MANAGEDURL_PORT or "8080"
+local REDIS_HOST = os.getenv("REDIS_HOST")
+local REDIS_PORT = os.getenv("REDIS_PORT")
+local REDIS_PASS = os.getenv("REDIS_PASS")
+local REDIS_FIELD = "resources"
+local BASE_CONF_DIR = "/etc/api-gateway/managed_confs/"
+
+local _M = {}
+
+--------------------------
+---------- APIs ----------
+--------------------------
+
+--- Add an api to the Gateway
+-- PUT /APIs
+-- body:
+-- {
+--    "name": *(String) name of API
+--    "basePath": *(String) base path for api
+--    "tenantId": *(String) tenant id
+--    "resources": *(String) resources to add
+-- }
+function _M.addAPI()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  -- Check for api id and use existingAPI if it already exists in redis
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local apiId, existingAPI
+  local index = 1
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      apiId = word
+    end
+    index = index + 1
+  end
+  if apiId ~= nil then
+    existingAPI = redis.getAPI(red, apiId)
+    if existingAPI == nil then
+      request.err(404, utils.concatStrings({"Unknown API id ", apiId}))
+    end
+  end
+  -- Read in the PUT JSON Body
+  ngx.req.read_body()
+  local args = ngx.req.get_body_data()
+  if not args then
+    request.err(400, "Missing request body")
+  end
+  -- Convert json into Lua table
+  local decoded = cjson.decode(args)
+  -- Error checking
+  local fields = {"name", "basePath", "tenantId", "resources"}
+  for k, v in pairs(fields) do
+    local res, err = isValid(red, v, decoded[v])
+    if res == false then
+      request.err(err.statusCode, err.message)
+    end
+  end
+  -- Format basePath
+  local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
+  -- Add resources to redis and create nginx conf files
+  for path, resource in pairs(decoded.resources) do
+    local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+    addResource(red, resource, gatewayPath, decoded.tenantId)
+  end
+  -- Return managedUrl object
+  local uuid = existingAPI ~= nil and existingAPI.id or utils.uuid()
+  local managedUrl = utils.concatStrings({"http://", MANAGEDURL_HOST, ":", MANAGEDURL_PORT, "/api/", decoded.tenantId})
+  if basePath:sub(1,1) ~= '' then
+    managedUrl = utils.concatStrings({managedUrl, "/", basePath})
+  end
+  local managedUrlObj = {
+    id = uuid,
+    name = decoded.name,
+    basePath = utils.concatStrings({"/", basePath}),
+    tenantId = decoded.tenantId,
+    resources = decoded.resources,
+    managedUrl = managedUrl
+  }
+  managedUrlObj = cjson.encode(managedUrlObj):gsub("\\", "")
+  -- Add API object to redis
+  redis.addAPI(red, uuid, managedUrlObj)
+  redis.close(red)
+  -- Return managed url object
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, managedUrlObj)
+end
+
+--- Check JSON body fields for errors
+-- @param red Redis client instance
+-- @param field name of field
+-- @param object field object
+function isValid(red, field, object)
+  -- Check that field exists in body
+  if not object then
+    return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) }
+  end
+  -- Additional check f or tenantId
+  if field == "tenantId" then
+    local tenant = redis.getTenant(red, object)
+    if tenant == nil then
+      return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) }
+    end
+  end
+  -- Additional checks for resource object
+  if field == "resources" then
+    local resources = object
+    if next(object) == nil then
+      return false, { statusCode = 400, message = "Empty resources object." }
+    end
+    for path, resource in pairs(resources) do
+      -- Check that resource path begins with slash
+      if path:sub(1,1) ~= '/' then
+        return false, { statusCode = 400, message = "Resource path must begin with '/'." }
+      end
+      -- Check operations object
+      if not resource.operations or next(resource.operations) == nil then
+        return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
+      end
+      for verb, verbObj in pairs(resource.operations) do
+        local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
+        if allowedVerbs[verb:upper()] == nil then
+          return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
+        end
+        -- Check required fields
+        local requiredFields = {"backendMethod", "backendUrl"}
+        for k, v in pairs(requiredFields) do
+          if verbObj[v] == nil then
+            return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
+          end
+          if v == "backendMethod" then
+            local backendMethod = verbObj[v]
+            if allowedVerbs[backendMethod:upper()] == nil then
+              return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
+            end
+          end
+        end
+        -- Check optional fields
+        local policies = verbObj.policies
+        if policies then
+          for k, v in pairs(policies) do
+            if v.type == nil then
+              return false, { statusCode = 400, message = "Missing field 'type' in policy object." }
+            end
+          end
+        end
+        local security = verbObj.security
+        if security and security.type == nil then
+          return false, { statusCode = 400, message = "Missing field 'type' in security object." }
+        end
+      end
+    end
+  end
+  -- All error checks passed
+  return true
+end
+
+--- Helper function for adding a resource to redis and creating an nginx conf file
+-- @param red
+-- @param resource
+-- @param gatewayPath
+-- @param tenantId
+function addResource(red, resource, gatewayPath, tenantId)
+  -- Create resource object and add to redis
+  local redisKey = utils.concatStrings({"resources", ":", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+  local apiId
+  local operations
+  for k, v in pairs(resource) do
+    if k == 'apiId' then
+      apiId = v
+    elseif k == 'operations' then
+      operations = v
+    end
+  end
+  local resourceObj = redis.generateResourceObj(operations, apiId)
+  redis.createResource(red, redisKey, REDIS_FIELD, resourceObj)
+  filemgmt.createResourceConf(BASE_CONF_DIR, tenantId, gatewayPath, resourceObj)
+end
+
+--- Get one or all APIs from the gateway
+-- GET /APIs
+function _M.getAPIs()
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local id
+  local index = 1
+  local tenantQuery = false
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      id = word
+    elseif index == 4 then
+      if word == 'tenant' then
+        tenantQuery = true
+      else
+        request.err(400, "Invalid request")
+      end
+    end
+    index = index + 1
+  end
+  if id == nil then
+    getAllAPIs()
+  else
+    if tenantQuery == false then
+      getAPI(id)
+    else
+      getAPITenant(id)
+    end
+  end
+end
+
+--- Get all APIs in redis
+function getAllAPIs()
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local res = redis.getAllAPIs(red)
+  redis.close(red)
+  local apiList = {}
+  for k, v in pairs(res) do
+    if k%2 == 0 then
+      apiList[#apiList+1] = cjson.decode(v)
+    end
+  end
+  apiList = cjson.encode(apiList)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, apiList)
+end
+
+--- Get API by its id
+-- @param id of API
+function getAPI(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local api = redis.getAPI(red, id)
+  if api == nil then
+    request.err(404, utils.concatStrings({"Unknown api id ", id}))
+  end
+  redis.close(red)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, cjson.encode(api))
+end
+
+--- Get belongsTo relation tenant
+-- @param id id of API
+function getAPITenant(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local api = redis.getAPI(red, id)
+  if api == nil then
+    request.err(404, utils.concatStrings({"Unknown api id ", id}))
+  end
+  local tenantId = api.tenantId
+  local tenant = redis.getTenant(red, tenantId)
+  if tenant == nil then
+    request.err(404, utils.concatStrings({"Unknown tenant id ", tenantId}))
+  end
+  redis.close(red)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, cjson.encode(tenant))
+end
+
+--- Delete API from gateway
+-- DELETE /APIs/<id>
+function _M.deleteAPI()
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local index = 1
+  local id
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      id = word
+    end
+    index = index + 1
+  end
+  if id == nil then
+    request.err(400, "No id specified.")
+  end
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local api = redis.getAPI(red, id)
+  if api == nil then
+    request.err(404, utils.concatStrings({"Unknown API id ", id}))
+  end
+  -- Delete all resources for the API
+  local basePath = api.basePath:sub(2)
+  for path, v in pairs(api.resources) do
+    local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+    deleteResource(red, gatewayPath, api.tenantId)
+  end
+  redis.deleteAPI(red, id)
+  redis.close(red)
+  request.success(200, {})
+end
+
+--- Helper function for deleting resource in redis and appropriate conf files
+-- @param red redis instance
+-- @param gatewayPath path in gateway
+-- @param tenantId tenant id
+function deleteResource(red, gatewayPath, tenantId)
+  local redisKey = utils.concatStrings({"resources:", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+  redis.deleteResource(red, redisKey, REDIS_FIELD)
+  filemgmt.deleteResourceConf(BASE_CONF_DIR, tenantId, gatewayPath)
+end
+
+-----------------------------
+---------- Tenants ----------
+-----------------------------
+
+--- Add a tenant to the Gateway
+-- PUT /Tenants
+-- body:
+-- {
+--    "namespace": *(String) tenant namespace
+--    "instance": *(String) tenant instance
+-- }
+function _M.addTenant()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  -- Check for tenant id and use existingTenant if it already exists in redis
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local tenantId, existingTenant
+  local index = 1
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      tenantId = word
+    end
+    index = index + 1
+  end
+  if tenantId ~= nil then
+    existingTenant = redis.getTenant(red, tenantId)
+    if existingTenant == nil then
+      request.err(400, utils.concatStrings({"Unknown tenant id ", tenantId}))
+    end
+  end
+  -- Read in the PUT JSON Body
+  ngx.req.read_body()
+  local args = ngx.req.get_body_data()
+  if not args then
+    request.err(400, "Missing request body")
+  end
+  -- Convert json into Lua table
+  local decoded = cjson.decode(args)
+  -- Error checking
+  local fields = {"namespace", "instance"}
+  for k, v in pairs(fields) do
+    if not decoded[v] then
+      request.err(400, utils.concatStrings({"Missing field '", v, "' in request body."}))
+    end
+  end
+  -- Return tenant object
+  local uuid = existingTenant ~= nil and existingTenant.id or utils.uuid()
+  local tenantObj = {
+    id = uuid,
+    namespace = decoded.namespace,
+    instance = decoded.instance
+  }
+  tenantObj = cjson.encode(tenantObj)
+  redis.addTenant(red, uuid, tenantObj)
+  redis.close(red)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, tenantObj)
+end
+
+--- Get one or all tenants from the gateway
+-- GET /Tenants
+function _M.getTenants()
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local id
+  local index = 1
+  local apiQuery = false
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      id = word
+    elseif index == 4 then
+      if word:lower() == 'apis' then
+        apiQuery = true
+      else
+        request.err(400, "Invalid request")
+      end
+    end
+    index = index + 1
+  end
+  if id == nil then
+    getAllTenants()
+  else
+    if apiQuery == false then
+      getTenant(id)
+    else
+      getTenantAPIs(id)
+    end
+  end
+end
+
+--- Get all tenants in redis
+function getAllTenants()
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local res = redis.getAllTenants(red)
+  redis.close(red)
+  local tenantList = {}
+  for k, v in pairs(res) do
+    if k%2 == 0 then
+      tenantList[#tenantList+1] = cjson.decode(v)
+    end
+  end
+  tenantList = cjson.encode(tenantList)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, tenantList)
+end
+
+--- Get tenant by its id
+-- @param id tenant id
+function getTenant(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local tenant = redis.getTenant(red, id)
+  if tenant == nil then
+    request.err(404, utils.concatStrings({"Unknown tenant id ", id }))
+  end
+  redis.close(red)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, cjson.encode(tenant))
+end
+
+--- Get APIs associated with tenant
+-- @param id tenant id
+function getTenantAPIs(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local res = redis.getAllAPIs(red)
+  redis.close(red)
+  local apiList = {}
+  for k, v in pairs(res) do
+    if k%2 == 0 then
+      local decoded = cjson.decode(v)
+      if decoded.tenantId == id then
+        apiList[#apiList+1] = decoded
+      end
+    end
+  end
+  apiList = cjson.encode(apiList)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, apiList)
+end
+
+--- Delete tenant from gateway
+-- DELETE /Tenants/<id>
+function _M.deleteTenant()
+  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+  local index = 1
+  local id
+  for word in string.gmatch(uri, '([^/]+)') do
+    if index == 3 then
+      id = word
+    end
+    index = index + 1
+  end
+  if id == nil then
+    request.err(400, "No id specified.")
+  end
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  redis.deleteTenant(red, id)
+  redis.close(red)
+  request.success(200, {})
+end
+
+------------------------------
+----- Pub/Sub with Redis -----
+------------------------------
+
+--- Subscribe to redis
+-- GET /subscribe
+function _M.subscribe()
+  -- Initialize and connect to redis
+  local redisGetClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local redisSubClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 60000) -- read_reply will timeout every minute
+  logger.debug(utils.concatStrings({"\nConnected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
+  redis.subscribe(redisSubClient, redisGetClient)
+  ngx.exit(200)
+end
+
+--- Unsusbscribe to redis
+-- GET /unsubscribe
+function _M.unsubscribe()
+  -- Initialize and connect to redis
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  redis.unsubscribe(red)
+  request.success(200, "Unsubscribed to redis")
+end
+
+---------------------------
+------ Subscriptions ------
+---------------------------
+
+--- Add an apikey/subscription to redis
+-- PUT /subscriptions
+-- Body:
+-- {
+--    key: *(String) key for tenant/api/resource
+--    scope: *(String) tenant or api or resource
+--    tenant: *(String) tenant id
+--    resource: (String) url-encoded resource path
+--    api: (String) api id
+-- }
+function _M.addSubscription()
+  -- Validate body and create redisKey
+  local redisKey = validateSubscriptionBody()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  redis.createSubscription(red, redisKey)
+  -- Add current redis connection in the ngx_lua cosocket connection pool
+  redis.close(red)
+  request.success(200, "Subscription created.")
+end
+
+--- Delete apikey/subscription from redis
+-- DELETE /subscriptions
+-- Body:
+-- {
+--    key: *(String) key for tenant/api/resource
+--    scope: *(String) tenant or api or resource
+--    tenant: *(String) tenant id
+--    resource: (String) url-encoded resource path
+--    api: (String) api id
+-- }
+function _M.deleteSubscription()
+  -- Validate body and create redisKey
+  local redisKey = validateSubscriptionBody()
+  -- Initialize and connect to redis
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  -- Return if subscription doesn't exist
+  redis.deleteSubscription(red, redisKey)
+  -- Add current redis connection in the ngx_lua cosocket connection pool
+  redis.close(red)
+  request.success(200, "Subscription deleted.")
+end
+
+--- Check the request JSON body for correct fields
+-- @return redisKey subscription key for redis
+function validateSubscriptionBody()
+  -- Read in the PUT JSON Body
+  ngx.req.read_body()
+  local args = ngx.req.get_post_args()
+  if not args then
+    request.err(400, "Missing request body.")
+  end
+  -- Convert json into Lua table
+  local decoded
+  if next(args) then
+    decoded = utils.convertJSONBody(args)
+  else
+    request.err(400, "Request body required.")
+  end
+  -- Check required fields
+  local requiredFieldList = {"key", "scope", "tenant"}
+  for i, field in ipairs(requiredFieldList) do
+    if not decoded[field] then
+      request.err(400, utils.concatStrings({"\"", field, "\" missing from request body."}))
+    end
+  end
+  -- Check if we're using tenant or resource or api
+  local resource = decoded.resource
+  local apiId = decoded.apiId
+  local redisKey
+  local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenant})
+  if decoded.scope == "tenant" then
+    redisKey = prefix
+  elseif decoded.scope == "resource" then
+    if resource ~= nil then
+      redisKey = utils.concatStrings({prefix, ":resource:", resource})
+    else
+      request.err(400, "\"resource\" missing from request body.")
+    end
+  elseif decoded.scope == "api" then
+    if apiId ~= nil then
+      redisKey = utils.concatStrings({prefix, ":api:", apiId})
+    else
+      request.err(400, "\"apiId\" missing from request body.")
+    end
+  else 
+    request.err(400, "Invalid scope")
+  end
+  redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
+  return redisKey
+end
+
+--- Parse the request uri to get the redisKey, tenant, and gatewayPath
+-- @param requestURI String containing the uri in the form of "/resources/<tenant>/<path>"
+-- @return list containing redisKey, tenant, gatewayPath
+function parseRequestURI(requestURI)
+  local list = {}
+  for i in string.gmatch(requestURI, '([^/]+)') do
+    list[#list + 1] = i
+  end
+  if not list[1] or not list[2] then
+    request.err(400, "Request path should be \"/resources/<tenant>/<url-encoded-resource>\"")
+  end
+
+  return list  --prefix, tenant, gatewayPath, apiKey
+end
+
+return _M
diff --git a/api-gateway-config/scripts/lua/policies/mapping.lua b/api-gateway-config/scripts/lua/policies/mapping.lua
new file mode 100644
index 0000000..6748fe3
--- /dev/null
+++ b/api-gateway-config/scripts/lua/policies/mapping.lua
@@ -0,0 +1,243 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module mapping
+-- Process mapping object, turning implementation details into request transformations
+-- @author Cody Walker (cmwalker), Alex Song (songs), David Green (greend)
+
+local logger = require "lib/logger"
+local utils = require "lib/utils"
+local cjson = require "cjson"
+
+local _M = {}
+
+local body = nil
+local query = nil
+local headers = nil
+local path = nil
+
+--- Implementation for the mapping policy.
+-- @param map The mapping object that contains details about request tranformations
+function processMap(map)
+  getRequestParams()
+  for k, v in pairs(map) do
+    if v.action == "insert" then
+      insertParam(v)
+    elseif v.action == "remove" then
+      removeParam(v)
+    elseif v.action == "transform" then
+      transformParam(v)
+    elseif v.action == "default" then
+      checkDefault(v)
+    else
+      logger.err(utils.concatStrings({'Map action not recognized. Skipping... ', v.action}))
+    end
+  end
+  finalize()
+end
+
+--- Get request body, params, and headers from incoming requests
+function getRequestParams()
+  ngx.req.read_body()
+  body = ngx.req.get_body_data()
+  body = (body and cjson.decode(body)) or {}
+  headers = ngx.req.get_headers()
+  path = ngx.var.uri
+  query = parseUrl(ngx.var.backendUrl)
+  local incomingQuery = ngx.req.get_uri_args()
+  for k, v in pairs (incomingQuery) do
+    query[k] = v
+  end
+end
+
+--- Insert parameter value to header, body, or query params into request
+-- @param m Parameter value to add to request
+function insertParam(m)
+  local v = nil
+  local k = m.to.name
+  if m.from.value ~= nil then
+    v = m.from.value
+  elseif m.from.location == 'header' then
+    v = headers[m.from.name]
+  elseif m.from.location == 'query' then
+    v = query[m.from.name]
+  elseif m.from.location == 'body' then
+    v = body[m.from.name]
+  elseif m.from.location == 'path' then
+    v = ngx.var[utils.concatStrings({'path_', m.from.name})]
+  end
+  -- determine to where
+  if m.to.location == 'header' then
+    insertHeader(k, v)
+  elseif m.to.location == 'query' then
+    insertQuery(k, v)
+  elseif m.to.location == 'body' then
+    insertBody(k, v)
+  elseif m.to.location == 'path' then
+    insertPath(k,v)
+  end
+end
+
+--- Remove parameter value to header, body, or query params from request
+-- @param m Parameter value to remove from request
+function removeParam(m)
+  if m.from.location == "header" then
+    removeHeader(m.from.name)
+  elseif m.from.location == "query" then
+    removeQuery(m.from.name)
+  elseif m.from.location == "body" then
+    removeBody(m.from.name)
+  end
+end
+
+--- Move parameter value from one location to another in the request
+-- @param m Parameter value to move within request
+function transformParam(m)
+  if m.from.name == '*' then
+    transformAllParams(m.from.location, m.to.location)
+  else
+    insertParam(m)
+    removeParam(m)
+  end
+end
+
+--- Checks if the header has been set, and sets the header to a value if found to be null.
+-- @param m Header name and value to be set, if header is null.
+function checkDefault(m)
+  if m.to.location == "header" and headers[m.to.name] == nil then
+    insertHeader(m.to.name, m.from.value)
+  elseif m.to.location == "query" and query[m.to.name] == nil then
+    insertQuery(m.to.name, m.from.value)
+  elseif m.to.location == "body" and body[m.to.name] == nil then
+    insertBody(m.to.name, m.from.value)
+  end
+end
+
+--- Function to handle wildcarding in the transform process.
+-- If the value in the from object is '*', this function will pull all values from the incoming request
+-- and move them to the location provided in the to object
+-- @param s The source object from which we pull all parameters
+-- @param d The destination object that we will move all found parameters to.
+function transformAllParams(s, d)
+  if s == 'query' then
+    for k, v in pairs(query) do
+      local t = {}
+      t.from = {}
+      t.from.name = k
+      t.from.location = s
+      t.to = {}
+      t.to.name = k
+      t.to.location = d
+      insertParam(t)
+      removeParam(t)
+    end
+  elseif s == 'header' then
+    for k, v in pairs(headers) do
+      local t = {}
+      t.from = {}
+      t.from.name = k
+      t.from.location = s
+      t.to = {}
+      t.to.name = k
+      t.to.location = d
+      insertParam(t)
+      removeParam(t)
+    end
+  elseif s == 'body' then
+    for k, v in pairs(body) do
+      local t = {}
+      t.from = {}
+      t.from.name = k
+      t.from.location = s
+      t.to = {}
+      t.to.name = k
+      t.to.location = d
+      insertParam(t)
+      removeParam(t)
+    end
+  elseif s == 'path' then
+    for k, v in pairs(path) do
+      local t = {}
+      t.from = {}
+      t.from.name = k
+      t.from.location = s
+      t.to = {}
+      t.to.name = k
+      t.to.location = d
+      insertParam(t)
+      removeParam(t)
+    end
+  end
+end
+
+function finalize()
+  local bodyJson = cjson.encode(body)
+  ngx.req.set_body_data(bodyJson)
+  ngx.req.set_uri_args(query)
+end
+
+function insertHeader(k, v)
+  ngx.req.set_header(k, v)
+  headers[k] = v
+end
+
+function insertQuery(k, v)
+  query[k] = v
+end
+
+function insertBody(k, v)
+  body[k] = v
+end
+
+function insertPath(k, v)
+  v = ngx.unescape_uri(v)
+  local primedUri = path:gsub("%{(%w*)%}", v)
+  ngx.req.set_uri(primedUri)
+end
+
+function removeHeader(k)
+  ngx.req.clear_header(k)
+end
+
+function removeQuery(k)
+  query[k] = nil
+end
+
+function removeBody(k)
+  body[k] = nil
+end
+
+function parseUrl(url)
+  local map = {}
+  for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do
+    map[ k ] = decodeQuery(v)
+  end
+  return map
+end
+
+function decodeQuery(param)
+  local decoded = param:gsub('+', ' '):gsub('%%(%x%x)',
+    function(hex) return string.char(tonumber(hex, 16)) end)
+  return decoded
+end
+
+_M.processMap = processMap
+
+return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/policies/rateLimit.lua b/api-gateway-config/scripts/lua/policies/rateLimit.lua
new file mode 100644
index 0000000..71a763b
--- /dev/null
+++ b/api-gateway-config/scripts/lua/policies/rateLimit.lua
@@ -0,0 +1,73 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module rateLimit
+-- Process a rateLimit object, setting the rate and interval to the request
+-- @author David Green (greend), Alex Song (songs)
+
+local REDIS_HOST = os.getenv("REDIS_HOST")
+local REDIS_PORT = os.getenv("REDIS_PORT")
+local REDIS_PASS = os.getenv("REDIS_PASS")
+local req = require "lib/resty/limit/req"
+local utils = require "lib/utils"
+local logger = require "lib/logger"
+local request = require "lib/request"
+
+local _M = {}
+
+--- Limit a resource/api/tenant, based on the passed in rateLimit object
+-- @param obj rateLimit object containing interval, rate, scope, subscription fields
+-- @param apiKey optional api key to use if subscription set to true
+function limit(obj, apiKey)
+  local i = 60 / obj.interval
+  local r = i * obj.rate
+  r = utils.concatStrings({tostring(r), 'r/m'})
+  local k
+  -- Check scope
+  if obj.scope == 'tenant' then
+    k = utils.concatStrings({"tenant:", ngx.var.tenant})
+  elseif obj.scope == 'api' then
+    k = utils.concatStrings({"tenant:", ngx.var.tenant, ":api:", ngx.var.apiId})
+  elseif obj.scope == 'resource' then
+    k = utils.concatStrings({"tenant:", ngx.var.tenant, ":resource:", ngx.var.gatewayPath})
+  end
+  -- Check subscription
+  if obj.subscription ~= nil and obj.subscription == true and apiKey ~= nil then
+    k = utils.concatStrings({k, ':subscription:', apiKey})
+  end
+  -- Perform rate limiting
+  local config = {
+    key = k,
+    zone = 'rateLimiting',
+    rate = r,
+    interval = obj.interval,
+    log_level = ngx.NOTICE,
+    rds = {host = REDIS_HOST, port = REDIS_PORT, pass = REDIS_PASS}
+  }
+  local ok = req.limit(config)
+  if not ok then
+    logger.err('Rate limit exceeded. Sending 429')
+    request.err(429, 'Rate limit exceeded.')
+  end
+end
+
+_M.limit = limit
+
+return _M
diff --git a/api-gateway-config/scripts/lua/policies/security.lua b/api-gateway-config/scripts/lua/policies/security.lua
new file mode 100644
index 0000000..8b845fc
--- /dev/null
+++ b/api-gateway-config/scripts/lua/policies/security.lua
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module mapping
+-- Process mapping object, turning implementation details into request transformations
+-- @author Cody Walker (cmwalker), Alex Song (songs)
+
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+local request = require "lib/request"
+
+local REDIS_HOST = os.getenv("REDIS_HOST")
+local REDIS_PORT = os.getenv("REDIS_PORT")
+local REDIS_PASS = os.getenv("REDIS_PASS")
+
+local _M = {}
+
+--- Validate that the given subscription is in redis
+-- @param tenant the namespace
+-- @param gatewayPath the gateway path to use, if scope is resource
+-- @param apiId api Id to use, if scope is api
+-- @param scope scope of the subscription
+-- @param apiKey the subscription api key
+-- @param return boolean value indicating if the subscription exists in redis
+function validateAPIKey(tenant, gatewayPath, apiId, scope, apiKey)
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local k
+  if scope == 'tenant' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant})
+  elseif scope == 'resource' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':resource:', gatewayPath})
+  elseif scope == 'api' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':api:', apiId})
+  end
+  k = utils.concatStrings({k, ':key:', apiKey})
+  local exists = red:exists(k)
+  redis.close(red)
+  return exists == 1
+end
+
+--- Process the security object
+-- @param securityObj security object from nginx conf file
+-- @return apiKey api key for the subscription
+function processAPIKey(securityObj)
+  local tenant = ngx.var.tenant
+  local gatewayPath = ngx.var.gatewayPath
+  local apiId = ngx.var.apiId
+  local scope = securityObj.scope
+  local header = (securityObj.header == nil) and 'x-api-key' or securityObj.header
+  local apiKey = ngx.var[utils.concatStrings({'http_', header}):gsub("-", "_")]
+  if not apiKey then
+    request.err(401, utils.concatStrings({'API key header "', header, '" is required.'}))
+  end
+  local ok = validateAPIKey(tenant, gatewayPath, apiId, scope, apiKey)
+  if not ok then
+    request.err(401, 'Invalid API key.')
+  end
+  return apiKey
+end
+
+_M.processAPIKey = processAPIKey
+
+return _M
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
new file mode 100644
index 0000000..d1b45ec
--- /dev/null
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -0,0 +1,129 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module Routing
+-- Used to dynamically handle nginx routing based on an object containing implementation details
+-- @author Cody Walker (cmwalker), Alex Song (songs)
+
+
+local utils = require "lib/utils"
+local request = require "lib/request"
+local url = require "url"
+-- load policies
+local security = require "policies/security"
+local mapping = require "policies/mapping"
+local rateLimit = require "policies/rateLimit"
+
+local _M = {}
+
+--- Main function that handles parsing of invocation details and carries out implementation
+-- @param obj Lua table object containing implementation details for the given resource
+-- {
+--   {{GatewayMethod (GET / PUT / POST / DELETE)}} = {
+--      "backendMethod": (GET / PUT / POST / DELETE) - Method to use for invocation (if different from gatewayMethod),
+--      "backendUrl": STRING - fully composed url of backend invocation,
+--      "policies": LIST - list of table objects containing type and value fields
+--    }, ...
+-- }
+function processCall(obj)
+  local found = false
+  for verb, opFields in pairs(obj.operations) do
+    if string.upper(verb) == ngx.req.get_method() then
+      -- Check if auth is required
+      local apiKey
+      if (opFields.security and opFields.security.type ~= nil and string.lower(opFields.security.type) == 'apikey') then
+        apiKey = security.processAPIKey(opFields.security)
+      end
+      -- Parse backend url
+      local u = url.parse(opFields.backendUrl)
+      ngx.req.set_uri(getUriPath(u.path))
+      ngx.var.backendUrl = opFields.backendUrl
+      -- Set upstream - add port if it's in the backendURL
+      local upstream = utils.concatStrings({u.scheme, '://', u.host})
+      if u.port ~= nil and u.port ~= '' then
+        upstream = utils.concatStrings({upstream, ':', u.port})
+      end
+      ngx.var.upstream = upstream
+      -- Set backend method
+      if opFields.backendMethod ~= nil then
+        setVerb(opFields.backendMethod)
+      end
+      -- Parse policies
+      if opFields.policies ~= nil then
+        parsePolicies(opFields.policies, apiKey)
+      end
+      found = true
+      break
+    end
+  end
+  if found == false then
+    request.err(404, 'Whoops. Verb not supported.')
+  end
+end
+
+--- Function to read the list of policies and send implementation to the correct backend
+-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
+-- @param apiKey optional subscription api key
+function parsePolicies(obj, apiKey)
+  for k, v in pairs (obj) do
+    if v.type == 'reqMapping' then
+      mapping.processMap(v.value)
+    elseif v.type == 'rateLimit' then
+      rateLimit.limit(v.value, apiKey)
+    end
+  end
+end
+
+--- Given a verb, transforms the backend request to use that method
+-- @param v Verb to set on the backend request
+function setVerb(v)
+  if (string.lower(v) == 'post') then
+    ngx.req.set_method(ngx.HTTP_POST)
+  elseif (string.lower(v) == 'put') then
+    ngx.req.set_method(ngx.HTTP_PUT)
+  elseif (string.lower(v) == 'delete') then
+    ngx.req.set_method(ngx.HTTP_DELETE)
+  elseif (string.lower(v) == 'patch') then
+    ngx.req.set_method(ngx.HTTP_PATCH)
+  elseif (string.lower(v) == 'head') then
+    ngx.req.set_method(ngx.HTTP_HEAD)
+  elseif (string.lower(v) == 'options') then
+    ngx.req.set_method(ngx.HTTP_OPTIONS)
+  else
+    ngx.req.set_method(ngx.HTTP_GET)
+  end
+end
+
+function getUriPath(backendPath)
+  local uriPath
+  local i, j = ngx.var.uri:find(ngx.var.gatewayPath)
+  local incomingPath = ((j and ngx.var.uri:sub(j + 1)) or nil)
+  -- Check for backendUrl path
+  if backendPath == nil or backendPath== '' or backendPath== '/' then
+    uriPath = (incomingPath and incomingPath ~= '') and incomingPath or '/'
+  else
+    uriPath = utils.concatStrings({backendPath, incomingPath})
+  end
+  return uriPath
+end
+
+_M.processCall = processCall
+
+return _M
diff --git a/api-gateway-config/tests/fakengx.lua b/api-gateway-config/tests/fakengx.lua
new file mode 100644
index 0000000..add7f71
--- /dev/null
+++ b/api-gateway-config/tests/fakengx.lua
@@ -0,0 +1,526 @@
+-- Copyright (c) 2012 Dimitrij Denissenko
+--
+--  Permission is hereby granted, free of charge, to any person obtaining a copy of
+--  this software and associated documentation files (the "Software"), to deal in
+--  the Software without restriction, including without limitation the rights to
+--  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+--  of the Software, and to permit persons to whom the Software is furnished to do
+--  so, subject to the following conditions:
+--
+--  The above copyright notice and this permission notice shall be included in all
+--  copies or substantial portions of the Software.
+--
+--  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+--  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+--  SOFTWARE.
+
+local bit    = require 'bit'
+local socket = require 'socket'
+local sha1   = require 'sha1'
+local md5    = require 'md5'
+local mime   = require 'mime'
+local CRC32  = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }
+
+-- Helpers
+local encode_param = function(str)
+  return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_])", function (c)
+    return string.format("%%%02X", string.byte(c))
+  end)
+end
+
+local encode_params = function(tab)
+  local list = {}
+  for k, v in pairs(tab) do
+    table.insert(list, encode_param(k) .. "=" .. encode_param(v))
+  end
+  return table.concat(list, "&")
+end
+
+local function reverse_merge(src, defs)
+  local opts = {}
+  for k,v in pairs(src) do opts[k] = v end
+  for k,v in pairs(defs) do
+    if src[k] then v = src[k] end
+    opts[k] = v
+  end
+  return opts
+end
+
+local function stub_options(opts, method)
+  opts = opts or {}
+  if method and opts["method"] == nil then opts["method"] = method end
+  if type(opts.args) == "table" then opts["args"] = encode_params(opts.args) end
+  return opts
+end
+
+local function stub_response(res)
+  return reverse_merge(res or {},  { status = 200, headers = {}, body = "" })
+end
+
+local control_chars = {
+  ["\a"] = "\\a",  ["\b"] = "\\b", ["\f"] = "\\f",  ["\n"] = "\\n",
+  ["\r"] = "\\r",  ["\t"] = "\\t", ["\v"] = "\\v",  ["\\"] = "\\\\"
+}
+
+local function replace_control_char(c)
+  return control_chars[c]
+end
+
+local function stub_format(uri, opts)
+  local pad = 0
+  for k,_ in pairs(opts) do
+    if k ~= "method" and #k > pad then pad = #k end
+  end
+
+  local msg = "  " .. (opts.method or "GET") .. " " .. uri .. "\n"
+  for k,v in pairs(opts) do
+    if k ~= "method" then
+      k   = k .. ":" .. string.rep(" ", pad + 1 - #k)
+      msg = msg .. "  " .. k .. v:gsub("(%c)", replace_control_char) .. "\n"
+    end
+  end
+  return msg
+end
+
+-- Capture Registry
+local Captures = {}
+
+function Captures:new()
+  local this = { stubs = {} }
+  setmetatable(this, { __index = self })
+  return this
+end
+
+function Captures:length()
+  return #self.stubs
+end
+
+function Captures:each(fun)
+  for i=self:length(),1,-1 do
+    local stub = self.stubs[i]
+    fun(stub)
+  end
+end
+
+function Captures:find(uri, opts)
+  opts = stub_options(opts, "GET")
+
+  for i=self:length(),1,-1 do
+    local stub = self.stubs[i]
+    if uri == stub.uri then
+      local is_match = true
+
+      for k,v in pairs(stub.opts) do
+        if type(v) == 'function' then
+          is_match = v(opts[k])
+        elseif type(v) == 'string' and v:sub(1, 2) == "~>" then
+          is_match = tostring(opts[k]):match(v:sub(3)) and true
+        elseif opts[k] ~= v then
+          is_match = false
+        end
+        if not is_match then break end
+      end
+      if is_match then return stub end
+    end
+  end
+
+  return nil
+end
+
+function Captures:stub(uri, opts, res)
+  local stub = { uri = uri, opts = stub_options(opts), res = stub_response(res), calls = {} }
+  table.insert(self.stubs, stub)
+  return stub
+end
+
+-- TCP Proxy
+local TCP = {}
+
+function TCP:new()
+  return setmetatable({ host = nil, port = 0, timeout = 0, keepalive = {-1, 0}, data = {} }, { __index = self })
+end
+
+function TCP:connect(host, port)
+  self.host = host
+  self.port = port
+  return true, nil
+end
+
+function TCP:settimeout(value)
+  self.timeout = value
+end
+
+function TCP:setkeepalive(...)
+  self.keepalive = {...}
+end
+
+function TCP:send(msg)
+  table.insert(self.data, msg)
+end
+
+-- UDP Proxy
+local UDP = {}
+
+function UDP:new()
+  return setmetatable({ host = nil, port = 0, timeout = 0, data = {}, closed = false }, { __index = self })
+end
+
+function UDP:setpeername(host, port)
+  self.host = host
+  self.port = port
+  return true, nil
+end
+
+function UDP:settimeout(value)
+  self.timeout = value
+end
+
+function UDP:send(msg)
+  table.insert(self.data, msg)
+  return true, nil
+end
+
+function UDP:close()
+  self.closed = true
+  return true, nil
+end
+
+-- DICT Proxy
+local SharedDict = {}
+
+function SharedDict:new()
+  return setmetatable({ data = {} }, { __index = self })
+end
+
+function SharedDict:get(key)
+  return self.data[key], 0
+end
+
+function SharedDict:set(key, value)
+  self.data[key] = value
+  return true, nil, false
+end
+
+function SharedDict:add(key, value)
+  if self.data[key] ~= nil then
+    return false, "exists", false
+  end
+
+  self.data[key] = value
+  return true, nil, false
+end
+
+function SharedDict:replace(key, value)
+  if self.data[key] == nil then
+    return false, "not found", false
+  end
+
+  self.data[key] = value
+  return true, nil, false
+end
+
+function SharedDict:delete(key)
+  self.data[key] = nil
+end
+
+function SharedDict:incr(key, value)
+  if not self.data[key] then
+    return nil, "not found"
+  elseif type(self.data[key]) ~= "number" then
+    return nil, "not a number"
+  end
+
+  self.data[key] = self.data[key] + value
+  return self.data[key], nil
+end
+
+-- NGX Prototype
+local protoype = {
+
+  -- Log constants
+  STDERR = 0,
+  EMERG  = 1,
+  ALERT  = 2,
+  CRIT   = 3,
+  ERR    = 4,
+  WARN   = 5,
+  NOTICE = 6,
+  INFO   = 7,
+  DEBUG  = 8,
+
+  -- HTTP Method Constants
+  HTTP_GET    = "GET",
+  HTTP_HEAD   = "HEAD",
+  HTTP_POST   = "POST",
+  HTTP_PUT    = "PUT",
+  HTTP_DELETE = "DELETE",
+
+  -- HTTP Status Constants
+  HTTP_OK                        = 200,
+  HTTP_CREATED                   = 201,
+  HTTP_ACCEPTED                  = 202,
+  HTTP_NO_CONTENT                = 204,
+  HTTP_PARTIAL_CONTENT           = 206,
+  HTTP_SPECIAL_RESPONSE          = 300,
+  HTTP_MOVED_PERMANENTLY         = 301,
+  HTTP_MOVED_TEMPORARILY         = 302,
+  HTTP_SEE_OTHER                 = 303,
+  HTTP_NOT_MODIFIED              = 304,
+  HTTP_BAD_REQUEST               = 400,
+  HTTP_UNAUTHORIZED              = 401,
+  HTTP_FORBIDDEN                 = 403,
+  HTTP_NOT_FOUND                 = 404,
+  HTTP_NOT_ALLOWED               = 405,
+  HTTP_REQUEST_TIME_OUT          = 408,
+  HTTP_CONFLICT                  = 409,
+  HTTP_LENGTH_REQUIRED           = 411,
+  HTTP_PRECONDITION_FAILED       = 412,
+  HTTP_REQUEST_ENTITY_TOO_LARGE  = 413,
+  HTTP_REQUEST_URI_TOO_LARGE     = 414,
+  HTTP_UNSUPPORTED_MEDIA_TYPE    = 415,
+  HTTP_RANGE_NOT_SATISFIABLE     = 416,
+  HTTP_CLOSE                     = 444,
+  HTTP_NGINX_CODES               = 494,
+  HTTP_REQUEST_HEADER_TOO_LARGE  = 494,
+  HTTP_INTERNAL_SERVER_ERROR     = 500,
+  HTTP_NOT_IMPLEMENTED           = 501,
+  HTTP_BAD_GATEWAY               = 502,
+  HTTP_SERVICE_UNAVAILABLE       = 503,
+  HTTP_GATEWAY_TIME_OUT          = 504,
+  HTTP_INSUFFICIENT_STORAGE      = 507,
+
+}
+
+-- NGX Builder
+local fakengx = {}
+
+-- Constructor
+function fakengx.new()
+  local ngx = {}
+  for k, v in pairs(protoype) do
+    ngx[k] = v
+  end
+  setmetatable(ngx, getmetatable(protoype))
+
+  -- Create namespaces
+  ngx.req       = {}
+  ngx.re        = {}
+  ngx.socket    = {}
+  ngx.thread    = {}
+  ngx.location  = {}
+  ngx.shared    = {}
+
+  -- Create shared dict API
+  setmetatable(ngx.shared, {
+    __index = function(t, k)
+      t[k] = SharedDict:new()
+      return t[k]
+    end
+  })
+
+  function ngx._reset()
+    ngx.status    = 200
+    ngx.var       = {}
+    ngx.ctx       = {}
+    ngx.header    = {}
+    ngx.arg       = {}
+
+    -- Internal Registries
+    ngx._captures = Captures:new()
+    ngx._sockets  = {}
+    ngx._body     = ""
+    ngx._log      = ""
+    ngx._exit     = nil
+
+    for k,_ in pairs(ngx.shared) do
+      ngx.shared[k] = nil
+    end
+  end
+
+  -- Reset once
+  ngx._reset()
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.print
+  function ngx.print(s)
+    ngx._body = ngx._body .. s
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.say
+  function ngx.say(s)
+    ngx.print(s .. "\n")
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.log
+  function ngx.log(level, ...)
+    local args = {...}
+    for i=1,#args do args[i] = tostring(args[i]) or "nil" end
+    ngx._log = ngx._log .. "LOG(" .. tostring(level) .. "): " .. table.concat(args) .. "\n"
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.time
+  function ngx.time()
+    if not ngx._time then
+      ngx._time = os.time()
+    end
+    return ngx._time
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.update_time
+  function ngx.update_time()
+    ngx._time = nil
+    ngx._now = nil
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.now
+  function ngx.now()
+    if not ngx._now then
+      ngx._now = socket.gettime()
+    end
+    return ngx._now
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.cookie_time
+  function ngx.cookie_time(t)
+    return os.date('!%a, %d-%b-%Y %H:%M:%S GMT', t)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.exit
+  function ngx.exit(status)
+    if status > ngx.status then ngx.status = status end
+    ngx._exit = status
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.crc32_short
+  function ngx.crc32_short(s)
+    local crc, l, i = 0xFFFFFFFF, string.len(s)
+    for i = 1, l, 1 do
+     crc = bit.bxor(bit.rshift(crc, 8), CRC32[bit.band(bit.bxor(crc, string.byte(s, i)), 0xFF) + 1])
+    end
+    return bit.bxor(crc, -1) % 2^32
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.hmac_sha1
+  function ngx.hmac_sha1(secret_key, str)
+    return sha1.hmac_sha1_binary(secret_key, str)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.sha1_bin
+  function ngx.sha1_bin(str)
+    return sha1.sha1_binary(str)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.md5
+  function ngx.md5(str)
+    return md5.sumhexa(str)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.md5_bin
+  function ngx.md5_bin(str)
+    return md5.sum(str)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.escape_uri
+  function ngx.escape_uri(str)
+    return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_ ])", function (c)
+      return string.format("%%%02X", string.byte(c))
+    end):gsub(" ", "+")
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.unescape_uri
+  function ngx.unescape_uri(str)
+    return tostring(str):gsub("+", " "):gsub("\r\n", "\n"):gsub("%%(%x%x)", function(h)
+      return string.char(tonumber(h,16))
+    end)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_args
+  function ngx.encode_args(tab)
+    return encode_params(tab)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture
+  function ngx.location.capture(uri, opts)
+    local stub = ngx._captures:find(uri, opts)
+    if not stub then
+      local msg = "\n\nUnstubbed request:\n\n" .. stub_format(uri, opts or {}) .. "\nStubbed were:\n"
+      ngx._captures:each(function(stub)
+        msg = msg .. "\n" .. stub_format(stub.uri, stub.opts or {})
+      end)
+      error(msg)
+    end
+
+    table.insert(stub.calls, { uri = uri, opts = opts })
+    return stub.res
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture_multi
+  function ngx.location.capture_multi(...)
+    local requests  = ...
+    local responses = {}
+    for i, request in ipairs(requests) do
+      table.insert(responses, ngx.location.capture(request[1], request[2]))
+    end
+    return unpack(responses)
+  end
+
+  -- Stub a capture
+  function ngx.location.stub(...)
+    return ngx._captures:stub(...)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.req.read_body
+  function ngx.req.read_body()
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.tcp
+  function ngx.socket.tcp()
+    local sock = TCP:new()
+    table.insert(ngx._sockets, sock)
+    return sock
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.udp
+  function ngx.socket.udp()
+    local sock = UDP:new()
+    table.insert(ngx._sockets, sock)
+    return sock
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_base64
+  function ngx.encode_base64(s)
+    return mime.b64(s)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.decode_base64
+  function ngx.decode_base64(s)
+    return mime.unb64(s)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.spawn
+  function ngx.thread.spawn(fun, ...)
+    return { fun = fun, args = {...} }
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.wait
+  function ngx.thread.wait(thread)
+    return true, thread.fun(unpack(thread.args))
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.re.gmatch
+  function ngx.re.gmatch(s, pattern)
+    return string.gmatch(s, pattern)
+  end
+
+  -- http://wiki.nginx.org/HttpLuaModule#ngx.re.match
+  function ngx.re.match(s, pattern)
+    return string.match(s, pattern)
+  end
+
+  return ngx
+end
+
+return fakengx
diff --git a/api-gateway-config/tests/install-deps.sh b/api-gateway-config/tests/install-deps.sh
new file mode 100755
index 0000000..d44356a
--- /dev/null
+++ b/api-gateway-config/tests/install-deps.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Install global dependencies
+luarocks install busted
+luarocks install luacov
+# Install test dependencies
+mkdir -p lua_modules
+luarocks install --tree lua_modules lua-cjson
+luarocks install --tree lua_modules luabitop
+luarocks install --tree lua_modules luasocket
+luarocks install --tree lua_modules sha1
+luarocks install --tree lua_modules md5
+luarocks install --tree lua_modules fakeredis
\ No newline at end of file
diff --git a/api-gateway-config/tests/run-tests.sh b/api-gateway-config/tests/run-tests.sh
new file mode 100755
index 0000000..7d27b35
--- /dev/null
+++ b/api-gateway-config/tests/run-tests.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Run unit tests
+busted -c --output=TAP --helper=set_paths spec/test.lua
+
+# Generate code coverage report
+luacov ../scripts/lua/
+cat luacov.report.out
+rm luacov.report.out && rm luacov.stats.out
\ No newline at end of file
diff --git a/api-gateway-config/tests/set_paths.lua b/api-gateway-config/tests/set_paths.lua
new file mode 100644
index 0000000..bdc8afb
--- /dev/null
+++ b/api-gateway-config/tests/set_paths.lua
@@ -0,0 +1,11 @@
+-- add lua_modules to package.path and package.cpath
+local version = _VERSION:match("%d+%.%d+")
+local f = assert(io.popen('pwd', 'r'))
+local pwd = assert(f:read('*a')):sub(1, -2)
+f:close()
+package.path = package.path ..
+    ';' .. pwd .. '/lua_modules/share/lua/' .. version .. '/?.lua' ..
+    ';' .. pwd .. '/lua_modules/share/lua/' .. version .. '/?/init.lua' ..
+    ';' .. pwd .. '/../scripts/lua/?.lua'
+package.cpath = package.cpath ..
+    ';' .. pwd .. '/lua_modules/lib/lua/' .. version .. '/?.so'
\ No newline at end of file
diff --git a/api-gateway-config/tests/spec/test.lua b/api-gateway-config/tests/spec/test.lua
new file mode 100644
index 0000000..b3e88a0
--- /dev/null
+++ b/api-gateway-config/tests/spec/test.lua
@@ -0,0 +1,247 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+-- Unit tests for the apigateway using the busted framework.
+-- @author Alex Song (songs)
+
+local fakengx = require 'fakengx'
+local fakeredis = require 'fakeredis'
+local cjson = require 'cjson'
+local request = require 'lib/request'
+local utils = require 'lib/utils'
+local logger = require 'lib/logger'
+local redis = require 'lib/redis'
+local mapping = require 'policies/mapping'
+
+
+------------------------------------
+---- Unit tests for lib modules ----
+------------------------------------
+
+describe('Testing Request module', function()
+  before_each(function()
+    _G.ngx = fakengx.new()
+  end)
+
+  it('should return correct error response', function()
+    local code = 500
+    local msg = 'Internal server error\n'
+    request.err(code, msg)
+    assert.are.equal(ngx._body, 'Error: ' .. msg)
+    assert.are.equal(ngx._exit, code)
+  end)
+
+  it('should return correct success response', function()
+    local code = 200
+    local msg ='Success!\n'
+    request.success(code, msg)
+    assert.are.equal(ngx._body, msg)
+    assert.are.equal(ngx._exit, code)
+  end)
+end)
+
+
+describe('Testing utils module', function()
+  before_each(function()
+    _G.ngx = fakengx.new()
+  end)
+
+  it('should concatenate strings properly', function()
+    local expected = 'hello' .. 'gateway' .. 'world'
+    local generated = utils.concatStrings({'hello', 'gateway', 'world'})
+    assert.are.equal(expected, generated)
+  end)
+
+  it('should serialize lua table', function()
+    -- Empty table
+    local expected = {}
+    local serialized = utils.serializeTable(expected)
+    loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+    assert.are.same(expected, generated)
+
+    -- Simple table
+    expected = {
+      test = true
+    }
+    serialized = utils.serializeTable(expected)
+    loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+    assert.are.same(expected, generated)
+
+    -- Complex nested table
+    expected = {
+      test1 = {
+        nested = 'value'
+      },
+      test2 = true
+    }
+    serialized = utils.serializeTable(expected)
+    loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+    assert.are.same(expected, generated)
+  end)
+
+  it('should convert templated path parameter', function()
+    -- TODO: Add test cases for convertTemplatedPathParam(m)
+  end)
+end)
+
+
+describe('Testing logger module', function()
+  it('Should handle error stream', function()
+    local msg = 'Error!'
+    logger.err(msg)
+    local expected = 'LOG(4): ' .. msg .. '\n'
+    local generated = ngx._log
+    assert.are.equal(expected, generated)
+  end)
+end)
+
+
+describe('Testing Redis module', function()
+  before_each(function()
+    _G.ngx = fakengx.new()
+    red = fakeredis.new()
+  end)
+
+  it('should generate resource object to store in redis', function()
+    -- Resource object with no policies or security
+    local apiId = 12345
+    local operations = {
+      GET = {
+        backendUrl = 'https://httpbin.org/get',
+        backendMethod = 'GET'
+      }
+    }
+    local resourceObj = {
+      apiId = apiId,
+      operations = operations
+    }
+    local expected = resourceObj
+    local generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+    assert.are.same(expected, generated)
+
+    -- Resource object with policy added
+    local policyList = [[
+      [{
+          "type":"rateLimit",
+          "value":[{
+              "interval":60,
+              "rate":100,
+              "scope":"api",
+              "subscription": "true"
+          }]
+      }]
+    ]]
+    resourceObj.operations.GET.policies = cjson.decode(policyList)
+    expected = resourceObj
+    generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+    assert.are.same(expected, generated)
+
+    -- Resource object with security added
+    local securityObj = [[
+      {
+        "type":"apiKey",
+        "scope":"api",
+        "header":"myheader"
+      }
+    ]]
+    resourceObj.operations.GET.security = cjson.decode(securityObj)
+    expected = resourceObj
+    generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+    assert.are.same(expected, generated)
+
+    -- Resource object with multiple operations
+    resourceObj.operations.PUT = {
+        backendUrl = 'https://httpbin.org/get',
+        backendMethod = 'PUT',
+        security = {}
+    }
+    expected = resourceObj
+    generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+    assert.are.same(expected, generated)
+  end)
+
+  it('should get a resource from redis', function()
+    local key = 'resources:guest:hello'
+    local field = 'resources'
+    -- resource doesn't exist in redis
+    local generated = redis.getResource(red, key, field)
+    assert.are.same(nil, generated)
+
+    -- resource exists in redis
+    local expected = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+    red:hset(key, field, expected)
+    generated = redis.getResource(red, key, field)
+    assert.are.same(expected, generated)
+  end)
+
+  it('should create a resource in redis', function()
+    local key = 'resources:guest:hello'
+    local field = 'resources'
+    local expected = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+    redis.createResource(red, key, field, expected)
+    local generated = redis.getResource(red, key, field)
+    assert.are.same(expected, generated)
+  end)
+
+  it('should delete a resource in redis', function()
+    -- Key doesn't exist - throw 404
+    local key = 'resources:guest:hello'
+    local field = 'resources'
+    redis.deleteResource(red, key, field)
+    assert.are.equal(ngx._exit, 404)
+    -- Key exists - deleted properly
+    local resourceObj = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+    redis.createResource(red, key, field, resourceObj)
+    local expected = 1
+    local generated = redis.deleteResource(red, key, field)
+    assert.are.same(expected, generated)
+  end)
+
+  it('shoud create an API Key subscription', function()
+    local key = 'subscriptions:test:apikey'
+    redis.createSubscription(red, key)
+    assert.are.same(true, red:exists(key))
+  end)
+
+  it('should delete an API Key subscription', function()
+    -- API key doesn't exist in redis - throw 404
+    local key = 'subscriptions:test:apikey'
+    redis.deleteSubscription(red, key)
+    assert.are.equal(404, ngx._exit)
+
+    -- API key to delete exists in redis
+    red:set(key, '')
+    redis.deleteSubscription(red, key)
+    assert.are.equal(false, red:exists(key))
+  end)
+end)
+
+--TODO: filemgmt
+
+---------------------------------------
+---- Unit tests for policy modules ----
+---------------------------------------
+
+--TODO: mapping, rateLimit, security
+describe('Testing mapping module', function()
+  before_each(function()
+    _G.ngx = fakengx.new()
+  end)
+end)
diff --git a/doc/policies.md b/doc/policies.md
new file mode 100644
index 0000000..6317547
--- /dev/null
+++ b/doc/policies.md
@@ -0,0 +1,124 @@
+Policies
+==============
+The following defines the different policies that can be used when creating an API. The currently supported policies are:
+`reqMapping`, `rateLimit`.
+
+
+###rateLimit:
+_interval:_ the time interval that the rate is applied to.  
+_rate:_ the number of calls allowed per interval of time.  
+_scope:_ `api`, `tenant`, `resource`.  
+_subscription:_ `true`, `false`.  
+If subscription is `true`, the rateLimit applies to each user with a vaild subscription.  
+If subscription is `false`, the rateLimit applies the collective usage from all users.  
+```
+  "interval":60,
+  "rate":10,
+  "scope":"api"
+  "subscription": "false"
+```
+This will set a rateLimit ratio of 10 calls per 60 second, at an API level.  
+This rateLimit is shared across all users (subescription:false).  
+
+###reqMapping:
+Supported actions: `remove`, `default`, `insert`, `transform`.  
+Supported locations: `body`, `path`, `header`, `query`.  
+
+_remove:_
+```
+{
+   "action":"remove",
+   "from":{
+      "value":"<password>"
+      "location":"body"
+   }
+}
+```
+This will remove the `password` field from the body of the incoming request, so it's not passed to the backendURL  
+
+_default:_  
+Only `body`, `header`, `query` parameters can have default values.  
+```
+{
+   "action":"default",
+   "from":{
+      "value":"BASIC XXX"
+   },
+   "to":{
+      "name":"Authorization",
+      "location":"header"
+   }
+}
+```
+This will assign the value of `BASIC XXX` to a `header` called `Authorization` but only if the value is not already set.  
+
+_insert:_
+```
+{
+   "action":"insert",
+   "from":{
+      "value":"application/json"
+   },
+   "to":{
+      "name":"Content-type",
+      "location":"header"
+   }
+}
+```
+This will insert the value of `application/json` into a `header` named `Content-type` on the backend request
+
+_transform:_
+```
+{
+   "action":"transform",
+   "from":{
+      "name":"*",
+      "location":"query"
+   },
+   "to":{
+      "name":"*",
+      "location":"body"
+   }
+}
+```
+This will transform all incoming `query` parameters into `body` parameters in the outgoing request to the backendURL.  
+Where `*` is a wild card, or you can use the variable name.  
+
+_Path Parameter Mappings:_  
+To map a path parameter from the incoming Url to a path parameter on the backend Url, you will need to wrap brackets `{}` around the path parameter on the incoming Url as well as the backend Url, for example:  
+`IP:Port/resources/tenant_id/serverless/{myAction}/restified`
+```
+"backendURL":"https://openwhisk.stage1.ng.bluemix.net/api/v1/namespaces/APIC-Whisk_test/actions/{ACTION}?blocking=true&result=true",
+"policies":
+  [{
+    "type": "reqMapping",
+    "value": [{
+        "action": "transform",
+        "from": {
+          "name": "myAction",
+          "location": "path"
+        },
+        "to": {
+          "name": "ACTION",
+          "location": "path"
+        }
+      }]
+  }]
+```
+If a path is then invoked on `/serverless/Hello World/restified`, then the value from `{myAction}`, which is `Hello World`, will be assigned to the variable `ACTION` on the backend path.
+
+
+##Security
+Supported types: `apiKey`.  
+_scope:_ `api`, `tenant`, `resource`.  
+_header:_ _(optional)_ custom name of auth header (default is x-api-key)  
+
+```
+"security": {
+        "type":"apiKey",
+        "scope":"api",
+        "header":"<MyCustomAuthHeader>"
+    }
+```
+This will add security of an `apiKey`, at the API level, and uses the header call `myCustomAuthHeader`.  
+NOTE: Security added at the Tenant level will affect all APIs and resources under that Tenant. Likewise, security added at the API level will affect all resources under that API.
\ No newline at end of file
diff --git a/doc/routes.md b/doc/routes.md
new file mode 100644
index 0000000..74ac384
--- /dev/null
+++ b/doc/routes.md
@@ -0,0 +1,276 @@
+Routes
+==============
+The following defines the interface for managing APIs and Tenants. These endpoints are exposed to port 9000.
+
+## APIs
+
+### PUT /apis
+Create a new API. Note that you should first create a tenant and obtain its `tenantId`. For API policy definitions, see [here](policies.md).
+
+_body_:
+```
+{
+  "name": *(string),
+  "basePath": *(string),
+  "tenantId": *(string),
+  "resources": {
+    "path": {
+      "operations": {
+        "get": {
+          "backendMethod": *(string),
+          "backendUrl": *(string),
+          "policies": [
+            {
+              "type": *(string),
+              "value": {}
+            }
+          ]
+        },
+        ...
+      }
+    }
+  }
+}
+```
+
+_returns:_
+```
+{
+  "id": (string),
+  "name": (string),
+  "basePath": (string),
+  "tenantId": (string),
+  "resources": {
+   ...
+  }
+}
+```
+
+### PUT /apis/{id}
+Update attributes for a given API.
+
+_body_:
+```
+{
+  "name": *(string),
+  "basePath": *(string),
+  "tenantId": *(string),
+  "resources": {
+    "path": {
+      "operations": {
+        "get": {
+          "backendMethod": *(string),
+          "backendUrl": *(string),
+          "policies": [
+            {
+              "type": *(string),
+              "value": {}
+            }
+          ]
+        },
+        ...
+      }
+    }
+  }
+}
+```
+
+_returns:_
+```
+{
+  "id": (string),
+  "name": (string),
+  "basePath": (string),
+  "tenantId": (string),
+  "resources": {
+   ...
+  }
+}
+```
+
+### GET /apis
+Find all instances of APIs added to the gateway.
+
+_returns:_
+```
+[
+  {
+    "id": (string),
+    "name": (string),
+    "basePath": (string),
+    "tenantId": (string),
+    "resources": {
+     ...
+    }
+  }
+]
+```
+
+### GET /apis/{id}
+Find an API by its id.
+
+_returns:_
+```
+{
+  "id": (string),
+  "name": (string),
+  "basePath": (string),
+  "tenantId": (string),
+  "resources": {
+   ...
+  }
+}
+```
+
+### GET /apis/{id}/tenant
+Find the tenant associated with this API.
+
+_returns:_
+```
+{
+ "id": (string),
+ "namespace" (string),
+ "instance" (string)
+}
+```
+
+
+### DELETE /apis/{id}
+Delete the API
+
+_returns:_
+```
+{}
+```
+
+## Tenants
+
+### PUT /tenants
+Create a new tenant.
+
+_body:_
+```
+{
+ "namespace": *(string),
+ "instance": *(string)
+}
+```
+_returns:_
+```
+{
+ "id": (string),
+ "namespace" (string),
+ "instance" (string)
+}
+```
+
+### PUT /tenants/{id}
+Update attributes for a given tenant.
+
+_body:_
+```
+{
+ "namespace": *(string),
+ "instance": *(string)
+}
+```
+_returns:_
+```
+{
+ "id": (string),
+ "namespace" (string),
+ "instance" (string)
+}
+```
+
+### GET /tenants
+Find all instances of tenants added to the gateway.
+
+_returns:_
+```
+[
+ {
+  "id": (string),
+  "namespace" (string),
+  "instance" (string)
+ }
+]
+```
+
+### GET /tenants/{id}
+Find a tenant by its id.
+
+_returns:_
+```
+{
+ "id": (string),
+ "namespace" (string),
+ "instance" (string)
+}
+```
+
+### DELETE /tenants/{id}
+Delete the tenant.
+
+_returns:_
+```
+{}
+```
+
+### GET /tenants/{id}/apis
+Get all APIs for the given tenant.
+
+_returns:_
+```
+[
+  {
+    "id": (string),
+    "name": (string),
+    "basePath": (string),
+    "tenantId": (string),
+    "resources": {
+     ...
+    }
+  }
+]
+```
+
+
+## Subscriptions
+### PUT /subscriptions
+Add/update an api key for the specified tenant, resource, or api.
+
+_body:_
+```
+{
+  "key": *(string) The api key to store to redis.
+  "scope": *(string) The scope to use the api key. "tenant", "resource", or "api".
+  "tenant": *(string) Tenant guid.
+  "resource": (string) Resource path. Required if scope is "resource".
+  "api": (string) API Guid. Required if scope is "API".
+}
+```
+
+_Returns:_
+```
+Subscription created.
+```
+
+### DELETE /subscriptions
+Delete an api key associated with the specified tenant, resource or api.
+
+_body:_
+```
+{
+  "key": *(string) The api key to delete.
+  "scope": *(string) The scope to use the api key. "tenant", "resource", or "api".
+  "tenant": *(string) Tenant guid.
+  "resource": (string) Resource path. Required if scope is "resource".
+  "api": (string) API Guid. Required if scope is "API".
+}
+```
+
+_Returns:_
+```
+Subscription deleted.
+```
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index c71a149..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-gateway:
-  image: adobeapiplatform/apigateway
-  links:
-    - redis:redis.docker
-  volumes:
-    - ~/tmp/apiplatform/apigateway/api-gateway-config/:/etc/api-gateway
-  ports:
-  - "80:80"
-  environment:
-  - LOG_LEVEL=info
-  - DEBUG=true
-redis:
-  image: redis:2.8
-  ports:
-  - "6379:6379"
diff --git a/init.sh b/init.sh
index 2c7967f..b81d4d1 100755
--- a/init.sh
+++ b/init.sh
@@ -24,37 +24,14 @@
 debug_mode=${DEBUG}
 log_level=${LOG_LEVEL:-warn}
 marathon_host=${MARATHON_HOST}
+redis_host=${REDIS_HOST}
+redis_port=${REDIS_PORT}
 sleep_duration=${MARATHON_POLL_INTERVAL:-5}
 # location for a remote /etc/api-gateway folder.
 # i.e s3://api-gateway-config
 remote_config=${REMOTE_CONFIG}
 remote_config_sync_interval=${REMOTE_CONFIG_SYNC_INTERVAL:-10s}
 
-function start_zmq_adaptor()
-{
-    echo "Starting ZeroMQ adaptor ..."
-    zmq_port=$(echo $ZMQ_PUBLISHER_PORT)
-    # use -d flag to start API Gateway ZMQ adaptor in debug mode to print all messages sent by the GW
-    zmq_adaptor_cmd="api-gateway-zmq-adaptor"
-    if [[ -n "${zmq_port}" ]]; then
-        echo "... ZMQ will publish messages on:" ${zmq_port}
-        zmq_adaptor_cmd="${zmq_adaptor_cmd} -p ${zmq_port}"
-    fi
-    if [ "${debug_mode}" == "true" ]; then
-        echo "   ...  in DEBUG mode "
-        zmq_adaptor_cmd="${zmq_adaptor_cmd} -d"
-    fi
-
-    $zmq_adaptor_cmd >> /dev/stderr &
-    sleep 3s
-    # allow interprocess communication by allowing api-gateway processes to write to the socket
-    chown nginx-api-gateway:nginx-api-gateway /tmp/nginx_queue_listen
-    chown nginx-api-gateway:nginx-api-gateway /tmp/nginx_queue_push
-}
-# keep the zmq adaptor running using a simple loop
-while true; do zmq_pid=$(ps aux | grep api-gateway-zmq-adaptor | grep -v grep) || ( echo "Restarting api-gateway-zmq-adaptor" && start_zmq_adaptor ); sleep 60; done &
-
-
 echo "Starting api-gateway ..."
 if [ "${debug_mode}" == "true" ]; then
     echo "   ...  in DEBUG mode "
@@ -68,41 +45,17 @@
 echo resolver $(awk 'BEGIN{ORS=" "} /nameserver/{print $2}' /etc/resolv.conf | sed "s/ $/;/g") > /etc/api-gateway/conf.d/includes/resolvers.conf
 echo "   ...  with dns $(cat /etc/api-gateway/conf.d/includes/resolvers.conf)"
 
-sync_cmd="echo checking for changes ..."
-if [[ -n "${remote_config}" ]]; then
-    echo "   ... using a remote config from: ${remote_config}"
-    if [[ "${remote_config}" =~ ^s3://.+ ]]; then
-      sync_cmd="aws s3 sync --exclude *resolvers.conf --exclude *environment.conf.d/*vars.server.conf --exclude *environment.conf.d/*upstreams.http.conf --delete ${remote_config} /etc/api-gateway/"
-      echo "   ... syncing from s3 using command ${sync_cmd}"
-    else
-      echo "   ... but this REMOTE_CONFIG is not supported "
-    fi
-fi
-api-gateway-config-supervisor \
-        --reload-cmd="api-gateway -s reload" \
-        --sync-folder=/etc/api-gateway \
-        --sync-interval=${remote_config_sync_interval} \
-        --sync-cmd="${sync_cmd}" \
-        --http-addr=127.0.0.1:8888 &
-
-if [[ -n "${marathon_host}" ]]; then
-    echo "  ... starting Marathon Service Discovery on ${marathon_host}"
-    touch /var/run/apigateway-config-watcher.lastrun
-    # start marathon's service discovery
-    while true; do /etc/api-gateway/marathon-service-discovery.sh > /dev/stderr; sleep ${sleep_duration}; done &
-    # start simple statsd logger
-    #
-    # ASSUMPTION: there is a graphite app named "api-gateway-graphite" deployed in marathon
-    #
-    while true; do \
-        statsd_host=$(curl -s ${marathon_host}/v2/apps/api-gateway-graphite/tasks -H "Accept:text/plain" | grep 8125 | awk '{for(i=3;i<=NF;++i) printf("%s ", $i) }' | awk '{for(i=1;i<=NF;++i) sub(/:[[:digit:]]+/,"",$i); print }' ); \
-        if [[ -n "${statsd_host}" ]]; then python /etc/api-gateway/scripts/python/logger/StatsdLogger.py --statsd-host=${statsd_host} > /var/log/api-gateway/statsd-logger.log; fi; \
-        sleep 6; \
-    done &
-fi
-
 echo "   ... testing configuration "
 api-gateway -t -p /usr/local/api-gateway/ -c /etc/api-gateway/api-gateway.conf
 
 echo "   ... using log level: '${log_level}'. Override it with -e 'LOG_LEVEL=<level>' "
-api-gateway -p /usr/local/api-gateway/ -c /etc/api-gateway/api-gateway.conf -g "daemon off; error_log /dev/stderr ${log_level};"
+api-gateway -p /usr/local/api-gateway/ -c /etc/api-gateway/api-gateway.conf -g "daemon off; error_log /dev/stderr ${log_level};" &
+
+if [[ -n "${redis_host}" && -n "${redis_port}" ]]; then
+    sleep 1  # sleep until api-gateway is set up
+    curl -s http://0.0.0.0:9000/subscribe # subscribe to redis key changes for routes
+else
+    echo "REDIS_HOST and/or REDIS_PORT not defined"
+fi
+
+