Is there a way to change the http status codes returned by Amazon API Gateway?

91,812

Solution 1

Update per 20-9-2016

Amazon finally made this easy using the Lambda Proxy integration. This allows your Lambda function to return proper HTTP codes and headers:

let response = {
    statusCode: '400',
    body: JSON.stringify({ error: 'you messed up!' }),
    headers: {
        'Content-Type': 'application/json',
    }
};

context.succeed(response);

Say goodbye request/response mapping in the API Gateway!

Option 2

Integrate an existing Express app with Lambda/API Gateway using aws-serverless-express.

Solution 2

Here's the fastest way to return custom HTTP Status Codes and a custom errorMessage:

In the API Gateway dashboard, do the following:

  1. In the method for your resource, click on method response
  2. In the HTTP Status table, click add response and add in each HTTP Status Code you would like to use.
  3. In the method for your resource, click on integration response
  4. Add an integration response for each of the HTTP Status Codes you created earlier. Make sure input passthrough is checked. Use lambda error regex to identify which status code should be used when you return an error message from your lambda function. For example:

    // Return An Error Message String In Your Lambda Function
    
    return context.fail('Bad Request: You submitted invalid input');
    
    // Here is what a Lambda Error Regex should look like.
    // Be sure to include the period and the asterisk so any text
    // after your regex is mapped to that specific HTTP Status Code
    
    Bad Request: .*
    
  5. Your API Gateway route should return this:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. I see no way to copy these settings and re-use it for different methods, so we have much annoying redundant manual inputting to do!

My Integration Responses look like this:

aws api gateway lambda error response handling

Solution 3

To be able to return a custom error object as JSON you have to jump through a couple of hoops.

First, you must fail the Lambda and pass it a stringified JSON object:

exports.handler = function(event, context) {
    var response = {
        status: 400,
        errors: [
            {
              code:   "123",
              source: "/data/attributes/first-name",
              message:  "Value is too short",
              detail: "First name must contain at least three characters."
            },
            {
              code:   "225",
              source: "/data/attributes/password",
              message: "Passwords must contain a letter, number, and punctuation character.",
              detail: "The password provided is missing a punctuation character."
            },
            {
              code:   "226",
              source: "/data/attributes/password",
              message: "Password and password confirmation do not match."
            }
        ]
    }

    context.fail(JSON.stringify(response));
};

Next, you setup the regex mapping for each of the status codes you would like to return. Using the object I defined above you would setup this regex for 400:

.*"status":400.*

Finally, you setup a Mapping Template to extract the JSON response from the errorMessage property returned by Lambda. The Mapping Template looks like this:

$input.path('$.errorMessage')

I wrote an article on this that goes into more detail and explains the response flow from Lambda to API Gateway here: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object-and-status-code-from-api-gateway-with-lambda/

Solution 4

1) Configure your API Gateway resource to use Lambda Proxy Integration by checking the checkbox labeled "Use Lambda Proxy integration" on the "Integration Request" screen of the API Gateway resource definition. (Or define it in your cloudformation/terraform/serverless/etc config)

2) Change your lambda code in 2 ways

  • Process the incoming event (1st function argument) appropriately. It is no longer just the bare payload, it represents the entire HTTP request including headers, query string, and body. Sample below. Key point is that JSON bodies will be strings requiring explicit JSON.parse(event.body) call (don't forget try/catch around that). Example is below.
  • Respond by calling the callback with null then a response object that provides the HTTP details including statusCode, body, and headers.
    • body should be a string, so do JSON.stringify(payload) as needed
    • statusCode can be a number
    • headers is an object of header names to values

Sample Lambda Event Argument for Proxy Integration

{
    "resource": "/example-path",
    "path": "/example-path",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "US",
        "Content-Type": "application/json",
        "Host": "exampleapiid.execute-api.us-west-2.amazonaws.com",
        "User-Agent": "insomnia/4.0.12",
        "Via": "1.1 9438b4fa578cbce283b48cf092373802.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "oCflC0BzaPQpTF9qVddpN_-v0X57Dnu6oXTbzObgV-uU-PKP5egkFQ==",
        "X-Forwarded-For": "73.217.16.234, 216.137.42.129",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "queryStringParameters": {
        "bar": "BarValue",
        "foo": "FooValue"
    },
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "accountId": "666",
        "resourceId": "xyz",
        "stage": "dev",
        "requestId": "5944789f-ce00-11e6-b2a2-dfdbdba4a4ee",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "apiKey": null,
            "sourceIp": "73.217.16.234",
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "insomnia/4.0.12",
            "user": null
        },
        "resourcePath": "/example-path",
        "httpMethod": "POST",
        "apiId": "exampleapiid"
    },
    "body": "{\n  \"foo\": \"FOO\",\n  \"bar\": \"BAR\",\n  \"baz\": \"BAZ\"\n}\n",
    "isBase64Encoded": false
}

Sample Callback Response Shape

callback(null, {
  statusCode: 409,
  body: JSON.stringify(bodyObject),
  headers: {
    'Content-Type': 'application/json'
  }
})

Notes - I believe the methods on context such as context.succeed() are deprecated. They are no longer documented although they do still seem to work. I think coding to the callback API is the correct thing going forward.

Solution 5

I wanted an error from Lambda to be proper 500 error, after doing a lot of research, came up with the below, that works:

On LAMBDA

For a good response, I am returning as below:

exports.handler = (event, context, callback) => {
    // ..

    var someData1 =  {
        data: {
            httpStatusCode: 200,
            details: [
                {
                    prodId: "123",
                    prodName: "Product 1"
                },
                {
                    "more": "213",
                    "moreDetails": "Product 2"
                }
            ]
        }
    };
    return callback(null, someData1);
}

For a bad response, returning as below

exports.handler = (event, context, callback) => {
    // ..

    var someError1 = {
        error: {
            httpStatusCode: 500,
            details: [
                {
                    code: "ProductNotFound",
                    message: "Product not found in Cart",
                    description: "Product should be present after checkout, but not found in Cart",
                    source: "/data/attributes/product"
                },
                {
                    code: "PasswordConfirmPasswordDoesntMatch",
                    message: "Password and password confirmation do not match.",
                    description: "Password and password confirmation must match for registration to succeed.",
                    source: "/data/attributes/password",
                }
            ]
        }
    };

    return callback(new Error(JSON.stringify(someError1)));
}

On API Gateway

For a GET METHOD, say GET of /res1/service1:

Through Method Response > Add Response, added 3 responses:
- 200
- 300
- 400

Then,

Through 'Integration Response' > 'Add integration response', create a Regex for 400 errors (client error):

Lambda Error Regex    .*"httpStatusCode":.*4.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  


Similarly, create a Regex for 500 errors (server error):

Lambda Error Regex    .*"httpStatusCode":.*5.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  

Now, publish /res1/service1, hit the published URL, that is connected to above lambda

Used Advanced REST client (or Postman) chrome plugin, you will see proper http codes like server error (500) or 400, instead of 200 http response code for all requests where were given in "httpStatusCode".

From the 'Dashboard' of API, in API Gateway, we can see the http status codes like below:

400 & 500 errors

Share:
91,812

Related videos on Youtube

MonkeyBonkey
Author by

MonkeyBonkey

CTO of Pictorious.com, a mobile app for turning photo sharing into a fun meme-game.

Updated on December 06, 2021

Comments

  • MonkeyBonkey
    MonkeyBonkey over 2 years

    For instance if I want to return a specific 400 error for invalid parameters or perhaps a 201 when the lambda function call resulted in a create.

    I'd like to have different http status codes but it looks like api gateway always returns a 200 status code even if the lambda function is returning an error.

    • MonkeyBonkey
      MonkeyBonkey almost 9 years
      so it looks like the issue I was having was that I was returning a custom error type - which makes the errorMessage regex not work correctly. Returning a standard string in the fail response from lambda will make the below solution work - returning your own custom error object however, will not.
    • Relu Mesaros
      Relu Mesaros over 7 years
      my solution was to switch from Serveless version 0.5 to 1.0. Also, I'm using the response from Serveless documentation, specifying the statusCode in the response object as a property. Hope it helps
  • MonkeyBonkey
    MonkeyBonkey almost 9 years
    so it looks like my issue was that the regex trigger was never working since I return an error object from lambda in the fail method, rather than just a string. e.g. return context.fail(new Error('bad one'))
  • ac360
    ac360 almost 9 years
    MonkeyBonkey, It also may be possible to return an error object by using Output Mapping. I'm going to dig into this today and possibly update this answer if it works.
  • Darren Ehlers
    Darren Ehlers over 8 years
    I follow this, but my Lambda func calls 3rd party API, returns JSON like this: { "code": 401, "error": "The access token provided has expired" } I'm trying to work out a way to parse out and set the HTTP Response Code (ie: 401) and the message, without having to add a Response Method for each possible server message. All I get in the Output Mapping Template is the composited error message from the context.fail(errorMsg), and I can't see a way to "parse" out the code & message to generate the JSON output.
  • kalisjoshua
    kalisjoshua over 8 years
    What if your lambda function returns an error object instead of a string? I want to return validation messages to indicate all (possible) errors.
  • Akshay Dhalwala
    Akshay Dhalwala over 8 years
    Most of my experimentation and research has indicated that your lambda function can't really return an object, at least not if you want to use with API Gateway. No matter what you do, context.fail will always be transformed into { "errorMessage" : <context.fail error parameter> } OR { "errorMessage": <error string message>, "errorType": <ErrorType>, "stackTrace": blahblahblah} if your code threw an error. the only way that api gateway can allow for different status codes is to basically do regex tests on specifically the "errorMessage" property
  • Carl
    Carl over 8 years
    @kalisjoshua I recently published a fairly detailed post on error handling with API Gateway/Lambda: jayway.com/2015/11/07/…
  • routeburn
    routeburn over 8 years
    What's the equivalent of context.fail for Python Lambda's?
  • devxoul
    devxoul over 8 years
  • Ben Davis
    Ben Davis over 8 years
    Is there no way to change the status code in non-error responses? What if I wanted to send "201 Created" along with the created object?
  • Cory Mawhorter
    Cory Mawhorter over 8 years
    @BenDavis then i believe you'd make 201 the default response code (instead of 200) and it would be what gets returned for all non-error responses.
  • Ben Davis
    Ben Davis over 8 years
    @CoryMawhorter that doesn't really solve the problem. A PUT request on a resource could result in either 200 OK or 201 CREATED.
  • Cory Mawhorter
    Cory Mawhorter over 8 years
    @BenDavis AFAIK that's not possible. You seem to be able to set all other things with api gateway other than the response code. In fact, the whole default response thing is pretty bad and leads to unforeseen error responses being treated as successes. It all feels extremely clunky.
  • Relu Mesaros
    Relu Mesaros over 7 years
    I can't integrate it, I mean, I get 200 status and the created response(the created error). Am I missing something ? How does the "s-function.json" looks like ?
  • Eric Eijkelenboom
    Eric Eijkelenboom over 7 years
    For the simplest example, look at AWS's own Lambda blueprint called microservice-http-endpoint (in the AWS Lambda console). You mention "s-function.json", which sounds like you are using the Serverless framework (serverless.com). This is another beast entirely and should not be confused with aws-serverless-express or 'raw' Lambda/API Gateway. My answer does not describe how to make this work using the Serverless framework.
  • Relu Mesaros
    Relu Mesaros over 7 years
    yep, aws-serverless-express is another 'beast', I'm reading about now .. the 'express' module is required in order to use aws-serverless-express ?
  • Mirko Vukušić
    Mirko Vukušić about 7 years
    amazing find! It just saved me hours of banging my head! And its far from obvious.
  • Carlos Ballock
    Carlos Ballock about 7 years
    Mirko, I'm glad it helped you!
  • Widdershin
    Widdershin almost 7 years
    For anyone wondering, this can also be achieved using the new callback style. Just do callback(null, {statusCode: 200, body: 'whatever'}).
  • Sushil
    Sushil almost 7 years
    @unclemeat I have the same questions. Did you figure it out? How to do this in python?
  • unclemeat
    unclemeat almost 7 years
    @Sushil yeah, you just return the JSON like in the response variable above.
  • Perimosh
    Perimosh over 6 years
    @kennbrodhagen do you know about API Gateway and Java Lambdas? I'm using a kind of the same reg exp, and it is not working for me. I use .*statusCode":422.*
  • kennbrodhagen
    kennbrodhagen over 6 years
    @Perimosh check out this article that explains how to do this with Java Exceptions: aws.amazon.com/blogs/compute/…
  • Jithu R Jacob
    Jithu R Jacob over 6 years
    @Sushil I have solved this in Python with LambdaProxyIntegration and returning return { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
  • Casey Gibson - AOAUS
    Casey Gibson - AOAUS almost 6 years
    You also need to make sure you have "Use Lambda Proxy integration" turned on in the "Integration Request" settings.
  • Rod
    Rod over 5 years
    what if the example was a lambda to lambda call. is this still what the called lambda would return? and how can i read that httpStatus on the calling lambda.
  • rboy
    rboy over 4 years
    If you're using Java you need to return a POJO object class and not a String JSON
  • Andy N
    Andy N about 4 years
    This does not work. I still get 200 status returned with this entire response output. Cannot set the api to actually return 409 status
  • UserJ
    UserJ over 3 years
    @CoryMawhorter I have same issue, I need to send 202 for asynchronous request. Can you let me know how to do it
  • donutello
    donutello about 3 years
    I have another one issue here -- I want to return any of 1701 or 13550 status codes, but it is always 502!
  • Ryan Schaefer
    Ryan Schaefer about 3 years
    those aren't valid status codes... developer.mozilla.org/en-US/docs/Web/HTTP/Status
  • Ricardo Nolde
    Ricardo Nolde about 2 years
    The problem with 4xx and 5xx using this setup is usually related to CORS. This is the way to fix it.