Appearance
Split PDF
Splits a PDF into one single-page PDF per page and returns a link to each, in page order. The source can be a public http(s):// URL or an s3:// object.
| Slug | split-pdf |
| Sync | POST /v1/utilities/split-pdf → runSplitPdf |
| Async | POST /v1/utilities/split-pdf/jobs → createSplitPdfJob |
| Max duration | 900 s (15 min) |
| Output | N application/pdf files, one per page, plus pageCount |
Quick example
bash
curl -X POST https://api.servicelabs.dev/v1/utilities/split-pdf \
-H "Authorization: Bearer $DU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": { "url": "https://example.com/statement.pdf" } }'js
const base = 'https://api.servicelabs.dev'
const headers = {
Authorization: `Bearer ${process.env.DU_API_KEY}`,
'Content-Type': 'application/json',
}
// 1. start the job
const started = await fetch(`${base}/v1/utilities/split-pdf/jobs`, {
method: 'POST',
headers,
body: JSON.stringify({ input: { url: 's3://finnoto-data/uploads/statement.pdf' } }),
}).then((r) => r.json())
// 2. long-poll until it finishes (each call waits up to 30 s)
let job = started
while (!['succeeded', 'failed', 'cancelled'].includes(job.status)) {
job = await fetch(`${base}/v1/jobs/${started.id}?wait=30`, { headers }).then((r) => r.json())
}
if (job.status === 'succeeded') {
for (const page of job.result.files) console.log(page.pageNumber, page.url)
} else {
console.error(job.error)
}python
import os, requests
job = requests.post(
"https://api.servicelabs.dev/v1/utilities/split-pdf",
headers={"Authorization": f"Bearer {os.environ['DU_API_KEY']}"},
json={"input": {"url": "https://example.com/statement.pdf"}},
timeout=100,
).json()
for f in job["result"]["files"]:
print(f["pageNumber"], f["url"])json
// → 200 OK (abridged)
{
"id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
"utility": "split-pdf",
"status": "succeeded",
"timings": { "queueMs": 0, "upstreamMs": 1320, "storageMs": 95, "totalMs": 1438 },
"result": {
"pageCount": 3,
"files": [
{
"url": "https://files.servicelabs.dev/split-pdf/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/page-1.pdf",
"key": "split-pdf/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/page-1.pdf",
"name": "page-1.pdf",
"size": 48211,
"contentType": "application/pdf",
"pageNumber": 1
},
{ "name": "page-2.pdf", "pageNumber": 2, "…": "…" },
{ "name": "page-3.pdf", "pageNumber": 3, "…": "…" }
]
},
"error": null
}Input
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | The PDF to split. http://, https:// or s3://, ≤ 4096 chars. |
No other input fields are accepted.
Result
| Field | Description |
|---|---|
result.pageCount | Number of pages produced (= files.length). |
result.files[] | One entry per page, in page order. |
files[].pageNumber | 1-indexed page number. |
files[].name | page-<N>.pdf (no zero padding — use pageNumber to order). |
files[].contentType | application/pdf. |
Limits & behaviour
| Limit | Value |
|---|---|
| Maximum source size | 150 MB (streamed with a running size check — oversized downloads abort early) |
| Maximum pages | 500 |
| Source download timeout | 60 s |
| Encrypted / password-protected PDFs | rejected — decrypt first |
| Empty (0-page) PDFs | rejected |
- Splitting is fast and memory-light (pages are copied, not re-rendered), so even 500-page documents finish well inside the time limit. Most of the wall-clock time is the download and the copy of N files to storage.
- Pages are written sequentially. For documents with hundreds of pages prefer the async route: the sync route answers
202with the job id if it runs past 90 s anyway.
Errors specific to this utility
| Situation | Outcome |
|---|---|
url missing or not http(s):// / s3:// | 400 invalid_request — no job |
| Download failed, or source > 150 MB | failed job, 422 upstream_rejected |
| Not a valid PDF / unsupported PDF | failed job, 422 upstream_rejected (Invalid or unsupported PDF: …) |
| Password-protected PDF | failed job, 422 upstream_rejected (Password-protected PDFs are not supported) |
| More than 500 pages, or 0 pages | failed job, 422 upstream_rejected |
| Unexpected failure mid-split | failed job, 502 upstream_error |
See Conventions & errors for the full table.