# AUTO-GENERATED tier-A mirror spec — do not edit.
# Source: openapi/runtime/files.yaml (runtime 0.71.2)
#  + policy: openapi/mirror-policies/files.json
# Regenerate: node scripts/generate-mirror-specs.mjs
openapi: 3.0.3
info:
  title: naturali.ai — Files API
  version: 1.0.0
  description: >-
    Files: the stored bytes a project works from — uploaded directly or as base64, downloaded raw or
    as base64, tagged, and referenced by a document for ingestion. A fully runtime-backed module —
    this spec is generated verbatim from the runtime's own, re-rooted under
    /v1/projects/{project_id}. The project in the path is authorized by naturali and enforced
    upstream by the project's scoped credential.


    This module mirrors the upstream runtime verbatim (tier A, #304): paths are the runtime's own
    re-rooted under /v1/projects/{project_id}, and every field, method, status code and error shape
    passes through unchanged. Errors raised by the runtime arrive in its envelope; errors raised by
    naturali itself (authentication, project resolution, an unreachable runtime) use naturali's.
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: "{baseUrl}"
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Files
    description: Manage files
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/projects/{project_id}/files:
    get:
      tags:
        - Files
      summary: List all files
      description: Returns a list of all stored files
      operationId: listFiles
      parameters:
        - name: path_prefix
          in: query
          required: false
          description: "Only files under this directory. The prefix is a path boundary, not a substring:
            `/reports` returns `/reports/q1.txt` and never `/reports-archive/q1.txt`, and `/`
            selects the whole project. A leading slash is optional and a trailing one is ignored, so
            `reports`, `/reports` and `/reports/` are the same filter. `%` and `_` are literal
            characters, not wildcards. Naming a directory under `/.system/` is what includes
            platform-written files, which a list without this parameter leaves out."
          schema:
            type: string
            example: /reports/
        - $ref: "#/components/parameters/TagsQuery"
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of files returned successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/FileRecord"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Files
      summary: Create a file
      description: Creates a new file record in the system
      operationId: createFile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prefix:
                  type: string
                  description: Directory within the project (e.g. /images). Optional; defaults to / (root). Combined
                    with filename to form the file's key (path).
                  example: /images
                filename:
                  type: string
                  description: Original / download name and the key's leaf segment (e.g. logo.png).
                  example: logo.png
                content_type:
                  type: string
                  description: MIME type of the file
                  example: application/pdf
                size:
                  type: integer
                  nullable: true
                  description: File size in bytes
                  example: 1024
                metadata:
                  $ref: "#/components/schemas/MetadataBag"
      responses:
        "201":
          description: File created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileRecord"
        "409":
          description: The project's `storage_bytes` quota is exceeded (`QUOTA_STORAGE_EXCEEDED`). Delete
            stored content, or raise the quota — no window reset clears a stored total, so no
            `Retry-After` is sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/upload:
    post:
      tags:
        - Files
      summary: Upload a file
      description: Uploads a file to the server and stores it in the configured storage directory
      operationId: uploadFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: File content
                project_id:
                  x-naturali-ref: projects
                  type: string
                  description: Project ID to associate the file with. Optional when authenticating with a
                    project-scoped API key, which defaults to the key's project; required otherwise.
                  example: proj_V1StGXR8Z5jdHi6B
                prefix:
                  type: string
                  description: Directory within the project (e.g. /images). Optional; defaults to / (root).
                  example: /images
                filename:
                  type: string
                  description: Original / download name. Optional; defaults to the uploaded file's name.
                  example: logo.png
                metadata:
                  $ref: "#/components/schemas/MetadataBagText"
      responses:
        "201":
          description: File uploaded successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileRecord"
        "400":
          description: Missing file or invalid project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The project's `storage_bytes` quota is exceeded (`QUOTA_STORAGE_EXCEEDED`). Delete
            stored content, or raise the quota — no window reset clears a stored total, so no
            `Retry-After` is sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: The upload is over this deployment's byte ceiling (`UPLOAD_TOO_LARGE`;
            `FILE_UPLOAD_MAX_BYTES`, 25 MB by default). The request is refused while the body is
            still streaming, so nothing was stored.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/upload/base64:
    post:
      tags:
        - Files
      summary: Upload a file using base64 encoding
      description: Uploads a file to the server using base64-encoded content
      operationId: uploadFileBase64
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UploadFileBase64Request"
      responses:
        "201":
          description: File uploaded successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileRecord"
        "400":
          description: Missing content or invalid project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The project's `storage_bytes` quota is exceeded (`QUOTA_STORAGE_EXCEEDED`). Delete
            stored content, or raise the quota — no window reset clears a stored total, so no
            `Retry-After` is sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/{file_id}:
    get:
      tags:
        - Files
      summary: Get a file by ID
      description: Returns the data and metadata of a specific file
      operationId: getFile
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
            example: abc123
      responses:
        "200":
          description: File found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileRecord"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Files
      summary: Delete a file
      description: Removes a file from the system by ID
      operationId: deleteFile
      parameters:
        - name: file_id
          in: path
          required: true
          description: ID of the file to delete
          schema:
            type: string
            example: abc123
      responses:
        "204":
          description: File deleted successfully
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/{file_id}/download:
    get:
      tags:
        - Files
      summary: Download a file
      description: Streams the file content to the client
      operationId: downloadFile
      x-mcp-exclude: true
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
        - name: token
          in: query
          required: false
          description: Signed single-file download token, an alternative to a bearer credential (issued for
            ingestion-rule converters).
          schema:
            type: string
            example: file_abc123
      responses:
        "200":
          description: File content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/{file_id}/metadata:
    patch:
      tags:
        - Files
      summary: Update file metadata
      description: Updates the metadata field of a file
      operationId: updateFileMetadata
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
            example: file_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                metadata:
                  description: Replaces the stored bag; `null` clears it.
                  allOf:
                    - $ref: "#/components/schemas/NullableMetadataBag"
                prefix:
                  type: string
                  description: New directory — moves the file. The resulting path (prefix + filename) must be unique
                    within the project.
                  example: /reports
                filename:
                  type: string
                  description: New filename — renames the key's leaf and the download name.
                  example: renamed-file.txt
      responses:
        "200":
          description: Metadata updated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileRecord"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: A file already exists at the target path in this project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/{file_id}/download/base64:
    get:
      tags:
        - Files
      summary: Download file as base64
      description: Returns the file content encoded as base64
      operationId: downloadFileBase64
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
        - name: token
          in: query
          required: false
          description: Signed single-file download token, an alternative to a bearer credential (issued for
            ingestion-rule converters).
          schema:
            type: string
      responses:
        "200":
          description: File content as base64
          content:
            application/json:
              schema:
                type: object
                properties:
                  content:
                    type: string
                    description: Base64-encoded file content
                  filename:
                    type: string
                    description: Original filename
                  content_type:
                    type: string
                    description: MIME type of the file
                  size:
                    type: integer
                    nullable: true
                    description: File size in bytes
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/files/{file_id}/tags:
    get:
      tags:
        - Files
      summary: Get file tags
      description: Returns all tags attached to the file
      operationId: getFileTags
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
      responses:
        "200":
          description: File tags
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Files
      summary: Replace file tags
      description: Replaces all tags on the file with the provided tags
      operationId: replaceFileTags
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagBag"
      responses:
        "200":
          description: Tags replaced
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      tags:
        - Files
      summary: Merge file tags
      description: Merges provided tags into the file's existing tags (existing tags are preserved unless
        overridden)
      operationId: mergeFileTags
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagBag"
      responses:
        "200":
          description: Tags merged
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: File not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
components:
  schemas:
    UploadFileBase64Request:
      type: object
      required:
        - content
      additionalProperties: false
      properties:
        content:
          type: string
          description: Base64-encoded file content
          example: SGVsbG8gV29ybGQ=
        prefix:
          type: string
          description: Directory within the project (e.g. /documents). Optional; defaults to / (root).
          example: /documents
        filename:
          type: string
          description: Original / download name and the key's leaf segment.
          example: document.txt
        content_type:
          type: string
          description: MIME type of the file
          example: text/plain
        metadata:
          $ref: "#/components/schemas/MetadataBag"
    FileRecord:
      type: object
      description: Stored file metadata
      properties:
        id:
          type: string
          description: Unique file identifier
          example: abc123
        prefix:
          type: string
          readOnly: true
          description: Directory of the file (the `path` without its last segment). Read-only — set it via
            `prefix` on write.
          example: /images
        filename:
          type: string
          description: Original / download name and the key's leaf segment.
          example: logo.png
        path:
          type: string
          nullable: true
          readOnly: true
          description: Full key of the file within the project — `prefix` + `/` + `filename` (e.g.
            /images/logo.png). Read-only; unique per project; the file's identity and policy-SRN
            target.
          example: /images/logo.png
        content_type:
          type: string
          nullable: true
          description: MIME type of the file
          example: application/pdf
        size:
          type: integer
          nullable: true
          description: File size in bytes
          example: 1024
        metadata:
          $ref: "#/components/schemas/NullableMetadataBag"
        tags:
          $ref: "#/components/schemas/TagBag"
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error. Every error response uses this shape, so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: "Machine-readable error code: lower_snake for an error naturali raises
                (`access_denied`), UPPER_SNAKE for one the runtime reports (`RESOURCE_NOT_FOUND`)."
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: Structured context for an error naturali raises, such as the `resource` and `limit` of
                a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
    MetadataBag:
      type: object
      description: >-
        Caller-owned annotations on a resource, stored as the object they were written as: the types
        a value was written with are the types a read returns, so a filter can ask an ordering
        question about a number. Unlike other body fields, keys are stored and returned verbatim in
        the casing supplied — they are not converted between snake_case and camelCase.


        No key is reserved, and that is the point: every piece of state the platform owns lives in
        its own typed column, so nothing written here reaches platform state. The platform never
        reads the bag — it is not an IAM context, not a policy input and not part of a prompt —
        which is what separates it from a tag bag.
      example:
        author: John
        revision: 2
    MetadataBagText:
      type: string
      description: A `MetadataBag` as JSON text. A multipart field carries text, so the object travels
        serialized on those surfaces and is parsed on arrival; a JSON body carries the object
        itself. Text that is not a JSON object is `400 VALIDATION_FAILED`.
      example: '{"author":"John","revision":2}'
    NullableMetadataBag:
      type: object
      nullable: true
      description: A `MetadataBag` on a field where `null` is meaningful — a full-replacement update that
        clears the bag, or a record whose bag was never set.
      example:
        author: John
        revision: 2
    TagBag:
      type: object
      additionalProperties:
        type: string
      x-cli-flag-name: tags
      description: >-
        Key-value labels on a resource. A flat object of string values — an array, a nested object
        or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and
        stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB
        containment wherever tags are read: the `?tags=` filter and knowledge search.


        Keys beginning `system.` are reserved: the platform writes them to record which
        conversation, actor, agent and role a row came from, and a write naming one is refused with
        `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag.


        The bag is bounded, because every pair reaches the IAM context of every access check on the
        resource: at most 50 keys, each key at most 128 characters and each value at most 256. A
        write past a bound — including a merge that would grow the stored bag past the key count —
        is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys
        are the platform's and do not count against the 50.
      example:
        team: finance
        env: prod
  parameters:
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    TagsQuery:
      name: tags
      in: query
      required: false
      description: >
        Filter by tag pairs, written `key:value` (split on the first colon, so a value may contain
        colons). Repeat the parameter for several pairs; **all** must be present with exactly that
        value.
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
      example:
        - env:prod
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: "A connected app's OAuth access token, issued by this API's authorization server
        (discovery: /.well-known/oauth-authorization-server). Its one scope carries every operation,
        confined to the projects the user chose when approving the app."
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: The credential is scoped to a different project, or the caller's role in the project
        does not carry this action.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
