Perception
Classify
Label a document, or each of its pages, with classes you define.
POST /v1/documents/classify reads the original file and scores it against your classes. It doesn't take a parse_result source, so send a url or a workspace_file.
# 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": "invoice.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=@invoice.pdf \
-F 'metadata={"path":"invoice.pdf"};type=application/json' | jq -r .result.file.file_id)
# 2. Run the tool on it
curl "https://console.nace.ai/v1/documents/classify?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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
]
}'Configuration
POST /v1/documents/classify 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
Your labels. Each class needs a stable id and a label, and should have a description the model can use.
curl https://console.nace.ai/v1/documents/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
]
}'| Option | Default | What it does |
|---|---|---|
classes | Required | Your labels. Each needs a stable id and a label, and should have a description the model can use. |
classes[].criteria | None | Extra rules, as a list of strings, the model applies when scoring this class. |
classes[].subclasses | None | Finer labels under this class, each with an id, a label, and an optional description and criteria. |
Class ids are trimmed, must be unique within the request (and subclass ids within their class), and can't contain /. other, blank_page, unreadable_content and __ndi_unclassified are reserved. A request that breaks one of these rules is refused with 422.
Granularity
Score the whole file once, or each page.
curl https://console.nace.ai/v1/documents/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
],
"granularity": "page"
}'| Granularity | Behavior |
|---|---|
document | One result for the whole file (default). |
page | One result per page. |
Unknown
Decide what happens to a unit that fits none of your classes.
curl https://console.nace.ai/v1/documents/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
],
"unknown_policy": "force_best"
}'| Policy | Behavior |
|---|---|
allow | May mark a unit unknown (default). |
force_best | Commits an ambiguous unit, even a blank or unreadable page, to the closest of your classes. A unit that couldn't be parsed, or a page the model never read, is still unknown. |
Output
How many ranked labels to keep, and whether each carries a reason.
curl https://console.nace.ai/v1/documents/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
],
"output": { "max_alternatives": 5, "include_reason": false }
}'| Option | Default | What it does |
|---|---|---|
output.max_alternatives | 3 | How many ranked labels to keep per unit, counting the winner, from 1 to 10. |
output.include_reason | true | When true, each label may carry a short reason. |
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/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
],
"page_ranges": [{ "start": 1, "end": 3 }]
}'| Option | Default | What it does |
|---|---|---|
page_ranges | All pages | One-based, inclusive ranges to read and classify, up to 200 ranges and 500 pages in all. Not for workbooks: a spreadsheet source (xlsx, xlsm, xls, csv or parquet) is refused with 422 invalid_request and detail {"field": "page_ranges", "reason": "spreadsheet_source", "file_type": "spreadsheet"}. A workspace file's file_type tells you beforehand. |
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 Classify reads the file itself. Parse has the full description of each mode.
curl https://console.nace.ai/v1/documents/classify \
-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": "contract", "label": "Contract", "description": "A signed agreement between two parties" }
],
"parse_mode": "high"
}'| Option | Default | What it does |
|---|---|---|
parse_mode | low | How hard to work when parsing the file: low, medium or high, as on Parse. |
Result
This is the result for a two-page file classified with granularity: "page": an invoice, then a contract.
{
"units": [
{
"granularity": "page",
"page_range": { "start": 1, "end": 1 },
"unknown": false,
"labels": [
{
"rank": 1,
"class_id": "invoice",
"subclass_id": null,
"label": "Invoice",
"reason": "Lists line items, a total and a supplier name.",
"confidence": 0.97
},
{
"rank": 2,
"class_id": "contract",
"subclass_id": null,
"label": "Contract",
"reason": "No signatures or terms.",
"confidence": 0.03
}
]
},
{
"granularity": "page",
"page_range": { "start": 2, "end": 2 },
"unknown": false,
"labels": [
{
"rank": 1,
"class_id": "contract",
"subclass_id": null,
"label": "Contract",
"reason": "States terms and has signature lines.",
"confidence": 0.94
},
{
"rank": 2,
"class_id": "invoice",
"subclass_id": null,
"label": "Invoice",
"reason": "No line items or total.",
"confidence": 0.06
}
]
}
],
"unknown_units": 0,
"pages_read_fraction": 1,
"disclosures": []
}result.units has one entry for the document, or one per page with granularity: "page". The result also carries:
| Field | What it holds |
|---|---|
unknown_units | How many units none of your classes won. |
pages_read_fraction | The share of expected pages the model read, from 0 to 1. |
disclosures | Notes on how the result was made, each with a code. A classification_unread_pages entry lists the pages the model never read in unread_pages. |
Each unit:
| Field | What it holds |
|---|---|
granularity, page_range | document or page, and the unit's { "start", "end" } pages for a page unit. |
unknown | true when none of your classes won this unit. |
labels | Classes for this unit, best first. |
labels[].rank | 1 is the assigned class. Later ranks are runners-up. |
labels[].class_id | One of your class ids, or the reserved other when none matched part of the unit. |
labels[].subclass_id | The subclass, when one matched. |
labels[].label | The class's display name. |
labels[].reason | Why the model chose it, with output.include_reason. |
labels[].confidence | The model's probability for this class, from 0 to 1. See below. |
confidence is the model's probability that a unit belongs to the class, from 0 to 1. It is worked out like this:
- The base score: the model scores the unit with your classes in two different orders, and the two results are averaged.
- A class covering several parts of a document: the probabilities of those parts are averaged, weighted by the pages read in each.
- No probability available: when any label in a unit has no probability, the labels carry another number instead: the share of the unit's read pages or sheets that each class covers.
- Ranking: labels are ranked by the pages they cover, so a runner-up can have a higher
confidencethan rank 1.rankis a position, not a score. - Unread pages: pages the model never read count toward neither the weights nor the shares.
Classify doesn't read audio, video or JSONL files. See Format support.