> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ndi.nace.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# run_sql

> 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.



## OpenAPI

````yaml /openapi.json post /v1/workspaces/{workspace_id}/tools/run-sql
openapi: 3.1.0
info:
  description: >-
    Document intelligence: parse, split, classify, extract, and ground files, or
    build a searchable workspace. Authenticate with ``X-API-Key``.
  title: NDI Platform API
  version: 0.1.0
servers:
  - url: https://ndi-api.nace.ai
security: []
tags: []
paths:
  /v1/workspaces/{workspace_id}/tools/run-sql:
    post:
      tags:
        - tools
      summary: run_sql
      description: >-
        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.
      operationId: run_sql_v1_workspaces__workspace_id__tools_run_sql_post
      parameters:
        - description: Workspace identifier.
          in: path
          name: workspace_id
          required: true
          schema:
            description: Workspace identifier.
            format: uuid
            title: Workspace Id
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunSqlRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunSqlResponse'
          description: Successful Response
        '504':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Gateway Timeout
        default:
          content:
            application/json:
              example:
                error:
                  code: invalid_request
                  detail: null
                  message: >-
                    Request body has extra fields that this operation does not
                    accept.
                  request_id: req-01j9k2n3p4q5r6s7t8v9
                  retryable: false
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: >-
            Typed error envelope used by every 4xx and 5xx. ``error.code``
            distinguishes the failure; HTTP status is a consequence of the code.
      security:
        - APIKeyHeader: []
components:
  schemas:
    RunSqlRequest:
      additionalProperties: false
      description: |-
        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.
      properties:
        sql:
          maxLength: 4096
          minLength: 1
          title: Sql
          type: string
      required:
        - sql
      title: RunSqlRequest
      type: object
    RunSqlResponse:
      additionalProperties: false
      description: |-
        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.
      properties:
        duplicate:
          default: false
          title: Duplicate
          type: boolean
        result:
          $ref: '#/components/schemas/QueryTablePreview'
        result_citation:
          anyOf:
            - $ref: '#/components/schemas/QueryTableCitation'
            - type: 'null'
        result_materialization_error:
          anyOf:
            - type: string
            - type: 'null'
          title: Result Materialization Error
        source_paths:
          description: >-
            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).
          items:
            type: string
          title: Source Paths
          type: array
        sql:
          description: The normalized statement that ran — the result's provenance.
          title: Sql
          type: string
      required:
        - sql
        - result
      title: RunSqlResponse
      type: object
    ErrorEnvelope:
      description: Every non-2xx body on ``/v1``.
      example:
        error:
          code: invalid_request
          detail: null
          message: Request body has extra fields that this operation does not accept.
          request_id: req-01j9k2n3p4q5r6s7t8v9
          retryable: false
      properties:
        error:
          $ref: '#/components/schemas/Error'
      required:
        - error
      title: ErrorEnvelope
      type: object
    QueryTablePreview:
      additionalProperties: false
      description: One retained result table with a bounded inline preview.
      properties:
        columns:
          default: []
          items:
            $ref: '#/components/schemas/QueryTableColumn'
          maxItems: 50
          title: Columns
          type: array
        handle:
          title: Handle
          type: string
        path:
          anyOf:
            - type: string
            - type: 'null'
          title: Path
        preview_truncated:
          default: false
          title: Preview Truncated
          type: boolean
        row_count:
          title: Row Count
          type: integer
        rows:
          default: []
          items:
            additionalProperties:
              type: string
            type: object
          maxItems: 20
          title: Rows
          type: array
      required:
        - handle
        - row_count
      title: QueryTablePreview
      type: object
    QueryTableCitation:
      additionalProperties: false
      description: Provenance for one generated TablesQA result file.
      properties:
        grounding:
          anyOf:
            - $ref: '#/components/schemas/TableQueryGrounding'
            - type: 'null'
          description: >-
            Exact source regions and bounded receipt data when capture-time
            provenance was available.
        result_file:
          $ref: '#/components/schemas/QueryTableResultFile'
        source_paths:
          items:
            type: string
          title: Source Paths
          type: array
        sql:
          title: Sql
          type: string
      required:
        - result_file
        - source_paths
        - sql
      title: QueryTableCitation
      type: object
    Error:
      description: The error object, per spec 12 §1.8.
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
        detail:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          description: Structured payload, when the code carries one.
          title: Detail
        message:
          description: Human- and model-readable; says what to do differently.
          title: Message
          type: string
        request_id:
          description: Per-request correlation id; matches the ``X-Request-Id`` header.
          title: Request Id
          type: string
        retryable:
          description: Whether retrying the identical request could succeed.
          title: Retryable
          type: boolean
      required:
        - code
        - message
        - retryable
        - request_id
      title: Error
      type: object
    QueryTableColumn:
      additionalProperties: false
      description: One public column in a retained result preview.
      properties:
        data_type:
          title: Data Type
          type: string
        name:
          title: Name
          type: string
      required:
        - name
        - data_type
      title: QueryTableColumn
      type: object
    TableQueryGrounding:
      additionalProperties: false
      description: >-
        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.
      properties:
        columns:
          items:
            $ref: '#/components/schemas/TableQueryColumn'
          title: Columns
          type: array
        grounding_reason:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            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.
          title: Grounding Reason
        grounding_status:
          anyOf:
            - enum:
                - exact
                - unavailable
                - zero_result
              type: string
            - type: 'null'
          description: >-
            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.
          title: Grounding Status
        preview_truncated:
          default: false
          description: Whether the preview cut rows or columns.
          title: Preview Truncated
          type: boolean
        regions:
          description: >-
            Original-file coordinates for the verified claims whose source file
            has an addressable grid.
          items:
            discriminator:
              mapping:
                row_range:
                  $ref: '#/components/schemas/RowRangeQueryRegion'
                sheet:
                  $ref: '#/components/schemas/SheetQueryRegion'
              propertyName: kind
            oneOf:
              - $ref: '#/components/schemas/SheetQueryRegion'
              - $ref: '#/components/schemas/RowRangeQueryRegion'
          title: Regions
          type: array
        result_id:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            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.
          title: Result Id
        row_count:
          default: 0
          description: Rows in the query's full result, not in the preview below.
          title: Row Count
          type: integer
        rows:
          description: >-
            Leading result rows, values rendered as text — a bounded preview,
            never the full result.
          items:
            additionalProperties:
              type: string
            type: object
          title: Rows
          type: array
        source_paths:
          description: >-
            The uploaded table files this query read — directly, or through a
            prior result it built on — e.g. 'planning/FS GF Custom.xlsx'.
          items:
            type: string
          title: Source Paths
          type: array
        sql:
          title: Sql
          type: string
        supports_answer:
          default: true
          description: >-
            False for exploratory SQL the tables engine ran but did not mark as
            part of the answer's calculation trail.
          title: Supports Answer
          type: boolean
        verified:
          default: false
          description: >-
            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.
          title: Verified
          type: boolean
      required:
        - sql
      title: TableQueryGrounding
      type: object
    QueryTableResultFile:
      additionalProperties: false
      description: A workspace-visible Parquet file retained from a TablesQA query.
      properties:
        download_url:
          title: Download Url
          type: string
        download_url_expires_at:
          format: date-time
          title: Download Url Expires At
          type: string
        file_id:
          format: uuid
          title: File Id
          type: string
        file_name:
          title: File Name
          type: string
        format:
          const: parquet
          title: Format
          type: string
        handle:
          title: Handle
          type: string
        path:
          title: Path
          type: string
        preview_url:
          title: Preview Url
          type: string
        row_count:
          title: Row Count
          type: integer
      required:
        - file_id
        - handle
        - path
        - file_name
        - format
        - row_count
        - download_url
        - preview_url
        - download_url_expires_at
      title: QueryTableResultFile
      type: object
    ErrorCode:
      description: |-
        Every error the ``/v1`` surface can return.

        Clients must treat this as an **open** enum: a new member is an additive
        change (spec 12 §9), and a client that crashes on an unknown code makes
        every future error a breaking change.
      enum:
        - invalid_request
        - unsupported_file_type
        - file_too_large
        - invalid_schema
        - invalid_sql
        - invalid_path_prefix
        - command_not_permitted
        - batch_too_large
        - domain_validation_failed
        - default_label_required
        - wait_required_for_zero_retention
        - workspace_not_found
        - file_not_found
        - job_not_found
        - upload_not_found
        - node_not_found
        - schema_not_found
        - domain_not_found
        - session_not_found
        - access_label_not_found
        - path_conflict
        - schema_version_conflict
        - access_label_name_taken
        - workspace_name_taken
        - access_label_in_use
        - upload_expired
        - file_not_ingested
        - workspace_deleting
        - workspace_not_deletable
        - ingestion_in_progress
        - job_not_cancellable
        - confirmation_required
        - domain_not_published
        - domain_in_use
        - reserved_domain_slug
        - kg_not_built
        - kg_build_in_progress
        - session_busy
        - range_not_satisfiable
        - result_expired
        - access_denied
        - access_backstop_violation
        - access_mode_not_implemented
        - rate_limited
        - concurrency_limit_reached
        - quota_exceeded
        - corrupt_file
        - zero_byte_file
        - encrypted_file
        - parse_failed
        - ocr_failed
        - conversion_failed
        - sheetless_workbook
        - unplayable_media
        - unclassified_pages
        - job_cancelled
        - job_timeout
        - upstream_unavailable
        - internal_error
        - unauthorized
        - not_implemented
      title: ErrorCode
      type: string
    TableQueryColumn:
      additionalProperties: false
      description: One column of an executed SQL query's result preview.
      properties:
        data_type:
          title: Data Type
          type: string
        name:
          title: Name
          type: string
      required:
        - name
        - data_type
      title: TableQueryColumn
      type: object
    RowRangeQueryRegion:
      additionalProperties: false
      description: >-
        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``.
      properties:
        basis:
          default: verified
          description: >-
            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'.
          enum:
            - verified
            - inputs
            - provenance
          title: Basis
          type: string
        bounding:
          default: false
          description: >-
            True when this is a bounding range over a large match set rather
            than exact runs.
          title: Bounding
          type: boolean
        columns:
          description: Cited column names, in table order.
          items:
            type: string
          title: Columns
          type: array
        derivative_path:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Pageable derivative (parquet or jsonl) when it is not the uploaded
            source itself.
          title: Derivative Path
        end_row:
          description: Last row of the run, 0-based and inclusive.
          title: End Row
          type: integer
        filter_columns:
          description: >-
            Columns the verified claim filtered on to select these rows, minus
            any already in columns. Empty on legacy payloads.
          items:
            type: string
          title: Filter Columns
          type: array
        kind:
          const: row_range
          default: row_range
          title: Kind
          type: string
        matched_rows:
          default: 0
          description: How many source rows the region covers.
          title: Matched Rows
          type: integer
        read_columns:
          description: >-
            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.
          items:
            type: string
          title: Read Columns
          type: array
        region_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          description: >-
            Opaque identity for paging this exact region when a receipt has
            several regions on one file.
          title: Region Id
        runs:
          description: >-
            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.
          items:
            $ref: '#/components/schemas/ArtifactRowRun'
          title: Runs
          type: array
        runs_replayable:
          default: false
          description: >-
            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.
          title: Runs Replayable
          type: boolean
        runs_truncated:
          default: false
          description: >-
            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.
          title: Runs Truncated
          type: boolean
        scan_index:
          anyOf:
            - type: integer
            - type: 'null'
          description: >-
            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.
          title: Scan Index
        scan_label:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            The scan named the way the query spells it: 'main query', 'CTE
            <name>', or 'subquery <n>'. Present exactly when scan_index is.
          title: Scan Label
        source_path:
          description: Uploaded file path this region belongs to.
          title: Source Path
          type: string
        start_row:
          description: First row of the run, 0-based and inclusive.
          title: Start Row
          type: integer
      required:
        - source_path
        - start_row
        - end_row
      title: RowRangeQueryRegion
      type: object
    SheetQueryRegion:
      additionalProperties: false
      description: >-
        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.
      properties:
        a1_range:
          description: A1 range on the original sheet, e.g. 'B12:D15'.
          title: A1 Range
          type: string
        basis:
          default: verified
          description: >-
            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'.
          enum:
            - verified
            - inputs
            - provenance
          title: Basis
          type: string
        bounding:
          default: false
          description: >-
            True when this is a bounding box over a large match set rather than
            exact runs.
          title: Bounding
          type: boolean
        kind:
          const: sheet
          default: sheet
          title: Kind
          type: string
        matched_rows:
          default: 0
          description: How many source rows the region covers.
          title: Matched Rows
          type: integer
        scan_index:
          anyOf:
            - type: integer
            - type: 'null'
          description: >-
            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.
          title: Scan Index
        scan_label:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            The scan named the way the query spells it: 'main query', 'CTE
            <name>', or 'subquery <n>'. Present exactly when scan_index is.
          title: Scan Label
        sheet_name:
          anyOf:
            - type: string
            - type: 'null'
          description: Workbook tab, when applicable.
          title: Sheet Name
        source_path:
          description: Uploaded file path this region points at.
          title: Source Path
          type: string
      required:
        - source_path
        - a1_range
      title: SheetQueryRegion
      type: object
    ArtifactRowRun:
      additionalProperties: false
      description: One inclusive 0-based run of matched rows in the pageable artifact.
      properties:
        end:
          description: Last matched row of the run, 0-based and inclusive.
          title: End
          type: integer
        start:
          description: First matched row of the run, 0-based and inclusive.
          title: Start
          type: integer
      required:
        - start
        - end
      title: ArtifactRowRun
      type: object
  securitySchemes:
    APIKeyHeader:
      in: header
      name: X-API-Key
      type: apiKey

````