Split
Classify decides. Split cuts. POST /v1/split calls no model. It takes page ranges — yours, or the ones a page-mode classify run produced — and extracts them as real files. It has no classes, no instructions, no rules and no saved config.
Split takes PDF only, unencrypted. Office, image, audio and video inputs fail the run with unsupported_split_source. Classify accepts anything parseable; split does not. The asymmetry is deliberate — timecode split has a different output shape and is not part of this verb.
Two input forms — exactly one
Send exactly one of segments / classifyRunId. Neither is 400 segments_required; both is 400 segments_conflict.
Segments must ascend, must not overlap, must start at 1 or higher, and must end inside the source’s page count. Gaps are fine — drop a separator page on purpose if you want to.
Example response
Children carry parentFileId back to the source, inherit the parent’s metadata, and gain metadata.parentRunId set to this run’s id — so your correlation id survives into every downstream extract. They land in the parent’s space; you do not choose it.
One segment covering the whole file creates nothing. It comes back with fileId set to the parent’s id. A byte-identical copy would double the storage, the next parse and the bill for no gain.
Three calls, and how to skip one
It is three calls, and that is the point. Between classify and split you can read the segments, correct one, or decide the packet was one document all along.
A caller who already knows the ranges skips step 1 entirely and posts segments to split. That path runs no model and bills no classification.
The 100-segment cap matches /v1/extract’s 100-file cap, so a split packet always fits one downstream call.
What you pay for
Split bills per page cut, and it is the cheaper of the two verbs because it is deterministic byte work: no model runs. usage itemizes {pages, split}.
The children are parsed again. Child PDFs are byte extractions, not parsed at split time — the first verb that consumes a child parses it. A 400-page packet therefore pays parse 400, classify 400, split 400, then roughly 400 more parsed pages across the children before extraction starts. Run materialize: false first on a large packet to see the ranges before you commit to that.
Dry runs, retries and failures
materialize: falsecreates nothing and cannot half-fail. It is the safe first call on a large packet.- If materialization fails part-way the run is terminal
failedand the children already created are deleted.output.splitsis published only on success, so you never see an orphan. - A queue retry never creates a duplicate child.
POST /v1/runs/spr_…/keepmoves the parent and every child into a space and clears the TTL. Otherwise the children expire with the run.
Limits
A split of a 300-page packet normally returns 202 with a run id. Pass ?wait=0 and poll, or use a webhook.
Errors
A classify run expires with its TTL, 24 hours by default. Classify on Monday and split on Wednesday and you get classify_run_mismatch. Split promptly, or keep the segments on your side and post them explicitly.