---
title: "read_file"
method: POST
path: "/v1/workspaces/{workspace_id}/tools/read-file"
tags: ["tools"]
---

# read_file

`POST /v1/workspaces/{workspace_id}/tools/read-file`

## Request

- Base URL: `https://ndi-api.nace.ai`
- URL: `https://ndi-api.nace.ai/v1/workspaces/{workspace_id}/tools/read-file`
- Auth: API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/nace/apis/ndi-platform-api/docs/v1/workspaces/:workspace_id/tools/read-file/post.ts> (a function with typed inputs; its response is checked against the schema)

Read the text of one uploaded file.

**Cost class: fast.**

``path`` names an **uploaded source file** — the only namespace this tool
serves.  A workspace-internal path is refused with ``invalid_request``
naming the uploaded file to use instead.

A file with several components (a spreadsheet's tabs, a pdf's sections)
answers with their text concatenated in the file's own order, each part
under a one-line header naming the component; ``components_returned`` and
``components_omitted`` report exactly what made it into the budget.  Scope
the read with ``component=<name>`` (the names ``file_metadata`` lists)
when only one tab or section matters, and with ``pages`` (1-based) for
paged documents — spreadsheets have no pages, so scope those by component
instead.  Images are refused — ask about them with ``qa_file``.

## Path parameters

- `workspace_id` string, uuid, required: Workspace identifier.

## Headers

- `Idempotency-Key` string, nullable: Use the same key to recover an earlier request. A replay returned as a Job carries Idempotent-Replayed: true and replayed: true without a new charge; Job credits keeps the original cost. Re-serving a settled inline tool answer reports zero credits for that call and also carries Idempotent-Replayed: true; a fresh re-execution after a transient failure carries no header. The key is kept with the job for replay and never cleared, so it must not contain personal data.

## Request body

- ReadFileRequest: Request body for ``read-file``. ``path`` names an **uploaded source file** — the only namespace this tool serves. A workspace-internal derivative path is refused with ``invalid_request`` naming the uploaded file that owns it. ``component`` scopes the read to one public component by name — a spreadsheet tab or a pdf section, as listed by ``file-metadata``. ``pages`` is 1-based and selects on the file's absolute page numbers; out-of-range pages are ignored (absent from ``pages_returned``). Selected pages come back de-duplicated in document order, whatever order they were asked in. Spreadsheets have no pages — scope those with ``component`` instead.
  - `component` string, nullable: One tab or section to read, named as file-metadata lists it; omitted: the whole file.
  - `line_end` integer, nullable: Last line to return, inclusive and 1-based like line_start. Omitted: the last line.
  - `line_start` integer, nullable: First line to return, 1-based, counted in the text this read would return without a line range (after component and page selection). Omitted: line 1.
  - `max_chars` integer: Character budget for the returned text; past it `truncated` is true.
  - `pages` integer[], nullable: 1-based page numbers to read; omitted: every page. Spreadsheets have no pages.
  - `path` string, required: The uploaded file's full path relative to the workspace root, exactly as it was uploaded and as listings and search results print it — e.g. `evidence/doc-7f83/contract.pdf`. Not a bare file name (a file's `file_name`), a folder, a file id, or a path that starts with the workspace id.

## Response `200`

Successful Response

- ReadFileResponse: Response body for ``read-file``. A file with several components (a spreadsheet's tabs, a pdf's sections) answers with their text concatenated in the file's own order, each part prefixed by a one-line header naming the component. ``components_returned`` lists what contributed to ``content``; ``components_omitted`` lists components that were cut for size — the character budget ran out, or a part was too large to serve — or that have no text rendering at all (a query-only tab; ask it with ``qa-file``) — so an absent component is always visible, never a silently missing tab. Unnamed components (a recording, a raw upload) are listed by kind. A table a PDF page holds comes as rows — one ``| a | b |`` line each under a header that joins the stacked column headings — the way grep and hybrid-search print it.
  - `components_omitted` string[], required
  - `components_returned` string[], required
  - `content` string, required
  - `credits` integer, nullable: Credits billed for this call; zero for a free answer or replay, null when a billing receipt is unavailable.
  - `file_id` string, uuid, required
  - `job_id` string, uuid, nullable: The external operation job; absent for commands inside a search job.
  - `line_count` integer, nullable: How many lines the whole selected text has, when the request named a line range; None otherwise.
  - `line_end` integer, nullable: Last line returned (inclusive) when the request named a line range; None otherwise or when line_start is past the end of the text.
  - `line_start` integer, nullable: First line returned (1-based) when the request named a line range; None otherwise.
  - `pages_returned` integer[], required
  - `path` string, required
  - `sql_tables` string[]: The run-sql names of the tables holding the rows of the long tables printed in the returned PDF sections — count, total or rank those rows there rather than from the text. Empty for a spreadsheet or CSV tab: its table name is file-metadata's `table_name`.
  - `table` GrepTable: The table a matching line is a row of, so the row reads without another call.
    - `first_line` integer, required: Line of the table's first row (its header), in read-file's line numbers.
    - `header` string, required: The column names: the table's first row, or for an HTML table every header row joined per column with ' > ' (empty when it has none), clipped when very wide.
    - `last_line` integer, required: Line of the table's last row.
    - `rows` integer, nullable: For a table stored as one HTML line (PDF ingestion): how many data rows it has.
    - `title` string, nullable, required: The text printed just above the table (its caption, units), clipped; None when there is none.
  - `truncated` boolean, required
  - `waiver` 'repeat_of_job' | 'no_job', nullable: Why this call is free: repeat_of_job reuses an earlier answer; no_job answers without creating a job.

## Other responses

- `504`: Gateway Timeout
- `default`: Typed error envelope used by every 4xx and 5xx. ``error.code`` distinguishes the failure; HTTP status is a consequence of the code.

## Changes

- **2026-11-07** `d002a3589a67`: 2 info
  - added the optional property `credits` to the response with the `200` status
  - added the optional property `waiver` to the response with the `200` status

[Operation history](https://skmtc.dev/nace/apis/ndi-platform-api/changes/v1/workspaces/:workspace_id/tools/read-file/post.md)

---

[API](https://skmtc.dev/nace/apis/ndi-platform-api.md) - [All operations](https://skmtc.dev/nace/apis/ndi-platform-api/llms.txt) - [OpenAPI document](https://skmtc.dev/nace/apis/ndi-platform-api/revisions/d002a3589a67?raw)
