Workspaces

Files and ingestion

List, read, replace and delete the files in a workspace, and ingest them so search can find them.

Every file you upload lives in a workspace, at a path you choose. The routes on this page all sit under /v1/workspaces/{workspace_id}. Listing, reading, replacing and deleting files is free and works at any balance.

  • Add files with Uploads, passing the workspace's workspace_id. An upload without one goes to the server instead, and isn't part of a workspace. In the dashboard, see Workspaces.
  • Use a file in a tool job as a workspace_file source, straight away, when your account owns the workspace. A file in a workspace you were invited into from another account is refused with 422 invalid_source.
  • Search only finds a file once it's ingested. See Search.

In the CLI, every command here takes --workspace ID:

  • Without it, the CLI uses $NACE_WORKSPACE_ID, then the workspace nace workspace-use saved. With none, the command exits with a usage error that says how to pick one. See Workspaces.
  • nace workspace-file and nace workspace-file-delete also take the file as ws:<workspace_id>/<file_id>, which names its workspace too.

List files

curl "https://console.nace.ai/v1/workspaces/$WORKSPACE_ID/files?path_prefix=reports/&ingestion_status=stale" \
  -H "Authorization: Bearer $NACE_API_KEY"
QueryCLI flagWhat it does
path_prefix--path-prefixOnly files whose path starts with this.
ingestion_status--statusOnly files in these statuses. Repeat it for more than one.
categories--categoryOnly files in these categories, set by ingestion. Repeatable.
file_types--file-typeOnly these extensions, such as pdf or xlsx. Repeatable.
modified_after, modified_before--modified-after, --modified-beforeOnly files whose updated_at falls after or before this time.
limit--limit1 to 200, default 50.
cursor--cursorThe previous page's next_cursor. Keep going until it's null.

The CLI also takes --all, for every page, and --json, to print the page as JSON.

Each page has:

FieldWhat it holds
itemsThe workspace files on this page.
next_cursorPass it as cursor for the next page. null on the last one.
total_countEvery file that matches, across all pages.
coverage.restricted_candidatesHow many files the workspace's access labels kept from you.
coverage.labels_requiredThe labels that would show you those files.
coverage.request_access_hintA hint, as text, on how to get access to them.

When nothing was kept from you, as in a workspace without labels, the coverage fields are 0, [] and null.

Read a file

GET .../files/{file_id} returns the file plus download_url and preview_url: links to its bytes that work without your API key until download_url_expires_at, 15 minutes on. download_url saves the file; preview_url opens it in a browser. A file that doesn't exist, or was deleted, is 404 file_not_found.

curl https://console.nace.ai/v1/workspaces/$WORKSPACE_ID/files/$FILE_ID \
  -H "Authorization: Bearer $NACE_API_KEY"

Metadata

GET .../files/{file_id}/metadata (files.metadata, --metadata) returns what ingestion learned about the file. Most of it is empty until the file is ingested.

FieldWhat it does
fileThe file.
laneHow it was read: document, table, media or json.
page_count, sheet_names, duration_msPages of a document, sheets of a workbook, length of audio or video. null when they don't apply.
summary, keywordsA summary and keywords for the whole file.
documents, sheets, mediaPer-document, per-sheet or per-recording detail: category, summary, keywords, page range or size.
outputsWhat ingestion produced from the file. See Outputs.
representationsWhether each is current: search_index and organized_files. Read this to know the file is searchable now.
last_errorWhy the last ingestion failed, or null.

Versions

GET .../files/{file_id}/versions (files.versions, --versions) pages through the file's versions, newest first, with limit and cursor as above. Only the current version's bytes are kept. Each version has:

FieldWhat it holds
version, is_currentIts number, and whether it's the current one.
size_bytesIts size.
observed_hash, ingested_hashThe hash of its bytes, and of the bytes last ingested.
created_at, ingested_atWhen it was uploaded, and when it was ingested.
replaced_atWhen a newer version replaced it. null on the current one.

Outputs

Ingestion turns a file into outputs, listed in its metadata's outputs. Each has output_id, kind, path, generation (the file version it came from) and created_at. Its kind is one of:

kindWhat it is
derived_fileA file stored in the workspace, such as a document's Markdown, or a recording's transcript or frames.
opensearch_docA search-index entry, with nothing to download.

Read one output: GET .../files/{file_id}/outputs/{output_id} (files.output) returns a derived_file with download_url, preview_url and download_url_expires_at. Any other kind is 422 invalid_request.

List the files in a folder output, such as a recording's frames: GET .../files/{file_id}/outputs/{output_id}/members (files.output_members, outputMembers in TypeScript). It pages with offset and limit (1 to 200, default 50). A single-file output has no members. It returns:

FieldWhat it holds
membersThe files, each with path, file_name, size_bytes, download_url and preview_url.
total_countHow many files the output holds.
download_url_expires_atWhen the links stop working.

Replace a file

PUT .../files/{file_id} gives the file new bytes, at the same path.

  • The body is multipart, like an upload: the new bytes in file, and optionally a metadata part with labels, which replaces them ({} clears them). A body that isn't multipart/form-data is 415 unsupported_media_type.
  • The answer is 200 with a file_replace job that has already succeeded, with result.file and result.previous_version.
  • Up to about 4.5 MB. For a larger file, upload it to the same path instead, as below.
  • Not retried: Python's files.replace sends it once unless you pass retry, since a retry after the first one landed would add a second version.
curl -X PUT "https://console.nace.ai/v1/workspaces/$WORKSPACE_ID/files/$FILE_ID" \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -F file=@report-v2.pdf

After a replace:

  • A file that was ingested becomes stale until you ingest it again; one that wasn't stays discovered.
  • Jobs that read the file from then on get the new version.

Uploading to the same path with on_conflict: "new_version" does the same; see Same path again. Use that for a file over about 4.5 MB, and in the CLI, which has no replace command.

Delete a file

DELETE .../files/{file_id} removes the file, its outputs and its search-index entries, and cancels ingestions still running on it. Deleting can't be undone.

  • The answer is 202 with a file_delete job, or 200 when it finishes within wait_seconds.
  • Waiting and retries: like ingestion, it takes ?wait_seconds (capped at 60) and an Idempotency-Key. It has no body.
curl -X DELETE "https://console.nace.ai/v1/workspaces/$WORKSPACE_ID/files/$FILE_ID" \
  -H "Authorization: Bearer $NACE_API_KEY"

The job's result has file_id, the counts of what was deleted (outputs_deleted, index_documents_deleted), jobs_cancelled and verified, which is true once every trace is confirmed gone.

Ingest files

Pricing to be announced.

Ingestion reads your files and builds what search needs: the search index, metadata and outputs. It's the only call that makes files searchable. Upload a batch first, then ingest it in one run.

curl https://console.nace.ai/v1/workspaces/$WORKSPACE_ID/ingestions \
  -H "Authorization: Bearer $NACE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ingest-reports-2026-10" \
  -d '{ "selection": { "type": "path_prefix", "path_prefix": "reports/", "stale_only": true } }'
API bodySDKCLIWhat it does
selection with file_idsfile_ids--file-id, repeatableIngest these files: 1 to 1000 ids.
selection with path_prefixpath_prefix--path-prefixIngest the files under this folder. "" is the whole workspace.
selection.stale_onlystale_only--stale-onlyWith a path prefix, only files that changed since they were last ingested.
reingest_unchangedreingest_unchanged--reingest-unchangedRebuild files that are already ingested and unchanged. Off by default, so those are skipped.
  • In the API, selection is required: {"type": "file_ids", "file_ids": [...]} or {"type": "path_prefix", "path_prefix": "reports/"}.
  • In the SDKs and the CLI, pass file ids or a path prefix, not both. With neither, the whole workspace is ingested.

What comes back:

  • The answer is 202 with an ingestion job, or 200 when it finishes within wait_seconds. Read it as in Jobs and results.
  • Waiting and retries: it takes ?wait_seconds=N (0 to 60; more waits 60) and an Idempotency-Key. Retry identical inputs with the same key to get the original job; use a new key for a changed selection. Changed inputs are not a supported retry: they may replay an earlier job or be rejected.
  • One bad file doesn't fail the run: the job succeeds, and its result reports each file.
Result fieldWhat it holds
outcomesOne per file: file_id, path, status (ingested, skipped_unchanged, skipped_empty or failed), lane, units and, when it failed, error.
files_ingested, files_skipped, files_empty, files_failedHow many files had each outcome. skipped_empty is a valid file with no rows, such as an empty Parquet file.
units_totalThe units ingested, across every file.

Search reads only what ingestion built, so it misses any file that isn't ingested.

Ingestion status

Each file's ingestion_status says where it stands, and the workspace's stats count them in files_by_ingestion_status, so you can tell what needs ingesting without listing every file.

StatusWhat it means
discoveredUploaded, never ingested.
queued, ingestingAn ingestion is on its way.
ingestedSearchable.
staleChanged since it was ingested. Ingest again, with stale_only.
expiredThe derived content is no longer available. Ingest again to make it searchable.
failedThe last ingestion failed; see last_error in its metadata.
not_requiredValid, but holds nothing to index, such as an empty Parquet file.

The workspace file object

FieldWhat it holds
file_id, workspace_idThe file's id and its workspace. Together they make a workspace_file source, or ws:<workspace_id>/<file_id> in the CLI.
path, file_name, file_typeWhere it is in the workspace, its name and its extension.
size_bytesThe current version's size.
versionStarts at 1, and goes up with each replacement.
observed_hash, ingested_hashThe hash of the current bytes, and of the bytes last ingested (null if never).
ingestion_status, ingested_atWhere it stands (see Ingestion status) and when it was last ingested.
categoryThe category ingestion gave it, or null.
access_label, access_label_sourceIts access label, or null when the workspace has none.
expires_atLegacy expiry timestamp, or null. Standard files and ingested content stay until explicit deletion.
labelsYour own key-value metadata, stored as given.
created_at, updated_atWhen it was uploaded and last changed.

On this page