Perception
Split
Cut a scanned packet or a workbook into the documents it contains, each with a class.
POST /v1/documents/split finds the separate documents inside one file. A PDF becomes page-range segments. A workbook becomes one segment per worksheet, and each sheet is classified against your classes.
# 1. Upload the file to your account (see Uploads)
GRANT=$(curl -s https://console.nace.ai/v1/documents/upload-grants \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "path": "packet.pdf" }')
WORKSPACE_ID=$(jq -r .workspace_id <<< "$GRANT")
FILE_ID=$(curl -s "$(jq -r .upload_url <<< "$GRANT")" \
-H "X-Upload-Token: $(jq -r .token <<< "$GRANT")" \
-F file=@packet.pdf \
-F 'metadata={"path":"packet.pdf"};type=application/json' | jq -r .result.file.file_id)
# 2. Run the tool on it
curl "https://console.nace.ai/v1/documents/split?wait_seconds=60" \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
]
}'The source can be a url, a workspace_file or a parse_result. See Sources.
Configuration
POST /v1/documents/split has several groups of options. Each example is a whole request with that option set, for the file you uploaded above. For the SDKs and the CLI, see the Python, TypeScript and CLI pages.
Classes
The kinds of document to cut on. Each class has an id, a label and a description that tells the model what it is.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
]
}'| Option | Default | What it does |
|---|---|---|
classes | Required | The document classes to cut on, each with an id, a label and an optional description. |
classes[].subclasses | None | Finer classes under this one, each with an id, a label and an optional description. Used for workbooks only: each sheet is matched against the subclasses. Other files ignore them. |
classes[].instance_key | None | { "name", "description", "required" }: a value that tells one document of this class from the next, such as an invoice number. Segments of the class then carry it in instance. |
Class ids are trimmed, must be unique within the request (and subclass ids within their class), and can't contain /. blank_page, unreadable_content and __ndi_unclassified are reserved. A request that breaks one of these rules is refused with 422.
Unknown pages
Decide what happens to pages no class matches.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"unknown_policy": "force"
}'| Policy | Behavior |
|---|---|
include | Keeps them as unclassified (default). |
force | Gives them the best class. |
error | Fails the job. |
Overlap
Decide what happens to a page where one document ends and the next begins.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"overlap_policy": "shared_boundary_page"
}'| Policy | Behavior |
|---|---|
exclusive | Keeps segments apart (default). |
shared_boundary_page | Puts a boundary page in both neighbouring segments. |
Split rules
Free-text guidance on how to cut the packet.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"split_rules": "A new invoice starts on every page with a supplier header"
}'| Option | Default | What it does |
|---|---|---|
split_rules | None | Free-text guidance on how to cut the packet. |
Output
Ask for each segment's Markdown or a file to download.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"output": { "include_content": true }
}'| Option | Default | What it does |
|---|---|---|
output.include_content | false | When true, each segment carries its Markdown in content. |
output.materialize_files | false | When true, each document segment carries a file to download. Workbook segments always come as a single-sheet .xlsx. |
Pages
Parse only some pages of the file. Leave it out to read every page. Not for workbooks.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"page_ranges": [{ "start": 1, "end": 3 }]
}'| Option | Default | What it does |
|---|---|---|
page_ranges | All pages | One-based, inclusive ranges, up to 200 ranges and 500 pages in all. Not for workbooks. |
Parse mode
See measured latency by mode for the parsing stage. This operation adds its own work, so a Parse measurement is not its total latency.
How hard to work when Split reads the file itself. Parse has the full description of each mode.
curl https://console.nace.ai/v1/documents/split \
-H "Authorization: Bearer $NACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"type": "workspace_file",
"workspace_id": "'"$WORKSPACE_ID"'",
"file_id": "'"$FILE_ID"'"
},
"classes": [
{ "id": "invoice", "label": "Invoice", "description": "A supplier invoice" },
{ "id": "receipt", "label": "Receipt", "description": "A payment receipt" }
],
"parse_mode": "high"
}'| Option | Default | What it does |
|---|---|---|
parse_mode | low | How hard to work when Split parses the file itself: low, medium or high, as on Parse. Only low with a parse_result source. |
Result
result.segments is the ordered list of cuts. This is the result for a four-page packet: an invoice (pages 1 and 2), a receipt (page 3) and a page that no class matched (page 4).
{
"segments": [
{
"id": "seg_1",
"sequence": 0,
"start_page": 1,
"end_page": 2,
"status": "matched",
"class": {
"id": "invoice",
"label": "Invoice",
"description": "A supplier invoice",
"parent_id": null,
"subcategory_id": null
},
"classification_confidence": { "band": "high", "score": 0.96 },
"boundary_confidence": { "band": "high", "score": 0.91 },
"instance": null,
"content": null,
"artifacts": []
},
{
"id": "seg_2",
"sequence": 1,
"start_page": 3,
"end_page": 3,
"status": "matched",
"class": {
"id": "receipt",
"label": "Receipt",
"description": "A payment receipt",
"parent_id": null,
"subcategory_id": null
},
"classification_confidence": { "band": "medium", "score": 0.74 },
"boundary_confidence": { "band": "high", "score": 0.88 },
"instance": null,
"content": null,
"artifacts": []
},
{
"id": "seg_3",
"sequence": 2,
"start_page": 4,
"end_page": 4,
"status": "unclassified",
"class": null,
"classification_confidence": { "band": "unavailable", "score": null },
"boundary_confidence": { "band": "medium", "score": 0.6 },
"instance": null,
"content": null,
"artifacts": []
}
]
}The fields of each segment:
| Field | What it holds |
|---|---|
id, sequence | The segment's id, and its position in the packet from 0. |
start_page, end_page | The inclusive page range. For a workbook, both are the sheet's index plus one, and sheet_name names the sheet. |
status | matched when a class won, or unclassified when unknown_policy is include and none did. |
class | The class that won, or null when status is unclassified. |
class.id | The class's id. For a workbook sheet that matched a subclass, "<class>/<subclass>". |
class.label | The class's label. |
class.description | The class's description. |
class.parent_id | For a workbook sheet that matched a subclass, the class. Otherwise null. |
class.subcategory_id | For a workbook sheet that matched a subclass, the subclass. Otherwise null. |
classification_confidence | How sure the class is, as { "band", "score" }. |
classification_confidence.band | low, medium, high or unavailable. |
classification_confidence.score | The score, or null when there's none. |
boundary_confidence | How sure the cut is, as { "band", "score" }. |
boundary_confidence.band | low, medium, high or unavailable. |
boundary_confidence.score | The score, or null when there's none. |
instance | { "key", "value" } when the class has an instance_key. |
content | The segment's Markdown with output.include_content, otherwise null. |
artifacts | Files to download when output.materialize_files ran. |
To go further, run Extract or Ground on each segment, or on the file it produced.
Split doesn't read audio, video, JSONL or Parquet files. See Format support.