payload_too_large
HTTP 413 retryable: false REST and MCP
The request carried more bytes than the limit for that kind of request, and it was stopped at the size check rather than being partially processed.
Why it happens
Size is counted as the body streams, not taken from Content-Length, because Content-Length can be absent, understated or simply wrong and a limit that trusts it is not a limit. The cap applied depends on the route: 1 MiB for a JSON body, 10 MiB for the binary upload route. Attachments are checked again later, both individually at 10 MiB and in aggregate at 15 MiB, and again at completion against the size that was actually declared and reserved, because the only number worth trusting is the one measured server-side.
Every condition below was read in the implementation at the file and line given. They are the branches that raise this code, not a summary of what the name suggests.
- The streamed request body exceeds 1 MiB for a JSON route, or 10 MiB for the binary upload content route.
packages/api/src/index.ts:73 - An upload PUT carries more than 10 MiB of bytes.
packages/api/src/index.ts:304 - A declared upload size at reservation exceeds 10 MiB, is not a safe integer, or is not positive.
packages/domain/src/services.ts:3535 - Uploaded bytes exceed the size the upload reserved, even when under the 10 MiB ceiling.
packages/domain/src/services.ts:3646 - The verified stored size at completion is larger than the reserved declaration.
packages/domain/src/services.ts:3706 - A single attachment on a send exceeds 10 MiB.
packages/domain/src/outbound-files.ts:20 - The attachments on one send total more than 15 MiB.
packages/domain/src/outbound-files.ts:23 - An inbound provider webhook payload exceeds its byte cap.
packages/domain/src/resend-ingress.ts:39
How to fix it
- Check which limit you hit before shrinking anything. A 1 MiB JSON cap and a 15 MiB attachment aggregate are different problems with different fixes.
- Declare the real byte length when reserving an upload. The completion check compares measured bytes against your declaration, so an understated declaration fails at the end rather than at the start.
- Keep the combined text and HTML body under 512 KB. That limit is enforced by the send schema and reports as invalid_request, not as this code.
- Link large files rather than attaching them. A send allows at most 10 attachments, each at most 10 MiB, totalling at most 15 MiB.
Why it works this way
Counting streamed bytes rather than reading Content-Length is the load-bearing detail. A client that omits the header, or understates it, would otherwise walk straight past a check that looks correct in the code.
Messages you will see
The message field is written for a human reading a log and is not a contract
value. Branch on code instead. These are the exact strings this code is sent
with today, listed so that a search for one of them lands here.
Where it appears
These are the documented surfaces that raise it. The REST base is https://api.emailforagents.ai/v1, and the remote MCP endpoint is https://mcp.emailforagents.ai/mcp.
POST /projects/{project_id}/uploadsPUT /projects/{project_id}/uploads/{upload_id}/contentPOST /projects/{project_id}/uploads/{upload_id}/completePOST /projects/{project_id}/inboxes/{inbox_id}/messages
The response
Read request_id off the body or the x-request-id header before
doing anything else. It is the only handle on one specific attempt, and for a 503 it is
the only handle at all, because the underlying detail stays server-side.
HTTP/1.1 413
content-type: application/json
x-request-id: req_...
{
"error": {
"code": "payload_too_large",
"message": "Request body exceeds the allowed size.",
"request_id": "req_...",
"retryable": false
}
}
Verified against packages/api/src/index.ts:73. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one
place a code is mapped to a status.