openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Label
  version: 1.0.0
  description: |
    Defines **object-detection label classes** — a named class (with an optional
    annotation `keyboard_shortcut` and a reference crop `media_photo`) linked to
    the products / variants / brands / categories / sub-categories / groups it
    represents. Labels are the vocabulary every other object-detection service
    speaks: datasets group them (`ai-object-detection-dataset`), task
    annotations point at them, categories and segments list them, and the label
    report (`ai-object-detection-label-report`) measures their annotation
    health.

    **Dims reclassifier inputs.** `label_group` places the label in a family of
    sibling labels (size / flavour variants of one line); a detection may only
    be re-labelled BETWEEN labels sharing a group. `physical_size` (`w_cm`,
    `h_cm`, centimetres) is the expected front-face size — optional, and the
    reclassifier skips labels without it. It can be filled by hand or from the
    label report's `mode=dims` smart analyzer.

    **Who calls it.** Back-office admins (label screen). Scoped by
    `company_namespace[]` (injected from the caller's session); `name` is
    unique per namespace and must match `^[a-zA-Z_][a-zA-Z0-9_\s]*$`.
    Soft-delete via `disabled`; `DELETE` is blocked while any dataset still
    lists the label in `dataset_labels`. Creating or updating a label with a
    `media_photo` links that media document to the label so it is not swept as
    orphaned media. `PATCH` is not allowed (400).

    **Population.** `populatedKeys[]` on find/get embeds the linked documents
    under `<field>_populated` (`label_group`, `media_photo`, `variants`,
    `products`, `product_categories`, `product_subcategories`,
    `product_brands`, `product_groups`).
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-label:
    get:
      summary: Find labels
      operationId: findAiObjectDetectionLabels
      parameters:
        - in: query
          name: _id
          description: Filter by label `_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` (single value or list).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: search
          description: Case-insensitive substring / regex match on `name`.
          schema: { type: string }
          example: heinz
        - in: query
          name: label_group
          description: Filter by `ai-object-detection-label-group` id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: variants
          description: Filter by linked variant id(s) — pass once or as `?variants[]=`.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: products
          description: Filter by linked product id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: product_categories
          description: Filter by linked product category id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: product_subcategories
          description: Filter by linked product sub-category id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: product_brands
          description: Filter by linked product brand id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: product_groups
          description: Filter by linked product group id(s).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: disabled
          description: "`true` returns only soft-deleted labels, `false` only live ones. Omitted returns both."
          schema: { type: boolean }
        - in: query
          name: from_updatedAt
          description: Return labels with `updatedAt` on/after this Unix timestamp (ms); snapped to start of day unless `exact_time=true`.
          schema: { type: number }
        - in: query
          name: to_updatedAt
          description: Return labels with `updatedAt` on/before this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: from_createdAt
          description: Return labels with `createdAt` on/after this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: to_createdAt
          description: Return labels with `createdAt` on/before this Unix timestamp (ms).
          schema: { type: number }
        - in: query
          name: populatedKeys
          description: Joins to embed under `<field>_populated`.
          schema:
            type: array
            items:
              type: string
              enum:
                - label_group
                - media_photo
                - variants
                - products
                - product_categories
                - product_subcategories
                - product_brands
                - product_groups
        - in: query
          name: per_page
          description: Page size (capped by the server's pagination max).
          schema: { type: integer, minimum: 1 }
          example: 20
        - in: query
          name: page
          description: 1-based page number.
          schema: { type: integer, minimum: 1 }
          example: 1
        - in: query
          name: sort
          description: Field to sort by. Defaults to `_id`.
          schema: { type: string, default: _id }
        - in: query
          name: sortPageOrder
          description: Sort direction. Defaults to descending.
          schema: { type: string, enum: [asc, dsc], default: dsc }
      responses:
        "200":
          description: Paginated list of labels.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OdLabelFindResult"
    post:
      summary: Create a label
      description: "Creates a label. When `media_photo` is set the media document is linked to the new label. `name` must be unique per namespace."
      operationId: createAiObjectDetectionLabel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OdLabelCreateBody"
      responses:
        "201":
          description: The created label.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OdLabelSchema"
  /ai-object-detection-label/{id}:
    get:
      summary: Get a label by id
      operationId: getAiObjectDetectionLabel
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
        - in: query
          name: populatedKeys
          description: Joins to embed under `<field>_populated` (same set as on find).
          schema:
            type: array
            items:
              type: string
              enum:
                - label_group
                - media_photo
                - variants
                - products
                - product_categories
                - product_subcategories
                - product_brands
                - product_groups
      responses:
        "200":
          description: The label.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OdLabelSchema"
        "400":
          description: No label with that id in the caller's namespace.
    put:
      summary: Update a label
      description: "Standard put (mongoose `updateOne` with validators). Set `disabled: true` to soft-delete. If the result carries a `media_photo` the media document is (re-)linked to the label."
      operationId: updateAiObjectDetectionLabel
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OdLabelUpdateBody"
      responses:
        "200":
          description: The label after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OdLabelSchema"
        "404":
          description: No label with that id in the caller's namespace.
    delete:
      summary: Soft-delete a label
      description: "Sets `disabled: true`. Rejected with 400 while the label is listed in any dataset's `dataset_labels` — remove it from those datasets first."
      operationId: removeAiObjectDetectionLabel
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The label after soft-deletion.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OdLabelSchema"
        "400":
          description: The label is still associated with one or more datasets, or was not found.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api-key
      description: |
        Server-issued API key. Also accepted via the `x-api-key` header or the
        `?apiKey=` query parameter as fallbacks.
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
      description: |
        Raw JWT in the `Authorization` header — **no `Bearer ` prefix**.
        Obtained from `POST /authenticate` (admin / rep / client login).
  schemas:
    OdLabelPhysicalSize:
      type: object
      description: Expected physical front-face size in centimetres. Each axis accepts 0.1–500.
      properties:
        w_cm: { type: number, minimum: 0.1, maximum: 500 }
        h_cm: { type: number, minimum: 0.1, maximum: 500 }
    OdLabelSchema:
      type: object
      properties:
        _id: { type: string }
        name:
          type: string
          description: "Unique per namespace; must match `^[a-zA-Z_][a-zA-Z0-9_\\s]*$`."
        keyboard_shortcut:
          type: string
          description: "Annotation hot-key — one lower-case letter or digit, excluding `i`, `o` and `_`."
        media_photo:
          type: string
          description: Reference crop media id.
        products: { type: array, items: { type: string } }
        variants: { type: array, items: { type: string } }
        product_brands: { type: array, items: { type: string } }
        product_categories: { type: array, items: { type: string } }
        product_subcategories: { type: array, items: { type: string } }
        product_groups: { type: array, items: { type: string } }
        label_group:
          type: string
          description: "`ai-object-detection-label-group` id — sibling family for the dims reclassifier."
        physical_size:
          $ref: "#/components/schemas/OdLabelPhysicalSize"
        disabled: { type: boolean }
        company_namespace:
          type: array
          items: { type: string }
          description: Tenant key (server-injected).
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    OdLabelCreateBody:
      type: object
      description: "Body for creating a label. `company_namespace` is optional for SDK callers and otherwise injected from the caller's session."
      required: [name]
      properties:
        name:
          type: string
          description: "Unique per namespace; must match `^[a-zA-Z_][a-zA-Z0-9_\\s]*$`."
        keyboard_shortcut:
          type: string
          description: "One lower-case letter or digit, excluding `i`, `o` and `_`."
        media_photo:
          type: string
          description: Reference crop media id (linked to the label on save).
        products: { type: array, items: { type: string } }
        variants: { type: array, items: { type: string } }
        product_brands: { type: array, items: { type: string } }
        product_categories: { type: array, items: { type: string } }
        product_subcategories: { type: array, items: { type: string } }
        product_groups: { type: array, items: { type: string } }
        label_group:
          type: string
          description: "`ai-object-detection-label-group` id."
        physical_size:
          $ref: "#/components/schemas/OdLabelPhysicalSize"
        company_namespace:
          type: array
          items: { type: string }
          description: Optional tenant namespace override for SDK callers.
    OdLabelUpdateBody:
      type: object
      description: "Body for updating a label. `company_namespace` is derived from the caller's session — do not send it. Set `disabled: true` to soft-delete."
      properties:
        name: { type: string }
        keyboard_shortcut: { type: string }
        media_photo: { type: string }
        products: { type: array, items: { type: string } }
        variants: { type: array, items: { type: string } }
        product_brands: { type: array, items: { type: string } }
        product_categories: { type: array, items: { type: string } }
        product_subcategories: { type: array, items: { type: string } }
        product_groups: { type: array, items: { type: string } }
        label_group: { type: string }
        physical_size:
          $ref: "#/components/schemas/OdLabelPhysicalSize"
        disabled:
          type: boolean
          description: Soft-delete flag.
    OdLabelFindResult:
      type: object
      description: Standard paginated result envelope.
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/OdLabelSchema"
        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 }
