Perception

Parse

Turn a file into Markdown, plain text, layout blocks and optional chunks.

POST /v1/documents/parse converts a document into structured text. OCR runs when the file needs it, and ocr_applied on the result says whether it did.

Parse output is untrusted data. Markdown, text, blocks and chunks may reproduce instructions from the document, including fine print. Parse does not flag those instructions as prompt injection or certify them as safe. Keep document content separate from trusted agent instructions and authorize tool actions independently of it.

The examples below use an uploaded workspace_file source. For a URL source, source must include type: "url", url (a public https:// download link), and file_name (for example, "report.pdf", including its extension). Parse chooses the parser from file_name; leaving it out returns 422 invalid_request. A parse_result source instead requires type: "parse_result" and job_id from a successful Parse job in your account. See Documents for source types and reuse.

# 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": "report.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=@report.pdf \
  -F 'metadata={"path":"report.pdf"};type=application/json' | jq -r .result.file.file_id)

# 2. Run the tool on it
curl "https://console.nace.ai/v1/documents/parse?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"'"
    },
    "page_ranges": [{ "start": 1, "end": 3 }],
    "output": { "formats": ["markdown", "blocks"] },
    "chunking": { "strategy": "page" }
  }'

Streaming

The console always shows ready pages or sheets as they arrive. There is no response-mode switch. Reading a preview does not run Parse again. A completed run still has a final result for exports, finalized tables, descriptions and source highlights.

API delivery remains job-based. POST /v1/documents/parse defaults to wait_seconds=0, returning a job ID immediately. Existing complete-result clients keep using jobs.get or jobs.wait. To display previews, connect to GET /v1/documents/jobs/{id}/parse-progress/events with the same Bearer API key. There is no stream request flag.

# JOB_ID is returned by POST /v1/documents/parse?wait_seconds=0
curl -N "https://console.nace.ai/v1/documents/jobs/$JOB_ID/parse-progress/events" \
  -H "Authorization: Bearer $NACE_API_KEY"
# Fetch each snapshot unit's content_url with the same Authorization header.
# client is a NaceClient; uploaded is from client.documents.upload(...)
created = client.documents.parse(uploaded.as_source(), wait_seconds=0)
seen = set()
while True:
    current = client.jobs.get(created.job_id)
    if current.is_terminal:
        break
    for snapshot in client.jobs.parse_progress_events(created.job_id):
        for unit in snapshot.units:
            key = (snapshot.generation, unit.index, unit.revision)
            if key not in seen:
                part = client.jobs.parse_progress_part(created.job_id, unit.content_url)
                print(part.index, part.markdown)
                seen.add(key)
        if snapshot.status in ("succeeded", "failed", "cancelled"):
            break
final = client.jobs.wait(created.job_id)

Each connection lasts up to 55 seconds. Reconnect after a clean close: the first snapshot contains current state, not every past event. jobs.events() is the separate job-status stream; it does not deliver page text. Handle transport errors and an unavailable event by continuing normal job polling; closing a preview connection never cancels the job. Browser integrations should stream through their own server: native EventSource cannot attach a Bearer header, and API keys belong on the server.

Render units by their original index, even if later pages arrive first. Replace a changed revision instead of appending a duplicate; discard the previous preview when generation changes. Fetch content_url only for visible units and cache each immutable revision. Keep displayed pages and scroll position when the job completes; opening the final result should be an explicit action. Terminal snapshots have no units, so retain what was already displayed.

“All pages parsed” is followed by “Preparing final result” while tables and the full document are assembled. Only the normal job's terminal state and available result mean delivery is finished. Use job.timing.first_response_ms and job.timing.total_ms for First response and Total processing time; the response retains the timing breakdown. These are server measurements, excluding upload, browser transport/rendering and idle time waiting for another poll. Missing measurements stay unknown.

Available at every effort. Native OCR publishes pages; presentations and diagrams publish pages, and spreadsheets publish sheets after shared preparation. Some formats/providers, cache followers and Zero retention return everything together. An immediate cache hit can go straight to its final result. Streaming never guarantees a separate event for every file type. Unit previews are capped at 64K characters (truncated); snapshots at 1,000 units (more). Use the final result for complete text, exports, usage and billing.

Preview metadata and event connections count against the independent job-read per-minute window, separate from creates; individual part reads do not. Cancellation, deleted/expired sources and revoked access stop preview reads. Treat a part's 404/410 as unavailable and keep normal job polling; do not spin indefinitely on it.

Configuration

POST /v1/documents/parse 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.

Pages

Parse only some pages of the file. Leave it out to read every page.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "page_ranges": [{ "start": 1, "end": 3 }]
  }'
OptionDefaultWhat it does
page_rangesAll pagesOne-based, inclusive ranges, up to 200 ranges and 500 pages in all. Leave it out to parse the whole document.

Parse mode

How much effort parsing takes.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "parse_mode": "high"
  }'
ModeBehavior
lowNative OCR (default).
mediumRoutes the file to the best parser.
highAdds page verification and correction. Parse costs twice its standard USD price, Split and Classify cost 50% more, and Extract costs one-third more.

Only low works with a parse_result source.

Measured latency by mode

Our production smoke measurement on 2026-11-05, through console.nace.ai, observed these times for one-page synthetic invoice PDFs:

ModeParse server totalFresh jobs
low3.3 s1
medium3.4 s1
high6.2 s1

We uploaded three distinct invoices, then ran POST /v1/documents/parse?wait_seconds=60 sequentially with default output options: fixtures/t6_batch/inv_030.pdf, inv_031.pdf and inv_032.pdf, respectively, from nace-ndi-test at commit 43e5f7c. Every job succeeded with one page and a Parse cost line of execution: "fresh".

These numbers use timing.total_ms, divided by 1,000: server preparation, queueing, NDI processing and active result preparation. Upload, client network/rendering and idle time before a later read are excluded. These are individual production examples, not medians, an SLA or a per-page scaling rule. File complexity, page count, OCR, parser routing, queue load, verification and reuse affect latency. A higher mode does not guarantee a slower result on every run.

In the same five-job smoke run, Extract on fixtures/t7_odd/multipage_table.pdf (two pages, its fixture extraction schema) took 4.8 s in low and 3.1 s in high, measured as finished_at - created_at (NDI queue plus processing). Both jobs reused an earlier parse (cost.lines recorded execution: "reused" on Parse), so these are warm Extract observations and cannot establish fresh parsing latency or predict another environment's timings. Those Extract observations predate timing coverage for all document kinds and omitted timing, so their duration is not the full Drex server total used in the table above. We made no additional production jobs for this measurement.

For a fresh comparison, check cost.lines[].execution before quoting a run. Split, Classify and Extract add their own work, so Parse totals do not predict those operations' totals. Streaming can publish a usable preview before the final result; the table measures the final result, not that first preview.

Output formats

Choose which renderings of the document come back in result.document. A format you leave out comes back as null.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "output": { "formats": ["markdown", "text"] }
  }'
FormatBehavior
markdownThe document as Markdown, for display (default).
blocksThe layout elements, each with a location (default).
textThe document as stripped plain text.

Send a list, such as ["markdown", "text"]. The default is markdown and blocks.

OptionDefaultWhat it does
output.include_page_markerstrueInserts --- Page N --- between PDF pages in markdown and text.
output.return_ocr_datafalseWhen true, adds word- and line-level OCR geometry on document.ocr. PDF sources only: on any other file the create is refused with 422 invalid_request.

Table format

Controls how tables are written inside markdown and in table blocks. text always flattens tables to plain text.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "output": { "table_format": "markdown" }
  }'
FormatBehavior
htmlTables as HTML (default).
markdownTables as Markdown tables.

Chunking

By default Parse returns no chunks. For retrieval, ask for chunks you can embed and search separately.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "chunking": { "strategy": "page" }
  }'
StrategyBehavior
nonechunks stays empty (default).
pageOne chunk per page.
sectionSplits at the section headings.

Figures

Decide what happens to the figures in the document.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "figures": { "mode": "describe" }
  }'
ModeBehavior
includeKeeps the figure's crop (default).
omitDrops figures.
describeKeeps the crop and captions it, with one vision call per figure.
OptionDefaultWhat it does
output.include_imagesfalseWhen true, each block of type: "figure" carries a crop link in image_url, for PDF and image sources. It needs blocks in output.formats and figures.mode other than omit; a document with no detected figures gets no links. The dashboard's Include Images switch is on by default and shows each figure as an image in the results; it is unavailable while Blocks is off or figures are omitted.

Diagrams

Decide what happens to flowchart-like figures.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "diagrams": { "mode": "mermaid" }
  }'
ModeBehavior
omitLeaves them as images (default).
mermaidRewrites them as Mermaid.

Spreadsheets

For workbooks, choose the sheets to read and what to include from them.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "spreadsheet": { "sheets": ["Summary"] }
  }'
OptionDefaultWhat it does
spreadsheet.sheetsAll sheetsOnly these sheets, in workbook order.
spreadsheet.include_hidden_sheetstrueWhen false, skips the sheets the workbook marks hidden.
spreadsheet.include_hidden_rowstrueWhen false, skips the rows the workbook marks hidden.
spreadsheet.include_hidden_columnstrueWhen false, skips the columns the workbook marks hidden.
spreadsheet.include_formulasfalseWhen true, adds formula source text. Needs output.table_format: "html" and markdown or blocks output. Cached values are always there.

Password

Open an encrypted PDF.

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "workspace_file",
      "workspace_id": "'"$WORKSPACE_ID"'",
      "file_id": "'"$FILE_ID"'"
    },
    "password": "..."
  }'
OptionDefaultWhat it does
passwordNonePassword for an encrypted PDF. Never stored or returned. Not with a parse_result source.

Result

A finished parse has these fields on result:

FieldWhat it is
result_typeAlways "parse".
file_nameThe name of the parsed file.
laneThe kind of file: pdf, document (Word), presentation, spreadsheet (workbooks, CSV and Parquet), image, html, plaintext, markdown, json, email, diagram, audio or video.
document.markdownThe document as Markdown, for display. null when markdown isn't in output.formats.
document.textThe document as stripped plain text. null unless text is in output.formats.
document.blocksThe document's layout elements, each with a location. See Blocks.
document.chunksPieces sized for retrieval. Empty unless chunking.strategy is page or section. See Chunks.
document.page_countHow many pages the document has.
ocr_appliedWhether OCR ran on the file.
unitsThe job's standard-rate usage. See Usage fields.
disclosuresAnything about how the result was made that you might not assume, such as content cut short (below), each with a code.
{
  "result_type": "parse",
  "file_name": "report.pdf",
  "lane": "pdf",
  "document": {
    "page_count": 96,
    "markdown": "# Annual Report\n\n...",
    "text": null,
    "blocks": [ ... ],
    "chunks": []
  },
  "ocr_applied": false,
  "units": 96,
  "disclosures": []
}

Blocks

Each block is a layout element with a location. Its kind depends on the source:

location.kindWhere it points
page_regionA one-based page and polygons in normalized 0 to 1 coordinates. Use these to draw overlays.
sheet_rangeA workbook tab and an A1 cell range.
text_spanCharacter offsets into the derived text, char_start inclusive and char_end exclusive, with page when the span maps to one.
row_rangeA Parquet source: zero-based, inclusive start_row and end_row covering the file's rows. CSV files and workbooks use sheet_range.
json_pointerAn RFC 6901 pointer into a JSON source. The empty string is the root.

Chunks

chunking.strategy: "page" returns one page_region chunk per page. "section" follows the section headings.

Very large results

A result over 1 MiB comes back as a preview, and you fetch the full text from a link.

  • Documents: when document.markdown or document.text would be over 1 MiB of UTF-8, both hold a preview of at most 65,536 characters, cut at a line end.
  • Workbooks: when the sheets' combined rendering is over 1 MiB, every sheet gets a content_url and a preview.

An entry in result.disclosures says the content was cut short:

{ "code": "content_truncated", "target": "document_content", "unit": "characters", "original_size": 120000000, "retained_size": 65480 }

To get the full text:

  • A document: fetch document.content_url.
  • A workbook (target: "spreadsheet_region"): fetch each affected sheet's document.spreadsheet.sheets[].content_url.

The link answers with one of these:

StatusWhat it meansWhat to do
200The full Markdown.Stream it to a file.
202The file is still being prepared. The body is { "status": "pending", "retry_after_seconds": ... }, with a retry-after header.Wait, then send the same request again until it answers 200. Don't save a 202 body as the document.
409The content couldn't be prepared.Asking again won't help.

The SDKs and the CLI do this for you. jobs.download (jobs.fetchFile in TypeScript) waits for the file. nace parse fetches a document's full content itself, and for a workbook it prints a nace file JOB_ID URL command that saves each sheet.

A Parquet source (.parquet) of any size also carries rows_url, row_count and column_count on its one sheet; workbooks and CSV files never do. GET rows_url?start_row=0&limit=100 reads one page of rows, numbered from zero. limit is 1 to 100, default 50. The answer is the page:

{
  "path": "...",
  "start_row": 0,
  "limit": 100,
  "total_rows": 250000,
  "columns": [{ "name": "invoice_id", "data_type": "string" }],
  "rows": [{ "invoice_id": "INV-1042" }]
}

Each row maps column names to the cell's text. The SDKs read a page with jobs.rows, and the CLI prints rows as TSV with nace rows JOB_ID URL.

Spreadsheets, audio and video

FileFieldWhat it holds
Workbookdocument.spreadsheet.sheets[].cell_map_urlA link to a map from the rendered table to the sheet's physical rows and columns.
Office file converted for layoutA converted PDF linkA link to the converted PDF.
Audio or video (lane is "audio" or "video")transcript_urlA link to the transcript.
Audio or video, when you read the job by idresult.transcriptThe transcript inline: speakers, then ordered speech and silence segments with start_ms (inclusive) and end_ms (exclusive) that cover the whole recording.

Every one of these links points at https://console.nace.ai/v1/documents/jobs/{id}/... and opens with your API key. See Jobs and results.

Errors

A parse can fail in two ways.

The server refuses the create. It answers invalid_request_error with the document service's code:

CodeWhen
invalid_requestAn unknown body field, an option a parse_result source doesn't take, or output.return_ocr_data on a file that isn't a PDF.
invalid_sourceThe source is missing, isn't a url, parse_result or workspace_file, or names a job or workspace that isn't yours.
unsupported_file_typeParse doesn't read this type. See Format support.

The job is accepted and then fails. It ends with status: "failed" and a code:

CodeWhen
corrupt_fileThe file can't be read, for example because it is damaged.
invalid_requestA password on a file that isn't a PDF, or spreadsheet options that change a default on a file that isn't a workbook.

Next steps

Next, pass the finished job as {"type": "parse_result", "job_id": "..."} to Extract, Split or Ground, so the document isn't parsed and paid for twice.

On this page