DEV Community

Forgelab Africa
Forgelab Africa

Posted on

How to Merge Multiple PDFs with One API Call — Node.js, Python & curl

Merging PDFs sounds simple — until you try to do it in production.

If you've reached for pdf-lib, pdfkit, or Puppeteer to merge PDFs in Node.js, you know the pain:

  • Conflicting dependencies that break on deploy
  • Memory spikes with large files
  • Complex page ordering logic
  • CI pipelines that randomly fail

There's a cleaner approach: offload it to an API and stay focused on your actual product.

The Forgelab PDF Merge API

The Forgelab PDF API accepts multiple PDF files and returns a single merged file. One endpoint. Standard HTTP. Works from any language.

Endpoint: POST https://www.forgelab.africa/api/pdf/merge

Auth: Pass your API key as the X-API-Key header.

Files are merged in the order they are uploaded. Upload order = page order.


curl

curl -X POST https://www.forgelab.africa/api/pdf/merge \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "files=@document1.pdf" \
  -F "files=@document2.pdf" \
  -F "files=@document3.pdf" \
  --output merged.pdf
Enter fullscreen mode Exit fullscreen mode

That's the whole thing. No install step.


Node.js

const fs = require('fs');
const FormData = require('form-data');
const fetch = require('node-fetch');

async function mergePDFs(filePaths, outputPath) {
  const form = new FormData();

  for (const filePath of filePaths) {
    form.append('files', fs.createReadStream(filePath));
  }

  const response = await fetch('https://www.forgelab.africa/api/pdf/merge', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.FORGELAB_API_KEY,
      ...form.getHeaders(),
    },
    body: form,
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(`Merge failed: ${err.message}`);
  }

  const buffer = await response.buffer();
  fs.writeFileSync(outputPath, buffer);
  console.log(`Saved merged PDF to ${outputPath}`);
}

mergePDFs(
  ['invoice.pdf', 'receipt.pdf', 'contract.pdf'],
  'bundle.pdf'
);
Enter fullscreen mode Exit fullscreen mode

Key points:

  • Always check response.ok before writing the file — a 429 or 401 response body is JSON, not a PDF.
  • Use form.getHeaders() so the Content-Type boundary is set correctly.
  • Store your API key in an environment variable, never hardcoded.

Python

import os
import requests

def merge_pdfs(file_paths: list[str], output_path: str) -> None:
    api_key = os.environ["FORGELAB_API_KEY"]

    files = [
        ("files", (path, open(path, "rb"), "application/pdf"))
        for path in file_paths
    ]

    response = requests.post(
        "https://www.forgelab.africa/api/pdf/merge",
        headers={"X-API-Key": api_key},
        files=files,
    )

    response.raise_for_status()

    with open(output_path, "wb") as f:
        f.write(response.content)

    print(f"Saved merged PDF to {output_path}")

merge_pdfs(
    ["invoice.pdf", "receipt.pdf", "contract.pdf"],
    "bundle.pdf"
)
Enter fullscreen mode Exit fullscreen mode

raise_for_status() will raise an exception for 4xx/5xx responses, so you get clean error propagation without extra conditionals.


PHP

<?php

function mergePDFs(array $filePaths, string $outputPath): void
{
    $apiKey = getenv('FORGELAB_API_KEY');
    $curl   = curl_init();

    $postFields = [];
    foreach ($filePaths as $i => $path) {
        $postFields["files[$i]"] = new CURLFile($path, 'application/pdf');
    }

    curl_setopt_array($curl, [
        CURLOPT_URL            => 'https://www.forgelab.africa/api/pdf/merge',
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $postFields,
        CURLOPT_HTTPHEADER     => ["X-API-Key: $apiKey"],
        CURLOPT_RETURNTRANSFER => true,
    ]);

    $result   = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    curl_close($curl);

    if ($httpCode !== 200) {
        throw new \RuntimeException("PDF merge failed with HTTP $httpCode");
    }

    file_put_contents($outputPath, $result);
    echo "Saved merged PDF to $outputPath\n";
}

mergePDFs(
    ['invoice.pdf', 'receipt.pdf', 'contract.pdf'],
    'bundle.pdf'
);
Enter fullscreen mode Exit fullscreen mode

Error Reference

Status Meaning
200 Success — response body is the merged PDF binary
400 Invalid input — check your files are valid PDFs
401 Missing or invalid API key
429 Rate limit exceeded — upgrade your plan
500 Server error — retry with exponential backoff

Pricing

Plan Price Calls/month
Free $0 5 calls
Starter $5/month 100 calls
Pro $15/month 1,000 calls
Business $30/month 10,000 calls

Start on the free tier — no card required.

Get your API key at forgelab.africa.


Questions? Drop them in the comments or email info@forgelab.africa.

Top comments (0)