Split a PDF by bookmarks
A 400-page manual or report already knows where its own chapters begin, because its author bookmarked them, yet most people still split such files by counting pages by hand. This split PDF by bookmarks API reads a document's existing bookmark outline and cuts it into one separate file per top-level entry, turning a table of contents into an actual set of files.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The manual split nobody should have to do
Long PDFs — technical manuals, compiled reports, merged chapter drafts, government publications — are usually built with a bookmark outline already in place, added either by the authoring tool or by whoever assembled the final document. That outline is a perfectly structured map of where each section starts and ends, yet splitting the file still often means opening it, scrolling to find each boundary, and exporting page ranges by hand. This endpoint exists because that map was already there; it just needed to be read and acted on automatically.
How the split is decided
Call POST /pdf/split-by-bookmark with the source PDF and get a task_id back immediately, since the split runs asynchronously. The engine reads the document's existing bookmark outline and uses each top-level bookmark as a section boundary: every entry becomes the start of a new file, which runs until the next top-level bookmark begins or the document ends. The result is a set of files that mirrors the document's own table of contents, with page content, formatting and images preserved exactly as they appeared in the source.
Why bookmarks are the right signal to split on
PDF bookmarks, technically the document outline, have been part of the format since its early versions specifically to let long documents be navigated without scrolling through hundreds of pages — the same feature that shows a clickable table of contents in a PDF reader's sidebar. Because that outline is written by whoever produced the document and typically maps directly onto chapters, sections or standalone reports bundled into one file, it's a far more reliable split signal than fixed page counts or blank-page detection, which can misfire on documents that don't follow a strict page pattern.
Where it saves real time
It's built for exactly the kind of document that arrives as one large PDF but is consumed section by section: a compiled annual report where each department wants only its own chapter, a merged set of contracts that needs to become individual files again, a training manual whose modules should be distributed separately, or a bound government filing split back into its original component documents. Running this as one step in a pipeline replaces what would otherwise be a manual, error-prone page-counting exercise every time a new large document arrives.
Delivery and pricing
The resulting files are delivered by a signed webhook, recommended for automation, or a signed link valid for 24 hours; source and output are deleted after the retention window and never used for training. Pricing is a flat $0.002 per request regardless of how many sections the document splits into, and a task that fails after its retry attempts is never charged. This endpoint is live now and ready for production traffic.
What you can do with it
Distribute a training manual module by module
Split a single bookmarked training manual into one file per module, so each team only receives the section relevant to them instead of the entire document.
Break a compiled annual report into departmental chapters
Turn one large report PDF with bookmarks for each department into individual files, so each team gets exactly its own chapter without the rest of the document.
Recover individual contracts from a merged PDF
Split a bookmarked PDF containing several merged contracts back into separate files, one per contract, undoing an earlier merge step cleanly.
Separate a government filing into its component documents
Split a bound multi-document filing along its bookmark structure to recover the original standalone documents it was assembled from.
FAQ
How do I split a PDF by bookmarks using an API?
Send the PDF to POST /pdf/split-by-bookmark, which returns a task_id right away. The set of split files, one per top-level bookmark, arrives by webhook or signed link once processing completes.
Is the split PDF by bookmarks API free?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
What happens if my PDF has no bookmarks?
This endpoint splits based on an existing bookmark outline, so a document without any bookmarks has no boundaries to split on; add an outline first or use a page-range-based split instead.
Does it split on nested sub-bookmarks too?
It uses top-level bookmark entries as the section boundaries, so nested sub-bookmarks stay inside their parent section's file rather than creating additional splits.
Will formatting, images and layout stay intact after splitting?
Yes. Each output file preserves the exact page content, formatting and images from the source document; nothing is re-rendered or altered.
Can I split many bookmarked PDFs in bulk?
Yes. Each request is an independent asynchronous task, so you can submit a large batch of documents in parallel and collect each set of split files by webhook.
How are the output files named?
Each output file corresponds to one top-level bookmark section from the original document's outline, delivered together as the result of a single task.
Is this endpoint live right now?
Yes, it is live and accepting production traffic today.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/pdf/split-by-bookmark \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"pdf":"https://ejemplo.com/documento.pdf"}'const res = await fetch("https://api.kit.forhosting.com/pdf/split-by-bookmark", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"pdf": "https://ejemplo.com/documento.pdf"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/pdf/split-by-bookmark",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"pdf": "https://ejemplo.com/documento.pdf"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/pdf/split-by-bookmark", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"pdf":"https://ejemplo.com/documento.pdf"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"pdf":"https://ejemplo.com/documento.pdf"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/pdf/split-by-bookmark", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"pdf": "https://ejemplo.com/documento.pdf"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "pdf.split_by_bookmark",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
max_pages | 200 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
413 | input_too_large | The file exceeds the size limit. |