openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Model
  version: 1.0.0
  description: |
    Configures an **object-detection ML model** — its train/predict settings
    (`train_settings[]` / `predict_settings[]`), train/validation split
    (`test_size` / `validation_size`), and its `current_model_version`. Versions
    are managed via `ai-object-detection-model-version`.

    **`current_model_version` is the resolution target for every unpinned
    ("latest") reference** — `ai-object-detection-dataset.default_model_version`
    when null, category `model_settings` items with no `model_version`, and the
    inference lambda's fallback. It **auto-advances** to the newest version each
    time one finishes training (its weights upload), forward-only, so "latest"
    always tracks the most recently trained version without manual promotion.
    Populate it with `populatedKeys[]=current_model_version`: the version
    document is returned under `current_model_version_populated` with its
    `weight_best` / `weight_last` / `train_data` / `confusion_matrix` /
    `confusion_matrix_normalized` media documents populated in place (the
    inference lambda reads these for the XAI confusion-matrix part).

    **Settings semantics.** `train_settings[0]` (`epochs`, `imgsz`, ...) is what
    the Ultralytics HUB endpoint hands to the trainer; `predict_settings[0]`
    (`conf`, `iou`, `agnostic_nms`) are the inference defaults a caller can
    override per request. `validation_size` must be in (0.05, 0.4) and
    `test_size` in (0, 0.2); the model-version prep job uses them to split
    tasks into train/val/test. `name` must match `^[a-zA-Z_][a-zA-Z0-9_\s]*$`
    and is unique per namespace.

    **Who calls it.** Back-office admins. Scoped by `company_namespace[]`;
    soft-delete via `disabled`. `PATCH` is not allowed (400).
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-model:
    get:
      summary: Find models
      operationId: findAiObjectDetectionModels
      parameters:
        - in: query
          name: _id
          description: "Filter by model `_id`. Pass once or as `?_id[]=...` for multiple."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: name
          description: Exact-match on `name` (one or many).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: current_model_version
          description: Filter by the currently promoted version id (one or many).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: search
          description: Case-insensitive regex match on `name` (whitespace matches any run of characters).
          schema: { type: string }
        - in: query
          name: disabled
          description: Include soft-deleted models. Defaults to `false`.
          schema: { type: boolean, default: false }
        - in: query
          name: from_updatedAt
          description: Only models 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 models with `updatedAt` on/before this Unix timestamp (ms), end of that day.
          schema: { type: number }
        - in: query
          name: from_createdAt
          description: Only models with `createdAt` on/after this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: to_createdAt
          description: Only models with `createdAt` on/before this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: populatedKeys
          description: "Refs to populate. `current_model_version` is returned under `current_model_version_populated` (with its weight / train-data / confusion-matrix media populated in place); the original field keeps the id."
          schema:
            type: array
            items:
              type: string
              enum: [current_model_version]
        - 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 models.,
            content:
              {
                application/json:
                  {
                    schema: { $ref: "#/components/schemas/OdModelFindResult" },
                  },
              },
          }
    post:
      summary: Create a model
      operationId: createAiObjectDetectionModel
      requestBody:
        {
          required: true,
          content:
            {
              application/json:
                { schema: { $ref: "#/components/schemas/OdModelCreateBody" } },
            },
        }
      responses:
        "201":
          {
            description: The created model.,
            content:
              {
                application/json:
                  { schema: { $ref: "#/components/schemas/OdModelSchema" } },
              },
          }
  /ai-object-detection-model/{id}:
    get:
      summary: Get a model by id
      operationId: getAiObjectDetectionModel
      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: [current_model_version]
      responses:
        "200":
          {
            description: The model.,
            content:
              {
                application/json:
                  { schema: { $ref: "#/components/schemas/OdModelSchema" } },
              },
          }
        "400": { description: No model with that id. }
    put:
      summary: Update a model
      description: "Full-document style update (`updateOne` with validators). Set `disabled: true` to soft-delete."
      operationId: updateAiObjectDetectionModel
      parameters:
        [{ in: path, name: id, required: true, schema: { type: string } }]
      requestBody:
        {
          required: true,
          content:
            {
              application/json:
                { schema: { $ref: "#/components/schemas/OdModelUpdateBody" } },
            },
        }
      responses:
        "200":
          {
            description: The model after the update.,
            content:
              {
                application/json:
                  { schema: { $ref: "#/components/schemas/OdModelSchema" } },
              },
          }
        "404": { description: No model with that id. }
    delete:
      summary: Soft-delete a model
      description: "Sets `disabled: true`."
      operationId: removeAiObjectDetectionModel
      parameters:
        [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200":
          {
            description: The model after soft-deletion.,
            content:
              {
                application/json:
                  { schema: { $ref: "#/components/schemas/OdModelSchema" } },
              },
          }
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:
    OdTrainSettings:
      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 }
    OdPredictSettings:
      type: object
      additionalProperties: true
      description: "Free-form predict args. Inference reads `conf`, `iou` and `agnostic_nms` from the first element as defaults."
      properties:
        conf: { type: number }
        iou: { type: number }
        agnostic_nms: { type: boolean }
    OdModelSchema:
      type: object
      properties:
        _id: { type: string }
        name:
          {
            type: string,
            description: "Must match `^[a-zA-Z_][a-zA-Z0-9_\\s]*$`; unique per namespace.",
          }
        current_model_version:
          {
            type: string,
            description: "Auto-advanced (forward-only) to the newest trained version's `_id`.",
          }
        current_model_version_populated:
          type: object
          nullable: true
          additionalProperties: true
          description: "Present when `populatedKeys[]` includes `current_model_version` — the `ai-object-detection-model-version` document with `weight_best`, `weight_last`, `train_data`, `confusion_matrix` and `confusion_matrix_normalized` populated as media documents."
        train_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdTrainSettings" },
          }
        predict_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdPredictSettings" },
          }
        test_size:
          {
            type: number,
            description: "Test split ratio, exclusive range (0, 0.2). Default 0.05.",
          }
        validation_size:
          {
            type: number,
            description: "Validation split ratio, exclusive range (0.05, 0.4). Default 0.25.",
          }
        disabled: { type: boolean }
        company_namespace: { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    OdModelCreateBody:
      type: object
      description: "Body for creating a model. The tenant key (`company_namespace`) is optional for SDK callers and is otherwise injected from the caller's session."
      required: [name]
      properties:
        name:
          {
            type: string,
            description: "Must match `^[a-zA-Z_][a-zA-Z0-9_\\s]*$`.",
          }
        train_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdTrainSettings" },
          }
        predict_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdPredictSettings" },
          }
        current_model_version:
          {
            type: string,
            description: Normally left unset — advanced by the server when a version finishes training.,
          }
        test_size:
          {
            type: number,
            minimum: 0,
            maximum: 0.2,
            description: Exclusive bounds.,
          }
        validation_size:
          {
            type: number,
            minimum: 0.05,
            maximum: 0.4,
            description: Exclusive bounds.,
          }
        company_namespace:
          type: array
          items: { type: string }
          description: Optional tenant namespace override for SDK callers.
    OdModelUpdateBody:
      type: object
      description: "Body for updating a model. `company_namespace` is derived from the caller's session — do not send it. Set `disabled: true` to soft-delete."
      properties:
        name: { type: string }
        train_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdTrainSettings" },
          }
        predict_settings:
          {
            type: array,
            items: { $ref: "#/components/schemas/OdPredictSettings" },
          }
        current_model_version: { type: string }
        test_size: { type: number }
        validation_size: { type: number }
        disabled: { type: boolean }
    OdModelFindResult:
      type: object
      properties:
        data:
          { type: array, items: { $ref: "#/components/schemas/OdModelSchema" } }
        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 }
