- AWS
API Gateway Lambda|REST APIとHTTP APIの違い
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¶meter1=value2¶meter2=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期連続の黒字決算。