Add support for new API Gateway service. (#50)

* Adding support for third-party API gateway provides.

Passing token will switch to using provider implementation
rather than built-in platform service.

* 3.6.0

* Fixing broken links and README.
diff --git a/README.md b/README.md
index bcbc188..27261c0 100644
--- a/README.md
+++ b/README.md
@@ -68,16 +68,19 @@
 
 - **api.** Full API URL for OpenWhisk platform, e.g. `https://openwhisk.ng.bluemix.net/api/v1/`. This value overrides `apihost` if both are present.
 - **namespace**. Namespace for resource requests, defaults to `_`.
-
 - **ignore_certs**. Turns off server SSL/TLS certificate verification. This allows the client to be used against local deployments of OpenWhisk with a self-signed certificate. Defaults to false.
+- **apigw_token**. API Gateway service authentication token. This is mandatory for using an external API Gateway service, rather than the built-in api gateway.
+- **apigw_space_guid**. API Gateway space identifier. This is optional when using an API gateway service, defaults to the authentication uuid.
 
 ### environment variables
 
-Client constructor will read values for the `apihost`, `namespace` and `api_key` options from the environment if the following parameters are set. Explicit parameter values override these values.
+Client constructor will read values for the `apihost`, `namespace`, `api_key`, `apigw_token` and `apigw_space_guid` options from the environment if the following parameters are set. Explicit options have precedence over environment values.
 
-- *\_\_OW_API_HOST*
-- *\_\_OW_NAMESPACE*
+- *__OW_API_HOST*
+- *__OW_NAMESPACE*
 - *__OW_API_KEY*
+- *__OW_APIGW_TOKEN*
+- *__OW_APIGW_SPACE_SUID*
 
 
 
@@ -414,9 +417,13 @@
 - `namespace` - set custom namespace for endpoint
 - `params` - JSON object containing parameters for the feed being invoked (default: `{}`)
 
-## api gateway (experimental)
+## api gateway
 
-*[API Gateway support](https://github.com/openwhisk/openwhisk/blob/master/docs/apigateway.md) is currently experimental and may be subject to breaking changes.*
+OpenWhisk supports a [built-in API gateway service](https://github.com/apache/incubator-openwhisk/blob/master/docs/apigateway.md) and external third-party providers. 
+
+This client library defaults to using the platform service. If the `apigw_token` parameter is passed into the client constructor, the implementation will switch to the [IBM Bluemix API Gateway](https://console.ng.bluemix.net/docs/openwhisk/openwhisk_apigateway.html#openwhisk_apigateway). 
+
+*The interface for managing routes through the library does not change between providers.* 
 
 ### list routes
 
diff --git a/lib/client.js b/lib/client.js
index f304913..38b7453 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -18,13 +18,22 @@
     const api = options.api ||
       this.url_from_apihost(options.apihost || process.env['__OW_API_HOST'])
 
+    // optional tokens for API GW service
+    const apigw_token = options.apigw_token || process.env['__OW_APIGW_TOKEN']
+    let apigw_space_guid = options.apigw_space_guid || process.env['__OW_APIGW_SPACE_GUID']
+
+    // unless space is explicitly passed, default to using auth uuid.
+    if (apigw_token && !apigw_space_guid) {
+      apigw_space_guid = api_key.split(':')[0]
+    }
+
     if (!api_key) {
       throw new Error(`${messages.INVALID_OPTIONS_ERROR} Missing api_key parameter.`)
     } else if (!api) {
       throw new Error(`${messages.INVALID_OPTIONS_ERROR} Missing either api or apihost parameters.`)
     }
 
-    return {api_key, api, ignore_certs, namespace: options.namespace}
+    return {api_key, api, ignore_certs, namespace: options.namespace, apigw_token, apigw_space_guid}
   }
 
   url_from_apihost (apihost) {
diff --git a/lib/routes.js b/lib/routes.js
index b716064..a7fc1b5 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -6,14 +6,27 @@
 const CREATE_PARAMS = ['relpath', 'operation', 'action']
 
 class Routes extends BaseOperation {
-  static get routeMgmtApiPath () {
-    return 'experimental/web/whisk.system/routemgmt'
+  routeMgmtApiPath (path) {
+    return this.has_access_token() ?
+      `web/whisk.system/apimgmt/${path}.http` :
+      `experimental/web/whisk.system/routemgmt/${path}.json`
   }
 
   list (options) {
     options = options || {}
     const qs = this.qs(options, ['relpath', 'basepath', 'operation', 'limit', 'skip'])
-    return this.client.request('GET', `${Routes.routeMgmtApiPath}/getApi.json`, { qs })
+    return this.client.request('GET', this.routeMgmtApiPath('getApi'), { qs })
+  }
+
+  qs (options, names) {
+    const result = super.qs(options, names)
+
+    if (this.has_access_token()) {
+      result.accesstoken = this.client.options.apigw_token
+      result.spaceguid = this.client.options.apigw_space_guid
+    }
+
+    return result
   }
 
   delete (options) {
@@ -23,7 +36,7 @@
 
     const qs = this.qs(options, ['relpath', 'basepath', 'operation'])
     qs.force = true
-    return this.client.request('DELETE', `${Routes.routeMgmtApiPath}/deleteApi.json`, { qs })
+    return this.client.request('DELETE', this.routeMgmtApiPath('deleteApi'), { qs })
   }
 
   create (options) {
@@ -34,7 +47,8 @@
     }
 
     const body = this.route_swagger_definition(options)
-    return this.client.request('POST', `${Routes.routeMgmtApiPath}/createApi.json`, { body })
+    const qs = this.qs(options, [])
+    return this.client.request('POST', this.routeMgmtApiPath('createApi'), { body, qs })
   }
 
   route_swagger_definition (params) {
@@ -59,7 +73,7 @@
     return {
       name: id,
       namespace: namespace,
-      backendMethod: 'POST',
+      backendMethod: this.action_url_method(),
       backendUrl: this.action_url_path(id, namespace),
       authkey: this.client.options.api_key
     }
@@ -69,9 +83,26 @@
     return params.basepath || '/'
   }
 
+  action_url_method () {
+    return this.has_access_token() ? 'GET' : 'POST'
+  }
+
   action_url_path (id, namespace) {
-    const path = `namespaces/${namespace}/actions/${id}`
-    return this.client.path_url(path)
+    if (!this.has_access_token()) {
+      return this.client.path_url(`namespaces/${namespace}/actions/${id}`)
+    }
+
+    // web action path must contain package identifier. uses default for
+    // non-explicit package.
+    if (!id.includes('/')) {
+      id = `default/${id}`
+    }
+
+    return this.client.path_url(`web/${namespace}/${id}.http`)
+  }
+
+  has_access_token () {
+    return !!this.client.options.apigw_token
   }
 }
 
diff --git a/package.json b/package.json
index 92b2509..efbb4c5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "openwhisk",
-  "version": "3.5.1",
+  "version": "3.6.0",
   "description": "JavaScript client library for the OpenWhisk platform",
   "main": "lib/main.js",
   "engines": {
diff --git a/test/integration/README.md b/test/integration/README.md
index 297b8fc..54fec55 100644
--- a/test/integration/README.md
+++ b/test/integration/README.md
@@ -3,13 +3,12 @@
 
 Running the integration tests requires the following environment variables to be defined.
 
-    export OW_API_KEY=<your api key>
-    export OW_API_URL=<openwhisk API url>
-    export __OW_NAMESPACE=<namespace to test against>
+    export __OW_API_KEY=<your api key>
+    export __OW_API_HOST=<openwhisk API hostname>
+    export __OW_NAMESPACE=<openwhisk namespace>
+    export __OW_APIGW_TOKEN=<api gateway token>
 
-You can retrieve these settings using the `wsk` CLI:
-
-    wsk property get --all
+You can retrieve these settings from the `.wskprops` file.
 
 Further, you need to create the following seed artifacts.
 
diff --git a/test/integration/actions.test.js b/test/integration/actions.test.js
index c335cec..bf5e204 100644
--- a/test/integration/actions.test.js
+++ b/test/integration/actions.test.js
@@ -5,26 +5,20 @@
 const Client = require('../../lib/client.js')
 const JSZip = require('jszip')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing __OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all actions using default namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.list().then(result => {
     t.true(Array.isArray(result))
     result.forEach(action => {
@@ -38,9 +32,7 @@
 })
 
 test('list all actions using options namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.list({namespace: NAMESPACE}).then(result => {
     t.true(Array.isArray(result))
     result.forEach(action => {
@@ -54,14 +46,12 @@
 })
 
 test('create, get and delete an action', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.create({actionName: 'random_action_test', action: 'function main() {return {payload:"testing"}}'}).then(result => {
     t.is(result.name, 'random_action_test')
     t.is(result.namespace, NAMESPACE)
@@ -77,14 +67,12 @@
 })
 
 test('create and update an action', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.create({actionName: 'random_update_tested', action: 'function main() {return {payload:"testing"}}'}).then(result => {
     t.is(result.name, 'random_update_tested')
     t.is(result.namespace, NAMESPACE)
@@ -99,14 +87,12 @@
 })
 
 test('create, get and delete with parameters an action', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.create({name: 'random_action_params_test', params: { hello: 'world' }, action: 'function main() {return {payload:"testing"}}'}).then(result => {
     t.is(result.name, 'random_action_params_test')
     t.is(result.namespace, NAMESPACE)
@@ -124,15 +110,13 @@
 })
 
 test('invoke action with fully-qualified name', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     console.dir(err)
     t.fail()
   }
 
-  const actions = new Actions(new Client(params))
+  const actions = new Actions(new Client())
   return actions.invoke({actionName: '/whisk.system/utils/sort', blocking: true}).then(invoke_result => {
     t.true(invoke_result.response.success)
     t.pass()
@@ -140,8 +124,6 @@
 })
 
 test('create, invoke and remove package action', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     console.dir(err)
@@ -153,7 +135,7 @@
   zip.file('index.js', 'function main(params) {return params};\nexports.main = main;')
 
   return zip.generateAsync({type: 'nodebuffer'}).then(content => {
-    const actions = new Actions(new Client(params))
+    const actions = new Actions(new Client())
     return actions.create({actionName: 'random_package_action_test', action: content}).then(result => {
       return actions.invoke({actionName: 'random_package_action_test', params: {hello: 'world'}, blocking: true}).then(invoke_result => {
         t.deepEqual(invoke_result.response.result, {hello: 'world'})
diff --git a/test/integration/activations.test.js b/test/integration/activations.test.js
index ea75526..fed912a 100644
--- a/test/integration/activations.test.js
+++ b/test/integration/activations.test.js
@@ -4,26 +4,20 @@
 const Activations = require('../../lib/activations.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing __OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all activations', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const activations = new Activations(new Client(params))
+  const activations = new Activations(new Client())
   return activations.list().then(result => {
     t.true(Array.isArray(result))
     result.forEach(r => t.is(r.namespace, NAMESPACE))
@@ -35,9 +29,7 @@
 })
 
 test('retrieve individual activations', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const activations = new Activations(new Client(params))
+  const activations = new Activations(new Client())
   return activations.list().then(result => {
     t.true(Array.isArray(result))
     return Promise.all(result.map(r => activations.get({activation: r.activationId})))
@@ -48,9 +40,7 @@
 })
 
 test('retrieve individual activation logs', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const activations = new Activations(new Client(params))
+  const activations = new Activations(new Client())
   return activations.list().then(result => {
     t.true(Array.isArray(result))
     return Promise.all(result.map(r => activations.logs({activation: r.activationId})))
@@ -61,9 +51,7 @@
 })
 
 test('retrieve individual activation result', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const activations = new Activations(new Client(params))
+  const activations = new Activations(new Client())
   return activations.list().then(result => {
     t.true(Array.isArray(result))
     return Promise.all(result.map(r => activations.result({activation: r.activationId})))
diff --git a/test/integration/feeds.test.js b/test/integration/feeds.test.js
index 238b640..6f1bd44 100644
--- a/test/integration/feeds.test.js
+++ b/test/integration/feeds.test.js
@@ -5,32 +5,26 @@
 const Triggers = require('../../lib/triggers.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('create and delete a feed', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const feeds = new Feeds(new Client(params))
-  const triggers = new Triggers(new Client(params))
+  const feeds = new Feeds(new Client())
+  const triggers = new Triggers(new Client())
   const feed_params = {
     feedName: '/whisk.system/alarms/alarm',
     trigger: `/${NAMESPACE}/sample_feed_trigger`,
diff --git a/test/integration/namespaces.test.js b/test/integration/namespaces.test.js
index 3b0cb58..6bf1825 100644
--- a/test/integration/namespaces.test.js
+++ b/test/integration/namespaces.test.js
@@ -4,26 +4,20 @@
 const Namespaces = require('../../lib/namespaces.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all namespaces', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const namespaces = new Namespaces(new Client(params))
+  const namespaces = new Namespaces(new Client())
   return namespaces.list().then(result => {
     t.true(Array.isArray(result))
     t.pass()
@@ -34,9 +28,7 @@
 })
 
 test('retrieve individual namespaces', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const namespaces = new Namespaces(new Client(params))
+  const namespaces = new Namespaces(new Client())
   return namespaces.list().then(result => {
     t.true(Array.isArray(result))
     return Promise.all(result.map(ns => namespaces.get({namespace: ns})))
diff --git a/test/integration/packages.test.js b/test/integration/packages.test.js
index a8d96d5..059d5dd 100644
--- a/test/integration/packages.test.js
+++ b/test/integration/packages.test.js
@@ -4,26 +4,20 @@
 const Packages = require('../../lib/packages.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all packages using default namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const packages = new Packages(new Client(params))
+  const packages = new Packages(new Client())
   return packages.list().then(result => {
     t.true(Array.isArray(result))
     result.forEach(packageName => {
@@ -37,9 +31,7 @@
 })
 
 test('list all packages using options namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const packages = new Packages(new Client(params))
+  const packages = new Packages(new Client())
   return packages.list({namespace: NAMESPACE}).then(result => {
     t.true(Array.isArray(result))
     result.forEach(packageName => {
@@ -53,14 +45,12 @@
 })
 
 test('create, get and delete an package', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const packages = new Packages(new Client(params))
+  const packages = new Packages(new Client())
   return packages.create({packageName: 'random_package_test'}).then(result => {
     t.is(result.name, 'random_package_test')
     t.is(result.namespace, NAMESPACE)
@@ -76,14 +66,12 @@
 })
 
 test('create and update an package', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const packages = new Packages(new Client(params))
+  const packages = new Packages(new Client())
   return packages.create({packageName: 'random_package_update_test'}).then(result => {
     t.is(result.name, 'random_package_update_test')
     t.is(result.namespace, NAMESPACE)
diff --git a/test/integration/routes.test.js b/test/integration/routes.test.js
index b855bf4..6ae02fb 100644
--- a/test/integration/routes.test.js
+++ b/test/integration/routes.test.js
@@ -5,27 +5,22 @@
 const Routes = require('../../lib/routes.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'APIGW_TOKEN']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
-
-test('create, retrieve and delete action route', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const routes = new Routes(new Client(params))
-  const actions = new Actions(new Client(params))
+test.serial('create, retrieve and delete action route', t => {
+  const token = process.env.__OW_APIGW_TOKEN
+  delete process.env.__OW_APIGW_TOKEN
+  const routes = new Routes(new Client())
+  const actions = new Actions(new Client())
+  process.env.__OW_APIGW_TOKEN = token
 
   return actions.create({actionName: 'routeAction', action: ''}).then(() => {
     return routes.create({action: 'routeAction', basepath: '/testing', relpath: '/foo/bar', operation: 'POST'}).then(() => {
@@ -46,3 +41,27 @@
     t.fail()
   })
 })
+
+test.serial('create, retrieve and delete action route using token', t => {
+  const routes = new Routes(new Client())
+  const actions = new Actions(new Client())
+
+  return actions.create({actionName: 'routeAction', action: ''}).then(() => {
+    return routes.create({action: 'routeAction', basepath: '/testing', relpath: '/foo/bar', operation: 'POST'}).then(() => {
+      return routes.list({basepath: '/testing'}).then(results => {
+        t.is(results.apis.length, 1)
+        const apidoc = results.apis[0].value.apidoc
+        t.is(apidoc.basePath, '/testing')
+        t.true(apidoc.paths.hasOwnProperty('/foo/bar'))
+        const path = apidoc.paths['/foo/bar']
+        t.true(path.hasOwnProperty('post'))
+        t.is(path.post['x-openwhisk'].action, 'routeAction')
+
+        return routes.delete({basepath: '/testing'}).then(() => actions.delete({actionName: 'routeAction'}))
+      })
+    })
+  }).catch(err => {
+    console.log(err)
+    t.fail()
+  })
+})
diff --git a/test/integration/rules.test.js b/test/integration/rules.test.js
index ed05e34..22bcdda 100644
--- a/test/integration/rules.test.js
+++ b/test/integration/rules.test.js
@@ -5,26 +5,20 @@
 const Triggers = require('../../lib/triggers.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all rules using default namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const rules = new Rules(new Client(params))
+  const rules = new Rules(new Client())
   return rules.list().then(result => {
     t.true(Array.isArray(result))
     result.forEach(rule => {
@@ -38,9 +32,7 @@
 })
 
 test('list all rules using options namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const rules = new Rules(new Client(params))
+  const rules = new Rules(new Client())
   return rules.list({namespace: NAMESPACE}).then(result => {
     t.true(Array.isArray(result))
     result.forEach(rule => {
@@ -55,15 +47,13 @@
 
 // Running update tests conconcurrently leads to resource conflict errors.
 test.serial('create, get and delete a rule', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const rules = new Rules(new Client(params))
-  const triggers = new Triggers(new Client(params))
+  const rules = new Rules(new Client())
+  const triggers = new Triggers(new Client())
   return triggers.create({triggerName: 'sample_rule_trigger'}).then(() => {
     return rules.create({ruleName: 'random_rule_test', action: `/${NAMESPACE}/hello`, trigger: `/${NAMESPACE}/sample_rule_trigger`}).then(result => {
       t.is(result.name, 'random_rule_test')
@@ -83,15 +73,13 @@
 })
 
 test.serial('create and update a rule', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const rules = new Rules(new Client(params))
-  const triggers = new Triggers(new Client(params))
+  const rules = new Rules(new Client())
+  const triggers = new Triggers(new Client())
   return triggers.create({triggerName: 'sample_rule_trigger'}).then(() => {
     return rules.create({ruleName: 'random_update_test', action: `/${NAMESPACE}/hello`, trigger: `/${NAMESPACE}/sample_rule_trigger`}).then(result => {
       t.is(result.name, 'random_update_test')
diff --git a/test/integration/triggers.test.js b/test/integration/triggers.test.js
index 18543f5..b976411 100644
--- a/test/integration/triggers.test.js
+++ b/test/integration/triggers.test.js
@@ -4,26 +4,20 @@
 const Triggers = require('../../lib/triggers.js')
 const Client = require('../../lib/client.js')
 
-const API_KEY = process.env.OW_API_KEY
-const API_URL = process.env.OW_API_URL
-const NAMESPACE = process.env['__OW_NAMESPACE']
+const envParams = ['API_KEY', 'API_HOST', 'NAMESPACE']
 
-if (!API_KEY) {
-  throw new Error('Missing OW_API_KEY environment parameter')
-}
+// check that mandatory configuration properties are available
+envParams.forEach(key => {
+  const param = `__OW_${key}` 
+  if (!process.env.hasOwnProperty(param)) {
+    throw new Error(`Missing ${param} environment parameter`)
+  }
+})
 
-if (!API_URL) {
-  throw new Error('Missing OW_API_URL environment parameter')
-}
-
-if (!NAMESPACE) {
-  throw new Error('Missing OW_NAMESPACE environment parameter')
-}
+const NAMESPACE = process.env.__OW_NAMESPACE
 
 test('list all triggers using default namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
-  const triggers = new Triggers(new Client(params))
+  const triggers = new Triggers(new Client())
   return triggers.list().then(result => {
     t.true(Array.isArray(result))
     result.forEach(trigger => {
@@ -37,9 +31,7 @@
 })
 
 test('list all triggers using options namespace', t => {
-  const params = {api: API_URL, api_key: API_KEY}
-
-  const triggers = new Triggers(new Client(params))
+  const triggers = new Triggers(new Client())
   return triggers.list({namespace: NAMESPACE}).then(result => {
     t.true(Array.isArray(result))
     result.forEach(trigger => {
@@ -53,14 +45,12 @@
 })
 
 test('create, get and delete an trigger', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const triggers = new Triggers(new Client(params))
+  const triggers = new Triggers(new Client())
   return triggers.create({triggerName: 'random_trigger_test'}).then(result => {
     t.is(result.name, 'random_trigger_test')
     t.is(result.namespace, NAMESPACE)
@@ -78,14 +68,12 @@
 })
 
 test('create and update an trigger', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const triggers = new Triggers(new Client(params))
+  const triggers = new Triggers(new Client())
   return triggers.create({triggerName: 'random_create_update_test'}).then(result => {
     t.is(result.name, 'random_create_update_test')
     t.is(result.namespace, NAMESPACE)
@@ -101,14 +89,12 @@
 })
 
 test('fire a trigger', t => {
-  const params = {api: API_URL, api_key: API_KEY, namespace: NAMESPACE}
-
   const errors = err => {
     console.log(err)
     t.fail()
   }
 
-  const triggers = new Triggers(new Client(params))
+  const triggers = new Triggers(new Client())
   return triggers.create({triggerName: 'random_fire_test'}).then(result => {
     return triggers.invoke({triggerName: 'random_fire_test'}).then(update_result => {
       t.true(update_result.hasOwnProperty('activationId'))
diff --git a/test/unit/client.test.js b/test/unit/client.test.js
index 5526ed6..c7eaed3 100644
--- a/test/unit/client.test.js
+++ b/test/unit/client.test.js
@@ -13,30 +13,49 @@
 })
 
 test('should support explicit constructor options', t => {
-  const client = new Client({namespace: 'ns', ignore_certs: true, api_key: 'aaa', api: 'my_host'})
+  const client = new Client({namespace: 'ns', ignore_certs: true, api_key: 'aaa', api: 'my_host', apigw_token: 'oauth_token', apigw_space_guid: 'space_guid'})
   t.is(client.options.api, 'my_host')
   t.true(client.options.ignore_certs)
   t.is(client.options.namespace, 'ns')
+  t.is(client.options.apigw_token, 'oauth_token')
+  t.is(client.options.apigw_space_guid, 'space_guid')
+})
+
+test('should use uuid from auth key as space guid if apigw_token present', t => {
+  const client = new Client({namespace: 'ns', ignore_certs: true, api_key: 'uuid:pass', api: 'my_host', apigw_token: 'oauth_token'})
+  t.is(client.options.apigw_space_guid, 'uuid')
 })
 
 test('should use environment parameters for options if not set explicitly.', t => {
   process.env['__OW_API_KEY'] = 'some_user:some_pass'
   process.env['__OW_API_HOST'] = 'mywhiskhost'
+  process.env['__OW_APIGW_TOKEN'] = 'my-token'
+  process.env['__OW_APIGW_SPACE_GUID'] = 'my-space'
   const client = new Client()
   t.is(client.options.api_key, process.env['__OW_API_KEY'])
   t.is(client.options.api, 'https://mywhiskhost/api/v1/')
+  t.is(client.options.apigw_token, 'my-token')
+  t.is(client.options.apigw_space_guid, 'my-space')
   delete process.env['__OW_API_KEY']
   delete process.env['__OW_API_HOST']
+  delete process.env['__OW_APIGW_TOKEN']
+  delete process.env['__OW_APIGW_SPACE_GUID']
 })
 
 test('should use options for parameters even if environment parameters are available.', t => {
   process.env['__OW_API_KEY'] = 'some_user:some_pass'
   process.env['__OW_API_HOST'] = 'mywhiskhost'
-  const client = new Client({apihost: 'openwhisk', api_key: 'mykey'})
+  process.env['__OW_APIGW_TOKEN'] = 'my-token'
+  process.env['__OW_APIGW_SPACE_GUID'] = 'my-space'
+  const client = new Client({apihost: 'openwhisk', api_key: 'mykey', apigw_token: 'token', apigw_space_guid: 'guid'})
   t.is(client.options.api_key, 'mykey')
   t.is(client.options.api, 'https://openwhisk/api/v1/')
+  t.is(client.options.apigw_token, 'token')
+  t.is(client.options.apigw_space_guid, 'guid')
   delete process.env['__OW_API_KEY']
   delete process.env['__OW_API_HOST']
+  delete process.env['__OW_APIGW_TOKEN']
+  delete process.env['__OW_APIGW_SPACE_GUID']
 })
 
 test('should throw error when missing API key option.', t => {
diff --git a/test/unit/routes.test.js b/test/unit/routes.test.js
index 3144e9f..567420f 100644
--- a/test/unit/routes.test.js
+++ b/test/unit/routes.test.js
@@ -3,28 +3,56 @@
 const test = require('ava')
 const Routes = require('../../lib/routes')
 
+test('should return experimental api path without api token', t => {
+  const client = { options: {} }
+  const routes = new Routes(client)
+  t.is(routes.routeMgmtApiPath('a'), 'experimental/web/whisk.system/routemgmt/a.json')
+})
+
+test('should return experimental api path with api token', t => {
+  const client = { options: {
+    apigw_token: true
+  }}
+  const routes = new Routes(client)
+  t.is(routes.routeMgmtApiPath('a'), 'web/whisk.system/apimgmt/a.http')
+})
+
 test('should list all routes', t => {
   t.plan(2)
-  const client = {}
-  const ns = '_'
+  const client = { options: {} }
   client.request = (method, path, options) => {
     t.is(method, 'GET')
-    t.is(path, `${Routes.routeMgmtApiPath}/getApi.json`)
+    t.is(path, routes.routeMgmtApiPath('getApi'))
   }
 
   const routes = new Routes(client)
   return routes.list()
 })
 
+test('should list all routes with apigw_token', t => {
+  t.plan(3)
+  const client = { options: {
+    apigw_token: 'token',
+    apigw_space_guid: 'space'
+  } }
+  client.request = (method, path, options) => {
+    t.is(method, 'GET')
+    t.is(path, routes.routeMgmtApiPath('getApi'))
+    t.deepEqual(options.qs, { spaceguid: 'space', accesstoken: 'token'})
+  }
+
+  const routes = new Routes(client)
+  return routes.list()
+})
+
+
 test('should list all routes with parameters', t => {
   t.plan(3)
-  const client = {}
-  const ns = '_'
-
+  const client = { options: {} }
   const options = {basepath: '/hello', relpath: '/foo/bar', operation: 'GET', limit: 10, skip: 10}
   client.request = (method, path, _options) => {
     t.is(method, 'GET')
-    t.is(path, `${Routes.routeMgmtApiPath}/getApi.json`)
+    t.is(path, routes.routeMgmtApiPath('getApi'))
     t.deepEqual(_options.qs, options)
   }
 
@@ -34,13 +62,12 @@
 
 test('should delete a route', t => {
   t.plan(3)
-  const client = {}
-  const ns = '_'
+  const client = { options: {} }
   const options = {force: true, basepath: '/hello'}
 
   client.request = (method, path, _options) => {
     t.is(method, 'DELETE')
-    t.is(path, `${Routes.routeMgmtApiPath}/deleteApi.json`)
+    t.is(path, routes.routeMgmtApiPath('deleteApi'))
     t.deepEqual(_options.qs, options)
   }
 
@@ -48,15 +75,31 @@
   return routes.delete({basepath: '/hello'})
 })
 
+test('should delete a route with apigw token', t => {
+  t.plan(3)
+  const client = { options: {
+    apigw_token: 'token',
+    apigw_space_guid: 'space'
+  } }
+
+  client.request = (method, path, options) => {
+    t.is(method, 'DELETE')
+    t.is(path, routes.routeMgmtApiPath('deleteApi'))
+    t.deepEqual(options.qs, { basepath: '/hello', force: true, spaceguid: 'space', accesstoken: 'token'})
+  }
+
+  const routes = new Routes(client)
+  return routes.delete({basepath: '/hello'})
+})
+
 test('should delete a route with parameters', t => {
   t.plan(3)
-  const client = {}
-  const ns = '_'
+  const client = { options: {} }
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   client.request = (method, path, _options) => {
     t.is(method, 'DELETE')
-    t.is(path, `${Routes.routeMgmtApiPath}/deleteApi.json`)
+    t.is(path, routes.routeMgmtApiPath('deleteApi'))
     t.deepEqual(_options.qs, options)
   }
 
@@ -65,8 +108,7 @@
 })
 
 test('delete routes without providing basepath', t => {
-  const client = {}
-  const ns = '_'
+  const client = { options: {} }
   const routes = new Routes(client)
   return t.throws(() => { routes.delete() }, /Missing mandatory basepath/)
 })
@@ -77,7 +119,6 @@
   const api_key = 'username:password'
   const client_options = { api_key }
   const client = { path_url, options: client_options }
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -98,7 +139,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }
 
@@ -106,13 +147,117 @@
   return routes.create({relpath: '/hello', operation: 'GET', action: 'helloAction'})
 })
 
+test('should create a route with apigw_token', t => {
+  t.plan(4)
+  const path_url = path => `https://openwhisk.ng.bluemix.net/api/v1/${path}`
+  const api_key = 'username:password'
+  const client_options = { api_key, apigw_token: 'token', apigw_space_guid: 'space' }
+  const client = { path_url, options: client_options }
+  const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
+
+  const body = {
+    apidoc: {
+      namespace: '_',
+      gatewayBasePath: '/',
+      gatewayPath: '/hello',
+      gatewayMethod: 'GET',
+      id: 'API:_:/',
+      action: {
+        name: 'helloAction',
+        namespace: '_',
+        backendMethod: 'GET',
+        backendUrl: 'https://openwhisk.ng.bluemix.net/api/v1/web/_/default/helloAction.http',
+        authkey: api_key }
+    }
+  }
+
+  client.request = (method, path, _options) => {
+    t.is(method, 'POST')
+    t.is(path, routes.routeMgmtApiPath('createApi'))
+    t.deepEqual(_options.body, body)
+    t.deepEqual(_options.qs, { spaceguid: 'space', accesstoken: 'token'})
+  }
+
+  const routes = new Routes(client)
+  return routes.create({relpath: '/hello', operation: 'GET', action: 'helloAction'})
+})
+
+test('should create a route with apigw_token and action with package', t => {
+  t.plan(4)
+  const path_url = path => `https://openwhisk.ng.bluemix.net/api/v1/${path}`
+  const api_key = 'username:password'
+  const client_options = { api_key, apigw_token: 'token', apigw_space_guid: 'space' }
+  const client = { path_url, options: client_options }
+  const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
+
+  const body = {
+    apidoc: {
+      namespace: '_',
+      gatewayBasePath: '/',
+      gatewayPath: '/hello',
+      gatewayMethod: 'GET',
+      id: 'API:_:/',
+      action: {
+        name: 'package/helloAction',
+        namespace: '_',
+        backendMethod: 'GET',
+        backendUrl: 'https://openwhisk.ng.bluemix.net/api/v1/web/_/package/helloAction.http',
+        authkey: api_key }
+    }
+  }
+
+  client.request = (method, path, _options) => {
+    t.is(method, 'POST')
+    t.is(path, routes.routeMgmtApiPath('createApi'))
+    t.deepEqual(_options.body, body)
+    t.deepEqual(_options.qs, { spaceguid: 'space', accesstoken: 'token'})
+  }
+
+  const routes = new Routes(client)
+  return routes.create({relpath: '/hello', operation: 'GET', action: 'package/helloAction'})
+})
+
+test('should create a route with apigw_token and action with package & ns', t => {
+  t.plan(4)
+  const path_url = path => `https://openwhisk.ng.bluemix.net/api/v1/${path}`
+  const api_key = 'username:password'
+  const client_options = { api_key, apigw_token: 'token', apigw_space_guid: 'space' }
+  const client = { path_url, options: client_options }
+  const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
+
+  const body = {
+    apidoc: {
+      namespace: '_',
+      gatewayBasePath: '/',
+      gatewayPath: '/hello',
+      gatewayMethod: 'GET',
+      id: 'API:_:/',
+      action: {
+        name: 'package/helloAction',
+        namespace: 'ns',
+        backendMethod: 'GET',
+        backendUrl: 'https://openwhisk.ng.bluemix.net/api/v1/web/ns/package/helloAction.http',
+        authkey: api_key }
+    }
+  }
+
+  client.request = (method, path, _options) => {
+    t.is(method, 'POST')
+    t.is(path, routes.routeMgmtApiPath('createApi'))
+    t.deepEqual(_options.body, body)
+    t.deepEqual(_options.qs, { spaceguid: 'space', accesstoken: 'token'})
+  }
+
+  const routes = new Routes(client)
+  return routes.create({relpath: '/hello', operation: 'GET', action: '/ns/package/helloAction'})
+})
+
 test('should create a route using global ns', t => {
   t.plan(3)
   const path_url = path => `https://openwhisk.ng.bluemix.net/api/v1/${path}`
   const api_key = 'username:password'
   const client_options = { api_key, namespace: 'global_ns'}
   const client = { path_url, options: client_options}
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -133,7 +278,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }
 
@@ -147,7 +292,6 @@
   const api_key = 'username:password'
   const client_options = { api_key }
   const client = { path_url, options: client_options }
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -168,7 +312,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }
 
@@ -182,7 +326,6 @@
   const api_key = 'username:password'
   const client_options = { api_key }
   const client = { path_url, options: client_options }
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -203,7 +346,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }
 
@@ -217,7 +360,6 @@
   const api_key = 'username:password'
   const client_options = { api_key }
   const client = { path_url, options: client_options }
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -238,7 +380,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }
 
@@ -252,7 +394,6 @@
   const api_key = 'username:password'
   const client_options = { api_key, namespace: 'global' }
   const client = { path_url, options: client_options }
-  const ns = '_'
   const options = {force: true, basepath: '/hello', relpath: '/bar/1', operation: 'GET'}
 
   const body = {
@@ -273,7 +414,7 @@
 
   client.request = (method, path, _options) => {
     t.is(method, 'POST')
-    t.is(path, `${Routes.routeMgmtApiPath}/createApi.json`)
+    t.is(path, routes.routeMgmtApiPath('createApi'))
     t.deepEqual(_options.body, body)
   }