Merge and Split Guide
Combine PDFs into one file, or split one PDF into several, from your own code. For every field and response, see the PDF Tools reference.
Choosing a path
Up to 4 MB of PDFs More than 4 MB
───────────────────── ──────────────────────────────────────
POST /pdf/merge → the merged PDF 1. POST /pdf/upload-url (per file)
POST /pdf/split → the PDF, or links 2. PUT the file to upload_url
to every file 3. POST /pdf/jobs → 202 job_id
4. GET /jobs/{job_id} → poll, or webhook
5. GET /jobs/{job_id}/files and /download
The size that matters is the total of the PDFs you send. A merge of five 500 KB invoices is instant; a 12 MB scan goes through an upload and a job.
Page count almost never forces a job on its own: an instant request can process 500 pages, and copying pages is fast. It's the upload size that decides.
Check files first
POST /pdf/inspect returns a file's page count and whether it's encrypted, and doesn't cost anything. It's worth calling when:
- a person picks the page ranges, so you can validate them before sending the real request
- files come from systems that sometimes encrypt their exports
- you split a run into files per person and want to confirm the page count matches the number of people
For a file you've already uploaded, send {"s3_key": "..."} instead of the file itself.
Merge recipes
Everything, in order
curl -X POST https://api.podpdf.com/pdf/merge \
-H "X-API-Key: $KEY" \
-F "files=@1-cover.pdf" -F "files=@2-contract.pdf" -F "files=@3-appendix.pdf" \
-o pack.pdf
Only the pages you need
-F 'page_ranges=["1", "1-4,9", null]'
The first file contributes page 1, the second pages 1–4 and 9, the third everything.
The same page twice
A cover page in front of two sections: "page_ranges": ["1", "2-end", "1"] with the same file three times, or "1,2-10,1" for one file.
Reverse order
Reverse the files list — there's no reverse option, because the order you send is the order you get.
Split recipes
| Goal | split |
|---|---|
| Pull out pages 4–7 as one file | {"mode": "extract", "pages": "4-7"} |
| Remove page 2 | {"mode": "extract", "pages": "1,3-end"} |
| Chapters at known pages | {"mode": "ranges", "ranges": ["1-12", "13-40", "41-end"]} |
| Two-page statements into one file each | {"mode": "every_n", "every_n": 2} |
| Every page separately | {"mode": "each_page"} |
Name the files with options.filename_pattern, for example "invoice-{index:03}" for invoice-001.pdf, invoice-002.pdf.
A complete job
This walks through splitting a 90 MB scanned batch into files of 10 pages.
1. Upload
UPLOAD=$(curl -s -X POST https://api.podpdf.com/pdf/upload-url \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d "{\"content_length_bytes\": $(wc -c < batch.pdf)}")
curl -X PUT "$(echo $UPLOAD | jq -r .upload_url)" -H "Content-Type: application/pdf" --data-binary @batch.pdf
2. Check it (optional)
curl -s -X POST https://api.podpdf.com/pdf/inspect \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d "{\"s3_key\": \"$(echo $UPLOAD | jq -r .s3_key)\"}"
3. Submit
curl -s -X POST https://api.podpdf.com/pdf/jobs \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d "{\"operation\": \"split\", \"inputs\": [{\"s3_key\": \"$(echo $UPLOAD | jq -r .s3_key)\", \"filename\": \"batch.pdf\"}], \"split\": {\"mode\": \"every_n\", \"every_n\": 10}}"
4. Wait
Poll GET /jobs/{job_id} every few seconds. While it runs you'll see output_count (how many files it will make) and processed_count (how many are done):
{ "job_id": "5b2d8f40-...", "status": "processing", "job_type": "pdfop", "operation": "split", "input_pages": 214, "output_count": 22, "processed_count": 9 }
Or subscribe a webhook to job.completed and job.failed — the payload has job_type: "pdfop", operation, output_count and files_url.
5. Collect the files
curl -s https://api.podpdf.com/jobs/$JOB/files -H "X-API-Key: $KEY" # the list
curl -s https://api.podpdf.com/jobs/$JOB/files/0/download -H "X-API-Key: $KEY" # one file
curl -s https://api.podpdf.com/jobs/$JOB/download -H "X-API-Key: $KEY" # all as a ZIP
Each call returns a fresh link valid for one hour. The files are kept for 7 days.
When things go wrong
| You see | Why | Do this |
|---|---|---|
PDF_ENCRYPTED | The file is password-protected or encrypted, even if it opens without a password | Export it again without protection |
INVALID_PDF with reason: "missing_page_objects" | The file is incomplete — often an interrupted download or upload | Get the file again |
PAGE_OUT_OF_RANGE | A range goes past the last page | Use details.total_pages, or end |
OUTPUT_TOO_LARGE | Shared images or fonts would be copied into too many files | Split into fewer files (every_n with a bigger number) |
PDF_JOB_ALREADY_ACTIVE | 2 jobs are already running | Wait for one to finish, then retry |
413 with no JSON body | The request was larger than the API accepts | Use the upload and job path |
A warnings entry | Forms, bookmarks or signatures couldn't be kept | Nothing to fix; decide whether the output is still what you need |
Failed operations aren't charged.