PDF Split API: Split PDF Pages by Range with FUNBREW PDF
Extract a single chapter from a 200-page report. Separate one invoice out of a merged batch PDF. Pull individual certificates out of a bulk-generated archive. These are the tasks the FUNBREW PDF split API was built for.
The split endpoint (POST /api/pdf/split) takes a PDF already stored in your FUNBREW account and extracts the pages you specify — by range, by individual page number, or any combination — returning a new, self-contained PDF file with a timed download URL.
This guide covers the full API specification, the pages parameter format, working code in cURL, Node.js, Python, and PHP, and practical use case patterns.
For generating PDFs before splitting them, see the PDF API Quickstart. For combining multiple PDFs into one, see the PDF Merge API Guide. For reducing file size after splitting, see the PDF Compress API Guide. For the complete overview of PDF manipulation endpoints, see the PDF Manipulation Guide.
How It Works
The split workflow has two steps:
- A PDF must already exist in your FUNBREW account (generated by a previous call to
POST /api/pdf/generateand still within its expiration window). - You call
POST /api/pdf/splitwith the filename and the pages you want to extract.
The server uses the FPDI library to import the specified pages and write them into a new PDF. The result is stored and returned as a download URL.
The endpoint requires the pdf.feature:split feature gate on your plan (SaaS edition only).
API Specification
POST /api/pdf/split
Content-Type: application/json
Authorization: Bearer {API key}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
filename |
string | Yes | Filename of an existing PDF in your account |
pages |
string | Yes | Page specification string (see format below) |
expiration_hours |
integer | No | Lifetime of the split file in hours (0–168, default 24) |
max_downloads |
integer | No | Maximum download count before expiry (0–100, default 10) |
The pages Parameter Format
The pages string is a comma-separated list of page numbers and ranges. All page numbers are 1-indexed.
| Format | Example | Meaning |
|---|---|---|
| Single page | "3" |
Page 3 only |
| Range | "1-5" |
Pages 1 through 5 inclusive |
| Multiple pages | "1,3,5" |
Pages 1, 3, and 5 |
| Mixed | "1-3,7,10-12" |
Pages 1–3, page 7, and pages 10–12 |
The output PDF contains the extracted pages in the order specified. Duplicate page numbers are deduplicated. If any page number exceeds the total page count, the entire request returns a 422 error.
Success Response
{
"success": true,
"data": {
"filename": "split-abc123.pdf",
"download_url": "https://pdf.funbrew.cloud/api/pdf/download/split-abc123.pdf",
"file_size": 102400,
"pages_extracted": 3,
"expires_at": "2026-05-20T10:00:00Z"
}
}
pages_extracted is the count of pages in the output file.
Error Responses
| Status | Reason |
|---|---|
| 422 | filename not found in your account, or file belongs to a different API key |
| 422 | File has expired and is no longer available |
| 422 | File not found on storage (already deleted) |
| 422 | Invalid page range — page number exceeds the PDF page count |
Code Examples
cURL
Generate a PDF first, then split pages 1–3 from it:
# Step 1: Generate a PDF (capture the returned filename)
RESPONSE=$(curl -s -X POST https://pdf.funbrew.cloud/api/pdf/generate \
-H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Chapter 1</h1><p>...</p><div style=\"page-break-after:always\"></div><h1>Chapter 2</h1><p>...</p><div style=\"page-break-after:always\"></div><h1>Chapter 3</h1><p>...</p>",
"options": { "format": "A4", "responseFormat": "url" }
}')
FILENAME=$(echo $RESPONSE | jq -r '.data.filename')
# Step 2: Extract pages 1-3
curl -s -X POST https://pdf.funbrew.cloud/api/pdf/split \
-H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"$FILENAME\",
\"pages\": \"1-3\",
\"expiration_hours\": 48,
\"max_downloads\": 5
}" | jq .
Extract individual pages (1, 3, and 5):
curl -s -X POST https://pdf.funbrew.cloud/api/pdf/split \
-H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"$FILENAME\",
\"pages\": \"1,3,5\"
}" | jq .
Node.js
const API_KEY = process.env.FUNBREW_PDF_API_KEY;
const BASE_URL = "https://pdf.funbrew.cloud";
/**
* Generate a PDF and return the server-side filename.
*/
async function generatePdf(html, options = {}) {
const response = await fetch(`${BASE_URL}/api/pdf/generate`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
options: { format: "A4", ...options, responseFormat: "url" },
}),
});
if (!response.ok) {
throw new Error(`PDF generation failed: HTTP ${response.status}`);
}
const { data } = await response.json();
return data.filename;
}
/**
* Split a stored PDF by page specification.
*
* @param {string} filename - Filename returned from generatePdf()
* @param {string} pages - Page spec e.g. "1-3", "1,3,5", "2-5,8"
* @param {object} options - { expirationHours, maxDownloads }
*/
async function splitPdf(filename, pages, options = {}) {
const response = await fetch(`${BASE_URL}/api/pdf/split`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
filename,
pages,
expiration_hours: options.expirationHours ?? 24,
max_downloads: options.maxDownloads ?? 10,
}),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`PDF split failed: HTTP ${response.status} — ${body}`);
}
return response.json();
}
// Example: generate a 5-page report, then extract chapters 2–3 (pages 2–3)
async function extractChapter(reportHtml, chapterPages) {
console.log("Generating report PDF...");
const filename = await generatePdf(reportHtml);
console.log(`Extracting pages: ${chapterPages}`);
const result = await splitPdf(filename, chapterPages, {
expirationHours: 72,
maxDownloads: 3,
});
console.log("Split complete:");
console.log(" Pages extracted:", result.data.pages_extracted);
console.log(" Download URL: ", result.data.download_url);
return result.data;
}
// Run
extractChapter(
"<h1>Chapter 1</h1><p>...</p>", // your full report HTML
"2-3"
).catch(console.error);
Python
import os
import httpx
from typing import Optional
API_KEY = os.environ["FUNBREW_PDF_API_KEY"]
BASE_URL = "https://pdf.funbrew.cloud"
def generate_pdf(html: str, **options) -> str:
"""Generate a PDF and return the server-side filename."""
response = httpx.post(
f"{BASE_URL}/api/pdf/generate",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"html": html,
"options": {"format": "A4", **options, "responseFormat": "url"},
},
timeout=120,
)
response.raise_for_status()
return response.json()["data"]["filename"]
def split_pdf(
filename: str,
pages: str,
expiration_hours: int = 24,
max_downloads: int = 10,
) -> dict:
"""
Split a stored PDF by page specification.
Args:
filename: Filename returned from generate_pdf()
pages: Page spec string, e.g. '1-3', '1,3,5', '2-5,8,10-12'
expiration_hours: 0–168
max_downloads: 0–100
"""
response = httpx.post(
f"{BASE_URL}/api/pdf/split",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"filename": filename,
"pages": pages,
"expiration_hours": expiration_hours,
"max_downloads": max_downloads,
},
timeout=120,
)
response.raise_for_status()
return response.json()
# Example: extract individual invoice pages from a merged batch PDF
def extract_invoice_pages(batch_filename: str, page_numbers: list[int]) -> str:
"""Extract specific pages from a batch PDF and return a download URL."""
pages_spec = ",".join(str(p) for p in page_numbers)
print(f"Extracting pages {pages_spec} from {batch_filename}...")
result = split_pdf(
filename=batch_filename,
pages=pages_spec,
expiration_hours=48,
max_downloads=5,
)
data = result["data"]
print(f"Pages extracted: {data['pages_extracted']}")
print(f"File size: {data['file_size']} bytes")
print(f"Download URL: {data['download_url']}")
return data["download_url"]
# Generate a batch PDF, then extract pages 1 and 3
if __name__ == "__main__":
batch_html = """
<html><body>
<div style="page-break-after:always"><h1>Invoice #001</h1><p>Acme Corp</p></div>
<div style="page-break-after:always"><h1>Invoice #002</h1><p>Beta Ltd</p></div>
<div><h1>Invoice #003</h1><p>Gamma GmbH</p></div>
</body></html>
"""
batch_filename = generate_pdf(batch_html)
download_url = extract_invoice_pages(batch_filename, [1, 3])
print("Done:", download_url)
PHP
<?php
class FunbrewPdfSplitter
{
private string $apiKey;
private string $baseUrl = 'https://pdf.funbrew.cloud';
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}
/**
* Generate a PDF and return the server-side filename.
*/
public function generatePdf(string $html, array $options = []): string
{
$payload = [
'html' => $html,
'options' => array_merge(['format' => 'A4', 'responseFormat' => 'url'], $options),
];
$ch = curl_init("{$this->baseUrl}/api/pdf/generate");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$this->apiKey}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 120,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new \RuntimeException("PDF generation failed: HTTP {$status} — {$body}");
}
return json_decode($body, true)['data']['filename'];
}
/**
* Split a stored PDF by page specification.
*
* @param string $filename Filename returned from generatePdf()
* @param string $pages Page spec e.g. '1-3', '1,3,5', '2-5,8'
* @param int $expirationHours 0–168 (default 24)
* @param int $maxDownloads 0–100 (default 10)
*/
public function split(
string $filename,
string $pages,
int $expirationHours = 24,
int $maxDownloads = 10
): array {
$payload = [
'filename' => $filename,
'pages' => $pages,
'expiration_hours' => $expirationHours,
'max_downloads' => $maxDownloads,
];
$ch = curl_init("{$this->baseUrl}/api/pdf/split");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$this->apiKey}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 120,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new \RuntimeException("PDF split failed: HTTP {$status} — {$body}");
}
return json_decode($body, true);
}
}
// Usage (Laravel)
$splitter = new FunbrewPdfSplitter(env('FUNBREW_PDF_API_KEY'));
// Generate a multi-page PDF
$filename = $splitter->generatePdf(
'<h1>Page 1</h1><div style="page-break-after:always"></div>'
. '<h1>Page 2</h1><div style="page-break-after:always"></div>'
. '<h1>Page 3</h1>'
);
// Extract pages 1 and 3
$result = $splitter->split(
filename: $filename,
pages: '1,3',
expirationHours: 48,
maxDownloads: 5
);
echo "Download URL: " . $result['data']['download_url'] . "\n";
echo "Pages extracted: " . $result['data']['pages_extracted'] . "\n";
Use Case Patterns
Large report: chapter-by-chapter distribution
Generate a full report once, then distribute each chapter as a separate PDF — no need to regenerate.
chapters = [
{"name": "executive-summary", "pages": "1-3"},
{"name": "financials", "pages": "4-12"},
{"name": "appendix", "pages": "13-20"},
]
# Generate the full report once
report_filename = generate_pdf(full_report_html)
# Split into chapter files
chapter_urls = {}
for chapter in chapters:
result = split_pdf(
filename=report_filename,
pages=chapter["pages"],
expiration_hours=168, # 7 days
max_downloads=50,
)
chapter_urls[chapter["name"]] = result["data"]["download_url"]
print(f"{chapter['name']}: {result['data']['download_url']}")
Invoice batch: isolate a single invoice for re-sending
When a client needs one invoice re-sent from a monthly batch PDF, extract just that page.
async function resendInvoice(batchFilename, invoicePageNumber, recipientEmail) {
// Extract the single invoice page
const result = await splitPdf(batchFilename, String(invoicePageNumber), {
expirationHours: 24,
maxDownloads: 2,
});
const downloadUrl = result.data.download_url;
// Send download link via your email provider
await sendEmail({
to: recipientEmail,
subject: "Your invoice (re-sent)",
body: `Download your invoice: ${downloadUrl}`,
});
console.log(`Invoice page ${invoicePageNumber} sent to ${recipientEmail}`);
return downloadUrl;
}
Certificate bulk generation: extract individual files
When generating bulk certificates, you may produce a single multi-page PDF containing one certificate per page. Split each page into an individual downloadable file.
import asyncio
import httpx
async def split_certificate_pages(
batch_filename: str,
total_pages: int,
api_key: str,
concurrency: int = 5,
) -> list[str]:
"""
Split a bulk certificate PDF into individual certificate files.
Returns a list of download URLs, one per certificate.
"""
sem = asyncio.Semaphore(concurrency)
async def split_one(page_num: int) -> str:
async with sem:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://pdf.funbrew.cloud/api/pdf/split",
headers={"Authorization": f"Bearer {api_key}"},
json={
"filename": batch_filename,
"pages": str(page_num),
"expiration_hours": 168,
"max_downloads": 3,
},
timeout=60,
)
response.raise_for_status()
data = response.json()["data"]
print(f" Page {page_num}: {data['download_url']}")
return data["download_url"]
tasks = [split_one(p) for p in range(1, total_pages + 1)]
return await asyncio.gather(*tasks)
# After bulk certificate generation, split into individual files
download_urls = asyncio.run(
split_certificate_pages(
batch_filename="batch-certificates-abc123.pdf",
total_pages=50,
api_key=os.environ["FUNBREW_PDF_API_KEY"],
)
)
print(f"Generated {len(download_urls)} individual certificate URLs")
Combining Split with Other PDF Operations
The split, merge, and compress endpoints can be chained to build powerful document workflows.
Split then compress
Extract a section of a large PDF and compress the result for email delivery.
# Step 1: Split pages 5-15 from a large PDF
split_result = split_pdf(
filename="large-report-abc.pdf",
pages="5-15",
expiration_hours=2, # Short-lived intermediate file
)
split_filename = split_result["data"]["filename"]
# Step 2: Compress the extracted section
compress_response = httpx.post(
f"{BASE_URL}/api/pdf/compress",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"filename": split_filename,
"quality": "low", # Maximum compression for email
"expiration_hours": 48,
},
timeout=60,
)
compress_response.raise_for_status()
final_url = compress_response.json()["data"]["download_url"]
print("Compressed extract ready:", final_url)
Generate, split, then merge
Generate separate documents, split specific sections from each, and merge into a custom deliverable.
// 1. Generate two source PDFs
const [sourceA, sourceB] = await Promise.all([
generatePdf(contractHtml),
generatePdf(appendixHtml),
]);
// 2. Extract only the signature pages
const [sigPageA, sigPageB] = await Promise.all([
splitPdf(sourceA, "5"), // page 5 of the contract
splitPdf(sourceB, "1,2"), // pages 1-2 of the appendix
]);
// 3. Merge the extracted pages into a single signature package
const mergeResponse = await fetch("https://pdf.funbrew.cloud/api/pdf/merge", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FUNBREW_PDF_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
filenames: [sigPageA.data.filename, sigPageB.data.filename],
expiration_hours: 168,
max_downloads: 2,
}),
});
const mergeResult = await mergeResponse.json();
console.log("Signature package:", mergeResult.data.download_url);
Common Failures and Fixes
File not found (422)
{
"success": false,
"message": "File not found or not owned by your company: report-abc.pdf"
}
Root causes:
- Different API key used for generation vs. split — Files are scoped to a company. Use the same API key (same company) for both steps.
- File expired before splitting — If
expiration_hourspassed since generation, the file is no longer available. Generate with a longer expiry, or split immediately after generation. - Typo in filename — Use the exact
data.filenamevalue returned by the generate endpoint.
Invalid page range (422)
{
"success": false,
"message": "Invalid page range. The PDF has 10 pages."
}
Page numbers must be between 1 and the total page count. Page numbers in a range must be in ascending order (1-5 is valid; 5-1 is not). Check the source PDF page count before calling the split endpoint if page numbers are dynamic.
File expired as an intermediate step
When chaining split with compress or merge, intermediate files can expire if pipeline steps are slow. Use a short but adequate expiration_hours on intermediate results:
# Intermediate split file: 1 hour is enough for a compress step that runs immediately
split_result = split_pdf(filename, pages="1-5", expiration_hours=1)
# Proceed immediately to the next step
compress_result = compress_pdf(split_result["data"]["filename"])
Related APIs and Guides
| Operation | Endpoint | Guide |
|---|---|---|
| Generate PDF | POST /api/pdf/generate |
PDF API Quickstart |
| Merge PDFs | POST /api/pdf/merge |
PDF Merge API Guide |
| Compress PDF | POST /api/pdf/compress |
PDF Compress API Guide |
| Download file | GET /api/pdf/download/{filename} |
API Documentation |
| PDF manipulation overview | — | PDF Manipulation Guide |
For the full API reference and authentication details, see the API Documentation. To test split interactively before writing code, use the FUNBREW PDF Playground. For real-world PDF automation patterns including bulk certificate extraction, see the Use Cases section.
Summary
Key points for the FUNBREW PDF split API:
- Two-step workflow: Generate a PDF with
POST /api/pdf/generate, then split it withPOST /api/pdf/splitusing the returned filename. - Flexible page spec: Combine ranges (
1-5) and individual pages (7,9) in any order in thepagesstring. - Pages are deduplicated: If you list the same page twice, it appears only once in the output.
- Invalid range = 422: Any page number outside the PDF's actual page count causes the entire request to fail — validate page counts when using dynamic inputs.
- Chain with merge and compress: Split, then merge selected pages, or compress the extracted result for email delivery.
- SaaS only: The
pdf.feature:splitgate must be enabled on your plan.