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 responseNot setting the correct
Content-Type
headerExpecting 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
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.