API Gateway から Lambda を呼ぶときは、ほぼ「プロキシ統合」を使います。 リクエストの中身がまるごと event として Lambda に渡り、Lambda の返り値がそのまま HTTP のレスポンスになる方式です。そのため、Lambda は決まった形の dict を返す必要があり、形が違うと API Gateway は 502 を返します。

import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,                                  # 整数
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": "hello"}, ensure_ascii=False),  # 文字列にする
    }
選ぶもの REST API HTTP API
位置づけ(ドキュメント) 機能が多い 機能を絞り、安い価格で提供
Lambda に渡る event ペイロード 1.0 の形 1.0 か 2.0 を選ぶ
statusCode を省略 できない 2.0 なら省略できる(200 として扱う)
CORS 自分で返す(プロキシ統合の場合) API の設定で付けられる
統合のタイムアウト 50ミリ秒〜29秒 最大30秒

確認環境:Python 3.11(Linux)と、AWS が公開している Lambda のランタイムクライアント awslambdaric 4.2.0 で、ハンドラに 1.0・2.0 の event を渡して返り値を取りました。API Gateway がその返り値をどう扱うか(502 になる条件など)は、AWS の公式ドキュメントと AWS re:Post のナレッジセンターに基づいて書いています(確認日 2026年10月12日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「API Gateway Lambda|REST APIとHTTP APIの違い」

REST API と HTTP API の違い(比較表)

ドキュメントの比較ページの冒頭には、次のように書かれています。API キー、クライアントごとのスロットリング、リクエストの検証、AWS WAF、プライベート API が要るなら REST API。それらが要らないなら HTTP API。

以下は、比較ページの表から主な行をまとめたものです(Yes=対応、No=非対応)。

エンドポイント・セキュリティ・認可

機能 REST API HTTP API
エッジ最適化エンドポイント Yes No
リージョンエンドポイント Yes Yes
プライベートエンドポイント Yes No
相互 TLS 認証 Yes Yes
AWS WAF Yes No
IAM 認可 Yes Yes
リソースポリシー Yes No
Amazon Cognito Yes Yes(JWT オーソライザーで)
Lambda オーソライザー Yes Yes
JWT オーソライザー No(Lambda オーソライザーで検証はできる) Yes

API の管理・開発

機能 REST API HTTP API
カスタムドメイン Yes Yes
API キー・使用量プラン Yes No
クライアントごとのレート制限 Yes No
CORS の設定 Yes Yes
テスト呼び出し(コンソール) Yes No
キャッシュ Yes No
自動デプロイ No Yes
カスタムのゲートウェイレスポンス Yes No
カナリアリリース Yes No
リクエストの検証 Yes No
リクエストボディの変換 Yes No

監視・統合

機能 REST API HTTP API
CloudWatch メトリクス Yes Yes
アクセスログ(CloudWatch Logs) Yes Yes
実行ログ Yes No
AWS X-Ray Yes No
Lambda 関数 Yes Yes
モック統合 Yes No
レスポンスストリーミング Yes No
AWS Cloud Map とのプライベート統合 No Yes

選び方の目安です。

こうしたい 選ぶもの
API キーで利用者ごとに回数を制限したい REST API
WAF で守りたい、VPC 内だけで公開したい REST API
入力の検証やキャッシュを API Gateway でしたい REST API
実行ログ・X-Ray で細かく調べたい REST API
JWT(Cognito など)で認可して、Lambda につなぐだけ HTTP API
設定を少なく、変更を自動でデプロイしたい HTTP API

Lambda に渡る event:ペイロード 1.0 と 2.0

REST API は 1.0 の形、HTTP API は 1.0 か 2.0 を選びます。 ドキュメントでは、HTTP API の Lambda 統合を AWS CLI・CloudFormation・SDK で作るときは payloadFormatVersion の指定が必須で、コンソールは指定しなければ最新の版を使います。

取り出したいもの 1.0 2.0
HTTP メソッド event["httpMethod"] event["requestContext"]["http"]["method"]
パス event["path"] event["rawPath"]
ルート event["resource"](例 /items/{id}) event["routeKey"](例 GET /items/{id})
クエリ queryStringParameters、重複は multiValueQueryStringParameters queryStringParameters(重複はカンマでつなぐ)、rawQueryString
ヘッダー headers、重複は multiValueHeaders headers(重複はカンマでつなぐ。名前は小文字)
Cookie headers の中 cookies(配列)
パスパラメータ pathParameters pathParameters
送信元 IP requestContext.identity.sourceIp requestContext.http.sourceIp
本文 body(文字列。無ければ null) body(文字列)
Base64 か isBase64Encoded isBase64Encoded

ドキュメントの例の 1.0 には "version": "1.0" が、2.0 には "version": "2.0" が入っています。2.0 の例の要点だけを抜き出すと次の形です。

{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/my/path",
  "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
  "cookies": ["cookie1", "cookie2"],
  "headers": { "header1": "value1", "header2": "value1,value2" },
  "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" },
  "requestContext": {
    "http": { "method": "POST", "path": "/my/path", "protocol": "HTTP/1.1", "sourceIp": "192.0.2.1", "userAgent": "agent" },
    "routeKey": "$default",
    "stage": "$default"
  },
  "body": "Hello from Lambda",
  "pathParameters": { "parameter1": "value1" },
  "isBase64Encoded": false
}

1.0 と 2.0 の両方を読めるハンドラ

import base64
import json

def parse(event):
    """ペイロード 1.0(REST API・HTTP API)と 2.0(HTTP API)の両方から取り出す"""
    if event.get("version") == "2.0":
        method = event["requestContext"]["http"]["method"]
        path = event["rawPath"]
    else:
        method = event["httpMethod"]
        path = event["path"]
    query = event.get("queryStringParameters") or {}   # 無いときは None で来る
    body = event.get("body") or ""
    if event.get("isBase64Encoded"):
        body = base64.b64decode(body).decode("utf-8")
    return method, path, query, body

CORS = {"Access-Control-Allow-Origin": "https://www.example.com"}

def lambda_handler(event, context):
    method, path, query, body = parse(event)
    data = {"method": method, "path": path, "query": query, "body": body}
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json", **CORS},
        "body": json.dumps(data, ensure_ascii=False),
    }

awslambdaric で動かした結果です。2.0 のほうは、?id=42&id=43 のクエリと、Base64 にした本文 {"name":"山田"} を渡しました。

渡した event ハンドラが返した body
1.0(GET /items?id=42) {"method": "GET", "path": "/items", "query": {"id": "42"}, "body": ""}
2.0(POST /items?id=42&id=43、Base64 の本文) {"method": "POST", "path": "/items", "query": {"id": "42,43"}, "body": "{\"name\":\"山田\"}"}

2.0 では同じ名前のクエリが "42,43" と1つの文字列になります。 値にカンマが入りうるときは、rawQueryString を urllib.parse.parse_qs で読むと、元の個別の値を取り出せます。

1.0 で、クエリが付いていないリクエストを受けると、queryStringParameters は null(Python では None)です。event["queryStringParameters"]["id"] と書くと、Lambda は次のエラーで終わりました。

{"errorMessage": "'NoneType' object is not subscriptable", "errorType": "TypeError", ...}

(event.get("queryStringParameters") or {}).get("id") のように書くと、この形のエラーを防げます。

Lambda の返し方の決まり

REST API(と HTTP API の 1.0)

ドキュメントでは、プロキシ統合の Lambda は次の形で返す決まりで、違う形なら API Gateway は 502 Bad Gateway を返すとされています。

{
  "isBase64Encoded": true|false,
  "statusCode": httpStatusCode,
  "headers": { "headerName": "headerValue" },
  "multiValueHeaders": { "headerName": ["headerValue", "headerValue2"] },
  "body": "..."
}
キー 決まり
statusCode HTTP のステータスコード(整数)
headers 1つの値のヘッダー
multiValueHeaders 複数の値のヘッダー(Set-Cookie を2つ返すなど)。headers と同じキーがあると、こちらの値が使われる
body 文字列。JSON を返すなら json.dumps してから入れる
isBase64Encoded バイナリを返すときに true。それ以外は false

AWS re:Post のナレッジセンター(API Gateway の 502 の記事)でも、「JSON を返すなら body を文字列に変換する」と書かれています。

HTTP API の 2.0

2.0 では、statusCode を書かずに dict を返すと、API Gateway が形を決めてくれます。 ドキュメントの説明では、次のように扱われます。

Lambda の返り値 API Gateway の解釈(ドキュメント)
"Hello from Lambda!" statusCode 200、content-type: application/json、body はその文字列
{"message": "Hello from Lambda!"} statusCode 200、body は { "message": "Hello from Lambda!" }(JSON の文字列)
statusCode を含む dict 自分で決めた形として扱う(cookies・headers・body など)
def lambda_handler(event, context):
    return {
        "statusCode": 201,
        "headers": {"Content-Type": "application/json"},
        "cookies": ["session=abc; HttpOnly; Secure", "theme=dark"],  # それぞれ set-cookie になる
        "body": json.dumps({"id": 42}),
    }

cookies は 2.0 だけの項目で、ドキュメントでは配列の要素ごとに set-cookie ヘッダーになります。2.0 には multiValueHeaders はありません。

返り値の形を再現:Lambda は成功しているのに 502 になる

502 の調べにくいところは、Lambda 側では「成功」で終わっていることです。 awslambdaric でハンドラを動かし、Lambda が API Gateway に返す本文を取りました。

ハンドラの return Lambda の結果(再現) API Gateway(REST)で
{"statusCode": 200, "body": json.dumps(...)} 成功 正しい形
{"statusCode": 200, "body": {"message": "hello"}} 成功(そのまま JSON で返った) body が文字列でない → 502 の原因
return を書き忘れ 成功。本文は null 決まりの形ではない → 502 の原因
{"statusCode": "200", "body": "hello"} 成功 statusCode は整数で返す
{"message": "hello"}(statusCode なし) 成功 1.0 では形が違う。2.0 なら 200 として扱われる
body を作る json.dumps に Decimal 失敗 TypeError: Object of type Decimal is not JSON serializable 関数のエラー → 502
body を作る json.dumps に datetime 失敗 TypeError: Object of type datetime is not JSON serializable 関数のエラー → 502
headers の値に datetime 失敗 Runtime.MarshalError(Unable to marshal response: ...) 関数のエラー → 502

上の3行目までは、Lambda のログにエラーが出ません。CloudWatch Logs で Lambda に例外が無いのに 502 になるときは、返り値の形を疑います。

Decimal・datetime のエラーの直し方は、関連記事「Float types are not supportedの直し方|DynamoDB」と、関連記事「datetime is not JSON serializableの直し方」で扱っています。

返り値を作る関数を1つにまとめる

形の間違いを防ぐには、レスポンスを作る関数を1つ用意して、どのハンドラもそれを通すのが確実です。

import json
from decimal import Decimal
from datetime import date, datetime

ALLOW_ORIGIN = "https://www.example.com"

def _default(o):
    if isinstance(o, Decimal):
        return int(o) if o == o.to_integral_value() else float(o)
    if isinstance(o, (datetime, date)):
        return o.isoformat()
    raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")

def response(status: int, data, headers: dict | None = None) -> dict:
    return {
        "statusCode": int(status),
        "headers": {
            "Content-Type": "application/json; charset=utf-8",
            "Access-Control-Allow-Origin": ALLOW_ORIGIN,
            **(headers or {}),
        },
        "body": json.dumps(data, ensure_ascii=False, default=_default),
        "isBase64Encoded": False,
    }

def lambda_handler(event, context):
    try:
        item_id = (event.get("pathParameters") or {}).get("id")
        if not item_id:
            return response(400, {"error": "id is required"})
        return response(200, {"id": item_id, "price": Decimal("1.5"), "at": datetime(2026, 10, 12)})
    except Exception as e:
        print(f"ERROR {e!r}")
        return response(500, {"error": "internal error"})

502 の原因と直し方

このエラーは Malformed Lambda proxy response という文面で検索されることが多いものです。AWS re:Post のナレッジセンター(REST API の Lambda プロキシ統合の 502)では、原因として「返り値の形」「Lambda を呼ぶ権限」「パッケージの権限やハンドラ名」「実行時のエラー」が挙がっています。ログの例も載っています。

Execution failed due to configuration error:
Method completed with status: 502
症状・ログ 原因 直し方
実行ログに Execution failed due to configuration error: と Method completed with status: 502 返り値が決まりの形でない statusCode(整数)と body(文字列)を返す
Lambda は成功、body が dict body を json.dumps していない json.dumps(data) を入れる
Lambda は成功、本文が null return を書き忘れた/条件によって何も返さない すべての分岐で return する
実行ログに Lambda execution failed with status 200 due to customer function error: 関数の中のエラー(例外・権限・読み込みの失敗) Lambda のログの errorType・errorMessage を見て直す
Lambda のログに Runtime.ImportModuleError ハンドラ名・zip の中身の誤り ハンドラ設定とパッケージを見直す
Lambda のログに Task timed out Lambda のタイムアウト Lambda のタイムアウト設定と処理時間を見直す
HTTP API で {"message":"Internal Server Error"} 権限か、返り値の形 アクセスログに $context.integrationErrorMessage を足して確かめる
CLI・CloudFormation で作った直後に失敗 API Gateway に Lambda を呼ぶ権限が無い aws lambda add-permission で許可する

Runtime.ImportModuleError は、関連記事「Lambda Unable to import moduleの直し方|Python」で扱っています。

REST API の実行ログは、比較表のとおり REST API だけの機能です。HTTP API では、ドキュメントの手順どおり、アクセスログの形式に $context.integrationErrorMessage を足して原因を見ます。ログの検索は、関連記事「CloudWatch LogsのAWS CLI tail・検索・クエリ例」で扱っています。

統合のタイムアウトは、REST API が 50ミリ秒〜29秒、HTTP API が最大30秒(引き上げ不可)です。Lambda の処理がこれより長いと、Lambda が動いていても API Gateway は先に応答を返します。

API Gateway に Lambda を呼ぶ権限を付ける

ドキュメントでは、コンソールで統合を作ると権限は自動で付き、CLI・CloudFormation・SDK で作るときは自分で付ける必要がある、と書かれています。

aws lambda add-permission \
  --function-name my-function \
  --statement-id apigateway-invoke-permissions \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:ap-northeast-1:123456789012:api-id/*/*/items"

--source-arn は api-id/ステージ/メソッド/パス の形です。ARN の書き方は関連記事「AWS ARNとは?形式の読み方とサービス別の書き方一覧」で扱っています。

CORS エラーの原因と直し方

REST API と HTTP API で、CORS ヘッダーを付ける場所が違います。

REST API(Lambda プロキシ統合) HTTP API(CORS を設定した場合)
本リクエストの Access-Control-Allow-Origin Lambda が返す API Gateway が付ける
プリフライト(OPTIONS) OPTIONS メソッドを用意(コンソールの「CORS を有効にする」でモック統合を作れる) API Gateway が自動で応答(OPTIONS ルートが無くても)
Lambda が返した CORS ヘッダー そのまま使われる 無視される
設定の反映 デプロイし直すと反映 自動デプロイなら反映

ドキュメントでは、REST API のプロキシ統合について「プロキシ統合は統合レスポンスを返さないので、Access-Control-Allow-Origin・Access-Control-Allow-Methods・Access-Control-Allow-Headers を返すのはバックエンドの役目」とされています。コンソールで「CORS を有効にする」を押しても、プロキシ統合の ANY メソッドには CORS ヘッダーが付きません。

ブラウザのエラー 主な原因 直し方
No 'Access-Control-Allow-Origin' header is present REST:Lambda の返り値にヘッダーが無い 上の response() のようにヘッダーを返す
同上(エラーのときだけ) 成功時しかヘッダーを付けていない。または 502 など、Lambda の返り値が使われていない エラーの返り値にもヘッダーを付ける。502 の原因を先に直す
プリフライトが 403・401 OPTIONS にオーソライザーが付いている HTTP API:認可なしの OPTIONS /{proxy+} ルートを足す(ドキュメントの方法)
プリフライトは通るが本リクエストで失敗 Access-Control-Allow-Headers に Authorization や Content-Type が無い 許可するヘッダーに足す
HTTP API で Lambda のヘッダーが効かない CORS を設定した API では、バックエンドの CORS ヘッダーは無視される API の CORS 設定(allowOrigins など)で決める
Cookie 付きで失敗 資格情報の許可(allowCredentials)が無い。ブラウザの CORS の仕様では、資格情報付きのリクエストに Origin の * は使えない Origin を具体的に書き、資格情報を許可する
設定したのに変わらない(REST) デプロイしていない ステージにデプロイし直す

HTTP API の CORS は、CLI では次のように設定します。

aws apigatewayv2 update-api --api-id api-id \
  --cors-configuration '{"AllowOrigins":["https://www.example.com"],"AllowMethods":["GET","POST","OPTIONS"],"AllowHeaders":["content-type","authorization"],"MaxAge":300}'

ドキュメントでは、CORS ヘッダーが返るのは、リクエストに origin ヘッダーがあるときです。curl で確かめるときは -H "Origin: https://www.example.com" を付けます。CORS の仕組みそのものは関連記事「CORSエラーの原因と解決方法|エラーメッセージ別に解説」、curl の使い方は関連記事「curlコマンドの使い方|よく使うオプションとAPIの確認方法」で扱っています。

curl -i -X OPTIONS "https://api-id.execute-api.ap-northeast-1.amazonaws.com/items" \
  -H "Origin: https://www.example.com" \
  -H "Access-Control-Request-Method: POST"

よくある質問

Q. 今から作るならどちらですか。 A. 比較ページの説明どおり、API キー・WAF・リクエストの検証・プライベート API・キャッシュなどが要るかで決まります。どれも要らなければ HTTP API が候補です。

Q. HTTP API は 1.0 と 2.0 のどちらにしますか。 A. 新しく書くなら、statusCode の省略や cookies が使える 2.0 が書きやすい形です。ドキュメントでは、カスタムドメインの API マッピングの値を path で受け取りたいときは 1.0 を使うよう書かれています(2.0 の rawPath には入らない)。

Q. ローカルで試せますか。 A. AWS SAM の sam local start-api で、API Gateway の形の event を Lambda に渡して試せます。関連記事「AWS SAM入門|template.yamlの書き方とbuild・deploy」で扱っています。Lambda のハンドラの書き方は、関連記事「AWS Lambda Python入門|3.14対応handler例とローカルテスト」で扱っています。

Q. ステータスコードは何を返せばよいですか。 A. 入力の誤りは 400、認証なしは 401、権限なしは 403、無いものは 404 です。一覧は関連記事「HTTPステータスコード 一覧|全コードの原因と対処・curl実例つき」、エラー本文の形は関連記事「APIエラーレスポンス設計|RFC 9457準拠のJSON例と実装コード」で扱っています。

まとめ

  • REST API は機能が多く、HTTP API は機能を絞って安い。 API キー・WAF・検証・キャッシュが要るなら REST API
  • event は REST=1.0、HTTP API=1.0 か 2.0。2.0 は requestContext.http.method・rawPath・cookies
  • 2.0 は重複したクエリ・ヘッダーがカンマでつながる。1.0 の queryStringParameters は無いと None
  • 返り値は statusCode(整数)と body(文字列)。違う形は 502
  • body が dict・return 忘れは、Lambda では成功で、ログに例外が出ない。502 なら返り値の形を見る
  • CORS は、REST のプロキシ統合は Lambda が返す、HTTP API は API の設定で付き、Lambda のヘッダーは無視される

参考・出典

確認日はいずれも 2026年10月12日です。

  • Amazon API Gateway デベロッパーガイド「Choose between REST APIs and HTTP APIs」:https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html
  • Amazon API Gateway デベロッパーガイド「Create AWS Lambda proxy integrations for HTTP APIs in API Gateway」:https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
  • Amazon API Gateway デベロッパーガイド「Lambda proxy integrations in API Gateway」:https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
  • Amazon API Gateway デベロッパーガイド「CORS for REST APIs in API Gateway」:https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
  • Amazon API Gateway デベロッパーガイド「Enable CORS on a resource using the API Gateway console」:https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html
  • Amazon API Gateway デベロッパーガイド「Configuring CORS for HTTP APIs in API Gateway」:https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html
  • Amazon API Gateway デベロッパーガイド「Troubleshooting issues with HTTP API Lambda integrations」:https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-troubleshooting-lambda.html
  • Amazon API Gateway デベロッパーガイド「Amazon API Gateway quotas」(REST API):https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-execution-service-limits-table.html
  • Amazon API Gateway デベロッパーガイド「Quotas for configuring and running an HTTP API」:https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-quotas.html
  • AWS re:Post ナレッジセンター「API Gateway の 502 エラー(Lambda プロキシ統合)」:https://repost.aws/knowledge-center/malformed-502-api-gateway

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「API Gateway Lambda|REST APIとHTTP APIの違い」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?