openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Model Version
  version: 1.0.0
  description: |
    Manages **training versions** of an object-detection model. A version pins a
    `model`, one or more `dataset`s (whose labels are unioned into
    `dataset_labels`), the `initial_weight` to start from, a
    `task_selection_mode` and optional `train_settings[]`; the server assigns a
    per-model `version_code`, stamps the `creator` from the JWT and sets
    `status: initiated`.

    **Async dataset prep.** Creating a version enqueues a job (via the
    `svFifoAlarm` SQS queue) that builds a YOLO dataset from the qualifying
    `ai-object-detection-task`s of the selected datasets — tasks with media, a
    non-`ignore` subset and at least one usable & confirmed annotation group
    (`smart` mode keeps only `manual` / `auto_edited` groups, `all` also keeps
    untouched `auto` groups). Unpinned tasks are dealt into train/val/test by the
    parent model's `validation_size` / `test_size`, the folder is zipped and
    stored as `train_data`, and `tasks_size`, `annotations_size` and
    `annotations_per_label[]` record what went into the zip. Progress is
    visible in `status` (`building_folder_in_progress` → … →
    `zip_file_completed`); failures set a `*_failed` status and append to
    `_errors[]`. A selection with no qualifying tasks fails with
    `building_folder_failed` instead of shipping an empty zip.

    **Training.** The run itself happens on a self-hosted agent using the
    snippet from `ai-object-detection-model-version-train-agent`. Per-epoch
    metrics stream into `ai-object-detection-model-version-epoch`; when the
    weights (`weight_best` / `weight_last`) and plots are uploaded through the
    `ul-hub` endpoints the version flips to `trained`, its best-epoch `metrics`
    / `model_stats` are snapshotted, and the parent model's
    `current_model_version` auto-advances (forward-only).

    **Who calls it.** Back-office admins. Scoped by `company_namespace[]`;
    soft-delete via `disabled`. Child of `ai-object-detection-model`.

    **Methods.** `find`, `get`, `create`, `remove`. `update` and `patch` return
    400 (a version's definition is immutable once created). `model`, `dataset`,
    `dataset_labels` and every media-backed field can be populated with
    `populatedKeys[]`; populated documents are returned under
    `<field>_populated` while the original field keeps the id(s).
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-model-version:
    get:
      summary: Find model versions
      operationId: findAiObjectDetectionModelVersions
      parameters:
        - in: query
          name: _id
          description: "Filter by version `_id`. Pass once or as `?_id[]=...` for multiple."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: model
          description: Filter by parent model id (one or many).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: version_code
          description: Filter by the per-model version number (one or many).
          schema:
            oneOf:
              - type: integer
              - type: array
                items: { type: integer }
        - in: query
          name: dataset
          description: Versions pinning any of the given dataset ids.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: dataset_labels
          description: Versions whose label set contains any of the given label ids.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: name
          description: Accepted by the backend but the schema has no `name` field — effectively a no-op.
          schema: { type: string }
        - in: query
          name: disabled
          description: Include soft-deleted versions. Defaults to `false`.
          schema: { type: boolean, default: false }
        - in: query
          name: from_updatedAt
          description: Only versions with `updatedAt` on/after this Unix timestamp (ms), start of that day in the caller's timezone.
          schema: { type: number }
        - in: query
          name: to_updatedAt
          description: Only versions with `updatedAt` on/before this Unix timestamp (ms), end of that day.
          schema: { type: number }
        - in: query
          name: from_createdAt
          description: Only versions with `createdAt` on/after this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: to_createdAt
          description: Only versions with `createdAt` on/before this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: populatedKeys
          description: "Refs to populate. Each is returned under `<field>_populated`; the original field keeps the id(s). Encode as `?populatedKeys[]=model&populatedKeys[]=weight_best`."
          schema:
            type: array
            items:
              type: string
              enum:
                [
                  model,
                  train_data,
                  dataset,
                  dataset_labels,
                  confusion_matrix_normalized,
                  args,
                  confusion_matrix,
                  F1_curve,
                  labels_correlogram,
                  labels,
                  P_curve,
                  PR_curve,
                  R_curve,
                  results_csv,
                  results,
                  val_batch0_labels,
                  val_batch0_pred,
                  weight_best,
                  weight_last,
                ]
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1, maximum: 50000 }
          example: 20
        - in: query
          name: page
          schema: { type: integer, minimum: 1 }
          example: 1
        - in: query
          name: sort
          description: Field to sort by. Defaults to `_id`.
          schema: { type: string }
        - in: query
          name: sortPageOrder
          description: Sort direction. Defaults to descending.
          schema: { type: string, enum: [asc, dsc] }
      responses:
        "200":
          {
            description: Paginated list of model versions.,
            content:
              {
                application/json:
                  {
                    schema:
                      { $ref: "#/components/schemas/OdModelVersionFindResult" },
                  },
              },
          }
    post:
      summary: Create a model version (starts dataset prep)
      description: |
        Validates that `model` exists in the namespace, derives `dataset_labels`
        from the selected datasets, assigns the next `version_code` for that
        model, stamps `creator` from the JWT, sets `status: initiated` and
        enqueues the dataset-prep job. If the queue call fails the request
        fails (the version stays `initiated`).
      operationId: createAiObjectDetectionModelVersion
      requestBody:
        {
          required: true,
          content:
            {
              application/json:
                {
                  schema:
                    { $ref: "#/components/schemas/OdModelVersionCreateBody" },
                },
            },
        }
      responses:
        "201":
          description: "The created version with `dataset`, `model` and `dataset_labels` populated in place; the prep job is queued (`status` is still `initiated` in the response)."
          content:
            application/json:
              schema:
                { $ref: "#/components/schemas/OdModelVersionCreateResult" }
        "400":
          {
            description: "`model` missing or not found in the namespace, or `dataset` empty.",
          }
  /ai-object-detection-model-version/{id}:
    get:
      summary: Get a model version by id
      operationId: getAiObjectDetectionModelVersion
      parameters:
        - { in: path, name: id, required: true, schema: { type: string } }
        - in: query
          name: populatedKeys
          description: Refs to populate (same semantics as on find).
          schema:
            type: array
            items:
              type: string
              enum:
                [
                  model,
                  train_data,
                  dataset,
                  dataset_labels,
                  confusion_matrix_normalized,
                  args,
                  confusion_matrix,
                  F1_curve,
                  labels_correlogram,
                  labels,
                  P_curve,
                  PR_curve,
                  R_curve,
                  results_csv,
                  results,
                  val_batch0_labels,
                  val_batch0_pred,
                  weight_best,
                  weight_last,
                ]
      responses:
        "200":
          {
            description: The model version (with metrics/artifacts once trained).,
            content:
              {
                application/json:
                  {
                    schema:
                      { $ref: "#/components/schemas/OdModelVersionSchema" },
                  },
              },
          }
        "400": { description: No version with that id. }
    delete:
      summary: Soft-delete a model version
      description: "Sets `disabled: true`."
      operationId: removeAiObjectDetectionModelVersion
      parameters:
        [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200":
          {
            description: The version after soft-deletion.,
            content:
              {
                application/json:
                  {
                    schema:
                      { $ref: "#/components/schemas/OdModelVersionSchema" },
                  },
              },
          }
components:
  securitySchemes:
    ApiKeyAuth:
      {
        type: apiKey,
        in: header,
        name: api-key,
        description: "Server-issued API key. Also `x-api-key` header or `?apiKey=` query.",
      }
    JwtAuth:
      {
        type: apiKey,
        in: header,
        name: Authorization,
        description: "Raw JWT — no `Bearer ` prefix. From `POST /authenticate`.",
      }
  schemas:
    OdModelVersionCreator:
      type: object
      description: Creator stamp taken from the JWT; only admins create versions.
      properties:
        _id: { type: string }
        type: { type: string, enum: [admin] }
        admin: { type: string }
        name: { type: string }
    OdModelVersionAnnotationPerLabel:
      type: object
      description: Per-label box count written into the YOLO label files — the class balance of the export.
      properties:
        label_id: { type: string }
        name: { type: string }
        size: { type: integer }
    OdModelVersionTrainSettings:
      type: object
      additionalProperties: true
      description: "Free-form Ultralytics train args. The HUB endpoint reads `epochs` and `imgsz` from the first element."
      properties:
        epochs: { type: integer }
        imgsz: { type: integer }
        batch: { type: integer }
    OdModelVersionMetrics:
      type: object
      description: |
        Headline metrics of the BEST epoch (highest mAP50-95 — the epoch
        `best.pt` was saved from), snapshotted when the weights upload
        completes training. The full per-epoch series lives in
        `ai-object-detection-model-version-epoch`; this copy exists so lists
        can show mAP without fetching a whole run. Absent on versions
        trained before this snapshot existed.
      properties:
        mAP50: { type: number }
        mAP50_95: { type: number }
        precision: { type: number }
        recall: { type: number }
        best_epoch:
          { type: integer, description: "`_index` of the best epoch." }
        epochs_reported: { type: integer }
    OdModelVersionModelStats:
      type: object
      description: Architecture cost reported by Ultralytics during training.
      properties:
        parameters: { type: integer }
        GFLOPs: { type: number }
        speed_PyTorch_ms: { type: number }
    OdModelVersionSchema:
      type: object
      description: "Object-detection model version. Prep-job outputs (`train_data`, sizes, per-label counts) appear as the job runs; weights, plots and metrics appear after training."
      properties:
        _id: { type: string }
        version_code:
          {
            type: integer,
            description: Server-assigned,
            1-based,
            increments per model within the namespace.,
          }
        model: { type: string, description: "`ai-object-detection-model` id." }
        dataset:
          {
            type: array,
            items: { type: string },
            description: "`ai-object-detection-dataset` ids.",
          }
        dataset_labels:
          {
            type: array,
            items: { type: string },
            description: Server-derived union of the datasets' labels.,
          }
        status:
          type: string
          enum:
            [
              initiated,
              setting_alarm_completed,
              building_folder_in_progress,
              building_folder_failed,
              building_yaml_file_in_progress,
              building_yaml_file_completed,
              stream_tasks_in_progress,
              stream_tasks_failed,
              compress_folder_in_progress,
              compress_folder_failed,
              uploading_zip_to_s3_in_progress,
              uploading_zip_to_s3_failed,
              zip_file_completed,
              trained,
            ]
          description: "Dataset-prep lifecycle, then `trained` once `ul-hub` receives the weights."
        suppress_exceeding_sizes:
          {
            type: boolean,
            description: Reserved flag; the split logic currently clamps shortfalls at 0 regardless.,
          }
        train_data:
          {
            type: string,
            description: Media id of the YOLO dataset zip produced by the prep job.,
          }
        creator: { $ref: "#/components/schemas/OdModelVersionCreator" }
        task_selection_mode:
          {
            type: string,
            enum: [smart, all],
            description: "`smart` = only manual / auto_edited annotation groups; `all` also includes untouched auto groups.",
          }
        tasks_size:
          {
            type: integer,
            description: Tasks that produced an image + label file in the zip.,
          }
        validation_size: { type: number }
        test_size: { type: number }
        train_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdModelVersionTrainSettings" },
          }
        annotations_per_label:
          {
            type: array,
            items:
              { $ref: "#/components/schemas/OdModelVersionAnnotationPerLabel" },
            description: Sorted by `size` descending.,
          }
        annotations_size:
          { type: integer, description: Boxes written into the label files. }
        confusion_matrix_normalized:
          { type: string, description: Media id (uploaded after training). }
        args:
          { type: string, description: "Media id of the run's `args.yaml`." }
        confusion_matrix: { type: string, description: Media id. }
        F1_curve: { type: string, description: Media id. }
        labels_correlogram: { type: string, description: Media id. }
        labels: { type: string, description: Media id. }
        P_curve: { type: string, description: Media id. }
        PR_curve: { type: string, description: Media id. }
        R_curve: { type: string, description: Media id. }
        results_csv: { type: string, description: Media id. }
        results: { type: string, description: Media id. }
        val_batch0_labels: { type: string, description: Media id. }
        val_batch0_pred: { type: string, description: Media id. }
        initial_weight:
          {
            type: string,
            description: "Base weights the run starts from (e.g. `yolov8n.pt` or a weights URL).",
          }
        weight_best:
          {
            type: string,
            description: "Media id of `best.pt` — set by the `ul-hub` weights upload.",
          }
        weight_last: { type: string, description: "Media id of `last.pt`." }
        metrics: { $ref: "#/components/schemas/OdModelVersionMetrics" }
        model_stats: { $ref: "#/components/schemas/OdModelVersionModelStats" }
        model_populated:
          {
            type: object,
            nullable: true,
            additionalProperties: true,
            description: "Present when `populatedKeys[]` includes `model`.",
          }
        dataset_populated:
          {
            type: array,
            items: { type: object, additionalProperties: true },
            description: "Present when `populatedKeys[]` includes `dataset`.",
          }
        dataset_labels_populated:
          {
            type: array,
            items: { type: object, additionalProperties: true },
            description: "Present when `populatedKeys[]` includes `dataset_labels`.",
          }
        train_data_populated:
          {
            type: object,
            nullable: true,
            additionalProperties: true,
            description: "Media document; present when `populatedKeys[]` includes `train_data`. Every other media-backed field has the same `<field>_populated` counterpart.",
          }
        disabled: { type: boolean }
        _errors:
          {
            type: array,
            items: { type: object, additionalProperties: true },
            description: Adapted errors pushed by the async prep pipeline.,
          }
        company_namespace: { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    OdModelVersionCreateBody:
      type: object
      description: "Body for creating a version. The tenant key (`company_namespace`) is optional for SDK callers and is otherwise injected from the caller's session. `version_code`, `creator`, `status` and `dataset_labels` are server-assigned."
      required: [model, dataset, initial_weight]
      properties:
        model:
          {
            type: string,
            description: "`ai-object-detection-model` id; must exist in the namespace.",
          }
        dataset:
          {
            type: array,
            minItems: 1,
            items: { type: string },
            description: "One or more `ai-object-detection-dataset` ids.",
          }
        dataset_labels:
          {
            type: array,
            items: { type: string },
            description: Ignored — recomputed by the server as the union of the datasets' labels.,
          }
        initial_weight:
          { type: string, description: "Base weights, e.g. `yolov8n.pt`." }
        task_selection_mode: { type: string, enum: [smart, all], default: all }
        suppress_exceeding_sizes: { type: boolean, default: false }
        train_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdModelVersionTrainSettings" },
          }
        validation_size: { type: number }
        test_size: { type: number }
        company_namespace:
          type: array
          items: { type: string }
          description: Optional tenant namespace override for SDK callers.
    OdModelVersionCreateResult:
      allOf:
        - $ref: "#/components/schemas/OdModelVersionSchema"
        - type: object
          description: "`create` returns the version with these three refs populated in place."
          properties:
            model:
              {
                type: object,
                additionalProperties: true,
                description: "The `ai-object-detection-model` document.",
              }
            dataset:
              {
                type: array,
                items: { type: object, additionalProperties: true },
                description: "The `ai-object-detection-dataset` documents.",
              }
            dataset_labels:
              type: array
              items:
                type: object
                properties:
                  _id: { type: string }
                  name: { type: string }
    OdModelVersionFindResult:
      type: object
      properties:
        data:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdModelVersionSchema" },
          }
        total_result: { type: number }
        current_count: { type: number }
        total_pages: { type: number }
        current_page: { type: number }
        per_page: { type: number }
        first_page_url: { type: string }
        last_page_url: { type: string }
        next_page_url: { type: string, nullable: true }
        prev_page_url: { type: string, nullable: true }
        path: { type: string }
