惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
B
Blog RSS Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
量子位
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
Stack Overflow Blog
Stack Overflow Blog
N
Netflix TechBlog - Medium
T
The Blog of Author Tim Ferriss
U
Unit 42
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
博客园 - 【当耐特】
云风的 BLOG
云风的 BLOG
博客园 - Franky
博客园 - 聂微东

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Merge Multiple PDFs with One API Call — Node.js, P...
Forgelab Afr · 2026-05-27 · via DEV Community

Forgelab Africa

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.