Endpoint reference
POST /api/split
Cut one PDF into several. Give page ranges, where each comma group becomes one part, or a fixed chunk size. Returns application/json with every part as base64, because one request cannot return several files.
Request
Method: POST. URL: https://pdfops.dev/api/split. Body: multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
pdf | File (PDF binary) | Yes | The PDF to work on, up to 4 MB and 2,000 pages. The whole request must stay under 4.5 MB, Vercel's platform request limit. |
ranges | String | One of ranges or every | Page ranges where each comma group is one part. 1-2,3-5 gives two parts (see Page lists). |
every | Integer string | One of ranges or every | A positive chunk size. every=2 on a 5-page PDF gives parts for pages 1-2, 3-4 and 5. |
password | String | No | Opens a PDF that needs a user password. Not needed for owner-locked files with an empty user password. |
The optional X-API-Key header works as on /api/merge: no header is anonymous, 100 requests per IP per month; a valid key uses your tier's quota; an invalid or revoked key returns 401 invalid_api_key. A successful (2xx) call counts 1 request. Check consumption with GET /api/usage.
Page lists
The pages value is a comma list of items: N (one page), A-B (an inclusive range) or A- (page A through the last page). Pages are 1-based. Spaces around items and around the dash are ignored. last and negative numbers are not supported. A page past the end of the PDF, a reversed range, an empty item or an empty value fails with invalid_pages.
Response
On success: 200 OK, Content-Type: application/json:
{
"parts": [
{ "pages": "1-2", "page_count": 2, "pdf": "JVBERi0xLjcK..." },
{ "pages": "3-5", "page_count": 3, "pdf": "JVBERi0xLjcK..." }
],
"count": 2
}
| Field | Type | Meaning |
|---|---|---|
count | number | Number of parts. |
parts | array | One object per part, in order. |
parts[].pages | string | The page label. For ranges, the group as you sent it. For every, a single page like 5 or a range like 3-4. |
parts[].page_count | number | Pages in this part. |
parts[].pdf | string | The part as a base64-encoded PDF. |
On any failure: 4xx or 500, Content-Type: application/json, body is { "error": "<code>", "details": "<explanation>" }.
Error codes
| Error code | Status | Trigger |
|---|---|---|
invalid_multipart | 400 | Request body could not be parsed as multipart/form-data. |
missing_pdf | 400 | The request does not have exactly one pdf file field. |
pdf_too_large | 400 | The pdf file is over 4 MB. |
invalid_pdf | 400 | The PDF could not be parsed (corrupt, or not a PDF). |
encrypted_pdf | 400 | The PDF needs a password we were not given. Owner-locked files with an empty user password (most government blanks) open on their own; for a real user password send it as the password form field. |
decompressed_too_large | 400 | Object streams inside the PDF decompress to over 32 MB. |
too_many_pages | 400 | The PDF has over 2,000 pages. |
invalid_split | 400 | Neither or both of ranges and every were sent. Send exactly one. |
invalid_every | 400 | every is not a positive integer. |
invalid_pages | 400 | ranges is empty, malformed or out of range, or selects more than 2,000 pages in total. details says which item failed. |
too_many_parts | 400 | The split would make more than 100 parts. |
output_too_large | 400 | The JSON response would be over 4 MB (a PDFops limit on JSON responses). Checked before each part is encoded, so nothing is built past the cap. Use fewer pages per request, or call /api/extract-pages once per range to get raw PDFs. |
copy_failed | 500 | A part could not be copied. Retryable. |
save_failed | 500 | PDF serialization failed. Retryable. |
request_too_large | 413 | The whole request body is over 4.5 MB (Vercel's platform request limit). Rejected before anything is parsed. |
invalid_api_key | 401 | An X-API-Key header was sent but the key is unknown or revoked. No fallback to the anonymous IP cap. Get a fresh key at /pricing. |
rate_limited (anonymous) | 429 | No X-API-Key header and the per-IP limit (100/month) is exhausted. Retry-After gives seconds until next calendar month. |
rate_limited (keyed) | 429 | Your key's tier quota (Free 250 / Indie 4,000 / Pro 25,000 per month) is exhausted. Retry-After gives seconds until next calendar month. |
Example
curl -X POST https://pdfops.dev/api/split \
-F "pdf=@contract-bundle.pdf" \
-F "ranges=1-2,3-5" \
-H "X-API-Key: pdfops_live_..." # optional, raises your quota to your tier
# returns JSON: {"parts":[{"pages":"1-2","page_count":2,"pdf":"<base64>"},...],"count":2}
Keyless calls work: 100 requests per IP per month with no key.
Using this for real? A free API key raises the keyless 100 calls per IP to 250 requests a month, with no card.
Code examples
Have an API key? Add an X-API-Key header to any request. It is optional, and it raises your quota from 100/IP/month to your tier's.
JavaScript (Node.js 22+) — built-in fetch + FormData
import { readFile, writeFile } from 'node:fs/promises';
const fd = new FormData();
fd.append('pdf', new Blob([await readFile('contract-bundle.pdf')], { type: 'application/pdf' }), 'contract-bundle.pdf');
fd.append('ranges', '1-2,3-5'); // or: fd.append('every', '2')
const res = await fetch('https://pdfops.dev/api/split', {
method: 'POST',
headers: { 'X-API-Key': process.env.PDFOPS_API_KEY }, // optional
body: fd,
});
if (!res.ok) {
const err = await res.json();
throw new Error(`PDFops split failed: ${err.error}: ${err.details}`);
}
const { parts } = await res.json();
for (const [i, part] of parts.entries()) {
await writeFile(`part-${i + 1}.pdf`, Buffer.from(part.pdf, 'base64'));
}
Behavioral notes
- Send exactly one of
rangesorevery. - Limits: 100 parts per request, 4 MB for the whole JSON response. The base64 text is about a third larger than the PDF bytes.
- Ranges can overlap or repeat.
ranges=1-3,2-4gives two parts that share pages 2 and 3. - Each part is deterministic. Dates are set to the Unix epoch and the producer to
pdfops.dev. - Limits: 4 MB per PDF, 2,000 pages, 4.5 MB per request (Vercel's platform limit).
FAQ
Why does split return JSON and not a PDF?
A request returns one body. Several PDFs do not fit in one application/pdf response, so each part is base64 inside a JSON object. Decode parts[].pdf to get the bytes.
How do I split a PDF into single pages?
Send every=1. Each page becomes one part, up to 100 parts per request.
Related: One new PDF from chosen pages · Delete pages · Join PDFs.
Get a free API key. Keyless calls get 100 requests per IP per month; a free key raises that to 250, with no card. Enter your email and the key arrives in your inbox.