Perception

Overview

Parse, split, classify, extract and ground documents with your Nace API key, billed from the same balance as evaluations.

Perception brings NDI (Nace Document Intelligence) to Nace Console. Use nace-sdk, nace-cli, or nace-mcp with your console key. The separate ndi-* packages require direct NDI service credentials.

Perception runs five tools on a document, each at POST /v1/documents/{tool}. They use the same nace_sk_ key and the same balance as POST /v1/systemone, at base URL https://console.nace.ai.

ToolUse it to
ParseTurn a file into Markdown, text, layout blocks or chunks for retrieval.
SplitCut a packet of several documents into segments, each with a class.
ClassifyLabel a document, or each page, with your classes.
ExtractFill a JSON schema, with a status and citations for each field.
GroundFind where each quote appears, with its exact location.

Tools chain: parse a file once, then pass the parse to Split, Extract or Ground so it isn't parsed and paid for again.

The Python and TypeScript SDKs and the CLI cover every route on these pages. Call them from your server: the /v1 routes send no Access-Control-Allow-Origin header, so a browser can't call them. Only the upload itself and a job's signed file links go straight from a browser.

Create a job

Every tool takes a JSON body with a source (the document to work on) and that tool's options:

curl https://console.nace.ai/v1/documents/parse \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-2026-10-01-0001" \
  -d '{
    "source": {
      "type": "url",
      "url": "https://example.com/invoice.pdf",
      "file_name": "invoice.pdf"
    },
    "output": { "formats": ["markdown"] }
  }'

The same job from the SDKs and the CLI:

parse.py
from nace_sdk import NaceClient, UrlSource

with NaceClient() as client:  # reads $NACE_API_KEY
    job = client.documents.parse(
        UrlSource(url="https://example.com/invoice.pdf", file_name="invoice.pdf"),
        output={"formats": ["markdown"]},
        idempotency_key="invoice-2026-10-01-0001",
    )
    done = client.jobs.wait(job.job_id)
parse.ts
import { NaceClient } from "nace-sdk";

const client = new NaceClient(); // reads NACE_API_KEY
const job = await client.documents.parse({
  source: { type: "url", url: "https://example.com/invoice.pdf", file_name: "invoice.pdf" },
  output: { formats: ["markdown"] },
  idempotency_key: "invoice-2026-10-01-0001",
});
const done = await client.jobs.wait(job.job_id);
CLI
nace parse https://example.com/invoice.pdf --format markdown --idempotency-key invoice-2026-10-01-0001

The answer is 202 with the job, status: "queued". Its job_id is what every other route takes: read it until it finishes, as in Jobs and results. The SDKs' jobs.wait does that for you, and the CLI waits unless you pass --async.

Drex gives every job an id for its charge. You get it in the x-drex-document-job response header and in the job's idempotency_key field. It is not the Idempotency-Key you sent. Include it, with request_id, when you contact support.

To read the header:

  • Python: job.raw_http_response.headers
  • TypeScript: (await client.documents.parse(params).withResponse()).response.headers

Every tool also takes an optional name for the job, up to 200 characters, and a project_id, a UUID you choose to group jobs; both come back on the job.

Wait in the same call

Add ?wait_seconds=N (0 to 60; a larger value waits 60) to hold the request open until the job finishes. When it finishes in time, the answer is 200 with the result. Otherwise it's 202, and you read the job as usual. The SDKs take it as wait_seconds and lengthen their request timeout by the same amount; the CLI takes --wait-seconds.

Retry safely

If a request times out or the connection drops, you can't tell whether the job was created. Sending it again could create a second job and bill you twice.

An idempotency key prevents that. Send an Idempotency-Key header with a value you choose, such as an invoice number. If the same request arrives again with the same key, Drex returns the job it already created instead of starting a new one. The key can be up to 200 characters and must be unique within your account.

  • Same key, same body: returns the same job, never a second one, and isn't billed again.
  • Same key, different body: refused with 409, code idempotency_conflict.
  • No key: every request creates a new job.

The SDKs send a key on every create: yours, or one they mint for the call and reuse across that call's automatic retries. A new call mints a new key, so to retry a create yourself later, pass your own (idempotency_key in both SDKs, --idempotency-key in the CLI). If a create ends with 503 job_pending, the SDK error (idempotency_key in Python, idempotencyKey in TypeScript) and the CLI's message give you the key that was sent, so you can resume the same job with it.

Sources

sourceWhat it is
{"type": "url", "url": "https://...", "file_name": "..."}A public link the document service downloads. url must start with https://. file_name is required: its extension tells the document service what kind of file it is, so it must be one the operation reads (see Format support). Otherwise the create is refused with 422, code unsupported_file_type.
{"type": "workspace_file", "workspace_id": "...", "file_id": "..."}A file you uploaded, with or without a workspace of your account's. A file in a workspace you were invited into from another account is refused with 422 invalid_source. See Uploads and Workspaces.
{"type": "parse_result", "job_id": "..."}One of your finished parse jobs, so Parse, Split, Extract or Ground can reuse it. Classify doesn't take it. Nothing is parsed again, so parse_mode can only be low, and Parse and Extract take no page_ranges.
A file on your computerUpload it with upload, then pass the result to a tool such as parse. The CLI does both when you give it a file path. See Uploads.

Every operation also accepts an optional corpus_sources: up to 50 workspace_file sources recorded with the request for display. The job doesn't read them, and they don't change the result. It may list only workspace_file sources that you uploaded, or from the workspaces your account owns. Any other source, or one that names another account's job or workspace, is refused with 422, code invalid_source, before anything is created or billed.

Billing

A job is billed from your account balance when it finishes. USD prices and when you are charged are on Pricing.

Errors and rate limits are on Errors.

On this page