Perception

Uploads

Uploads are free and work at any balance. 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. For a folder, the path its files go under: the folder's name by default, or / for the top of the workspace.
--on-conflict {reject,new_version}When a different file already has that path: reject (the default) fails with 409 path_conflict; new_version adds a version of that file.
--workspace IDThe workspace to upload into. Without it, the file goes to the server as a source for the tools.
-j, --jobs NFor a folder, how many files go up at once: 1 to 32 (default 8).
--ingestIngest the uploaded files afterwards, as nace ingest does. Needs --workspace. See Upload and ingest.
--asyncWith --ingest, print each ingestion's job id and return without waiting. Without --ingest, the command exits 2.
--wait-timeout SECONDSWith --ingest, how long to wait for each ingestion (default 3600). Past it, the command exits 1, and the ingestion goes on: read it later with nace job <id> --wait.
--jsonPrint the uploaded file's record (workspace_id, file_id, path and the rest) instead of its ws: source. For a folder or with --ingest, see JSON output.
  • Keep the file's extension in --path. The document service types an upload from its first bytes, and from the extension when those don't settle it.
  • Only --workspace picks the workspace here. $NACE_WORKSPACE_ID and the workspace nace workspace-use saved don't apply to uploads.
  • With --on-conflict new_version, jobs on that file that haven't started yet read the new version.

What it prints

Stdout gets ws:<workspace_id>/<file_id>, ready to pass as SOURCE.

  • The same file again: uploading the same bytes to the same path returns the file already there.
  • Files of 32 MiB or more go up in parts, which changes two things:
    • A path conflict ends with error: Upload did not finish: ... (path_conflict: ...) instead of a 409. It still exits 1.
    • If the 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.

JSON output

For one file, --json prints the uploaded file's record. For a folder, or with --ingest, it prints one object with these fields:

FieldWhat it holds
uploadedThe uploaded files' records, each with its path and file_id.
skippedEach skipped file's path and error.
failedEach failed file's path and error.
ingestionsWith --ingest, each ingestion job.

Upload a folder

nace upload ./fy25 --workspace <workspace_id> --ingest
# uploaded 2
#   fy25/a.pdf
#   fy25/q3/report.pdf
# ingested 2 | skipped 0 | failed 0 | 14 units
#   ingested  fy25/a.pdf
#   ingested  fy25/q3/report.pdf

When FILE is a folder, nace upload uploads every file under it, -j at a time.

  • Paths: each file keeps its path inside the folder, with / between folders, under --path: ./fy25/q3/report.pdf goes to fy25/q3/report.pdf.
  • Left out: files and folders whose names start with ., such as .DS_Store, .git and .env. A symlinked folder isn't followed.
  • Output: stdout gets uploaded <n>, then each uploaded file's workspace path, indented two spaces, in path order. --json gives each one's file_id too; pass it as ws:<workspace_id>/<file_id>.

When a file can't be uploaded:

What happenedWhat nace upload does
The document service doesn't take its type (422 unsupported_file_type)Skips it; that isn't a failure. When the uploads end, stderr lists the skipped files under warning: skipped <n> file(s) of a type the document service doesn't take:.
Any other error on that filePrints error: <path>: <error> and goes on with the other files. When the uploads end, stderr shows failed <n> file(s): run the same command again to retry them. Running it again is safe: a file already uploaded comes back as it is.
401, 403 or 404, which every file would getStops the run at once and exits 1.

The command exits 0 when every file was uploaded or skipped, and 1 when any failed.

Upload and ingest

--ingest works for one file or a folder. It starts an ingestion of the uploaded files, waits for it, and prints its summary and file lines as nace ingest does.

  • Batches: one ingestion per 1000 files, each batch its own block.
  • A file that fails to ingest: as with nace ingest, its batch adds error: <n> of <m> files failed to ingest. Every batch still runs, and then the command exits 1.
  • A workspace is required: a file uploaded without a workspace can't be ingested, so without --workspace the command exits 2 before any upload.
  • A failed upload: nothing is ingested.
  • Nothing to ingest: when every file of a folder was skipped, there is nothing to ingest: stderr says error: no uploaded file ids to ingest, and the command exits 1.

Upload grants

nace upload-grant mints a one-use token that lets another client, such as a browser, upload one file 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 IDThe workspace the upload goes into; the grant works only for it. Without it, the upload goes to the server, as with nace upload.
{
  "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. 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}What happens when a different file already has that path, checked when the session completes. reject, the default, fails the completion job with path_conflict. new_version adds a version of that file.
--idempotency-key KEYThe same key returns the same session. Without it, every run opens a new one.
--workspace IDThe workspace the file goes into. Without it, the upload goes to the server, as with nace upload.

Delete a file

nace upload-delete ws:<workspace_id>/<file_id> deletes a file you uploaded: every version of it and everything derived from it. It can't be undone. Pass the ws: source nace upload printed.

nace upload-delete ws:0f8b7c2e-.../3a91c5...
# deleted ws:0f8b7c2e-.../3a91c5...

It waits while the deletion runs, then says on stderr:

  • deleted <source> once every trace is confirmed gone.
  • still deleting <source> if it is still running. Run the command again to check on it.

Stdout stays empty. With --json, it prints the deletion as JSON: object, workspace_id, file_id and status.

ErrorWhen
404 workspace_not_foundThe workspace belongs to another account.
404 file_not_foundThe file isn't there.

On this page