AWS CloudFormation: How to use API Gateway custom domain mapping

Photo by Ken Suarez on Unsplash

AWS CloudFormation: How to use API Gateway custom domain mapping

How to resolve the error: Invalid stage identifier specified (Service: ApiGateway, Status Code: 400)

API Gateway is a very popular service, and for good reasons. Setting up API Gateway via CloudFormation is very common. But I often find new CloudFormation users struggle with the custom domain mapping.

Custom domain names are simpler and more intuitive URLs that you can provide to your API users. An API's specific stages and versions can be associated with a custom domain name and managed through API Gateway. A custom domain allows you to change the underlying API Gateway for upgrades, testing or DR purposes without affecting the end-users. Hence it's recommended to use a custom domain when possible.

In CloudFormation, API Gateway and its custom domain are defined in the Resources section. And the examples we often find online shows something like this:

Resources:
    MyAPIName:
        Type: "AWS::Serverless::Api"
        Properties:
            StageName: production
            Name: ModuleABCAPI
            EndpointConfiguration: Regional
            Cors:
                AllowMethods: "'POST, GET, OPTIONS'"
                AllowHeaders: "'Content-Type, X-Amz-Date, Authorization, X-Api-Key, x-requested-with'"
                AllowOrigin: "'*'"
                MaxAge: "'600'"
    MyAPIMapping:
        Type: AWS::ApiGateway::BasePathMapping
        Properties: 
           BasePath: management
           DomainName: api.appdomain.com
           RestApiId: !Ref MyAPIName
           Stage: production

But the deployment often fails with the below error:

Resource handler returned message: "Invalid stage identifier specified (Service: ApiGateway, Status Code: 400, .....xxxxx.... HandlerErrorCode: InvalidRequest)

So let's investigate further: The resource of type 'AWS::Serverless::Api' generates three resources, which are of type :

  • AWS::ApiGateway::RestApi
  • AWS::ApiGateway::Deployment
  • AWS::ApiGateway::Stage

You can also refer to the below snippet from the AWS documentation:

When an AWS::Serverless::Api is specified, AWS Serverless Application Model (AWS SAM) always generates an AWS::ApiGateway::RestApi base AWS CloudFormation resource. In addition, it also always generates an AWS::ApiGateway::Stage and an AWS::ApiGateway::Deployment resource.

The resource of type "AWS::ApiGateway::Stage" needs to be created before the resource "AWS::ApiGateway::BasePathMapping" starts its creation, as the StageName mentioned in the "AWS::ApiGateway::BasePathMapping" resource, and it should be present before the resource of type "AWS::ApiGateway::BasePathMapping" start its creation.

When defining our stack like the above example, creating the resource of type "AWS::ApiGateway::Stage" isn't guaranteed to start before the "AWS::ApiGateway::BasePathMapping". It intermittently fails with the "Invalid stage identifier specified" error.

The sequence in which the resources generated by the resource "AWS::Serverless::Api" are created is as follows:
AWS::ApiGateway::RestApi --> AWS::ApiGateway::Deployment --> AWS::ApiGateway::Stage

So, to avoid the error and to make sure that the creation of the resource "MyAPIMapping(AWS::ApiGateway::BasePathMapping)" starts only after the resource of type 'AWS::ApiGateway::Stage' is created, we need to use the 'DependsOn' attribute in the resource "MyAPIMapping" for the resource of type 'AWS::ApiGateway::Stage' as below:

Resources:
    MyAPIName:
        Type: "AWS::Serverless::Api"
        Properties:
            StageName: production
            Name: ModuleABCAPI
            EndpointConfiguration: Regional
            Cors:
                AllowMethods: "'POST, GET, OPTIONS'"
                AllowHeaders: "'Content-Type, X-Amz-Date, Authorization, X-Api-Key, x-requested-with'"
                AllowOrigin: "'*'"
                MaxAge: "'600'"
    MyAPIMapping:
        Type: AWS::ApiGateway::BasePathMapping
        DependsOn:
           - MyAPINameStage
        Properties: 
           BasePath: management
           DomainName: api.appdomain.com
           RestApiId: !Ref MyAPIName
           Stage: production

The Logical Name of the 'AWS::ApiGateway::Stage' resource type would be the concatenation of the API logical name and word Stage: Stage. You can also refer to this GitHub issue for further details.

So, based on our example, the DependsOn attribute in the 'MyAPIMapping' resource should refer to the "MyAPINameStage(AWS::ApiGateway::Stage)" resource as shown above.

Once the above attribute is added, the stack deployment should work. I hope you will find the info useful and possibly save some time in the process.