openapi: 3.0.3
info:
  title: Mihwar integrations
  version: 1.2.0
  description: |
    Machine-facing **ingestion** (API key) and **outbound webhook** contract (customer HTTPS endpoint).

    **Ingestion auth:** `Authorization: Bearer rn_live_…` (organization API key from Settings → Integrations).

    **Idempotency:** Optional `Idempotency-Key` on ingestion POST; duplicate keys return the same JSON status and body as the first successful request (use when retrying network failures so lines are not inserted twice).

    **Import batches:** Each successful ingestion creates an `ImportBatch` row. Responses include `importBatchId` for traceability (same ML joining behavior as UI CSV imports). Optional `externalSourceId` in the JSON body is copied into batch metadata for ERP correlation.

    **Bulk ingestion:** Up to **5000** lines per request; the server inserts in chunks of 500 rows. Split larger dumps across multiple requests (each may use its own idempotency key).

    **Webhooks:** Mihwar POSTs to URLs you register in the UI. Verify `X-Mihwar-Signature` with HMAC-SHA256 of the **raw** JSON body using your subscription secret.

    **SCIM (Enterprise):** Provision users/groups with `Authorization: Bearer scim_live_…` via `/api/scim/v2/*`.

servers:
  - url: https://app.example.com
    description: Replace with your Mihwar web base URL

paths:
  /api/integrations/v1/ingestion/order-lines:
    post:
      summary: Ingest order-history lines (SKU → catalog item)
      operationId: ingestOrderLines
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
          example: Bearer rn_live_xxxxxxxx
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IngestOrderLinesRequest'
            example:
              periodKind: DAY
              lines:
                - sku: COF-1KG
                  orderedQty: 12
                  orderDate: "2026-03-15"
                  deliveredQty: 12
      responses:
        "201":
          description: Lines inserted (unknown SKUs omitted unless `autoCreateItems=true`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IngestOrderLinesResponse'
        "401":
          description: Missing or invalid API key
        "413":
          description: Request body exceeds max lines (5000) or validation failure from limit
        "429":
          description: Per-key rate limit (default 120 requests/minute per key)

  /api/integrations/v1/inventory/items:
    get:
      summary: List inventory items
      operationId: listIntegrationInventoryItems
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: search
          in: query
          schema:
            type: string
        - name: includeArchived
          in: query
          schema:
            type: boolean
        - name: updatedAfter
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Paginated inventory list

  /api/integrations/v1/inventory/items/{id}:
    get:
      summary: Get inventory item by id
      operationId: getIntegrationInventoryItem
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Item detail
        "404":
          description: Item not found

  /api/integrations/v1/orders:
    get:
      summary: List purchase orders
      operationId: listIntegrationOrders
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
            enum: [DRAFT, SUBMITTED, APPROVED, RECEIVED, REJECTED, FULFILLED]
        - name: createdAfter
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Paginated order list

  /api/integrations/v1/orders/{id}:
    get:
      summary: Get order by id
      operationId: getIntegrationOrder
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Order detail
        "404":
          description: Order not found

  /api/integrations/v1/predictions:
    get:
      summary: List generated predictions
      operationId: listIntegrationPredictions
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: itemId
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Paginated prediction list

  /api/settings/webhooks:
    post:
      summary: Create webhook subscription (browser session + admin; not API-key)
      description: |
        Registers a customer URL. Mihwar delivers events with POST and headers below.
        Retries: asynchronous queue with exponential backoff and delivery attempt logging.
      operationId: createWebhookSubscription
      responses:
        "201":
          description: Created (secret shown once in app UI)

  /api/scim/v2/Users:
    get:
      summary: List SCIM users (Enterprise)
      operationId: scimListUsers
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
          example: Bearer scim_live_xxxxxxxx
        - name: startIndex
          in: query
          schema:
            type: integer
            minimum: 1
        - name: count
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - name: filter
          in: query
          schema:
            type: string
            example: userName eq "user@example.com"
      responses:
        "200":
          description: SCIM list response
    post:
      summary: Create SCIM user (Enterprise)
      operationId: scimCreateUser
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              required: [userName]
              properties:
                userName:
                  type: string
                  format: email
                active:
                  type: boolean
                name:
                  type: object
                  properties:
                    formatted:
                      type: string
      responses:
        "201":
          description: SCIM user created

  /api/scim/v2/Users/{id}:
    get:
      summary: Get SCIM user
      operationId: scimGetUser
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SCIM user
    patch:
      summary: Patch SCIM user
      operationId: scimPatchUser
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
      responses:
        "200":
          description: SCIM user updated
    delete:
      summary: Deprovision SCIM user
      operationId: scimDeleteUser
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: User deprovisioned

  /api/scim/v2/Groups:
    get:
      summary: List SCIM groups (mapped to roles)
      operationId: scimListGroups
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SCIM group list
    post:
      summary: Create SCIM group (role)
      operationId: scimCreateGroup
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
              required: [displayName]
              properties:
                displayName:
                  type: string
      responses:
        "201":
          description: SCIM group created

  /api/scim/v2/Groups/{id}:
    get:
      summary: Get SCIM group
      operationId: scimGetGroup
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SCIM group resource
    patch:
      summary: Patch SCIM group
      operationId: scimPatchGroup
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              type: object
      responses:
        "200":
          description: SCIM group updated
    delete:
      summary: Delete SCIM group
      operationId: scimDeleteGroup
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: SCIM group deleted

components:
  schemas:
    IngestOrderLinesRequest:
      type: object
      required: [lines]
      properties:
        periodKind:
          type: string
          enum: [DAY, WEEK, MONTH, CUSTOM]
          default: CUSTOM
        externalSourceId:
          type: string
          maxLength: 256
          description: Optional ERP export id or batch label (stored on ImportBatch for audit)
        autoCreateItems:
          type: boolean
          default: false
          description: Auto-create missing SKUs in inventory before inserting history lines.
        lines:
          type: array
          maxItems: 5000
          minItems: 1
          items:
            type: object
            required: [sku, orderedQty, orderDate]
            properties:
              sku:
                type: string
                description: Must match InventoryItem.sku in the tenant
              itemName:
                type: string
                description: Used when auto-creating unknown SKUs.
              orderedQty:
                type: number
                exclusiveMinimum: 0
              orderDate:
                type: string
                description: YYYY-MM-DD or ISO-8601 datetime
              deliveredQty:
                type: number
                minimum: 0
                description: Defaults to orderedQty when omitted

    IngestOrderLinesResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            inserted:
              type: integer
            importBatchId:
              type: string
              description: Import batch row tying these lines to ML date expansion / audit (same as UI imports)
            autoCreatedItems:
              type: integer
            skippedUnknownSku:
              type: integer
            missingSkus:
              type: array
              items:
                type: string
            skippedBadDate:
              type: integer
            batchedInserts:
              type: integer
              description: Number of DB chunks (500 rows each) used for this request

    WebhookDeliveryEnvelope:
      type: object
      required: [event, payload, sentAt]
      description: |
        JSON body Mihwar POSTs to your webhook URL. Compute HMAC-SHA256 over the **exact** UTF-8 body bytes
        with your subscription secret; header `X-Mihwar-Signature` is `sha256=<hex digest>`.
      properties:
        event:
          type: string
          enum:
            - order.created
            - order.status_changed
            - order.fulfilled
            - checklist.completed
            - checklist.missed
            - inventory.low_stock
            - inventory.stockout
            - inventory.stockout.resolved
            - import.completed
          example: order.status_changed
        payload:
          type: object
          description: Event-specific fields
          example:
            orderId: clxxxxxxxxxxxxxxxx
            status: SUBMITTED
        sentAt:
          type: string
          format: date-time
          description: ISO-8601 timestamp when Mihwar sent the request

    WebhookOrderStatusPayload:
      type: object
      description: Example payload for `order.status_changed`
      properties:
        orderId:
          type: string
        status:
          type: string
          enum: [SUBMITTED, APPROVED, RECEIVED, REJECTED, FULFILLED]

    WebhookLowStockPayload:
      type: object
      description: Example payload for `inventory.low_stock`
      properties:
        itemId:
          type: string
        itemName:
          type: string
        sku:
          type: string
        currentStock:
          type: number

  examples:
    WebhookOrderStatus:
      summary: order.status_changed
      value:
        event: order.status_changed
        payload:
          orderId: clxxxxxxxxxxxxxxxx
          status: APPROVED
        sentAt: "2026-03-30T12:00:00.000Z"
    WebhookLowStock:
      summary: inventory.low_stock
      value:
        event: inventory.low_stock
        payload:
          itemId: clitemxxxxxxxx
          itemName: Arabica Coffee Beans
          sku: COF-1KG
          currentStock: 8
        sentAt: "2026-03-30T12:05:00.000Z"
    WebhookChecklistMissed:
      summary: checklist.missed
      value:
        event: checklist.missed
        payload:
          checklistInstanceId: clinstxxxxxxxx
          templateId: cltplxxxxxxxx
          templateTitle: Daily opening checks
          dueDate: "2026-03-30T09:00:00.000Z"
        sentAt: "2026-03-30T12:10:00.000Z"
    WebhookStockout:
      summary: inventory.stockout
      value:
        event: inventory.stockout
        payload:
          itemId: clitemxxxxxxxx
          previousStock: 2
          nextStock: 0
          source: inventory_patch
        sentAt: "2026-03-30T12:15:00.000Z"

  headers:
    X-Mihwar-Event:
      description: Same as `event` in body; useful for routers
      schema:
        type: string
    X-Mihwar-Signature:
      description: 'HMAC-SHA256 of raw body, format `sha256=<hex>`'
      schema:
        type: string
        example: sha256=abc123...
