Workspaces
Navigate tools
client.tools gives your own agent the tools the document service's search agents work with, over the ingested files of a workspace: locate names the pages and tables that hold a piece of information, readFile reads them, and runSql queries the tables. Each call answers inline; nothing is a job to wait for. A file that isn't ingested isn't located or read. See Navigate tools for the loop an agent runs with them.
The calls are free. Locates are limited to 150 per account every 10 minutes, and file reads and SQL queries together to 600; past that, a call rejects with RateLimitError (rate_limited) once its retries are spent.
A call that times out keeps running on the document service, and the same Idempotency-Key gets its answer instead of running it again. So each method mints a key and retries for up to 15 minutes, as if you had passed retry: { maxRetries: 10, backoffMaxMs: 15_000, timeoutMs: 900_000 }; the fields of your own options.retry win over those. options.timeout defaults to the client's plus the 270 seconds the route waits for the service.
Locate
client.tools.locate(workspaceId, params, options?) returns APIPromise<LocateResponse>. text is the answer as the service's own agent reads it: each piece with the read-file call that opens it and its text (the best pages in full, the rest by their opening lines; every page by its opening lines with pointer: true), then the tables to query. pages, tables and population hold the same as fields; with every: true, a population_unrated above 0 means the members aren't all listed: narrow the need to a folder, kind or period. Locate keeps no memory between calls: send back what earlier calls offered and what you read to be shown something new. The first call after the workspace's files change reads them first and can take a minute or more. A workspace whose files are kept in your own storage rejects with UnprocessableEntityError. See Locate information.
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | string | Yes | The workspace. |
params.need | string | Yes | What is still needed, in a few words, up to 400 characters: a year, an entity, a line item, a definition. |
params.question | string | No | The whole question the need is part of, up to 4,096 characters. Default: the need. |
params.k | number | No | How many pieces to name, 1 to 24. Default: 12. |
params.every | boolean | No | The need names a kind of file and every one is wanted (each invoice, every statement): the model rates each file of that kind and lists the members in population. Default: false. |
params.path_prefix | string | No | Only files under this folder. Default: the whole workspace. |
params.paths | string[] | No | Only these files, by path, as well as any under path_prefix. |
params.exclude | LocatePiece[] | No | Pieces earlier locates offered, each { path, number, component }, left out of this answer. Up to 1,000. |
params.already_read | LocateRead[] | No | Reads already done, each { path, pages } or { path, line_start, line_end } with an optional component: their pieces are left out, and the model is told they didn't settle the need. Up to 1,000. |
params.pointer | boolean | No | Name every page in text by its opening lines only; a task's later locates pass it. Default: false, the best pages in full. |
params.idempotency_key | string | No | Up to 200 characters. Minted per call when omitted. |
params is a LocateParams.
const question = "What were total liabilities at year end?";
const found = await client.tools.locate(workspaceId, { need: "total liabilities FY2025", question });
console.log(found.text);
const exclude = found.pages.map(({ path, number, component }) => ({ path, number, component }));
const again = await client.tools.locate(workspaceId, { need: "total liabilities FY2025", question, exclude, pointer: true });Read a file
client.tools.readFile(workspaceId, params, options?) returns APIPromise<ReadFileResponse>. Its content is the text read; sql_tables names the SQL tables that hold the rows of the long tables in it. A file that isn't ingested yet rejects with ConflictError, code file_not_ingested. See Read a file.
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | string | Yes | The workspace. |
params.path | string | Yes | The file's path in the workspace, as it was uploaded. |
params.pages | number[] | No | Pages to read, 1-based. Default: every page. Spreadsheets have no pages: name a tab with component. |
params.component | string | No | One tab or section to read. Default: the whole file. |
params.max_chars | number | No | The most characters to return, 1 to 1,000,000; past it, truncated is true. Default: 200000. |
params.line_start | number | No | The first line to return, 1-based, in the text this read returns without a span. Default: line 1. |
params.line_end | number | No | The last line to return, inclusive. Default: the last line. |
params.idempotency_key | string | No | Up to 200 characters. Minted per call when omitted. |
params is a ReadFileParams.
const page = await client.tools.readFile(workspaceId, { path: "reports/fy2025.pdf", pages: [4] });
console.log(page.content);Run a SQL query
client.tools.runSql(workspaceId, params, options?) returns APIPromise<RunSqlResponse>. result holds the first rows and result.handle, a res_… name your next query in this workspace may read as a table: join it, total it, or page it with LIMIT and OFFSET. result_citation.result_file is the whole result as a Parquet file: its download_url works without the API key until download_url_expires_at. It is not one of the workspace's files. Table names come from locate's tables, readFile's sql_tables, and the refusal of a query that names an unknown table, which lists them all. See Run a SQL query.
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | string | Yes | The workspace. |
params.sql | string | Yes | One read-only SELECT (DuckDB dialect), up to 4,096 characters, over the workspace's tables and earlier res_… results. |
params.idempotency_key | string | No | Up to 200 characters. Minted per call when omitted. |
params is a RunSqlParams.
const first = await client.tools.runSql(workspaceId, {
sql: "SELECT account, amount FROM tb_fy2025 WHERE amount > 1000",
});
const total = await client.tools.runSql(workspaceId, {
sql: `SELECT sum(amount) AS total FROM ${first.result.handle}`,
});
console.log(total.result.rows);