---
title: "run_sql"
method: POST
path: "/v1/workspaces/{workspace_id}/tools/run-sql"
tags: ["tools"]
---

# run_sql

`POST /v1/workspaces/{workspace_id}/tools/run-sql`

## Request

- Base URL: `https://ndi-api.nace.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://ndi-api.nace.ai/v1/workspaces/{workspace_id}/tools/run-sql`
- Auth: API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/nace/apis/ndi-service/docs/v1/workspaces/:workspace_id/tools/run-sql/post.ts> (a function with typed inputs; its response is checked against the schema)

Run one caller-authored read-only SELECT over the workspace's tables.

**Cost class: fast.** No planner and no answering agent — the statement
runs directly in the guarded SQL engine, against every queryable tab in
the workspace registered as a view under the ``table_name`` that
``file-metadata`` and the folder-metadata table census publish. Joins
across files are ordinary SQL joins; earlier run-sql results are
registered too, so a returned ``res_…`` handle can be joined, aggregated,
or paged with LIMIT/OFFSET in the next statement.

The full result is retained as parquet whatever the preview shows —
``result_citation.result_file.download_url`` fetches it whole. A
statement that is not a read, or reads an unregistered table, is refused
with ``invalid_request`` naming the rule broken and the registered
tables. For a natural-language question — or when the answer should be
composed and cited for you — use ``query-tables`` instead.

## Path parameters

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

## Headers

- `Idempotency-Key` string, nullable: Replaying a request with the same key returns the original job with 200, never a second job. The key is kept with the job for replay and never cleared, so it must not contain personal data.

## Request body

- RunSqlRequest: One read-only SELECT over the workspace's queryable tables. Table names come from metadata — ``file-metadata`` publishes each queryable tab's ``table_name`` and ``folder-metadata``'s table census lists them all — and from earlier run-sql results (every response's ``res_…`` handle is a registered table on the next call, so results can be joined, aggregated, or paged with LIMIT/OFFSET). Joins across files are ordinary SQL joins. A statement that is not a read, or that reads an unregistered table, is refused with ``invalid_request`` naming the rule and the registered tables.
  - `sql` string, required: One read-only SELECT (DuckDB dialect) over the registered tables and earlier `res_…` handles.

## Response `200`

Successful Response

- RunSqlResponse: One executed statement: shape, bounded preview, and the retained result. The stored result is complete whatever the preview shows; ``result_citation.result_file.download_url`` fetches the whole parquet. ``duplicate`` marks a statement answered from an earlier identical run's stored result — free, and safe to build on.
  - `sql` string, required: The normalized statement that ran — the result's provenance.
  - `duplicate` boolean
  - `result` QueryTablePreview, required: One retained result table with a bounded inline preview.
    - `path` string, nullable
    - `handle` string, required
    - `row_count` integer, required
    - `columns` QueryTableColumn[]
      - `name` string, required
      - `data_type` string, required
    - `rows` object[]
    - `preview_truncated` boolean
  - `source_paths` string[]: The uploaded files the statement read — carried on the response itself so source attribution survives when no result file is materialized (job-attributed agent statements).
  - `result_citation` QueryTableCitation: Provenance for one generated TablesQA result file.
    - `result_file` QueryTableResultFile, required: A workspace-visible Parquet file retained from a TablesQA query.
      - `file_id` string, uuid, required
      - `handle` string, required
      - `path` string, required
      - `file_name` string, required
      - `format` 'parquet', required
      - `row_count` integer, required
      - `download_url` string, required
      - `preview_url` string, required
      - `download_url_expires_at` string, date-time, required
    - `source_paths` string[], required
    - `sql` string, required
    - `grounding` TableQueryGrounding: One SQL query executed against the caller's tables while answering. The grounding behind a computed evidence: when the agent answers a count/sum/join question it runs a SQL agent over the uploaded tables, and this is the receipt — the query text and a bounded preview of what it returned. Attached to the evidence it grounds rather than published globally, so a computed figure and its computation travel together. ``source_paths`` name uploaded files, resolved server-side through the caller's access gate exactly like ``source_path`` on the evidence itself.
      - `sql` string, required
      - `supports_answer` boolean: False for exploratory SQL the tables engine ran but did not mark as part of the answer's calculation trail.
      - `result_id` string, nullable: Stable identity of the stored query result behind this receipt. Lets the grounding-rows endpoint replay this exact query for matched-row pages beyond the regions' inline runs.
      - `source_paths` string[]: The uploaded table files this query read — directly, or through a prior result it built on — e.g. 'planning/FS GF Custom.xlsx'.
      - `row_count` integer: Rows in the query's full result, not in the preview below.
      - `columns` TableQueryColumn[]
        - `name` string, required
        - `data_type` string, required
      - `rows` object[]: Leading result rows, values rendered as text — a bounded preview, never the full result.
      - `preview_truncated` boolean: Whether the preview cut rows or columns.
      - `verified` boolean: True when at least one claim behind this query passed mechanical verification against the source data. Independent of 'regions': a source without an addressable grid verifies but cannot be pointed at, so an empty 'regions' never means unverified.
      - `regions` union[]: Original-file coordinates for the verified claims whose source file has an addressable grid.
        - union
          - SheetQueryRegion: A verified region on a source file with an addressable grid. Coordinates are expressed in the caller's uploaded workbook or CSV, never in the internal parquet derivative.
            - `kind` 'sheet'
            - `source_path` string, required: Uploaded file path this region points at.
            - `sheet_name` string, nullable: Workbook tab, when applicable.
            - `a1_range` string, required: A1 range on the original sheet, e.g. 'B12:D15'.
            - `matched_rows` integer: How many source rows the region covers.
            - `bounding` boolean: True when this is a bounding box over a large match set rather than exact runs.
            - `basis` 'verified' | 'inputs' | 'provenance': How this region was established. 'verified': a claim whose cited value was mechanically re-computed from exactly these rows. 'inputs': one table's contribution to a derived (multi-table) figure — the rows were verified to match the claim's filters, but the figure itself is not re-computable from this table alone. 'provenance': no claim covered this query, so the region records what the query's captured provenance proved it read — never a whole-table guess. Absent on legacy payloads, which are all 'verified'.
            - `scan_index` integer, nullable: Which scan of the query read these rows, when the region records one scan rather than the whole table. A query can read one file more than once — a narrow CTE and a broad one, say — and each such scan grounds separately, so this identifies the scan to replay for further row pages. Absent on a whole-table span, on verified regions, and on legacy payloads.
            - `scan_label` string, nullable: The scan named the way the query spells it: 'main query', 'CTE <name>', or 'subquery <n>'. Present exactly when scan_index is.
          - RowRangeQueryRegion: A verified region on a pageable tabular artifact, addressed by row number. A parquet upload stored as-is uses its own row order. A jsonl or parquet derivative uses the queried artifact's row order; ``derivative_path`` names that artifact when it differs from the caller-facing ``source_path``.
            - `kind` 'row_range'
            - `region_id` string, uuid, nullable: Opaque identity for paging this exact region when a receipt has several regions on one file.
            - `source_path` string, required: Uploaded file path this region belongs to.
            - `start_row` integer, required: First row of the run, 0-based and inclusive.
            - `end_row` integer, required: Last row of the run, 0-based and inclusive.
            - `columns` string[]: Cited column names, in table order.
            - `filter_columns` string[]: Columns the verified claim filtered on to select these rows, minus any already in columns. Empty on legacy payloads.
            - `read_columns` string[]: Columns the query read from this table (output lineage), minus any already in columns or filter_columns. Populated on a 'provenance'-basis region only; empty otherwise and on legacy payloads.
            - `matched_rows` integer: How many source rows the region covers.
            - `bounding` boolean: True when this is a bounding range over a large match set rather than exact runs.
            - `derivative_path` string, nullable: Pageable derivative (parquet or jsonl) when it is not the uploaded source itself.
            - `runs` ArtifactRowRun[]: Exact matched-row runs inside the span, so a bounding range stays pageable as matches only. May cover fewer than matched_rows when the verifier truncated its run list; empty on legacy payloads.
              - …
            - `runs_truncated` boolean: True when runs is an exact ordered prefix of matched islands and more matched rows exist. Later pages require guarded replay; false when the runs are complete or on legacy payloads.
            - `runs_replayable` boolean: True only when grounding-rows can reconstruct this exact region predicate for pages beyond runs. Regions without a safe replay fail closed instead of returning broader rows.
            - `basis` 'verified' | 'inputs' | 'provenance': How this region was established. 'verified': a claim whose cited value was mechanically re-computed from exactly these rows. 'inputs': one table's contribution to a derived (multi-table) figure — the rows were verified to match the claim's filters, but the figure itself is not re-computable from this table alone. 'provenance': no claim covered this query, so the region records what the query's captured provenance proved it read — never a whole-table guess. Absent on legacy payloads, which are all 'verified'.
            - `scan_index` integer, nullable: Which scan of the query read these rows, when the region records one scan rather than the whole table. A query can read one file more than once — a narrow CTE and a broad one, say — and each such scan grounds separately, so this identifies the scan to replay for further row pages. Absent on a whole-table span, on verified regions, and on legacy payloads.
            - `scan_label` string, nullable: The scan named the way the query spells it: 'main query', 'CTE <name>', or 'subquery <n>'. Present exactly when scan_index is.
      - `grounding_status` 'exact' | 'unavailable' | 'zero_result', nullable: Capture-time provenance status behind this query's regions. 'exact': the exact-provenance sidecar proved every row the query read, so its 'provenance'-basis regions are trustworthy (source regions may be broader than the minimal contributing set when grounding_reason carries a 'regions_broad:…' qualifier). 'unavailable': no exact proof could be captured (a legacy result, or a statement shape the capturer cannot instrument) — no 'provenance'-basis regions are attached, never a guessed whole-table span. 'zero_result': the query matched no rows, so there is nothing to ground. None on legacy payloads and on a query with no unclaimed table left to ground.
      - `grounding_reason` string, nullable: Qualification on grounding_status, when present. Set for 'unavailable' (explains why proof was not possible) and for 'exact' when capture succeeded but the source regions are broader than the minimal contributing set (e.g. 'regions_broad:narrowing_specs_unavailable'). None when grounding is fully exact or absent.
  - `result_materialization_error` string, nullable
  - `job_id` string, uuid, nullable: The external operation job; absent for commands inside a search job.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `402`: Payment Required
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict
- `410`: Gone
- `413`: Request Entity Too Large
- `422`: Unprocessable Entity
- `424`: Failed Dependency
- `429`: Too Many Requests
- `500`: Internal Server Error
- `501`: Not Implemented
- `503`: Service Unavailable
- `504`: Gateway Timeout

---

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