Perception

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. For a folder, the path its files go under, the folder's name unless you pass one; --path / puts them at the top of the workspace.
--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.
-j, --jobs NFor a folder, how many files go up at once: 1 to 32 (default 8).
--jsonPrint the uploaded file's record (workspace_id, file_id, path and the rest) instead of its ws: source. For a folder, print one object instead, with uploaded, skipped, failed: the uploaded files' records (each with its path and file_id), and each skipped or failed file's path and error.

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.

When FILE is a folder, nace upload uploads every file under it, -j at a time. Each keeps its path inside the folder, with / between folders, under --path: ./fy25/q3/report.pdf goes to fy25/q3/report.pdf. Files and folders whose names start with ., such as .DS_Store, .git and .env, are left out, and a symlinked folder isn't followed. 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>.

A file the document service refuses with 422 unsupported_file_type is skipped, not failed: 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 failure prints error: <path>: <error>, and the other files go on; when the uploads end, stderr shows failed <n> file(s): run the same command again to retry them, which is safe: a file already uploaded comes back as it is. The command exits 0 when every file was uploaded or skipped, and 1 when any failed. A 401, 403 or 404, which every file would get, stops the run at once and 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_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.

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. Stderr says deleted <source> once every trace is confirmed gone, or still deleting <source> if it is still running; run the command again to check on it. Stdout stays empty unless you pass --json, which prints the deletion as JSON (object, workspace_id, file_id, status). Another account's workspace fails with 404 workspace_not_found, and a file that isn't there with 404 file_not_found.

On this page