How to Return Binary Content with AWS API Gateway and Lambda

If you've ever tried returning an image or a PDF from a Lambda function through API Gateway, you've probably hit a wall. Maybe the browser downloads a corrupt file, or your API returns a string of unreadable characters. I’ve been there, and the fix wasn’t immediately obvious.

In this post, I’ll walk you through the complete setup for returning binary content (like images) from API Gateway using a Lambda function. I’ll also show how to configure it using SAM/CloudFormation and share a few lessons I wish I knew when I started.

Why Is Binary Content So Tricky with API Gateway?

API Gateway was originally built around text-based communication, like JSON or plain text. By default, it doesn’t expect raw binary. So when you return binary data, you must explicitly tell API Gateway how to handle it. If you skip any step, you’ll either get unreadable output or empty files.

Stack Used

  • AWS Lambda (Python)

  • API Gateway (REST, not HTTP API)

  • AWS SAM (Serverless Application Model)

  • Google Maps API to fetch image content

Step 1: Configure Binary Support in API Gateway

You need to declare which binary media types your API supports. This is done in the BinaryMediaTypes section of the AWS::ApiGateway::RestApi resource.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example SAM template for returning binary content through API Gateway

Globals:
  Function:
    Timeout: 30
    Runtime: python3.12

Resources:
  BinaryEnabledApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: binary-enabled-api
      BinaryMediaTypes:
        - "*/*"
      EndpointConfiguration:
        Types:
          - PRIVATE

  ApiRootResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref BinaryEnabledApi
      ParentId: !GetAtt BinaryEnabledApi.RootResourceId
      PathPart: "api"

  ImageResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref BinaryEnabledApi
      ParentId: !Ref ApiRootResource
      PathPart: "image"

  GetImageMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref BinaryEnabledApi
      ResourceId: !Ref ImageResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub
          - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations"
          - { LambdaArn: !GetAtt BinaryLambdaFunction.Arn }
        PassthroughBehavior: WHEN_NO_MATCH

  BinaryLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: binary-content-handler
      CodeUri: src/
      Handler: app.lambda_handler
      MemorySize: 128
      Tracing: Active
      Policies: AWSLambdaBasicExecutionRole

  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref BinaryLambdaFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryEnabledApi}/*/GET/api/image"

  ApiDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - GetImageMethod
    Properties:
      RestApiId: !Ref BinaryEnabledApi

  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: prod
      RestApiId: !Ref BinaryEnabledApi
      DeploymentId: !Ref ApiDeployment
      MethodSettings:
        - ResourcePath: "/*"
          HttpMethod: "*"
          MetricsEnabled: true
          LoggingLevel: INFO

Outputs:
  ApiInvokeUrl:
    Description: Invoke URL for the binary-enabled API
    Value: !Sub "https://${BinaryEnabledApi}.execute-api.${AWS::Region}.amazonaws.com/prod/api/image"

Important: Any time you update BinaryMediaTypes, make sure to redeploy your API and its stage. Changes won't take effect until that happens.

Step 2: Make Lambda Return the Right Format

Your Lambda function must return a Base64-encoded string with specific fields set in the response.

Example: Returning a Static Map Image

import base64
import requests

def lambda_handler(event, context):
    image_url = "https://maps.googleapis.com/maps/api/staticmap?center=New+York&zoom=13&size=600x300&key=YOUR_API_KEY"
    response = requests.get(image_url)

    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "image/png"
        },
        "body": base64.b64encode(response.content).decode("utf-8"),
        "isBase64Encoded": True
    }

Step 3: Test with Real Clients

Tools that won't work reliably:

  • AWS Console’s “Test” feature

  • CloudWatch Logs (shows only the base64 string)

Tools that work correctly:

  • Postman

  • Curl

  • Browsers

Example curl command:

curl -H "Authorization: ..." \
     https://your-api-id.execute-api.us-west-2.amazonaws.com/prod/api/image \
     --output result.png

Common Pitfalls

Here are the mistakes that cost me the most time:

  • Forgetting to set "isBase64Encoded": true in the Lambda response

  • Not setting the correct Content-Type header

  • Expecting binary output to work with the default test tools

  • Using HTTP API instead of REST API (binary support is limited in HTTP APIs)

  • Forgetting to redeploy after changing BinaryMediaTypes

Final Thoughts

Returning binary content through API Gateway is not intuitive at first, but once you know what to configure, it works reliably. This setup is especially useful for services that return images, PDFs, or any kind of binary payload directly from Lambda.

If this helped you get your API working, feel free to share it or reach out. I’d love to hear how you used it in your project.

References

0
Subscribe to my newsletter

Read articles from Renato Ramos Nascimento directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Renato Ramos Nascimento
Renato Ramos Nascimento

With over 14 years in software development, I specialize in backend systems using .NET, Python, and Java. I bring full lifecycle expertise, including requirements analysis, client/server and data layer development, automated testing (unit, integration, end-to-end), and CI/CD implementations using Docker, GitLab Pipelines, GitHub Actions, Terraform, and AWS CodeStar.