Contract API PDF generation fails on large documents with 'REQUEST_SIZE_LIMIT_EXCEEDED'

We’ve built a custom Apex REST API endpoint to generate contract PDFs on-demand from our contract management system. The endpoint works perfectly for standard contracts, but fails when generating comprehensive agreements that include multiple addendums and detailed terms.

Error from the API response:


System.LimitException: REQUEST_SIZE_LIMIT_EXCEEDED
Request size: 12.4 MB
Maximum allowed: 6 MB
Endpoint: /services/apexrest/contracts/*/pdf

Our PDF generation process uses Visualforce rendering with PageReference.getContentAsPDF(), which creates the PDF from contract data including all clauses, line items, and attached terms. For enterprise contracts with 50+ line items and extensive legal terms, the generated PDF exceeds the 6MB API gateway limit.

We need these comprehensive PDFs for legal review and customer delivery. Is there a way to optimize PDF generation via Apex REST to stay within limits, or do we need a completely different architecture? We’re on Summer '24 and this is blocking our contract approval workflow.

The 6MB limit is a hard REST API constraint that you can’t increase. Your options are to either optimize the PDF size or change your delivery method. For optimization, look at your Visualforce page - are you embedding high-resolution images or complex styling? Simplifying the template can dramatically reduce file size.

Instead of returning the PDF directly in the API response, generate it and store it as a ContentVersion (Salesforce Files), then return just the document ID. The client can then download the PDF through the standard Files API, which doesn’t have the same size restrictions. This also gives you version control and audit trail benefits for contract documents.

The ContentVersion approach sounds promising. But wouldn’t that still hit the same limit during the getContentAsPDF() call if the PDF is too large? Or is that limit only for the API response payload?

Another consideration: if you’re generating these PDFs frequently, you’re burning through API calls and compute resources. Implement caching - store generated PDFs and only regenerate when contract data actually changes. Use a hash of contract fields to detect changes efficiently.

Your PDF generation architecture needs a fundamental redesign to handle large documents within Salesforce’s governor limits. Here’s the comprehensive solution:

1. API Gateway Request Size Limits - Implement Asynchronous Pattern:

The 6MB REST API response limit is absolute and cannot be increased. Never return large PDFs directly in API responses. Instead, implement this pattern:

@HttpPost
global static String generateContractPDF(String contractId) {
  // Immediate response with job ID
  String jobId = generateAsyncPDF(contractId);
  return JSON.serialize(new Map<String, String>{
    'status' => 'processing',
    'jobId' => jobId
  });
}

The client polls a status endpoint until PDF is ready, then retrieves via ContentVersion API.

2. PDF Generation via Apex REST - Optimize and Store Pattern:

Replace direct PDF return with a storage-based approach:

Step 1: Generate PDF Asynchronously

@future(callout=true)
public static void generateAsyncPDF(Id contractId) {
  PageReference pdfPage = Page.ContractTemplate;
  pdfPage.getParameters().put('id', contractId);
  Blob pdfBlob = pdfPage.getContentAsPDF();
  storeAsContentVersion(pdfBlob, contractId);
}

Step 2: Store in Salesforce Files

ContentVersion cv = new ContentVersion();
cv.Title = 'Contract_' + contractId + '.pdf';
cv.PathOnClient = 'contract.pdf';
cv.VersionData = pdfBlob;
cv.IsMajorVersion = true;
insert cv;

Step 3: Link to Contract Record Create ContentDocumentLink to associate the file with the contract record for easy retrieval.

3. Document Optimization Strategies - Reduce PDF Size:

Template Optimization: Your Visualforce template likely contains inefficiencies. Implement these optimizations:

Remove Unnecessary Styling:

  • Avoid embedded fonts (use system fonts)
  • Minimize CSS complexity
  • Remove redundant styling
  • Use CSS classes instead of inline styles

Optimize Images:

  • Compress logos to < 50KB
  • Use SVG for vector graphics when possible
  • Lazy-load images only when needed
  • Set explicit image dimensions

Reduce Data Volume:

// Instead of loading all line items:
List<ContractLineItem> items = [
  SELECT Id, Name, Quantity, Price
  FROM ContractLineItem
  WHERE ContractId = :contractId
  LIMIT 1000
];

// Summarize if count exceeds threshold:
if (items.size() > 100) {
  // Generate summary view
  // Store detailed items in appendix
}

Conditional Content Loading: Only include detailed sections when necessary:

  • Legal terms: Link to standard terms document instead of embedding
  • Historical data: Summarize instead of full detail
  • Addendums: Generate separately and reference

Advanced Architecture Options:

Option A: Chunked PDF Generation For extremely large contracts, generate multiple PDFs:

public class ContractPDFGenerator {
  public void generateMultiPartPDF(Id contractId) {
    // Part 1: Main contract (< 5MB)
    generateMainContract(contractId);

    // Part 2: Line items (< 5MB)
    generateLineItemsAppendix(contractId);

    // Part 3: Legal terms (< 5MB)
    generateLegalTerms(contractId);

    // Create manifest
    createPDFManifest(contractId);
  }
}

Option B: External PDF Generation Service For consistently large documents, use an external service:

  1. Send contract data to external PDF service via callout
  2. Service generates PDF without Salesforce limits
  3. Service uploads to Salesforce via Files API
  4. Notify user via Platform Event

This bypasses Salesforce PDF generation limits entirely.

Option C: Hybrid Approach Implement intelligent routing:

if (estimatedPDFSize < 5000000) {
  // Generate in Salesforce
  generateInternalPDF(contractId);
} else {
  // Route to external service
  generateExternalPDF(contractId);
}

Implementation Best Practices:

Caching Strategy: Avoid regenerating unchanged PDFs:

if (contract.LastModifiedDate > lastPDFGenerationDate) {
  regeneratePDF();
} else {
  returnCachedPDF();
}

Progress Tracking: For large PDF generation, provide status updates:

  • Store generation progress in custom field
  • Update via platform events
  • Show real-time progress in UI

Error Handling: Implement robust error recovery:

try {
  Blob pdf = page.getContentAsPDF();
} catch (LimitException e) {
  if (e.getMessage().contains('HEAP_SIZE')) {
    // Trigger chunked generation
    generateChunkedPDF(contractId);
  }
}

Performance Monitoring: Track PDF generation metrics:

  • Average generation time by contract size
  • Failure rate for different document types
  • Cache hit rate
  • Storage consumption

API Design for Clients:

Provide a clean API experience:

POST /contracts/{id}/pdf - Initiates generation, returns job ID GET /contracts/{id}/pdf/status/{jobId} - Check generation status GET /contracts/{id}/pdf/download - Retrieve ContentVersion download URL

This decouples generation from retrieval and works within all Salesforce limits.

Testing Strategy:

  1. Test with contracts of varying sizes (1MB, 3MB, 5MB, 10MB)
  2. Verify async processing completes successfully
  3. Validate ContentVersion storage and retrieval
  4. Test concurrent PDF generation requests
  5. Verify cleanup of old PDF versions

By implementing this architecture, you can handle contract PDFs of any size while staying within Salesforce governor limits and providing a professional API experience.

You’re hitting governor limits because you’re trying to do too much in a single transaction. Consider breaking large contracts into sections - generate separate PDFs for main contract, addendums, and terms, then provide a ZIP download or multiple document links. Alternatively, use platform events to trigger async PDF generation with batch Apex, which has higher limits.

The getContentAsPDF() method has a 15MB heap size limit, which is separate from the 6MB API response limit. So yes, you can generate larger PDFs internally, you just can’t return them directly through REST API responses. The ContentVersion storage approach works because you’re using a different API endpoint for retrieval.