TIL / S3 presigned URLs get large uploads past API Gateway's payload limit

S3 presigned URLs get large uploads past API Gateway's payload limit

AWSAPI GatewayS3Lambda

The problem

An upload endpoint that takes the file as part of the request body has to pass through the whole chain: client to API Gateway to Lambda. API Gateway caps REST API payloads at 10 MB, hard, with no way to raise it (see the Amazon API Gateway quotas). Files above that size (in my case, large SVGs) simply can’t go through that path.

The fix

Don’t send the file through API Gateway at all. Have Lambda generate a presigned S3 PUT URL and hand it back to the client; the client uploads the bytes straight to S3, bypassing API Gateway’s payload limit entirely.

import boto3

s3 = boto3.client("s3")

def get_upload_url(key: str, content_type: str) -> str:
    return s3.generate_presigned_url(
        "put_object",
        Params={"Bucket": "uploads-bucket", "Key": key, "ContentType": content_type},
        ExpiresIn=300,
    )

Gotcha

The client’s Content-Type header on the actual PUT has to match what the URL was signed with exactly, or S3 rejects it with a signature mismatch - that’s the same constraint this site’s own presigned-upload code works around by using a raw fetch instead of a JSON API client (see frontend/lib/api/uploads.ts in the HealAll repo for a worked example).