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
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'
);
Key points:
- Always check
response.okbefore writing the file — a 429 or 401 response body is JSON, not a PDF. - Use
form.getHeaders()so theContent-Typeboundary 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"
)
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'
);
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)