openapi: 3.0.3
info:
  title: SOAT Files API
  version: 1.0.0
  description: API for managing files (Files resource)
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Files
    description: Manage files
security:
  - bearerAuth: []
paths:
  /api/v1/files:
    get:
      tags:
        - Files
      summary: List all files
      description: Returns a list of all stored files
      operationId: listFiles
      parameters:
        - name: project_id
          in: query
          required: false
          description: Filter files by project ID
          schema:
            type: string
            example: 'proj_V1StGXR8Z5jdHi6B'
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            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:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Public ID of the project. 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). 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
                  description: File size in bytes
                  example: 1024
                metadata:
                  type: string
                  description: JSON string with additional metadata
                  example: '{"author":"John"}'
      responses:
        '201':
          description: File created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileRecord'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/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-soat-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:
                  type: string
                  description: Additional metadata as a JSON string
                  example: '{"author":"John"}'
      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'
  /api/v1/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'
  /api/v1/files/presigned-url:
    post:
      tags:
        - Files
      summary: Create a presigned upload URL
      description: >-
        Creates a short-lived, single-use presigned upload URL — the
        local-storage equivalent of an S3 presigned URL. The client then uploads
        the file content directly to the returned `upload_url` via
        `POST /api/v1/files/upload/{token}`, bypassing MCP payload size limits.
        When the server is configured with `SOAT_BASE_URL`, `upload_url` is a
        fully-qualified absolute URL so MCP agents and other clients can POST to
        it without knowing the server base URL in advance.
      operationId: createPresignedUrl
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignedUrlRequest'
      responses:
        '201':
          description: Presigned URL created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignedUrlResponse'
        '400':
          description: Missing or invalid project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/files/upload/{token}:
    post:
      tags:
        - Files
      summary: Upload a file using an upload token
      description: >-
        Uploads file content authorized by a single-use token from
        `POST /api/v1/files/presigned-url`. No bearer credential is required —
        the token is the credential. Accepts either multipart/form-data (field
        `file`) or JSON with a base64-encoded `content` field.
      operationId: uploadFileWithToken
      security: []
      parameters:
        - name: token
          in: path
          required: true
          description: The single-use upload token (e.g. upt_...)
          schema:
            type: string
            example: 'upt_V1StGXR8Z5jdHi6B'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: File content
                metadata:
                  type: string
                  description: Additional metadata as a JSON string
                  example: '{"author":"John"}'
          application/json:
            schema:
              $ref: '#/components/schemas/UploadFileWithTokenRequest'
      responses:
        '201':
          description: File uploaded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileRecord'
        '400':
          description: Missing content
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Upload token not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Upload token already used
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          description: Upload token expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/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'
  /api/v1/files/{file_id}/download:
    get:
      tags:
        - Files
      summary: Download a file
      description: Streams the file content to the client
      operationId: downloadFile
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          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'
  /api/v1/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
              properties:
                metadata:
                  type: string
                  description: New metadata as a JSON string
                  example: '{"author":"Jane","tags":["report"]}'
                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'
  /api/v1/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
      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
                  contentType:
                    type: string
                    description: MIME type of the file
                  size:
                    type: integer
                    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'
  /api/v1/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:
                type: object
                additionalProperties:
                  type: string
        '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:
              type: object
              additionalProperties:
                type: string
      responses:
        '200':
          description: Tags replaced
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
        '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 with existing tags
      operationId: mergeFileTags
      parameters:
        - name: file_id
          in: path
          required: true
          description: File ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties:
                type: string
      responses:
        '200':
          description: Tags merged
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: File not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    UploadFileBase64Request:
      type: object
      required:
        - content
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project
          example: 'proj_V1StGXR8Z5jdHi6B'
        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:
          type: string
          description: JSON string with additional metadata
          example: '{"author":"John"}'
    PresignedUrlRequest:
      type: object
      required:
        - project_id
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project
          example: 'proj_V1StGXR8Z5jdHi6B'
        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: 'report.pdf'
        content_type:
          type: string
          description: MIME type of the file to be uploaded
          example: 'application/pdf'
    PresignedUrlResponse:
      type: object
      properties:
        upload_token:
          type: string
          description: Single-use token value
          example: 'upt_V1StGXR8Z5jdHi6B'
        upload_url:
          type: string
          description: >-
            Fully-qualified URL to POST the file content to (absolute when
            SOAT_BASE_URL is configured, relative otherwise).
          example: 'https://api.example.com/api/v1/files/upload/upt_V1StGXR8Z5jdHi6B'
        expires_at:
          type: string
          format: date-time
          description: Token expiration timestamp
    UploadFileWithTokenRequest:
      type: object
      properties:
        content:
          type: string
          description: Base64-encoded file content (alternative to multipart `file`)
          example: 'SGVsbG8gV29ybGQ='
        filename:
          type: string
          description: Original / download name (overrides the token's filename)
          example: 'document.txt'
        content_type:
          type: string
          description: MIME type of the file (overrides the token's content type)
          example: 'text/plain'
        metadata:
          type: string
          description: JSON string with additional metadata
          example: '{"author":"John"}'
    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'
        contentType:
          type: string
          description: MIME type of the file
          example: 'application/pdf'
        size:
          type: integer
          description: File size in bytes
          example: 1024
        metadata:
          type: string
          description: JSON string with additional metadata
          example: '{"author":"John"}'
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Error message
  responses:
    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: 'File not found'
