Repository: serverless/serverless (JavaScript)
Date: 2019-03-28 14:02:38+00:00
Task type: Feature

Issue:
Multiple usage plans
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal

## Description

Currently, only a single usage plan is supported. However, API Gateway supports multiple usage plans. For instance, suppose an API has tiers of access, a free tier and a paid tier. For this use case it makes sense to have separate usage plans and affiliate API keys with one or the other.

The config could look something like this:
```
provider:
  name: aws
  usagePlans:
    - free:
      quota:
        limit: 1000
        period: MONTH
      throttle:
        burstLimit: 1
        rateLimit: 1
    - paid:
      quota:
        limit: 1000000
        period: MONTH
      throttle:
        burstLimit: 1000
        rateLimit: 1000
```


Issue discussion:
This would be extremely helpful!
I definitely could use this functionality as well!

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index 8031b83d1f8..63ebd290228 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -543,6 +543,45 @@ Please note that those are the API keys names, not the actual values. Once you d
 
 Clients connecting to this Rest API will then need to set any of these API keys values in the `x-api-key` header of their request. This is only necessary for functions where the `private` property is set to true.
 
+You can also setup multiple usage plans for your API. In this case you need to map your usage plans to your api keys. Here's an example how this might look like:
+
+```yml
+service: my-service
+provider:
+  name: aws
+  apiKeys:
+    - free:
+      - myFreeKey
+      - ${opt:stage}-myFreeKey
+    - paid:
+      - myPaidKey
+      - ${opt:stage}-myPaidKey
+  usagePlan:
+    - free:
+        quota:
+          limit: 5000
+          offset: 2
+          period: MONTH
+        throttle:
+          burstLimit: 200
+          rateLimit: 100
+    - paid:
+        quota:
+          limit: 50000
+          offset: 1
+          period: MONTH
+        throttle:
+          burstLimit: 2000
+          rateLimit: 1000
+functions:
+  hello:
+    events:
+      - http:
+          path: user/create
+          method: get
+          private: true
+```
+
 ### Configuring endpoint types
 
 API Gateway [supports regional endpoints](https://aws.amazon.com/about-aws/whats-new/2017/11/amazon-api-gateway-supports-regional-api-endpoints/) for associating your API Gateway REST APIs with a particular region. This can reduce latency if your requests originate from the same region as your REST API and can be helpful in building multi-region applications.
diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md
index c8cc971df34..ca3b0aff6cc 100644
--- a/docs/providers/aws/guide/resources.md
+++ b/docs/providers/aws/guide/resources.md
@@ -82,9 +82,9 @@ If you are unsure how a resource is named, that you want to reference from your
 |ApiGateway::Method     | ApiGatewayMethod{normalizedPath}{normalizedMethod}      | ApiGatewayMethodUsersGet      |
 |ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer            | HelloApiGatewayAuthorizer     |
 |ApiGateway::Deployment | ApiGatewayDeployment{instanceId}                      | ApiGatewayDeployment12356789  |
-|ApiGateway::ApiKey     | ApiGatewayApiKey{SequentialID}                          | ApiGatewayApiKey1             |
-|ApiGateway::UsagePlan  | ApiGatewayUsagePlan                          | ApiGatewayUsagePlan             |
-|ApiGateway::UsagePlanKey     | ApiGatewayUsagePlanKey{SequentialID}                          | ApiGatewayUsagePlanKey1             |
+|ApiGateway::ApiKey     | ApiGatewayApiKey{OptionalNormalizedName}{SequentialID}  | ApiGatewayApiKeyFree1       |
+|ApiGateway::UsagePlan  | ApiGatewayUsagePlan{OptionalNormalizedName}             | ApiGatewayUsagePlanFree             |
+|ApiGateway::UsagePlanKey | ApiGatewayUsagePlanKey{OptionalNormalizedName}{SequentialID} | ApiGatewayUsagePlanKeyFree1  |
 |ApiGateway::Stage     | ApiGatewayStage                          | ApiGatewayStage             |
 |SNS::Topic             | SNSTopic{normalizedTopicName}                           | SNSTopicSometopic             |
 |SNS::Subscription      | {normalizedFunctionName}SnsSubscription{normalizedTopicName}   | HelloSnsSubscriptionSomeTopic             |
diff --git a/lib/plugins/aws/info/getApiKeyValues.js b/lib/plugins/aws/info/getApiKeyValues.js
index eea7e785402..397af77b485 100644
--- a/lib/plugins/aws/info/getApiKeyValues.js
+++ b/lib/plugins/aws/info/getApiKeyValues.js
@@ -9,7 +9,20 @@ module.exports = {
     info.apiKeys = [];
 
     // check if the user has set api keys
-    const apiKeyNames = this.serverless.service.provider.apiKeys || [];
+    const apiKeyDefinitions = this.serverless.service.provider.apiKeys;
+    const apiKeyNames = [];
+    if (_.isArray(apiKeyDefinitions) && apiKeyDefinitions.length) {
+      _.forEach(apiKeyDefinitions, (definition) => {
+        // different API key types are nested in separate arrays
+        if (_.isObject(definition)) {
+          const keyTypeName = Object.keys(definition)[0];
+          _.forEach(definition[keyTypeName], (keyName) => apiKeyNames.push(keyName));
+        } else if (_.isString(definition)) {
+          // plain strings are simple, non-nested API keys
+          apiKeyNames.push(definition);
+        }
+      });
+    }
 
     if (apiKeyNames.length) {
       return this.provider.request('APIGateway',
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index 41aace7c8d5..7f78238c3af 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -242,16 +242,25 @@ module.exports = {
   getMethodLogicalId(resourceId, methodName) {
     return `ApiGatewayMethod${resourceId}${this.normalizeMethodName(methodName)}`;
   },
-  getApiKeyLogicalId(apiKeyNumber) {
+  getApiKeyLogicalId(apiKeyNumber, apiKeyName) {
+    if (apiKeyName) {
+      return `ApiGatewayApiKey${this.normalizeName(apiKeyName)}${apiKeyNumber}`;
+    }
     return `ApiGatewayApiKey${apiKeyNumber}`;
   },
   getApiKeyLogicalIdRegex() {
     return /^ApiGatewayApiKey/;
   },
-  getUsagePlanLogicalId() {
+  getUsagePlanLogicalId(name) {
+    if (name) {
+      return `ApiGatewayUsagePlan${this.normalizeName(name)}`;
+    }
     return 'ApiGatewayUsagePlan';
   },
-  getUsagePlanKeyLogicalId(usagePlanKeyNumber) {
+  getUsagePlanKeyLogicalId(usagePlanKeyNumber, usagePlanKeyName) {
+    if (usagePlanKeyName) {
+      return `ApiGatewayUsagePlanKey${this.normalizeName(usagePlanKeyName)}${usagePlanKeyNumber}`;
+    }
     return `ApiGatewayUsagePlanKey${usagePlanKeyNumber}`;
   },
   getStageLogicalId() {
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js
index 069455a3e4b..2135bd8d270 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js
@@ -3,6 +3,23 @@
 const _ = require('lodash');
 const BbPromise = require('bluebird');
 
+function createApiKeyResource(that, apiKey) {
+  const resourceTemplate = {
+    Type: 'AWS::ApiGateway::ApiKey',
+    Properties: {
+      Enabled: true,
+      Name: apiKey,
+      StageKeys: [{
+        RestApiId: that.provider.getApiGatewayRestApiId(),
+        StageName: that.provider.getStage(),
+      }],
+    },
+    DependsOn: that.apiGatewayDeploymentLogicalId,
+  };
+
+  return _.cloneDeep(resourceTemplate);
+}
+
 module.exports = {
   compileApiKeys() {
     if (this.serverless.service.provider.apiKeys) {
@@ -10,30 +27,35 @@ module.exports = {
         throw new this.serverless.classes.Error('apiKeys property must be an array');
       }
 
-      _.forEach(this.serverless.service.provider.apiKeys, (apiKey, i) => {
-        const apiKeyNumber = i + 1;
+      const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
+      let keyNumber = 0;
 
-        if (typeof apiKey !== 'string') {
-          throw new this.serverless.classes.Error('API Keys must be strings');
+      _.forEach(this.serverless.service.provider.apiKeys, (apiKeyDefinition) => {
+        // if multiple API key types are used
+        if (_.isObject(apiKeyDefinition)) {
+          keyNumber = 0;
+          const name = Object.keys(apiKeyDefinition)[0];
+          _.forEach(apiKeyDefinition[name], (key) => {
+            if (!_.isString(key)) {
+              throw new this.serverless.classes.Error('API keys must be strings');
+            }
+            keyNumber += 1;
+            const apiKeyLogicalId = this.provider.naming
+              .getApiKeyLogicalId(keyNumber, name);
+            const resourceTemplate = createApiKeyResource(this, key);
+            _.merge(resources, {
+              [apiKeyLogicalId]: resourceTemplate,
+            });
+          });
+        } else {
+          keyNumber += 1;
+          const apiKeyLogicalId = this.provider.naming
+            .getApiKeyLogicalId(keyNumber);
+          const resourceTemplate = createApiKeyResource(this, apiKeyDefinition);
+          _.merge(resources, {
+            [apiKeyLogicalId]: resourceTemplate,
+          });
         }
-
-        const apiKeyLogicalId = this.provider.naming
-          .getApiKeyLogicalId(apiKeyNumber);
-
-        _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
-          [apiKeyLogicalId]: {
-            Type: 'AWS::ApiGateway::ApiKey',
-            Properties: {
-              Enabled: true,
-              Name: apiKey,
-              StageKeys: [{
-                RestApiId: this.provider.getApiGatewayRestApiId(),
-                StageName: this.provider.getStage(),
-              }],
-            },
-            DependsOn: this.apiGatewayDeploymentLogicalId,
-          },
-        });
       });
     }
     return BbPromise.resolve();
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
index 4206370ddcf..861e8b78ca8 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
@@ -3,52 +3,118 @@
 const _ = require('lodash');
 const BbPromise = require('bluebird');
 
+function createUsagePlanResource(that, name) {
+  const resourceTemplate = {
+    Type: 'AWS::ApiGateway::UsagePlan',
+    DependsOn: that.apiGatewayDeploymentLogicalId,
+    Properties: {
+      ApiStages: [
+        {
+          ApiId: that.provider.getApiGatewayRestApiId(),
+          Stage: that.provider.getStage(),
+        },
+      ],
+      Description: `Usage plan "${name}" for ${that.serverless.service.service} ${
+        that.provider.getStage()} stage`,
+      UsagePlanName: `${that.serverless.service.service}-${name}-${
+        that.provider.getStage()}`,
+    },
+  };
+  const template = _.cloneDeep(resourceTemplate);
+  // this is done for backward compatibility
+  if (name === 'default') {
+    // create old legacy resources
+    template.Properties.UsagePlanName =
+      `${that.serverless.service.service}-${that.provider.getStage()}`;
+    template.Properties.Description =
+      `Usage plan for ${that.serverless.service.service} ${that.provider.getStage()} stage`;
+    // assign quota
+    if (_.has(that.serverless.service.provider, 'usagePlan.quota')
+      && that.serverless.service.provider.usagePlan.quota !== null) {
+      _.merge(template, {
+        Properties: {
+          Quota: _.merge(
+            { Limit: that.serverless.service.provider.usagePlan.quota.limit },
+            { Offset: that.serverless.service.provider.usagePlan.quota.offset },
+            { Period: that.serverless.service.provider.usagePlan.quota.period }),
+        },
+      });
+    }
+    // assign throttle
+    if (_.has(that.serverless.service.provider, 'usagePlan.throttle')
+      && that.serverless.service.provider.usagePlan.throttle !== null) {
+      _.merge(template, {
+        Properties: {
+          Throttle: _.merge(
+            { BurstLimit: that.serverless.service.provider.usagePlan.throttle.burstLimit },
+            { RateLimit: that.serverless.service.provider.usagePlan.throttle.rateLimit }),
+        },
+      });
+    }
+  } else {
+    // assign quota
+    const quotaProperties = that.serverless.service.provider.usagePlan
+      .reduce((accum, planObject) => {
+        if (planObject[name] && planObject[name].quota) {
+          return planObject[name].quota;
+        }
+        return accum;
+      }, {});
+    if (!_.isEmpty(quotaProperties)) {
+      _.merge(template, {
+        Properties: {
+          Quota: _.merge(
+            { Limit: quotaProperties.limit },
+            { Offset: quotaProperties.offset },
+            { Period: quotaProperties.period }),
+        },
+      });
+    }
+    // assign throttle
+    const throttleProperties = that.serverless.service.provider.usagePlan
+      .reduce((accum, planObject) => {
+        if (planObject[name] && planObject[name].throttle) {
+          return planObject[name].throttle;
+        }
+        return accum;
+      }, {});
+    if (!_.isEmpty(throttleProperties)) {
+      _.merge(template, {
+        Properties: {
+          Throttle: _.merge(
+            { BurstLimit: throttleProperties.burstLimit },
+            { RateLimit: throttleProperties.rateLimit }),
+        },
+      });
+    }
+  }
+  return template;
+}
+
 module.exports = {
   compileUsagePlan() {
     if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) {
-      this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId();
-      _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
-        [this.apiGatewayUsagePlanLogicalId]: {
-          Type: 'AWS::ApiGateway::UsagePlan',
-          DependsOn: this.apiGatewayDeploymentLogicalId,
-          Properties: {
-            ApiStages: [
-              {
-                ApiId: this.provider.getApiGatewayRestApiId(),
-                Stage: this.provider.getStage(),
-              },
-            ],
-            Description: `Usage plan for ${this.serverless.service.service} ${
-              this.provider.getStage()} stage`,
-            UsagePlanName: `${this.serverless.service.service}-${
-              this.provider.getStage()}`,
-          },
-        },
-      });
-      if (_.has(this.serverless.service.provider, 'usagePlan.quota')
-        && this.serverless.service.provider.usagePlan.quota !== null) {
-        _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
-          [this.apiGatewayUsagePlanLogicalId]: {
-            Properties: {
-              Quota: _.merge(
-                { Limit: this.serverless.service.provider.usagePlan.quota.limit },
-                { Offset: this.serverless.service.provider.usagePlan.quota.offset },
-                { Period: this.serverless.service.provider.usagePlan.quota.period }),
-            },
-          },
+      const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
+      this.apiGatewayUsagePlanNames = [];
+
+      if (_.isArray(this.serverless.service.provider.usagePlan)) {
+        _.forEach(this.serverless.service.provider.usagePlan, (planObject) => {
+          const usagePlanName = Object.keys(planObject)[0];
+          const logicalId = this.provider.naming.getUsagePlanLogicalId(usagePlanName);
+          const resourceTemplate = createUsagePlanResource(this, usagePlanName);
+          _.merge(resources, {
+            [logicalId]: resourceTemplate,
+          });
+          this.apiGatewayUsagePlanNames.push(usagePlanName);
         });
-      }
-      if (_.has(this.serverless.service.provider, 'usagePlan.throttle')
-        && this.serverless.service.provider.usagePlan.throttle !== null) {
-        _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
-          [this.apiGatewayUsagePlanLogicalId]: {
-            Properties: {
-              Throttle: _.merge(
-                { BurstLimit: this.serverless.service.provider.usagePlan.throttle.burstLimit },
-                { RateLimit: this.serverless.service.provider.usagePlan.throttle.rateLimit }),
-            },
-          },
+      } else {
+        const usagePlanName = 'default';
+        const logicalId = this.provider.naming.getUsagePlanLogicalId();
+        const resourceTemplate = createUsagePlanResource(this, usagePlanName);
+        _.merge(resources, {
+          [logicalId]: resourceTemplate,
         });
+        this.apiGatewayUsagePlanNames.push(usagePlanName);
       }
     }
     return BbPromise.resolve();
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js
index ff22f2b6da5..3bc5839088b 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js
@@ -3,6 +3,25 @@
 const _ = require('lodash');
 const BbPromise = require('bluebird');
 
+function createUsagePlanKeyResource(that, usagePlanLogicalId, keyNumber, keyName) {
+  const apiKeyLogicalId = that.provider.naming.getApiKeyLogicalId(keyNumber, keyName);
+
+  const resourceTemplate = {
+    Type: 'AWS::ApiGateway::UsagePlanKey',
+    Properties: {
+      KeyId: {
+        Ref: apiKeyLogicalId,
+      },
+      KeyType: 'API_KEY',
+      UsagePlanId: {
+        Ref: usagePlanLogicalId,
+      },
+    },
+  };
+
+  return _.cloneDeep(resourceTemplate);
+}
+
 module.exports = {
   compileUsagePlanKeys() {
     if (this.serverless.service.provider.apiKeys) {
@@ -10,33 +29,40 @@ module.exports = {
         throw new this.serverless.classes.Error('apiKeys property must be an array');
       }
 
-      _.forEach(this.serverless.service.provider.apiKeys, (apiKey, i) => {
-        const usagePlanKeyNumber = i + 1;
+      const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
+      let keyNumber = 0;
 
-        if (typeof apiKey !== 'string') {
-          throw new this.serverless.classes.Error('API Keys must be strings');
+      _.forEach(this.serverless.service.provider.apiKeys, (apiKeyDefinition) => {
+        // if multiple API key types are used
+        if (_.isObject(apiKeyDefinition)) {
+          const name = Object.keys(apiKeyDefinition)[0];
+          if (!_.includes(this.apiGatewayUsagePlanNames, name)) {
+            throw new this.serverless.classes.Error(`API key "${name}" has no usage plan defined`);
+          }
+          keyNumber = 0;
+          _.forEach(apiKeyDefinition[name], (key) => {
+            if (!_.isString(key)) {
+              throw new this.serverless.classes.Error('API keys must be strings');
+            }
+            keyNumber += 1;
+            const usagePlanKeyLogicalId = this.provider.naming
+              .getUsagePlanKeyLogicalId(keyNumber, name);
+            const usagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId(name);
+            const resourceTemplate =
+              createUsagePlanKeyResource(this, usagePlanLogicalId, keyNumber, name);
+            _.merge(resources, {
+              [usagePlanKeyLogicalId]: resourceTemplate,
+            });
+          });
+        } else {
+          keyNumber += 1;
+          const usagePlanKeyLogicalId = this.provider.naming.getUsagePlanKeyLogicalId(keyNumber);
+          const usagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId();
+          const resourceTemplate = createUsagePlanKeyResource(this, usagePlanLogicalId, keyNumber);
+          _.merge(resources, {
+            [usagePlanKeyLogicalId]: resourceTemplate,
+          });
         }
-
-        const usagePlanKeyLogicalId = this.provider.naming
-          .getUsagePlanKeyLogicalId(usagePlanKeyNumber);
-
-        const apiKeyLogicalId = this.provider.naming
-          .getApiKeyLogicalId(usagePlanKeyNumber);
-
-        _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
-          [usagePlanKeyLogicalId]: {
-            Type: 'AWS::ApiGateway::UsagePlanKey',
-            Properties: {
-              KeyId: {
-                Ref: apiKeyLogicalId,
-              },
-              KeyType: 'API_KEY',
-              UsagePlanId: {
-                Ref: this.apiGatewayUsagePlanLogicalId,
-              },
-            },
-          },
-        });
       });
     }
     return BbPromise.resolve();


Test patch:
diff --git a/lib/plugins/aws/info/getApiKeyValues.test.js b/lib/plugins/aws/info/getApiKeyValues.test.js
index 4a04c8e4e73..74186f1ad40 100644
--- a/lib/plugins/aws/info/getApiKeyValues.test.js
+++ b/lib/plugins/aws/info/getApiKeyValues.test.js
@@ -27,7 +27,7 @@ describe('#getApiKeyValues()', () => {
     awsInfo.provider.request.restore();
   });
 
-  it('should add API Key values to this.gatheredData if API key names are available', () => {
+  it('should add API Key values to this.gatheredData if simple API key names are available', () => {
     // set the API Keys for the service
     awsInfo.serverless.service.provider.apiKeys = ['foo', 'bar'];
 
@@ -78,6 +78,78 @@ describe('#getApiKeyValues()', () => {
     });
   });
 
+  it('should add API Key values to this.gatheredData if typed API key names are available', () => {
+    // set the API Keys for the service
+    awsInfo.serverless.service.provider.apiKeys = [
+      { free: ['foo', 'bar'] },
+      { paid: ['baz', 'qux'] },
+    ];
+
+    awsInfo.gatheredData = {
+      info: {},
+    };
+
+    const apiKeyItems = {
+      items: [
+        {
+          id: '4711',
+          name: 'SomeRandomIdInUsersAccount',
+          value: 'ShouldNotBeConsidered',
+        },
+        {
+          id: '1234',
+          name: 'foo',
+          value: 'valueForKeyFoo',
+        },
+        {
+          id: '5678',
+          name: 'bar',
+          value: 'valueForKeyBar',
+        },
+        {
+          id: '9101112',
+          name: 'baz',
+          value: 'valueForKeyBaz',
+        },
+        {
+          id: '13141516',
+          name: 'qux',
+          value: 'valueForKeyQux',
+        },
+      ],
+    };
+
+    requestStub.resolves(apiKeyItems);
+
+    const expectedGatheredDataObj = {
+      info: {
+        apiKeys: [
+          {
+            name: 'foo',
+            value: 'valueForKeyFoo',
+          },
+          {
+            name: 'bar',
+            value: 'valueForKeyBar',
+          },
+          {
+            name: 'baz',
+            value: 'valueForKeyBaz',
+          },
+          {
+            name: 'qux',
+            value: 'valueForKeyQux',
+          },
+        ],
+      },
+    };
+
+    return awsInfo.getApiKeyValues().then(() => {
+      expect(requestStub.calledOnce).to.equal(true);
+      expect(awsInfo.gatheredData).to.deep.equal(expectedGatheredDataObj);
+    });
+  });
+
   it('should resolve if AWS does not return API key values', () => {
     // set the API Keys for the service
     awsInfo.serverless.service.provider.apiKeys = ['foo', 'bar'];
diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js
index ad62fad7357..da0557213e5 100644
--- a/lib/plugins/aws/lib/naming.test.js
+++ b/lib/plugins/aws/lib/naming.test.js
@@ -394,6 +394,10 @@ describe('#naming()', () => {
     it('should produce the given index with ApiGatewayApiKey as a prefix', () => {
       expect(sdk.naming.getApiKeyLogicalId(1)).to.equal('ApiGatewayApiKey1');
     });
+
+    it('should support API Key names', () => {
+      expect(sdk.naming.getApiKeyLogicalId(1, 'free')).to.equal('ApiGatewayApiKeyFree1');
+    });
   });
 
   describe('#getApiKeyLogicalIdRegex()', () => {
@@ -414,16 +418,26 @@ describe('#naming()', () => {
   });
 
   describe('#getUsagePlanLogicalId()', () => {
-    it('should return ApiGateway usage plan logical id', () => {
+    it('should return the default ApiGateway usage plan logical id', () => {
       expect(sdk.naming.getUsagePlanLogicalId())
         .to.equal('ApiGatewayUsagePlan');
     });
+
+    it('should return the named ApiGateway usage plan logical id', () => {
+      expect(sdk.naming.getUsagePlanLogicalId('free'))
+        .to.equal('ApiGatewayUsagePlanFree');
+    });
   });
 
-  describe('#getUsagePlanKeyLogicalId(keyIndex)', () => {
+  describe('#getUsagePlanKeyLogicalId()', () => {
     it('should produce the given index with ApiGatewayUsagePlanKey as a prefix', () => {
       expect(sdk.naming.getUsagePlanKeyLogicalId(1)).to.equal('ApiGatewayUsagePlanKey1');
     });
+
+    it('should support API Key names', () => {
+      expect(sdk.naming.getUsagePlanKeyLogicalId(1, 'free'))
+        .to.equal('ApiGatewayUsagePlanKeyFree1');
+    });
   });
 
   describe('#getStageLogicalId()', () => {
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js
index 188eb1743d7..6507de5bf2c 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js
@@ -17,10 +17,6 @@ describe('#compileApiKeys()', () => {
     serverless = new Serverless();
     serverless.setProvider('aws', new AwsProvider(serverless, options));
     serverless.service.service = 'first-service';
-    serverless.service.provider = {
-      name: 'aws',
-      apiKeys: ['1234567890'],
-    };
     serverless.service.provider.compiledCloudFormationTemplate = {
       Resources: {},
       Outputs: {},
@@ -30,59 +26,248 @@ describe('#compileApiKeys()', () => {
     awsCompileApigEvents.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest';
   });
 
-  it('should compile api key resource', () =>
-    awsCompileApigEvents.compileApiKeys().then(() => {
+  it('should support string notations', () => {
+    awsCompileApigEvents.serverless.service.provider.apiKeys = ['1234567890', 'abcdefghij'];
+
+    return awsCompileApigEvents.compileApiKeys().then(() => {
+      // key 1
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].Type
       ).to.equal('AWS::ApiGateway::ApiKey');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].Properties.Enabled
       ).to.equal(true);
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].Properties.Name
       ).to.equal('1234567890');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].Properties.StageKeys[0].RestApiId.Ref
       ).to.equal('ApiGatewayRestApi');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].Properties.StageKeys[0].StageName
       ).to.equal('dev');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
             awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1)
           ].DependsOn
       ).to.equal('ApiGatewayDeploymentTest');
-    })
-  );
 
-  it('throw error if apiKey property is not an array', () => {
-    awsCompileApigEvents.serverless.service.provider.apiKeys = 2;
-    expect(() => awsCompileApigEvents.compileApiKeys()).to.throw(Error);
+      // key2
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].Type
+      ).to.equal('AWS::ApiGateway::ApiKey');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].Properties.Enabled
+      ).to.equal(true);
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].Properties.Name
+      ).to.equal('abcdefghij');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].Properties.StageKeys[0].RestApiId.Ref
+      ).to.equal('ApiGatewayRestApi');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].Properties.StageKeys[0].StageName
+      ).to.equal('dev');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+            awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2)
+          ].DependsOn
+      ).to.equal('ApiGatewayDeploymentTest');
+    });
   });
 
-  it('throw error if an apiKey is not a string', () => {
-    awsCompileApigEvents.serverless.service.provider.apiKeys = [2];
-    expect(() => awsCompileApigEvents.compileApiKeys()).to.throw(Error);
+  describe('when using object notation', () => {
+    it('should support object notations', () => {
+      awsCompileApigEvents.serverless.service.provider.apiKeys = [
+        { free: ['1234567890', 'abcdefghij'] },
+        { paid: ['0987654321', 'jihgfedcba'] },
+      ];
+
+      return awsCompileApigEvents.compileApiKeys().then(() => {
+        // "free" plan resources
+        // "free" key 1
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].Type
+        ).to.equal('AWS::ApiGateway::ApiKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].Properties.Enabled
+        ).to.equal(true);
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].Properties.Name
+        ).to.equal('1234567890');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].Properties.StageKeys[0].RestApiId.Ref
+        ).to.equal('ApiGatewayRestApi');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].Properties.StageKeys[0].StageName
+        ).to.equal('dev');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'free')
+            ].DependsOn
+        ).to.equal('ApiGatewayDeploymentTest');
+        // "free" key 2
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].Type
+        ).to.equal('AWS::ApiGateway::ApiKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].Properties.Enabled
+        ).to.equal(true);
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].Properties.Name
+        ).to.equal('abcdefghij');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].Properties.StageKeys[0].RestApiId.Ref
+        ).to.equal('ApiGatewayRestApi');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].Properties.StageKeys[0].StageName
+        ).to.equal('dev');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'free')
+            ].DependsOn
+        ).to.equal('ApiGatewayDeploymentTest');
+
+        // "paid" plan resources
+        // "paid" key 1
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].Type
+        ).to.equal('AWS::ApiGateway::ApiKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].Properties.Enabled
+        ).to.equal(true);
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].Properties.Name
+        ).to.equal('0987654321');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].Properties.StageKeys[0].RestApiId.Ref
+        ).to.equal('ApiGatewayRestApi');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].Properties.StageKeys[0].StageName
+        ).to.equal('dev');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(1, 'paid')
+            ].DependsOn
+        ).to.equal('ApiGatewayDeploymentTest');
+        // "paid" key 2
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].Type
+        ).to.equal('AWS::ApiGateway::ApiKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].Properties.Enabled
+        ).to.equal(true);
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].Properties.Name
+        ).to.equal('jihgfedcba');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].Properties.StageKeys[0].RestApiId.Ref
+        ).to.equal('ApiGatewayRestApi');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].Properties.StageKeys[0].StageName
+        ).to.equal('dev');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+              awsCompileApigEvents.provider.naming.getApiKeyLogicalId(2, 'paid')
+            ].DependsOn
+        ).to.equal('ApiGatewayDeploymentTest');
+      });
+    });
   });
 });
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
index 168d83f5424..b46263c135b 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
@@ -35,45 +35,42 @@ describe('#compileUsagePlan()', () => {
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Type
       ).to.equal('AWS::ApiGateway::UsagePlan');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].DependsOn
       ).to.equal('ApiGatewayDeploymentTest');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Properties.ApiStages[0].ApiId.Ref
       ).to.equal('ApiGatewayRestApi');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Properties.ApiStages[0].Stage
       ).to.equal('dev');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Properties.Description
       ).to.equal('Usage plan for first-service dev stage');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Properties.UsagePlanName
       ).to.equal('first-service-dev');
+
+      expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal(['default']);
     });
   });
 
-  it('should compile custom usage plan resource', () => {
+  it('should support custom usage plan resource via single object notation', () => {
     serverless.service.provider.usagePlan = {
       quota: {
         limit: 500,
@@ -87,68 +84,173 @@ describe('#compileUsagePlan()', () => {
     };
 
     return awsCompileApigEvents.compileUsagePlan().then(() => {
+      const logicalId = awsCompileApigEvents.provider.naming.getUsagePlanLogicalId();
+
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Type
+          .Resources[logicalId].Type
       ).to.equal('AWS::ApiGateway::UsagePlan');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].DependsOn
+          .Resources[logicalId].DependsOn
       ).to.equal('ApiGatewayDeploymentTest');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.ApiStages[0].ApiId.Ref
+          .Resources[logicalId].Properties.ApiStages[0].ApiId.Ref
       ).to.equal('ApiGatewayRestApi');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.ApiStages[0].Stage
+          .Resources[logicalId].Properties.ApiStages[0].Stage
       ).to.equal('dev');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.Description
+          .Resources[logicalId].Properties.Description
       ).to.equal('Usage plan for first-service dev stage');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.Quota
+          .Resources[logicalId].Properties.Quota
       ).to.deep.equal({
         Limit: 500,
         Offset: 10,
         Period: 'MONTH',
       });
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.Throttle
+          .Resources[logicalId].Properties.Throttle
       ).to.deep.equal({
         BurstLimit: 200,
         RateLimit: 100,
       });
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[
-          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
-          ].Properties.UsagePlanName
+          .Resources[logicalId].Properties.UsagePlanName
       ).to.equal('first-service-dev');
+
+      expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal(['default']);
+    });
+  });
+
+  it('should support custom usage plan resources via array notation', () => {
+    const freePlanName = 'free';
+    const paidPlanName = 'paid';
+    const logicalIdFree = awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(freePlanName);
+    const logicalIdPaid = awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(paidPlanName);
+
+    serverless.service.provider.usagePlan = [
+      {
+        [freePlanName]: {
+          quota: {
+            limit: 1000,
+            offset: 100,
+            period: 'MONTH',
+          },
+          throttle: {
+            burstLimit: 1,
+            rateLimit: 1,
+          },
+        },
+      },
+      {
+        [paidPlanName]: {
+          quota: {
+            limit: 1000000,
+            offset: 200,
+            period: 'MONTH',
+          },
+          throttle: {
+            burstLimit: 1000,
+            rateLimit: 1000,
+          },
+        },
+      },
+    ];
+
+    return awsCompileApigEvents.compileUsagePlan().then(() => {
+      // resources for the "free" plan
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Type
+      ).to.equal('AWS::ApiGateway::UsagePlan');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].DependsOn
+      ).to.equal('ApiGatewayDeploymentTest');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.ApiStages[0].ApiId.Ref
+      ).to.equal('ApiGatewayRestApi');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.ApiStages[0].Stage
+      ).to.equal('dev');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.Description
+      ).to.equal(`Usage plan "${freePlanName}" for first-service dev stage`);
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.Quota
+      ).to.deep.equal({
+        Limit: 1000,
+        Offset: 100,
+        Period: 'MONTH',
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.Throttle
+      ).to.deep.equal({
+        BurstLimit: 1,
+        RateLimit: 1,
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdFree].Properties.UsagePlanName
+      ).to.equal(`first-service-${freePlanName}-dev`);
+
+      // resources for the "paid" plan
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Type
+      ).to.equal('AWS::ApiGateway::UsagePlan');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].DependsOn
+      ).to.equal('ApiGatewayDeploymentTest');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.ApiStages[0].ApiId.Ref
+      ).to.equal('ApiGatewayRestApi');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.ApiStages[0].Stage
+      ).to.equal('dev');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.Description
+      ).to.equal(`Usage plan "${paidPlanName}" for first-service dev stage`);
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.Quota
+      ).to.deep.equal({
+        Limit: 1000000,
+        Offset: 200,
+        Period: 'MONTH',
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.Throttle
+      ).to.deep.equal({
+        BurstLimit: 1000,
+        RateLimit: 1000,
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[logicalIdPaid].Properties.UsagePlanName
+      ).to.equal(`first-service-${paidPlanName}-dev`);
+
+      expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal([
+        freePlanName, paidPlanName,
+      ]);
     });
   });
 
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js
index 96a2129c61c..eb0f6f157b8 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js
@@ -17,10 +17,6 @@ describe('#compileUsagePlanKeys()', () => {
     serverless = new Serverless();
     serverless.setProvider('aws', new AwsProvider(serverless, options));
     serverless.service.service = 'first-service';
-    serverless.service.provider = {
-      name: 'aws',
-      apiKeys: ['1234567890'],
-    };
     serverless.service.provider.compiledCloudFormationTemplate = {
       Resources: {},
       Outputs: {},
@@ -28,48 +24,206 @@ describe('#compileUsagePlanKeys()', () => {
     awsCompileApigEvents = new AwsCompileApigEvents(serverless, options);
     awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
     awsCompileApigEvents.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest';
-    awsCompileApigEvents.apiGatewayUsagePlanLogicalId = 'UsagePlan';
   });
 
-  it('should compile usage plan key resource', () =>
-    awsCompileApigEvents.compileUsagePlanKeys().then(() => {
+  it('should support string notations', () => {
+    const defaultUsagePlanLogicalId = awsCompileApigEvents
+      .provider.naming.getUsagePlanLogicalId();
+    awsCompileApigEvents.apiGatewayUsagePlanNames = ['default'];
+    awsCompileApigEvents.serverless.service.provider.apiKeys = ['1234567890', 'abcdefghij'];
+
+    return awsCompileApigEvents.compileUsagePlanKeys().then(() => {
+      // key 1
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1)
           ].Type
       ).to.equal('AWS::ApiGateway::UsagePlanKey');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1)
           ].Properties.KeyId.Ref
       ).to.equal('ApiGatewayApiKey1');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1)
           ].Properties.KeyType
       ).to.equal('API_KEY');
-
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
           awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1)
           ].Properties.UsagePlanId.Ref
-      ).to.equal('UsagePlan');
-    })
-  );
+      ).to.equal(defaultUsagePlanLogicalId);
 
-  it('throw error if apiKey property is not an array', () => {
-    awsCompileApigEvents.serverless.service.provider.apiKeys = 2;
-    expect(() => awsCompileApigEvents.compileUsagePlanKeys()).to.throw(Error);
+      // key 2
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2)
+          ].Type
+      ).to.equal('AWS::ApiGateway::UsagePlanKey');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2)
+          ].Properties.KeyId.Ref
+      ).to.equal('ApiGatewayApiKey2');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2)
+          ].Properties.KeyType
+      ).to.equal('API_KEY');
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2)
+          ].Properties.UsagePlanId.Ref
+      ).to.equal(defaultUsagePlanLogicalId);
+    });
   });
 
-  it('throw error if an apiKey is not a string', () => {
-    awsCompileApigEvents.serverless.service.provider.apiKeys = [2];
-    expect(() => awsCompileApigEvents.compileUsagePlanKeys()).to.throw(Error);
+  describe('when using object notation', () => {
+    it('should support object notations', () => {
+      const freeUsagePlanName = 'free';
+      const paidUsagePlanName = 'paid';
+      const freeUsagePlanLogicalId = awsCompileApigEvents
+        .provider.naming.getUsagePlanLogicalId(freeUsagePlanName);
+      const paidUsagePlanLogicalId = awsCompileApigEvents
+        .provider.naming.getUsagePlanLogicalId(paidUsagePlanName);
+      awsCompileApigEvents.apiGatewayUsagePlanNames = [freeUsagePlanName, paidUsagePlanName];
+      awsCompileApigEvents.serverless.service.provider.apiKeys = [
+        { free: ['1234567890', 'abcdefghij'] },
+        { paid: ['0987654321', 'jihgfedcba'] },
+      ];
+
+      return awsCompileApigEvents.compileUsagePlanKeys().then(() => {
+        // "free" plan resources
+        // "free" key 1
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'free')
+            ].Type
+        ).to.equal('AWS::ApiGateway::UsagePlanKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'free')
+            ].Properties.KeyId.Ref
+        ).to.equal('ApiGatewayApiKeyFree1');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'free')
+            ].Properties.KeyType
+        ).to.equal('API_KEY');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'free')
+            ].Properties.UsagePlanId.Ref
+        ).to.equal(freeUsagePlanLogicalId);
+        // "free" key 2
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'free')
+            ].Type
+        ).to.equal('AWS::ApiGateway::UsagePlanKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'free')
+            ].Properties.KeyId.Ref
+        ).to.equal('ApiGatewayApiKeyFree2');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'free')
+            ].Properties.KeyType
+        ).to.equal('API_KEY');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'free')
+            ].Properties.UsagePlanId.Ref
+        ).to.equal(freeUsagePlanLogicalId);
+
+        // "paid" plan resources
+        // "paid" key 1
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'paid')
+            ].Type
+        ).to.equal('AWS::ApiGateway::UsagePlanKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'paid')
+            ].Properties.KeyId.Ref
+        ).to.equal('ApiGatewayApiKeyPaid1');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'paid')
+            ].Properties.KeyType
+        ).to.equal('API_KEY');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1, 'paid')
+            ].Properties.UsagePlanId.Ref
+        ).to.equal(paidUsagePlanLogicalId);
+        // "paid" key 2
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'paid')
+            ].Type
+        ).to.equal('AWS::ApiGateway::UsagePlanKey');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'paid')
+            ].Properties.KeyId.Ref
+        ).to.equal('ApiGatewayApiKeyPaid2');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'paid')
+            ].Properties.KeyType
+        ).to.equal('API_KEY');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[
+            awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2, 'paid')
+            ].Properties.UsagePlanId.Ref
+        ).to.equal(paidUsagePlanLogicalId);
+      });
+    });
+
+    it('should throw if api key name does not match a usage plan', () => {
+      awsCompileApigEvents.apiGatewayUsagePlanNames = ['default'];
+      awsCompileApigEvents.serverless.service.provider.apiKeys = [
+        { free: ['1234567890'] },
+      ];
+      expect(() => awsCompileApigEvents.compileUsagePlanKeys())
+        .to.throw(/has no usage plan defined/);
+    });
+
+    it('should throw if api key definitions are not strings', () => {
+      awsCompileApigEvents.apiGatewayUsagePlanNames = ['free'];
+      awsCompileApigEvents.serverless.service.provider.apiKeys = [
+        { free: [{ foo: 'bar' }] },
+      ];
+      expect(() => awsCompileApigEvents.compileUsagePlanKeys())
+        .to.throw(/must be strings/);
+    });
   });
 });