The PDF merge REST API — combine uploads and URLs into one PDF.
One endpoint. Any number of files. SelectPdf merges local PDFs and remote PDF URLs in the order you list them, with optional per-file passwords, viewer prefs and output metadata — REST-callable from any language.
PDF merge API in eight languages.
Open-source client libraries on GitHub for seven runtimes plus a Go example using the standard library. Same API surface across runtimes — learn once, deploy anywhere.
using System;
using SelectPdf.Api;
PdfMergeClient client = new PdfMergeClient(apiKey);
// Add files in the order they appear in the merged PDF
client.addUrlFile(testUrl); // remote URL
client.addFile(testPdf); // local upload
// client.addFile(testPdf, "src_password"); // with password
// Optional: protect + label the merged output
client.setUserPassword("secret");
client.setDocTitle("Combined statement");
// --- Merge to a local file ---
client.saveToFile(localFile);
Console.WriteLine("Pages: " + client.getNumberOfPages());
Console.WriteLine("Credits left: " + client.CreditsRemaining);
package com.selectpdf;
PdfMergeClient client = new PdfMergeClient(apiKey);
// Add files in the order they appear in the merged PDF
client.addUrlFile(testUrl); // remote URL
client.addFile(testPdf); // local upload
// client.addFile(testPdf, "src_password"); // with password
// Optional: protect + label the merged output
client.setUserPassword("secret");
client.setDocTitle("Combined statement");
// --- Merge to a local file ---
client.saveToFile(localFile);
System.out.printf("Pages: %d%n", client.getNumberOfPages());
UsageClient u = new UsageClient(apiKey);
System.out.println("Usage: " + u.getUsage(false));
require("SelectPdf.Api.php");
$client = new SelectPdf\Api\PdfMergeClient($apiKey);
// Add files in the order they appear in the merged PDF
$client->addUrlFile($testUrl); // remote URL
$client->addFile($testPdf); // local upload
// $client->addFile($testPdf, "src_password"); // with password
// Optional: protect + label the merged output
$client->setUserPassword("secret");
$client->setDocTitle("Combined statement");
// --- Merge to a local file ---
$client->saveToFile($localFile);
echo "Pages: " . $client->getNumberOfPages() . "\n";
$u = new \SelectPdf\Api\UsageClient($apiKey);
$usage = $u->getUsage(false);
echo "Credits: " . $usage["available"] . "\n";
import selectpdf
client = selectpdf.PdfMergeClient(apiKey)
# Add files in the order they appear in the merged PDF
client.addUrlFile(testUrl) # remote URL
client.addFile(testPdf) # local upload
# client.addFileWithPassword(testPdf, "src_password") # with password
# Optional: protect + label the merged output
client.setUserPassword("secret")
client.setDocTitle("Combined statement")
# --- Merge to a local file ---
client.saveToFile(localFile)
print("Pages:", client.getNumberOfPages())
usage = selectpdf.UsageClient(apiKey).getUsage()
print("Credits:", usage["available"])
var selectpdf = require('selectpdf');
var client = new selectpdf.PdfMergeClient(apiKey);
// Add files in the order they appear in the merged PDF
client.addUrlFile(testUrl); // remote URL
client.addFile(testPdf); // local upload
// client.addFile(testPdf, 'src_password'); // with password
// Optional: protect + label the merged output
client.setUserPassword('secret');
client.setDocTitle('Combined statement');
// --- Merge to a local file ---
client.saveToFile(localFile, function(err, file) {
if (err) return console.error('Merge error:', err);
console.log('Pages:', client.getNumberOfPages());
});
require 'selectpdf'
client = SelectPdf::PdfMergeClient.new(api_key)
# Add files in the order they appear in the merged PDF
client.add_url_file(test_url) # remote URL
client.add_file(test_pdf) # local upload
# client.add_file(test_pdf, 'src_password') # with password
# Optional: protect + label the merged output
client.user_password = 'secret'
client.doc_title = 'Combined statement'
# --- Merge to a local file ---
client.save_to_file(local_file)
print "Pages: #{client.number_of_pages}\n"
usage = SelectPdf::UsageClient.new(api_key).get_usage(false)
print "Credits: #{usage['available']}\n"
use SelectPdf;
use JSON;
my $client = SelectPdf::PdfMergeClient->new($apiKey);
# Add files in the order they appear in the merged PDF
$client->addUrlFile($test_url); # remote URL
$client->addFile($test_pdf); # local upload
# $client->addFileWithPassword($test_pdf, "src_password");
# Optional: protect + label the merged output
$client->setUserPassword("secret");
$client->setDocTitle("Combined statement");
# --- Merge to a local file ---
$client->saveToFile($local_file);
print "Pages: " . $client->getNumberOfPages() . "\n";
my $u = SelectPdf::UsageClient->new($apiKey);
print "Credits: " . $u->getUsage(0)->{"available"} . "\n";
// No dedicated Go SDK — POST /api2/pdfmerge/ over net/http.
package main
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"os"
)
const apiURL = "https://selectpdf.com/api2/pdfmerge/"
func main() {
b := &bytes.Buffer{}
w := multipart.NewWriter(b)
w.WriteField("key", apiKey)
w.WriteField("files_no", "2")
w.WriteField("url_1", testUrl) // file 1: remote URL
// file 2: upload as multipart part named "file_2"
f, _ := os.Open(testPdf)
defer f.Close()
part, _ := w.CreateFormFile("file_2", testPdf)
io.Copy(part, f)
w.Close()
req, _ := http.NewRequest("POST", apiURL, b)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create(localFile)
defer out.Close()
io.Copy(out, resp.Body)
}
Combine PDFs. From anywhere.
Local uploads + remote URLs
Each input slot is independent — pass url_N for a remote PDF or upload file_N as a multipart part. Mix the two within a single request.
Pages in the order you list them
Index 1 first, then 2, 3, … up to files_no. No re-sorting, no surprises — the merged PDF mirrors the order you specified.
Open password-protected PDFs
Pass password_N for any source that requires a password to open. The API decrypts before merging.
Password-protect the merged PDF
Set user_password (view) and owner_password (modify) on the merged output — independent of any source passwords.
Title, author, viewer prefs
Set doc_title / doc_subject / doc_keywords / doc_author + viewer page-layout and page-mode hints on the merged output.
202 + poll for the long ones
Sync for fast merges (up to timeout=120 s). For large jobs, set async=True: get a job ID, poll GET /api2/asyncjob/ until the merged PDF is ready.
One endpoint, two required things.
POST to /api2/pdfmerge/ as multipart/form-data. Pass your license key, the number of files, and one input per slot — either url_N for a remote PDF or a multipart file part named file_N. The response body is the merged PDF.
https://selectpdf.com/api2/pdfmerge/
POST /api2/pdfmerge/ HTTP/1.1 Host: selectpdf.com Content-Type: multipart/form-data; boundary=----X ------X Content-Disposition: form-data; name="key" YOUR_LICENSE_KEY ------X Content-Disposition: form-data; name="files_no" 2 ------X Content-Disposition: form-data; name="url_1" https://selectpdf.com/demo/files/selectpdf.pdf ------X Content-Disposition: form-data; name="url_2" https://example.com/another.pdf ------X--
files_no
REQUIRED
How many files to merge. Pair with url_N and/or file_N parts for each slot 1…N.
url_N · file_N
URL · OR · FILE
For each slot N from 1 to files_no, either a remote URL or a multipart file upload.
password_N
for any source PDF that requires a password to open. See the full
reference in the 25-row section below.
Three PDF merge API endpoints. One key.
All endpoints under https://selectpdf.com/api2/. The same license key authenticates every call.
/api2/pdfmerge/
Merge PDF files into a single output. The main endpoint — synchronous unless async=True. POST only (multipart/form-data); GET returns 400 from the controller. More details →
/api2/asyncjob/
Poll an async PDF-merge job. Returns the merged PDF once ready, or HTTP 202 while still running. More details →
/api2/usage/
Read your subscription: current plan, monthly limit, used and remaining credits. More details →
Every parameter, in one place.
The full PDF-merge parameter surface (25 rows, 22 swagger fields — `url_N` / `file_N` / `password_N` are listed individually for clarity but live in the swagger as a nested `input_files` array). Use the side navigation to jump between groups, or the search box to find a parameter by name.
Mandatory parameters
Only two parameters are required — your license key and the count of files to merge.
key
files_no
Per-file naming convention
Specify each input PDF (1 through files_no) either as a remote URL or as a multipart file upload. An optional per-file password unlocks protected sources before merging.
url_N
(file upload "file_N")
password_N
Async mode, timeout & JSON parameter blob
Submit asynchronously for large merges, control the sync timeout, or pass every parameter as a single JSON object.
async
True
False
timeout
raw_parameters
PDF document information
Set the document title, author, subject, keywords and creation-date stamp on the merged PDF.
doc_title
doc_subject
doc_keywords
doc_author
doc_add_creation_date
True
False
How the merged PDF opens in a viewer
Control the page layout, page mode and window behavior when the merged PDF is opened in a PDF viewer.
viewer_page_layout
0
1
2
3
viewer_page_mode
0
1
2
3
4
5
viewer_center_window
True
False
viewer_display_doc_title
True
False
viewer_fit_window
True
False
viewer_hide_menu_bar
True
False
viewer_hide_toolbar
True
False
viewer_hide_window_ui
True
False
Output password, filename, content-type
Password-protect the merged output, set the download filename, and pick the response Content-Type.
user_password
owner_password
pdf_name
response_content_type
0
1
For merges that outlast your HTTP connection.
Recommended for many large source PDFs or callers that cannot keep an HTTP connection open for the full merge. Submit once, poll the result when it is ready.
- POST
/api2/pdfmerge/withasync=True(and the rest of your parameters + files). - The server replies
202 Acceptedand returns the job ID in theX-SelectPdf-Job-Idresponse header. - Poll
GET /api2/asyncjob/?key=YOUR_KEY&job_id=…at a reasonable cadence. - Each poll:
202= still processing ·200= merged PDF returned in the response body ·499= merge failed (plain-text reason in the body).
https://selectpdf.com/api2/asyncjob/?key=YOUR_KEY&job_id=JOB_ID
The async parameter belongs to /api2/pdfmerge/ (see Advanced); job_id is a parameter of /api2/asyncjob/, not of this endpoint — the server returns the value to you in the X-SelectPdf-Job-Id response header.
HTTP status, plain English.
Standard HTTP semantics. The body of every non-200 contains a plain-text explanation — no error shapes to parse, no enum to memorize.
Content-Type: binary/octet-stream (or application/pdf if response_content_type=1).X-SelectPdf-Job-Id header; poll /api2/asyncjob/ until the merged PDF is ready.files_no or the naming convention; or an empty url_N was supplied. The response body explains in plain text.multipart/form-data.Retry-After header indicates how long to back off before retrying.v2).production for PDF merge (no public demo endpoint).worker for PDF merge (no in-process fallback).-1 means unlimited.429: suggested back-off in seconds.Read your subscription. Programmatically.
Two ways to know where your quota stands. Every successful merge already returns X-SelectPdf-Credits-Total and X-SelectPdf-Credits-Remaining response headers — read those for a zero-extra-cost live view of remaining credits. Use the dedicated /api2/usage/ endpoint below when you also need the subscription tier or a month-by-month history.
https://selectpdf.com/api2/usage/?key=YOUR_KEY&get_history=True
https://selectpdf.com/api2/usage/
{
"key": "YOUR_LICENSE_KEY",
"get_history": "True"
}
{
"status": "License key active.",
"subscription_type": "Entry Level",
"limit": 2000,
"used": 340,
"available": 1660,
"history": [
{ "year": 2026, "month": 4,
"conversions": 340, "credits": 340 },
{ "year": 2026, "month": 3,
"conversions": 1876, "credits": 1923 }
]
}
Concurrency scales with your plan.
Each plan allows a fixed number of simultaneous requests. Excess requests are queued or rejected with a 429 Too Many Requests. One conversion credit covers up to 50 pages of merged output — a 1-page and a 49-page merge cost the same. The API accepts PDF files up to 100 MB per source file.
Each 50 pages of merged output counts as one conversion credit. A 12-page, a 1-page and a 49-page merged PDF all cost the same. The API accepts source PDFs up to 100 MB each. See API pricing for plan details.
PDF merge API, answered.
Six quick answers about the PDF-merge REST API. For the full surface, see the 25-row reference above.
url_N for a remote PDF or upload a multipart file part named file_N. You can mix the two within a single merge request.password_N for any file slot whose PDF requires a password to open. The API will use that password to decrypt before merging.user_password (required to open the PDF) and/or owner_password (required to modify or remove restrictions). Both are passed once for the merged output, independent of any per-file source passwords.url_1 or file_1 first, then url_2 or file_2, and so on up to files_no.async=True. The API returns 202 Accepted with a job ID in the X-SelectPdf-Job-Id header; poll GET /api2/asyncjob/?key=…&job_id=… until the merged PDF is returned. The maximum synchronous timeout parameter is 120 seconds.