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

# file_metadata

> Return the metadata of one uploaded file without opening it.

**Cost class: fast.**

``path`` names an **uploaded source file** — the only namespace this tool
serves. The response describes the file's public components (a pdf's
sections, a spreadsheet's tabs, a recording, an image, or the raw upload
itself) with sizes, page or row extents, summaries, per-component
``readable`` / ``queryable`` capability flags — and, for queryable tabs,
the column schema with its profiled statistics (``columns``: names, SQL
types, null/distinct counts, value ranges, numeric sums, and a bounded
value census for low-cardinality columns), so a caller can aim
``query-tables`` at the right tabs without a probe query. The top-level ``summary`` /
``keywords`` are the primary segment's only — per-component detail lives on
the components — and fields with nothing to say are absent, not null. No
workspace-internal path appears anywhere; a workspace-internal path in the
request is refused with ``invalid_request`` naming the uploaded file to use
instead.

This is the *decide* step of the exploration loop: ``folder_metadata``
(or a search tool) locates a file; ``file_metadata`` says what is inside
it, how big each component is, and which content tool applies (a
``readable`` component suits ``read_file``, a ``queryable`` one suits
``qa_file``); then act — scoped to one component by name when only that
section or tab matters.

An un-ingested file answers 200 with its ``ingestion_status`` and no
components — exactly what a caller needs to decide what to do next; a
failed one carries ``ingestion_error`` with the recorded reason.



## OpenAPI

````yaml /openapi.json post /v1/workspaces/{workspace_id}/tools/file-metadata
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/file-metadata:
    post:
      tags:
        - tools
      summary: file_metadata
      description: >-
        Return the metadata of one uploaded file without opening it.


        **Cost class: fast.**


        ``path`` names an **uploaded source file** — the only namespace this
        tool

        serves. The response describes the file's public components (a pdf's

        sections, a spreadsheet's tabs, a recording, an image, or the raw upload

        itself) with sizes, page or row extents, summaries, per-component

        ``readable`` / ``queryable`` capability flags — and, for queryable tabs,

        the column schema with its profiled statistics (``columns``: names, SQL

        types, null/distinct counts, value ranges, numeric sums, and a bounded

        value census for low-cardinality columns), so a caller can aim

        ``query-tables`` at the right tabs without a probe query. The top-level
        ``summary`` /

        ``keywords`` are the primary segment's only — per-component detail lives
        on

        the components — and fields with nothing to say are absent, not null. No

        workspace-internal path appears anywhere; a workspace-internal path in
        the

        request is refused with ``invalid_request`` naming the uploaded file to
        use

        instead.


        This is the *decide* step of the exploration loop: ``folder_metadata``

        (or a search tool) locates a file; ``file_metadata`` says what is inside

        it, how big each component is, and which content tool applies (a

        ``readable`` component suits ``read_file``, a ``queryable`` one suits

        ``qa_file``); then act — scoped to one component by name when only that

        section or tab matters.


        An un-ingested file answers 200 with its ``ingestion_status`` and no

        components — exactly what a caller needs to decide what to do next; a

        failed one carries ``ingestion_error`` with the recorded reason.
      operationId: file_metadata_v1_workspaces__workspace_id__tools_file_metadata_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/FileMetadataRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileMetadataToolResponse'
          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:
    FileMetadataRequest:
      additionalProperties: false
      description: >-
        Request body for ``file-metadata``. ``path`` names an uploaded source
        file.
      properties:
        path:
          maxLength: 1024
          minLength: 1
          title: Path
          type: string
      required:
        - path
      title: FileMetadataRequest
      type: object
    FileMetadataToolResponse:
      additionalProperties: false
      description: >-
        Response body for ``file-metadata`` — everything about one uploaded
        file,

        with no workspace-internal path anywhere.


        ``summary`` is the file's *primary segment's* — the first logical

        document's for the document lane, the first summarized sheet's for

        tables — capped at FILE_SUMMARY_MAX_CHARS, never a concatenation across

        components (each component still carries its own).


        ``total_chars`` is a real character count and is only present when every

        text component carries one (new ingests); ``total_approx_bytes`` is the

        byte-based fallback that is always honest about being an estimate. An

        un-ingested file answers 200 with its ``ingestion_status`` and no

        components; a failed one additionally carries ``ingestion_error`` —

        metadata is exactly what a caller needs to decide what to do next with

        such a file.
      properties:
        components:
          items:
            discriminator:
              mapping:
                image:
                  $ref: '#/components/schemas/FileComponentInfo'
                media:
                  $ref: '#/components/schemas/MediaComponentInfo'
                raw:
                  $ref: '#/components/schemas/FileComponentInfo'
                section:
                  $ref: '#/components/schemas/FileComponentInfo'
                tab:
                  $ref: '#/components/schemas/FileComponentInfo'
              propertyName: kind
            oneOf:
              - $ref: '#/components/schemas/FileComponentInfo'
              - $ref: '#/components/schemas/MediaComponentInfo'
          title: Components
          type: array
        file_id:
          format: uuid
          title: File Id
          type: string
        ingestion_error:
          anyOf:
            - type: string
            - type: 'null'
          description: Why ingestion failed, when the ledger recorded a reason.
          title: Ingestion Error
        ingestion_status:
          $ref: '#/components/schemas/IngestionStatus'
        kind:
          anyOf:
            - type: string
            - type: 'null'
          description: Ingestion lane (document / table / media), when known.
          title: Kind
        page_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Page Count
        path:
          title: Path
          type: string
        size_bytes:
          description: Byte size of the uploaded source object.
          title: Size Bytes
          type: integer
        summary:
          default: ''
          title: Summary
          type: string
        tab_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Tab Count
        total_approx_bytes:
          anyOf:
            - type: integer
            - type: 'null'
          title: Total Approx Bytes
        total_chars:
          anyOf:
            - type: integer
            - type: 'null'
          title: Total Chars
      required:
        - path
        - file_id
        - ingestion_status
        - size_bytes
      title: FileMetadataToolResponse
      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
    FileComponentInfo:
      additionalProperties: false
      description: >-
        One public component of an uploaded file: a pdf section, a spreadsheet

        tab, an image, or the raw upload itself.


        ``readable`` / ``queryable`` are the capability flags that say which
        content

        tool applies to this component. ``char_count`` is a real character count

        when ingestion measured one; ``approx_bytes`` is the honest byte
        fallback

        (the size of the component's text representation) — the two are never

        conflated. ``error`` passes through a per-tab ingestion degradation:
        such a

        component stays visible but has no readable or queryable content.
      properties:
        approx_bytes:
          anyOf:
            - type: integer
            - type: 'null'
          title: Approx Bytes
        char_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Char Count
        column_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Column Count
        columns:
          anyOf:
            - items:
                $ref: '#/components/schemas/TableColumnInfo'
              maxItems: 200
              type: array
            - type: 'null'
          description: >-
            A queryable component's column schema (names and SQL types), from
            the table profile recorded at ingestion. Absent when the component
            is not queryable or the profile is unavailable.
          title: Columns
        columns_truncated:
          anyOf:
            - type: boolean
            - type: 'null'
          description: True when the schema was cut at the column cap; absent otherwise.
          title: Columns Truncated
        error:
          anyOf:
            - type: string
            - type: 'null'
          title: Error
        kind:
          enum:
            - section
            - tab
            - image
            - raw
          title: Kind
          type: string
        name:
          description: >-
            Public component name (subcategory or tab); empty when the file is
            the unit.
          title: Name
          type: string
        page_end:
          anyOf:
            - type: integer
            - type: 'null'
          description: Last source page covered, 1-based (sections).
          title: Page End
        page_start:
          anyOf:
            - type: integer
            - type: 'null'
          description: First source page covered, 1-based (sections).
          title: Page Start
        queryable:
          title: Queryable
          type: boolean
        readable:
          title: Readable
          type: boolean
        row_count:
          anyOf:
            - type: integer
            - type: 'null'
          description: 'Tabs: rows in the queryable table, else the source extent.'
          title: Row Count
        summary:
          default: ''
          title: Summary
          type: string
        table_name:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            SQL view name a queryable tab is registered under for run-sql;
            absent otherwise.
          title: Table Name
      required:
        - kind
        - name
        - readable
        - queryable
      title: FileComponentInfo
      type: object
    MediaComponentInfo:
      additionalProperties: false
      description: >-
        A recording's component: timeline facts, no paths — ask about it with
        the QA tool.
      properties:
        duration_ms:
          title: Duration Ms
          type: integer
        frame_count:
          title: Frame Count
          type: integer
        kind:
          const: media
          default: media
          title: Kind
          type: string
        modality:
          enum:
            - audio
            - video
          title: Modality
          type: string
        speaker_count:
          title: Speaker Count
          type: integer
        speech_segment_count:
          title: Speech Segment Count
          type: integer
        summary:
          default: ''
          title: Summary
          type: string
      required:
        - modality
        - duration_ms
        - speaker_count
        - speech_segment_count
        - frame_count
      title: MediaComponentInfo
      type: object
    IngestionStatus:
      description: >-
        The only thing that says whether a file is searchable.


        ``expired`` cannot be derived from the hash pair: retention clears

        ``ingested_hash``, so an aged-out file and a never-ingested file look

        identical. They call for different customer actions, so the status
        carries

        the distinction the hashes cannot.
      enum:
        - discovered
        - queued
        - ingesting
        - ingested
        - stale
        - expired
        - failed
        - not_required
      title: IngestionStatus
      type: string
    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
    TableColumnInfo:
      additionalProperties: false
      description: >-
        One column of a queryable component's schema, with the statistics
        ingestion profiled.


        The stats are the briefing the platform's own SQL engine reads before it

        writes a query — null/distinct counts, value range, numeric sum, and a

        bounded frequency census — served here so a caller can plan a

        ``query-tables`` selection without probe queries. Every rendered value
        is

        clipped server-side; fields are absent when the profile predates them or

        the type does not support them. The caller's access gate is the same one

        ``read-file`` and ``query-tables`` pass, so nothing here exceeds what
        those

        already serve.
      properties:
        data_type:
          description: SQL type of the column (e.g. BIGINT, VARCHAR, TIMESTAMP).
          title: Data Type
          type: string
        distinct_count:
          anyOf:
            - type: integer
            - type: 'null'
          description: >-
            APPROXIMATE distinct value count (HyperLogLog). Compare to the
            component's row_count for key candidates, but verify exact
            uniqueness with a query.
          title: Distinct Count
        max_value:
          anyOf:
            - type: string
            - type: 'null'
          description: Largest value as text, clipped.
          title: Max Value
        min_value:
          anyOf:
            - type: string
            - type: 'null'
          description: Smallest value as text, clipped; a format exemplar for ids.
          title: Min Value
        name:
          description: Column name as it must be quoted in SQL.
          title: Name
          type: string
        null_count:
          anyOf:
            - type: integer
            - type: 'null'
          description: Rows where this column is NULL.
          title: Null Count
        sample_values:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          description: >-
            The column's COMPLETE distinct value list (low-cardinality columns
            only), each value clipped. Superseded by ``top_values`` when the
            profile carries a census — then this field is omitted rather than
            duplicated.
          title: Sample Values
        sum_value:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Sum of the column as text; numeric columns on profiles that measured
            it.
          title: Sum Value
        top_values:
          anyOf:
            - items:
                $ref: '#/components/schemas/TableColumnValueCount'
              type: array
            - type: 'null'
          description: >-
            Frequency census: most common values WITH row counts, most frequent
            first. Complete when it lists every distinct value;
            ``top_values_coverage`` says how much of the column a cut census
            covers.
          title: Top Values
        top_values_coverage:
          anyOf:
            - type: number
            - type: 'null'
          description: Fraction of the column's non-null rows the census covers (0..1).
          title: Top Values Coverage
      required:
        - name
        - data_type
      title: TableColumnInfo
      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
    TableColumnValueCount:
      additionalProperties: false
      description: 'One census entry: a column value and how many rows carry it.'
      properties:
        count:
          description: Rows carrying this value.
          title: Count
          type: integer
        value:
          description: The value, rendered as text and clipped to the value character cap.
          title: Value
          type: string
      required:
        - value
        - count
      title: TableColumnValueCount
      type: object
  securitySchemes:
    APIKeyHeader:
      in: header
      name: X-API-Key
      type: apiKey

````