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, read_file reads them, and run_sql 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 raises NaceRateLimitError (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 unless you pass retry it retries for up to 15 minutes (RetryPolicy(max_retries=10, backoff_max=15.0, timeout=900.0)) rather than with the client's policy. timeout defaults to the client's plus the 270 seconds the route waits for the service.

Locate

client.tools.locate(workspace_id, *, need, **options)

ParameterTypeRequiredDescription
workspace_idstrYesThe workspace.
needstrYesWhat is still needed, in a few words, up to 400 characters: a year, an entity, a line item, a definition.
questionstrNoThe whole question the need is part of, up to 4,096 characters. Default: the need.
kintNoHow many pieces to name, 1 to 24. Default: 12.
everyboolNoThe 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.
path_prefixstrNoOnly files under this folder. Default: the whole workspace.
pathslist[str]NoOnly these files, by path, as well as any under path_prefix.
excludelist[LocatePiece]NoPieces earlier locates offered, each {"path", "number", "component"}, left out of this answer. Up to 1,000.
already_readlist[LocateRead]NoReads 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.
pointerboolNoName every page in text by its opening lines only; a task's later locates pass it. Default: False, the best pages in full.
idempotency_keystrNoUp to 200 characters. Default: one minted per call.

It returns a 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 raises NaceUnprocessableEntityError. See Locate information.

locate.py
question = "What were total liabilities at year end?"
found = client.tools.locate(workspace_id, need="total liabilities FY2025", question=question)
print(found.text)
offered = [{"path": p.path, "number": p.number, "component": p.component} for p in found.pages]
again = client.tools.locate(workspace_id, need="total liabilities FY2025", question=question, exclude=offered, pointer=True)

Read a file

client.tools.read_file(workspace_id, *, path, **options)

ParameterTypeRequiredDescription
workspace_idstrYesThe workspace.
pathstrYesThe file's path in the workspace, as it was uploaded.
pageslist[int]NoPages to read, 1-based. Default: every page. Spreadsheets have no pages: name a tab with component.
componentstrNoOne tab or section to read. Default: the whole file.
max_charsintNoThe most characters to return, 1 to 1,000,000; past it, truncated is True. Default: 200000.
line_startintNoThe first line to return, 1-based, in the text this read returns without a span. Default: line 1.
line_endintNoThe last line to return, inclusive. Default: the last line.
idempotency_keystrNoUp to 200 characters. Default: one minted per call.

It returns a ReadFileResponse: the text in content, the pages and components it holds, and sql_tables, the run_sql names of the tables holding the rows of its long tables. A file that isn't ingested yet raises NaceConflictError (file_not_ingested). See Read a file.

read_file.py
page = client.tools.read_file(workspace_id, path="reports/fy2025.pdf", pages=[4])
print(page.content)

Run a SQL query

client.tools.run_sql(workspace_id, *, sql, **options)

ParameterTypeRequiredDescription
workspace_idstrYesThe workspace.
sqlstrYesOne read-only SELECT (DuckDB dialect), up to 4,096 characters, over the workspace's tables and earlier res_… results.
idempotency_keystrNoUp to 200 characters. Default: one minted per call.

It returns a 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, read-file's sql_tables, and the refusal of a query that names an unknown table, which lists them all. See Run a SQL query.

run_sql.py
first = client.tools.run_sql(workspace_id, sql="SELECT account, amount FROM tb_fy2025 WHERE amount > 1000")
total = client.tools.run_sql(workspace_id, sql=f"SELECT sum(amount) AS total FROM {first.result.handle}")
print(total.result.rows)

On this page