Developer toolsDocs

CLI

Install the drex command, sign in, ask Drex's decision model calibrated questions, and run Perception's document tools, uploads, jobs and saved schemas from the shell.

nace-cli installs the nace command, which calls https://console.nace.ai from your shell. It is built on the Python SDK. It asks Drex's decision model calibrated questions with nace decide and runs Perception's five tools, uploads, jobs, job files and saved extraction schemas. This page covers installing, signing in and the flags every command takes, then nace decide and nace models, then the document commands, then exit codes, errors, retries and logging. Every command lists them all.

Install

pipx install nace-cli   # or: uv tool install nace-cli
nace login
nace parse https://example.com/report.pdf > report.md

nace-cli needs Python 3.11 or later. It checks TLS certificates against your operating system's certificate store, so a proxy certificate your system trusts works too.

nace version and nace --version print the CLI and SDK versions:

nace 0.1.0
nace-sdk 0.1.0

Sign in

nace login                         # type the key; it isn't echoed
nace login --api-key - < key.txt   # or read it from stdin, for scripts
FlagWhat it does
--api-key KEYThe nace_sk_ key. - reads the first line of stdin, or asks at a hidden prompt at a terminal. Omit it to type the key at a hidden prompt.
--base-url URLSave a different API host with the key.

nace login then:

  1. Checks that the key looks like a Drex key: nace_sk_ followed by 43 characters. Anything else exits 2 before any request.
  2. Checks it against Drex with GET /v1/models, which is free at any balance. It asks --base-url, else $NACE_BASE_URL, else the base URL saved earlier, else https://console.nace.ai. A refused key exits 1.
  3. Saves it to ~/.nace/config.toml, readable only by you, and prints saved API key to <path> on stderr.

Only --base-url is saved: $NACE_BASE_URL is used for the check but not written, and a base URL saved earlier stays until you pass another one. Without --api-key and without a terminal, as in CI, nace login exits 2: pass --api-key KEY or --api-key -.

nace logout deletes the config file, a saved base URL included, and the legacy ~/.drex/config.toml when no path variable is set, and prints removed <path> (or no saved API key) on stderr.

Credentials and settings

VariableWhat it does
NACE_API_KEYThe API key. Wins over api_key in the config file.
NACE_BASE_URLThe API host. Wins over base_url in the config file; without either, https://console.nace.ai.
NACE_CONFIG_PATHWhere the config file is, instead of ~/.nace/config.toml. nace login and nace logout use it too.
NACE_DEFAULT_DECISION_MODELThe model nace decide asks when neither -m nor --body names one. Default drex-latest.
NACE_LOG_LEVELLog each request to stderr. See Logging.

The names from before the packages were renamed from drex-* still work when the new ones are unset or blank: DREX_API_KEY, DREX_BASE_URL, DREX_CONFIG_PATH, DREX_DEFAULT_MODEL and DREX_LOG_LEVEL. So does the file drex login wrote, ~/.drex/config.toml, which is read when ~/.nace/config.toml doesn't exist and no path variable is set. nace login always writes ~/.nace/config.toml, and nace logout deletes both files.

The config file holds at most two keys:

~/.nace/config.toml
api_key = "nace_sk_..."
base_url = "https://console.nace.ai"   # only after nace login --base-url

With no key in either place, a command exits 2 with error: no API key and says to set $NACE_API_KEY or run nace login. An unreadable config file exits 2 too, unless $NACE_API_KEY is set.

Flags every API command takes

Every command except login, logout and version takes --json and --timeout, and every command, nace itself included, takes -h, --help. Put them after the command name: nace models --json, not nace --json models.

FlagWhat it does
--jsonPrint JSON instead of text. What each command prints is in its section.
--timeout SECONDSHow long each HTTP request may take (default 120). A document create with --wait-seconds N gets N seconds more. For file downloads and event streams it bounds only connecting, not how long the data takes to arrive.
-h, --helpPrint the command's flags and exit.

Text and JSON values

--state and --password read @FILE from a file and - from stdin; anything else is the text itself. Other text flags, such as --instructions, --description, --name and --file-name, take their value as given: --instructions @notes.txt sends the text @notes.txt. Flags that take JSON, such as --questions, --body, --options and --schema, read @FILE, -, or a bare path to a file that exists; anything else is parsed as inline JSON. Only one flag per command can read stdin.

Text piped in with - is read as UTF-8, and a byte-order mark at its start is dropped; stdout and stderr are UTF-8 too, on every platform. Windows PowerShell 5.1 replaces non-ASCII characters with ? before they reach any program it pipes to, so pass such text with @FILE there.

Ask questions

nace decide --state "I was charged twice for my March invoice." \
  --noul "wants_refund=Is the customer asking for a refund?" \
  --choice "topic=billing,shipping,other: Which topic is it?" \
  --score "urgency=low,medium,high: How urgent is it?"

Stdout gets one line per answer, and stderr gets the model, the input tokens, the model's time and the request_id:

wants_refund  noul    0.930
topic         choice  billing (confidence 0.880)
urgency       score   1.400 on low < medium < high (confidence 0.500)
drex-v1.5 · 61 input tokens · 143 ms · req_...
FlagWhat it does
--state STATEThe state: text, @FILE, or - for stdin. Text that starts with { or [ and parses as JSON is sent as JSON; anything else as a string. Required unless --body has one.
--noul NAME=INSTRUCTIONSA yes/no question. Repeatable.
--choice NAME=A,B,C[: INSTRUCTIONS]A pick-one question: labels before the first colon, separated by commas, then instructions. Repeatable.
--score NAME=LOW,...,HIGH[: INSTRUCTIONS]An ordered-levels question, levels from low to high. Repeatable.
--questions JSONA questions object, name to question: inline JSON, @FILE, a path, or -.
--body JSONA whole request body: inline JSON, @FILE, a path, or -.
-m, --model MODELThe model id. Default: --body's model, else $NACE_DEFAULT_DECISION_MODEL, else drex-latest. See nace models.
--jsonPrint the response body as Drex sent it, request_id included, instead of the lines above.

At least one question is required. Questions merge by name: --body's questions first, then --questions, then each shorthand flag, so a later question replaces an earlier one with the same name. --state and -m replace --body's state and model; its other fields are sent as given.

The shorthand labels can't contain , or : and carry no descriptions. For label descriptions or noul criteria, use --questions:

questions.json
{
  "wants_refund": {
    "type": "noul",
    "instructions": "Is the customer asking for a refund?",
    "criteria": { "true": "The customer asks for money back", "false": "Anything else" }
  },
  "topic": {
    "type": "choice",
    "instructions": "Which topic is it?",
    "criteria": { "billing": "Payments, invoices or charges", "shipping": "Delivery and tracking", "other": null }
  }
}
nace decide --state @ticket.txt --questions questions.json --json

See Questions for what each type accepts and Reading answers for the response.

List models

nace models lists the models your key can call, one per line: name, release date, -> <model> for an alias, then its description. --json prints the response body, request_id included. It is free at any balance. See Models.

Sources

Parse, Split, Classify, Extract and Ground take the document as their SOURCE argument:

SOURCEWhat it is
https://...A public link the document service downloads. See URLs.
A local file pathUploaded into your workspace first, then read from there. See Local files.
ws:<workspace_id>/<file_id>A file already in your workspace: what nace upload prints.
job:<job_id>One of your finished parse jobs, so the document isn't parsed and paid for again. Not for classify, and not with -p on parse or extract, --mode medium or high, or --password.

Anything else, such as an http:// link or a path that doesn't exist, exits 2 before any request.

URLs

The document service picks the parser from the file name's extension, so a URL source carries a file_name. nace takes it from the URL's last path segment, without the query and percent-decoded, when that segment has an extension: https://example.com/files/Q3%20report.pdf gives Q3 report.pdf. When the segment has none, the command exits 2; name the file with --file-name:

nace parse "https://example.com/download?id=7" --file-name report.pdf

Pass --file-name too when the last segment only looks like it has an extension, as in https://arxiv.org/pdf/1706.03762. --file-name applies only to an https:// source.

Local files

A local file is uploaded first, like nace upload. Stderr shows uploading <name>, then uploaded ws:<workspace_id>/<file_id>: pass that as SOURCE next time instead of uploading again.

FlagWhat it does
--upload-path PATHThe file's path in your workspace. Default: its file name. Keep the file's extension: the document service types an upload from its first bytes, and from this extension when those don't settle it.
--on-conflict {reject,new_version}What happens when a different file already has that path. reject, the default, fails with 409 path_conflict. new_version adds a version of that file, which jobs on it that haven't started yet then read.

Uploading the same bytes to the same path again reuses the file already there. An edited file under the same name fails with path_conflict, and stderr says how to go on: pass another --upload-path, or --on-conflict new_version. Both flags apply only to a local-file source. See Same path again.

Flags every tool shares

Parse, Split, Classify, Extract and Ground each create a job. They take these flags, on top of each tool's own flags below and --json and --timeout from Flags every API command takes:

FlagWhat it does
--file-name NAMEFor an https:// source: the file's name, whose extension picks the parser. See URLs.
--upload-path PATHFor a local file: its path in your workspace. See Local files.
--on-conflict {reject,new_version}For a local file: what to do when a different file has that path. See Local files.
-p, --pages SPEC1-based pages to read, inclusive: 3 or 1-5,8. Default: every page. At most 200 ranges and 500 pages. Not on ground, not with a job: source on parse or extract, and not for a workbook on split or classify.
--mode {low,medium,high}How much effort parsing takes (parse_mode, default low). Not on ground. medium and high parse again, so not with a job: source.
--option KEY=VALUEAny other request field. Repeatable. See Request fields.
--options JSONRequest fields as a JSON object: inline, @FILE, a path, or - for stdin.
--wait-seconds NAsk Drex to hold the create open up to N seconds, 0 to 60. A job that finishes by then comes back with its result in the same call.
--idempotency-key KEYUp to 200 characters. The same key and body return the same job and bill once; the same key with a different body is refused with 409 idempotency_conflict.

Without --idempotency-key, nace sends a new key on each run. Retries inside one run return the same job, but running the command again creates, and bills, a new one. See Retry safely. A create that fails after holding the request open for 30 seconds or more isn't retried at all; see Retries and timeouts.

nace doesn't print the create's x-drex-document-job header, Drex's id for the charge. The job's idempotency_key field, which -o json prints, holds the same id: quote it when you contact support.

Request fields

--options gives the base request, each --option is applied on top of it, and the flags above and each tool's own flags win over both.

  • A dotted key nests: --option output.table_format=markdown sends {"output": {"table_format": "markdown"}}.
  • A value is parsed as JSON when it can be. Lists, numbers and booleans therefore need JSON, quoted for the shell: --option 'output.formats=["markdown","text"]', --option output.include_images=true. Any other value is sent as a string. To send a string that would parse as JSON, such as 42 or true, give it as a JSON string: --option 'KEY="42"'.

These keys are taken out of the body and used like their flags:

KeyWhere it goes
wait_secondsThe wait_seconds query parameter. --wait-seconds wins.
idempotency_keyThe Idempotency-Key header. --idempotency-key wins.
classesSplit's and Classify's classes, when there's no --class or --classes.
schema, schema_id, schema_versionExtract's schema, when there's no -s or --schema-id.
instructionsExtract's instructions. --instructions wins.
targetsGround's targets, when there's no --target or --targets.
sourceRefused with exit 2: the source is always SOURCE, with --file-name for a URL.

Every other key goes into the request body as given, such as --option name="March invoices" for the job's display name, or project_id, a UUID you mint to group jobs. Each tool's options are on its page: Parse, Split, Classify, Extract and Ground.

Waiting and output

A tool creates the job, waits for it and prints its output. Stderr shows job <id> <status> when it's created and when it finishes. nace reads the job every second at first, then less often, up to every 10 seconds; reading a job is free.

FlagWhat it does
--asyncPrint only the job id and return at once. -o, --save and --json don't apply.
--wait-timeout SECONDSHow long to wait for the job (default 600). Past it, the command exits 1 and the job keeps running: read it later with nace job <id> --wait.
--watchPrint progress events to stderr first, as status · message · key=value ....
-o, --output {auto,md,json,result,id}What to print, from the table below. Default auto.
--save PATHWrite the output to PATH instead of stdout, and print saved <path> on stderr. - means stdout.
-oPrints
autoA parse's Markdown, or its text when you asked only for text (except a large document's full content; see Large documents and workbooks). Any other result as JSON.
mdA parse's Markdown, or its text. A job with neither exits 1.
jsonThe whole job: job_id, kind, status, result, error, credits and the rest, leaving out top-level fields that are null. --json is the same and wins over -o.
resultOnly the job's result, as JSON.
idOnly the job id. --save doesn't apply.

A job that hasn't finished, failed, or whose result has expired has no result, so auto and result print the whole job instead, with result_state saying why an expired one has none. When a job you wait for fails or is cancelled, stderr shows error: [<code>] job <id> <status>: <message>, such as error: [job_cancelled] job <id> cancelled: Deleted by the caller. for a job nace cancel stopped. [<code>] appears only when the job has an error code, and <message> is no error detail when it has no message. Stdout stays empty unless you asked for -o json, and the command exits 1. A --save file that can't be written prints the output to stdout and exits 1.

--watch reads the job's event stream until it ends, when the job does or after about five minutes. Only then does the --wait-timeout clock start. If the stream drops, stderr shows (event stream ended: ...) and the wait goes on.

Parse

nace parse https://example.com/report.pdf -p 1-3 --format markdown,blocks > report.md
nace parse ./locked.pdf --password -        # asks for the password, or reads it from a pipe
FlagWhat it does
--format FORMATSA comma list of markdown, text and blocks, sent as output.formats. Default: the API's markdown,blocks.
--password VALUEAn encrypted PDF's password: the text, @FILE, or - for stdin, without one trailing newline. At a terminal, - asks at a prompt that doesn't echo it. Not with a job: source. Debug logs mask it.

With -o auto, stdout gets the Markdown, or the text when you asked only for text, except for a large document's full content (below). Ask for blocks alone and you get the result JSON. See Parse.

Large documents and workbooks

For a very large document, the result's markdown is only a preview, and document.content_url links the full Markdown. See Very large results. With -o auto or md, nace parse and nace job fetch the full content instead, to stdout or --save, and say so on stderr. They wait up to --wait-timeout while it's being prepared. If the fetch fails, they print the preview, name the nace file <job> <content_url> command that fetches the rest on stderr, and exit 1. After --format text, the full content can be Markdown, since that can be the only form the service keeps; stderr says so.

For a workbook whose sheets each have a content_url, they print the preview and list one nace file <job> <content_url> per sheet on stderr. nace rows reads a sheet's rows.

Split

nace split https://example.com/packet.pdf \
  --class invoice:Invoice:"A supplier invoice" \
  --class receipt:Receipt:"A payment receipt"
FlagWhat it does
--class ID:LABEL[:DESCRIPTION]One class. Repeatable. It's split at the first two colons, so a description may contain colons but an id or a label can't.
--classes JSONClasses as a JSON array: inline, @FILE, a path, or -. Its entries come before any --class.

At least one --class or --classes is required, or classes in --options. Class fields beyond id, label and description go in --classes. See Split.

Classify

nace classify https://example.com/invoice.pdf \
  --class invoice:Invoice:"A supplier invoice" \
  --class contract:Contract:"A signed agreement between two parties" \
  --granularity page

Classify takes the same --class and --classes flags as Split, plus:

FlagWhat it does
--granularity {document,page}Label the whole file once, or each page (default document).

Classify reads the original file, so it doesn't take a job: source. See Classify.

Extract

nace extract https://example.com/invoice.pdf --schema invoice.schema.json -o result
nace extract job:<parse job id> --schema-id sch_... --schema-version 2
FlagWhat it does
-s, --schema SCHEMAThe fields to extract, as a JSON Schema: a file path, @FILE, inline JSON, or -.
--schema-id IDA saved schema (sch_...) instead, at its latest version.
--schema-version NWith --schema-id, use version N.
--instructions TEXTExtra guidance for filling the fields.

Pass exactly one of -s and --schema-id, on the command line or as schema or schema_id in --options. Neither, both, or --schema-version without --schema-id exits 2.

invoice.schema.json
{
  "type": "object",
  "properties": {
    "invoice_number": { "type": "string" },
    "total": { "type": "number" }
  },
  "required": ["invoice_number", "total"]
}

Point it at a finished parse to avoid parsing, and paying for, the document twice:

nace parse https://example.com/invoice.pdf -o id
# 6f1a2b3c-...
nace extract job:6f1a2b3c-... --schema invoice.schema.json

See Extract.

Ground

nace ground https://example.com/report.pdf \
  --target "total=1,200.50" \
  --target "Jane Smith"
FlagWhat it does
--target [ID=]TEXTOne quote to find. Repeatable. It's split at the first =, so total=1,200.50 has the id total. Without =, the id is t<N>, where N is its place among the --target flags: t2 above. A quote that contains = needs an id: --target "rule=a=b".
--targets JSONTargets as a JSON array: inline, @FILE, a path, or -, for fields such as hint and sheet. Its entries come before any --target.
--semanticDeprecated. Sets semantic: true on every target that doesn't set semantic itself. On PDF, Word and PowerPoint sources those targets fail with semantic_mode_deprecated. semantic belongs to each target; the request has no switch of its own.

At least one --target or --targets is required, or targets in --options. Ground reads the whole document, so it takes neither -p nor --mode. See Ground.

Uploads

Uploads are free and work at any balance. The bytes go straight to the document service, never through Drex. See Uploads.

Upload a file

nace upload ./invoice.pdf --path invoices/2026/invoice.pdf
# ws:0f8b7c2e-.../3a91c5...
FlagWhat it does
--path PATHThe file's path in your workspace. Default: its file name. Keep the file's extension: the document service types an upload from its first bytes, and from this extension when those don't settle it.
--on-conflict {reject,new_version}What happens when a different file already has that path. reject, the default, fails with 409 path_conflict. new_version adds a version of that file, which jobs on it that haven't started yet then read.
--jsonPrint the uploaded file's record (workspace_id, file_id, path and the rest) instead of its ws: source.

Stdout gets ws:<workspace_id>/<file_id>, ready to pass as SOURCE. Uploading the same bytes to the same path again returns the file already there. A file of 32 MiB or more goes up in parts. A conflict on such a file ends with error: Upload did not finish: ... (path_conflict: ...) instead of a 409, and it still exits 1. If its parts are still being assembled after about ten minutes, the command exits 1; upload the same file to the same path again later to get it.

Upload grants

nace upload-grant mints a one-use token that lets another client, such as a browser, upload one file into your workspace without your API key. It prints the grant as JSON, and a reminder on stderr to hand it only to that uploader.

FlagWhat it does
--path PATHPin the workspace path the upload must use.
--max-bytes NThe largest file the grant accepts.
--total-size-bytes NThe exact size the file must have.
--ttl-seconds NHow long the grant works, in seconds: at most 3600, the default.
{
  "workspace_id": "...",
  "upload_url": "https://.../v1/workspaces/.../files",
  "token": "...",
  "expires_at": "...",
  "max_bytes": null
}

The uploader posts the file to upload_url with the token in X-Upload-Token, as in One file and From a browser. The output is JSON with or without --json.

Upload sessions

nace upload-session opens a resumable upload in parts for a large file and prints it as JSON: session_id, session_token, chunk_size, total_parts, expires_at, upload_session_url and workspace_id. Another client then sends the parts and completes it, as in Large files: the CLI has no commands to send parts, complete, check or abort a session, and nace upload does all of that itself. Stderr reminds you that session_token writes into your workspace.

FlagWhat it does
--path PATHRequired. The file's path in your workspace.
--total-size-bytes NRequired. The file's exact size.
--ttl-seconds NHow long the session accepts parts, in seconds: at most 5400, the default.
--on-conflict {reject,new_version}As for nace upload, checked when the session completes.
--idempotency-key KEYThe same key returns the same session. Without it, every run opens a new one.

Jobs

Reading, listing and cancelling jobs is free and works at any balance. See Jobs and results.

List jobs

nace jobs --operation parse --status succeeded --limit 50
nace jobs --all --json > jobs.json

Stdout gets one line per job, newest first: its id, operation, status, credits and creation time.

FlagWhat it does
--operation {parse,split,classify,extract,ground}Only jobs of one operation.
--status {queued,running,succeeded,failed,cancelled}Only jobs in one status, as Drex last recorded it. A job whose status has changed since is left off the page, so a page can hold fewer than --limit jobs, or none, while more pages follow.
--limit NJobs per page, 1 to 50 (default 20). A larger number exits 2.
--cursor CURSORStart at this page: the next_cursor of the page before.
--allEvery job, across every page. Not with --cursor; --limit doesn't apply.
--jsonPrint {"items": [...], "next_cursor": ...}; next_cursor is null with --all.

When more pages follow, stderr says more jobs: --cursor <next> (or --all). With --status, a page can come back empty while later pages still match: stderr then says no jobs on this page; more may match, not no jobs.

Read a job

nace job <id>                    # the job as it is now
nace job <id> --wait -o result   # wait for it, then print its result
FlagWhat it does
--waitWait until the job finishes, as a tool does.
--wait-timeout SECONDSWith --wait, how long to wait (default 600).
--watchPrint progress events to stderr while waiting. Implies --wait.
-o, --output {auto,md,json,result,id}, --save PATHAs in Waiting and output. They apply with or without --wait.

Without --wait, nace job exits 0 whatever the job's status, and prints the whole job while it has no result. With --wait, a failed or cancelled job exits 1. For a large parse, it fetches the full content as in Large documents and workbooks.

Progress events

nace events <id> streams the job's progress events, one JSON object per line on stdout, until the job ends or about five minutes pass. Each event has sequence, at and status, plus progress (units_done, units_total, stage and so on) and message when the job has them. The first event is the job's state as it is now.

FlagWhat it does
--last-event-id IDResume after a dropped stream: pass the last event's sequence. The new stream starts from the job's current state and keeps numbering after ID.

The stream is a convenience for showing progress, and it counts against your per-minute limit: read the outcome with nace job.

Cancel a job

nace cancel <id> cancels a running job and drops any job, finished or not, from nace jobs. The job stays readable with nace job, and the work it did is still charged. Stderr says cancelled <id>; stdout stays empty.

Job files

A result links the job's own files: full Markdown, figure crops, ground crops, transcripts, converted PDFs, cell maps and spreadsheet rows. See Files a job produced. File downloads don't count against your per-minute limit; the request echo and the event stream do. Results and their files expire on the document service's retention schedule; after that, a command on a file fails with 404 or 410 result_expired and exits 1, while nace job still reads the job, without its result.

Download a file

nace file <id> <document.content_url>              # the full Markdown, saved as <id>.md
nace file <id> parse-images/<ref> --save figure-1.png
nace file <id> request --save -                    # to stdout
nace file <id> ground-crops/<ref> --link           # a five-minute signed link

PATH is a link from the job's result, or the part after the job id, such as parse-images/<ref>. A query or fragment in it is ignored. A path such as .., or a full link from another job's result, exits 2.

FlagWhat it does
--save PATHWhere to write the file; - for stdout. An existing file is replaced.
--linkPrint a stored file's signed link instead. It works for five minutes, without your API key. With --json, print {"url": ..., "expires_at": ...}.
--wait-timeout SECONDSHow long to wait for content the document service is still preparing (default 600). Past it, the command exits 1; the content is still being prepared, so run it again later.

Without --save, the file goes in the current directory: <id>.md for a document's full Markdown, else <id>-<kind>[-<digest>] plus the kind's extension, if it has one. <kind> is the path's first segment, and <digest> is the first 8 hex characters of the rest of the path's SHA-256, which keep files of one kind apart. A path with nothing after its kind gets no digest: request saves as <id>-request.json, and events as <id>-events. Stderr then says saved <path>.

KindExtension
document-content, spreadsheet-content.md
parse-images, ground-crops.png
converted-pdf.pdf
parse-transcript, ground-transcript, spreadsheet-cell-maps, spreadsheet-rows, request.json

Stored files (figure crops, ground crops, transcripts, converted PDFs and cell maps) come through their signed link, without your API key. Everything else streams from Drex: full Markdown, rows, the request and events. --link on one of those exits 2 and says to drop --link. Content whose preparation failed exits 1 with a 409. A file nace file can't write, at an unwritable --save path or in a current directory you can't write to, exits 2 with nothing on stdout.

Job request

nace job-request <id> prints the whole request the job ran under as JSON: request_type (the operation), source as the document service stored it, and the options, schema, classes or targets. A PDF password is never returned. It counts against your per-minute limit and expires with the job's result; after that, the command fails with 410 result_expired and exits 1. The output is JSON with or without --json.

Spreadsheet rows

nace rows <id> <rows_url> --all > sheet.tsv
nace rows <id> <rows_url> --start-row 100 --limit 50 --json

PATH is a sheet's rows_url from the parse result (only Parquet sources have one), or the part after the job id (spreadsheet-rows/<ref>). Stdout gets TSV: a line of column names, then one line per row. A cell's tabs, newlines, carriage returns and backslashes are written as \t, \n, \r and \\. Stderr says which rows you got, such as rows 0-49 of 1200; more: --start-row 50 (or --all).

FlagWhat it does
--start-row NThe first row, numbered from 0 (default 0).
--limit NRows per page, 1 to 100 (default 50).
--allEvery row from --start-row on, in pages of --limit (default 100).
--jsonPrint the page instead: path, start_row, limit, total_rows, columns (each with name and data_type) and rows (each keyed by column name). With --all, print {"columns": [...], "rows": [...], "total_rows": N}.

A negative --start-row or a --limit outside 1 to 100 exits 2.

Saved schemas

Save a JSON Schema once, then name it with nace extract --schema-id instead of sending it every time. Saved schemas are free and work at any balance. See Saved schemas.

List and read schemas

nace schemas                                 # yours and Nace's, by schema_id
nace schema sch_... > invoice.schema.json    # the JSON Schema itself
nace schema-versions sch_...                 # oldest first
nace schema-version sch_... 2
CommandWhat it prints
schemasOne line per schema at its latest version, sch_... vN name, sorted by schema_id the way the document service's database compares text: case-insensitively, with - and _ ignored at first, so not in byte order. It can also include schemas Nace provides, whose owner is platform.
schema SCHEMA_IDThe latest version's JSON Schema on stdout, ready for --schema, and its sch_... vN name line on stderr.
schema-versions SCHEMA_IDOne line per version, oldest first.
schema-version SCHEMA_ID VERSIONThat version's JSON Schema, like schema.

schemas and schema-versions page like nace jobs:

FlagWhat it does
--limit NItems per page, 1 to 200 (default 20). A larger number exits 2.
--cursor CURSORStart at this page: the next_cursor of the page before.
--allEvery item, across every page. Not with --cursor.
--jsonPrint {"items": [...], "next_cursor": ...}. Items from schemas leave out the JSON Schema; items from schema-versions include it as schema.

When more pages follow, stderr says more schemas: --cursor <next> (or --all), or more versions. An empty list says no schemas (or no versions) on stderr. With --json, schema and schema-version print the whole record: schema_id, name, description, version, owner, created_at, updated_at and schema.

Save a schema or a version

nace schema-create invoice --schema invoice.schema.json --description "Invoice header fields"
# sch_...  v1  invoice
nace schema-version-add sch_... --schema invoice-v2.schema.json
# sch_...  v2  invoice
CommandFlagWhat it does
schema-create NAME-s, --schema SCHEMARequired. The JSON Schema, as a JSON object: a file path, @FILE, inline JSON, or -.
--description TEXTWhat the schema is for.
schema-version-add SCHEMA_ID-s, --schema SCHEMARequired. The new version's JSON Schema.
--name NAMERename the schema; omit it to keep the current name.
--description TEXTOmit it to keep the current description.

Both print the saved version's sch_... vN name line on stdout, or the whole record with --json. A saved version can't be edited or deleted: add a version instead. Nace's own schemas can't take versions.

Neither command is retried automatically, because a retry could save a second schema or version. If one fails with a timeout or a 5xx, check nace schemas or nace schema-versions before you run it again.

Output and exit codes

Stdout holds the result; stderr holds status lines, hints and errors. So nace decide ... --json > answer.json and nace parse doc.pdf > doc.md capture only the result.

Exit codeWhen
0Success. nace job ID without --wait exits 0 whatever the job's status, so check status.
1A request failed: an API error, a connection error or a timeout. Also a job you waited for that failed, was cancelled or outlasted --wait-timeout; an upload or a download that failed after its requests went out; -o md on a job without Markdown; and a tool's or nace job's --save file that couldn't be written (the result goes to stdout instead).
2A usage or configuration error: an unknown flag, a bad value, a missing file, invalid JSON, no API key, or an argument the SDK refuses. Most are caught before any request. Two from nace file come after one: --link on a file that streams, and a file it can't write (an unwritable --save path, or without --save, the current directory), which leaves stdout empty. nace with no command prints its help and exits 2.

Handle errors

An API error prints one line with the status, error type, code, message and request_id. Below it come up to five fields that failed validation, from the error's issues or detail.errors, then (+N more):

error: 422 [invalid_request_error/invalid_request] The request does not match the schema for this method. See detail.errors for the fields that failed. (req_...)
  - output.formats.0: Input should be 'markdown', 'text' or 'blocks'

A 402 adds Top up or add a card at <your Drex host>/dashboard/billing, or Pay the unpaid invoice at <your Drex host>/dashboard/billing when its type is payment_required, and a 409 path_conflict adds how to upload anyway. The error types are in Errors.

On the document commands:

What happenedWhat you seeExit
A request the document service refused, such as an unsupported file type or an unknown fielderror: 422 [invalid_request_error/<code>] ..., then up to five fields from the error's detail.errors. See Errors.1
A different file at an upload path409 path_conflict, and a line naming --upload-path (--path on nace upload) and --on-conflict new_version. From 32 MiB, error: Upload did not finish: ... (path_conflict: ...).1
The same --idempotency-key with a different body409 idempotency_conflict.1
A create while the balance isn't positive and no card pays for usage beyond it402 [insufficient_credit], and Top up or add a card at <your Drex host>/dashboard/billing.1
A create while the account has an unpaid invoice402 [payment_required], and Pay the unpaid invoice at <your Drex host>/dashboard/billing.1
A job you waited for failed or was cancellederror: [<code>] job <id> <status>: <message>.1
The wait ran past --wait-timeouterror: job <id> still <status> after <N>s; read it later with .... The job keeps running.1
Content still being prepared after --wait-timeout, or content that couldn't be preparedAn error naming the job and path, or 409.1
Input nace refuses before any request: an http:// or missing SOURCE, --file-name without an https:// SOURCE, a job: SOURCE on classify, with -p on parse or extract, or with --mode medium or high or --password, both or neither of -s and --schema-id, a --limit out of rangeerror: ... on stderr, nothing on stdout.2

Retries and timeouts

nace uses the Python SDK's default retry policy. It retries a 408, a 429, a 5xx (unless the error says "retryable": false), a dropped connection or a timeout up to 2 times. It waits as Retry-After says, else backs off from 0.5 s up to 5 s. A retry starts only if its wait ends within 30 s of the first attempt; once started, it may run its full --timeout. So a request that fails after 30 s, such as one that hit a 120 s --timeout or a slow 529, is not retried. The CLI has no flag to change this; to retry longer, call the Python SDK with your own RetryPolicy (for example timeout=None).

nace schema-create and nace schema-version-add are never retried, because a retry could save a second schema or version. Document creates are retried safely: see Flags every tool shares.

Logging

Set NACE_LOG_LEVEL to send the SDK's log lines to stderr, each prefixed nace_sdk LEVEL:

LevelWhat you see
debugEvery API call's request and response, with headers and JSON body. The API key, upload tokens and a PDF password are masked; file bytes show only their size.
infoOne line per API call's response with its method, URL, status, time and request_id, plus a line per retry and per connection error.
warn, warning, errorOnly problems, such as an answer of a type the SDK doesn't know.
offNothing.

File downloads (nace file, --link, and the full content of a large parse) and event streams (nace events, --watch) aren't logged at either level, except a connection error at info.

NACE_LOG_LEVEL=debug nace models

Every command

CommandWhat it does
nace loginCheck and save an API key. See Sign in.
nace logoutDelete the saved key and config file.
nace versionPrint the CLI and SDK versions; same as nace --version.
nace decideAsk calibrated questions about a state. See Ask questions.
nace modelsList the models your key can call.
nace parseTurn a document into Markdown, text or layout blocks. See Parse.
nace splitCut a packet into the documents it contains. See Split.
nace classifyLabel a document, or each page. See Classify.
nace extractFill a JSON Schema, inline or saved. See Extract.
nace groundFind where each quote appears. See Ground.
nace uploadUpload a file into your workspace. See Upload a file.
nace upload-grantMint a one-use upload token for another client. See Upload grants.
nace upload-sessionOpen a resumable upload in parts. See Upload sessions.
nace jobsList your jobs. See List jobs.
nace jobRead one job, or wait for it. See Read a job.
nace job-requestPrint the request a job ran under. See Job request.
nace eventsStream a job's progress events. See Progress events.
nace cancelCancel a job, or drop it from the list. See Cancel a job.
nace fileSave a file a job produced, or print its signed link. See Download a file.
nace rowsPrint a parsed sheet's rows. See Spreadsheet rows.
nace schemasList saved extraction schemas. See List and read schemas.
nace schemaPrint a saved schema's JSON Schema. See List and read schemas.
nace schema-versionsList a saved schema's versions. See List and read schemas.
nace schema-versionPrint one version's JSON Schema. See List and read schemas.
nace schema-createSave a new extraction schema. See Save a schema or a version.
nace schema-version-addAdd a version to a saved schema. See Save a schema or a version.

On this page