API 500 Error on Published App

API Error 500 Fails in Production Due to Large Response Size


Summary

An API endpoint may work correctly in a development environment but fail in production with 500 Internal Server Error when the response payload is too large. In this case, the endpoint was returning a very large JSON response containing full call records, transcripts, recording links, metadata, and nested analysis data. The production environment was behind Google Frontend, which can enforce a 32 MB limit on dynamic responses. When the API response exceeded that limit, the proxy returned an empty 500 response with content-length: 0.

Symptoms

Common symptoms include:

  • The API works in development but fails in production.

  • The failing request returns 500 Internal Server Error.

  • The response body is empty.

  • The response header shows content-length: 0.

  • Smaller API endpoints work normally.

  • Authentication, cookies, and API keys appear to be valid.

  • The failing endpoint returns a very large JSON payload in development.

Example indicators:

Status Code: 500 Internal Server Error
content-length: 0
server: Google Frontend

Development may show the same endpoint working with a large response such as:

Status Code: 200 OK
content-length: 40592099

This means the response is around 40 MB, which is larger than the 32 MB production proxy limit.

Root Cause

The API was returning too much data in one response.

The list endpoint was sending full objects for every record, including heavy fields such as:

  • Full transcripts

  • Transcript objects

  • Word-level timing arrays

  • Recording URLs

  • Public log URLs

  • Access tokens

  • Dynamic variables

  • Full metadata

  • Full nested analysis objects

  • Other unused fields

Although this worked in development, the production proxy rejected the oversized dynamic response and returned an empty 500 error.

Why This Happens

Development environments may allow larger responses or bypass certain production proxy limits. Production traffic is often routed through a frontend proxy, CDN, load balancer, or managed application gateway. These layers may enforce response size limits.

When the backend generates a response larger than the allowed size, the request may fail before the browser receives the data. This can appear as a generic server error even though the backend logic and third-party API call are working.

Solution

Refactor the API into two separate data-loading flows:

1. Lightweight List Endpoint

The list endpoint should return only the fields needed to render the table, search, sort, filters, and metrics.

Example:

POST /api/list-records

This endpoint should return lightweight rows only, such as:

  • Record ID

  • Agent ID or name

  • Status

  • Start time

  • End time

  • Duration

  • Direction

  • Caller number

  • Destination number

  • Summary

  • Sentiment

  • Success status

  • Request type

  • Basic classification fields

It should not return full transcripts, recordings, large metadata, or deeply nested objects.

2. Detail Endpoint

A separate detail endpoint should fetch the full record only when the user clicks a specific row.

Example:

GET /api/get-record/:recordId

This endpoint can return the full object, including:

  • Full transcript

  • Recording URL

  • Full metadata

  • Full analysis

  • Detailed nested fields

  • Debug or log data

This keeps the initial list response small while still allowing users to access full details on demand.

Add Pagination

Even after stripping heavy fields, the list endpoint should use pagination.

Pagination means returning records in smaller chunks instead of sending the full history in one response.

Example:

Page 1: records 1–100
Page 2: records 101–200
Page 3: records 201–300

Recommended behavior:

  • Default page size: 50 or 100 records

  • Maximum page size: 100 records

  • Return a pagination_key, cursor, or next-page token

  • Let the frontend request the next page using that token

  • Keep all records accessible, but do not load them all at once

Pagination does not mean limiting the client to only 100 records total. It means loading all available records in smaller pages.

Add Response Size Logging

Add logging before sending the response:

const sizeBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
console.info("API response size bytes:", sizeBytes);

This helps confirm whether the endpoint is approaching production response limits.

Add a Safety Guard

Before sending a large response, check its size and return a clear error if it is too large.

Example:

const MAX_RESPONSE_BYTES = 20 * 1024 * 1024;

if (sizeBytes > MAX_RESPONSE_BYTES) {
return res.status(413).json({
error: "Response too large",
message: "Use pagination, reduce the date range, or lower the page size.",
sizeBytes
});
}

A 413 Response too large error is much easier to debug than a silent empty 500.

/api/list-records
→ Returns lightweight paginated table rows only

/api/get-record/:recordId
→ Returns full details for one selected record

The frontend should:

  1. Load the first page of lightweight records.

  2. Render the table normally.

  3. Use “Load more,” “Next page,” or infinite scroll for additional pages.

  4. Fetch full details only when the user opens a specific record.

Acceptance Criteria

The issue is considered resolved when:

  • The list endpoint no longer returns oversized payloads.

  • The production API no longer fails with empty 500 responses.

  • The list response stays well below the proxy limit.

  • The table remains visually unchanged.

  • Search, sorting, metrics, and filters still work.

  • Users can still access all records through pagination.

  • Full transcripts and detailed data load only when opening a specific record.

  • The application logs response sizes for monitoring.

  • Oversized responses return a clear 413 JSON error instead of a silent 500.

Conclusion

The failure was not caused by authentication, cookies, or third-party API access. The root cause was the size of the API response. The endpoint returned a large JSON payload that exceeded the production frontend proxy’s response limit.

The correct fix is to keep list responses lightweight, use pagination, and load heavy details only when requested for a specific record.