openapi: 3.1.0
info:
  title: New Archive Public API
  version: 1.0.0
  description: Machine-to-machine API for uploading files and searching a team library.
servers:
  - url: https://your-host/api/v1
    description: Replace your-host with the host of your New Archive deployment.
security:
  - apiKey: []
tags:
  - name: Files
  - name: Search
paths:
  /files:
    post:
      operationId: uploadFile
      summary: Upload a file
      description: Uploads one file up to 100 MiB. Requires the upload scope and the key owner's files.upload permission.
      tags: [Files]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                collection_id:
                  type: string
                  format: uuid
      responses:
        "202":
          description: Stored and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadAccepted"
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "415": { $ref: "#/components/responses/UnsupportedMediaType" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "507": { $ref: "#/components/responses/QuotaExceeded" }
        "500": { $ref: "#/components/responses/InternalError" }
  /files/{fileId}:
    get:
      operationId: getFile
      summary: Read file metadata and processing status
      description: Requires either the upload or search scope.
      tags: [Files]
      parameters:
        - name: fileId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: File metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/File"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "500": { $ref: "#/components/responses/InternalError" }
  /search:
    post:
      operationId: searchFiles
      summary: Search and filter the team library
      description: Requires the search scope. An empty JSON object lists files newest-first.
      tags: [Search]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchRequest"
      responses:
        "200":
          description: One page of matching files.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "500": { $ref: "#/components/responses/InternalError" }
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: na_ API key
      description: Create a scoped key under Settings → API keys.
  schemas:
    UploadAccepted:
      type: object
      required: [id, version_id, status]
      properties:
        id:
          type: string
          format: uuid
        version_id:
          type: string
          format: uuid
        status:
          type: string
          const: processing
    File:
      type: object
      required:
        - id
        - status
        - original_name
        - mime_type
        - size_bytes
        - width
        - height
        - taken_at
        - created_at
        - thumbnail_url
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [processing, ready, failed]
        original_name:
          type: string
        mime_type:
          type: string
        size_bytes:
          type: integer
          minimum: 0
        width:
          type: [integer, "null"]
          minimum: 0
        height:
          type: [integer, "null"]
          minimum: 0
        taken_at:
          type: [string, "null"]
          format: date-time
        created_at:
          type: string
          format: date-time
        thumbnail_url:
          type: [string, "null"]
          format: uri
    SearchRequest:
      type: object
      properties:
        q:
          type: string
          minLength: 1
        mode:
          type: string
          enum: [hybrid, text, vector]
          default: hybrid
        filters:
          $ref: "#/components/schemas/Filters"
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        cursor:
          type: string
    SearchResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/File"
        next_cursor:
          type: string
    Filters:
      type: object
      properties:
        q:
          type: string
          minLength: 1
        mode:
          type: string
          enum: [hybrid, text, vector]
        max_distance:
          type: number
          minimum: 0
          maximum: 1
        date: { $ref: "#/components/schemas/TristateMap" }
        import_date: { $ref: "#/components/schemas/TristateMap" }
        media_type: { $ref: "#/components/schemas/TristateMap" }
        orientation: { $ref: "#/components/schemas/TristateMap" }
        format: { $ref: "#/components/schemas/TristateMap" }
        iso: { $ref: "#/components/schemas/TristateMap" }
        focal_length: { $ref: "#/components/schemas/TristateMap" }
        focal_length_35mm: { $ref: "#/components/schemas/TristateMap" }
        aperture: { $ref: "#/components/schemas/TristateMap" }
        shutter_speed: { $ref: "#/components/schemas/TristateMap" }
        resolution: { $ref: "#/components/schemas/TristateMap" }
        file_size: { $ref: "#/components/schemas/TristateMap" }
        person_count: { $ref: "#/components/schemas/TristateMap" }
        lens: { $ref: "#/components/schemas/TristateMap" }
        color_space: { $ref: "#/components/schemas/TristateMap" }
        bit_depth: { $ref: "#/components/schemas/TristateMap" }
        file_type: { $ref: "#/components/schemas/TristateMap" }
        camera_make: { $ref: "#/components/schemas/TristateMap" }
        camera_model: { $ref: "#/components/schemas/TristateMap" }
        geography: { $ref: "#/components/schemas/TristateMap" }
        country: { $ref: "#/components/schemas/TristateMap" }
        state: { $ref: "#/components/schemas/TristateMap" }
        city: { $ref: "#/components/schemas/TristateMap" }
        suburb: { $ref: "#/components/schemas/TristateMap" }
        categories: { $ref: "#/components/schemas/TristateMap" }
        labels: { $ref: "#/components/schemas/TristateMap" }
        collections: { $ref: "#/components/schemas/TristateMap" }
        rating: { $ref: "#/components/schemas/TristateMap" }
        custom: { $ref: "#/components/schemas/TristateMap" }
        custom_ranges:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/NumberRange"
    TristateMap:
      type: object
      additionalProperties:
        type: string
        enum: [include, exclude]
    NumberRange:
      type: object
      properties:
        min:
          type: number
        max:
          type: number
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
  responses:
    InvalidRequest:
      description: Invalid request.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Forbidden:
      description: Missing scope, role permission, team membership, or plan access.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: File not found.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    PayloadTooLarge:
      description: File exceeds the 100 MiB direct-upload cap.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    UnsupportedMediaType:
      description: Unsupported normalized content type.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Per-key request budget exhausted.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    QuotaExceeded:
      description: Team storage quota is full.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
