Node.js Serverless on AWS Lambda · HTTP APIs on Lambda

Payload, Timeout and Streaming Limits

Why a report export works for small customers and fails with 413s and timeouts for big ones, and how to design around Lambda's limits.

Scenario

Example app: an analytics API has an endpoint that builds a CSV export of a customer's events and returns it in the response body.

What you see in production

Small customers download exports fine. Large customers get HTTP 413 or 502 errors ("response body too large") or 504 timeouts after about 30 seconds, although the function's own timeout is set to 5 minutes.

The naive code

export const handler = async (event) => {
  const rows = await loadAllEvents(event.pathParameters.customerId);   // could be millions
  return { statusCode: 200, headers: { "content-type": "text/csv" }, body: toCsv(rows) };
};

Reproduce it

Which exports fit through the front door?Try it yourself
const LIMITS = { responseMb: 6, apiTimeoutS: 29 };   // buffered response limit; typical API Gateway integration timeout
const customers = [
  { name: "small",  rows: 20_000,    bytesPerRow: 120, rowsPerSecond: 50_000 },
  { name: "medium", rows: 400_000,   bytesPerRow: 120, rowsPerSecond: 50_000 },
  { name: "large",  rows: 3_000_000, bytesPerRow: 120, rowsPerSecond: 50_000 },
];
for (const c of customers) {
  const mb = (c.rows * c.bytesPerRow) / 1_048_576;
  const seconds = c.rows / c.rowsPerSecond;
  const ok = mb <= LIMITS.responseMb && seconds <= LIMITS.apiTimeoutS;
  console.log(`${c.name.padEnd(6)} ${mb.toFixed(1).padStart(6)} MB ${seconds.toFixed(0).padStart(4)} s ->`,
    ok ? "inline response OK" : "too big or too slow: use S3 + presigned URL (async job)");
}

Output

small     2.3 MB    0 s -> inline response OK
medium   45.8 MB    8 s -> too big or too slow: use S3 + presigned URL (async job)
large   343.3 MB   60 s -> too big or too slow: use S3 + presigned URL (async job)

Root cause

Several limits apply, and the smallest one wins: Lambda's buffered request and response payload limit (6 MB each for synchronous invocations), API Gateway's integration timeout (29 seconds by default for REST APIs, 30 seconds maximum for HTTP APIs), and the function's own timeout (up to 15 minutes). A long function timeout does not help if the gateway gives up first. Building the whole file in memory also risks running out of memory.

The fix

Design large or slow operations asynchronously and move bytes through S3:

  1. POST /exports validates the request, writes a job record and enqueues work (SQS or Step Functions), returning 202 Accepted with a job ID.
  2. A worker function streams rows from the database into an S3 multipart upload, never holding the whole file in memory.
  3. GET /exports/{id} returns the status and, when ready, a short-lived presigned S3 URL for the client to download directly.

For uploads, give clients presigned PUT URLs so large files never pass through Lambda. For moderately large responses that must be inline, response streaming (function URLs and supported integrations) lets a function send data progressively beyond the buffered limit.

Trade-offs

  • Asynchronous jobs add polling or notifications (webhooks, WebSockets) and more states to handle.
  • Presigned URLs are bearer credentials: keep expiry short and scope them to one object.
  • Some API Gateway timeouts can be raised for certain API types at the cost of throttle capacity; prefer async design over long synchronous calls.

Detect it in production

  • Alarms on 413, 502 and 504 responses per route; the IntegrationLatency metric approaching the timeout.
  • Function Duration and Max Memory Used trending towards their limits for specific customers.
  • Size the largest tenants' data regularly; limits are usually discovered by the biggest customer.

Key points

  • The smallest limit wins: payload size, gateway timeout or function timeout.
  • Long or large work: async job + S3 + presigned URL.
  • Stream instead of buffering; presigned uploads keep big files out of Lambda.

Further reading: Lambda quotas · Response streaming for Lambda functions · Sharing objects with presigned URLs

Practice questions

A function has a 5-minute timeout, but API clients receive 504 after about 30 seconds. Why?

Answer

API Gateway's integration timeout (29 seconds by default for REST APIs, at most 30 seconds for HTTP APIs) is shorter than the function's timeout, so the gateway gives up first. Make long work asynchronous instead.

How should an API return a 500 MB export generated by Lambda?

Answer

Not inline. Run the export as an asynchronous job that streams the file into S3, then return a short-lived presigned URL so the client downloads directly from S3.

What does Lambda response streaming change?

Answer

The function can send the response progressively instead of buffering it, improving time to first byte and allowing responses beyond the buffered payload limit, through function URLs and supported integrations.

Open in the app to mark lessons complete, take notes and track your progress.