openapi: 3.1.0
info:
  title: Product Inventory Service
  version: (dev)
  description: Public per-product stock-rows endpoint. The path lives under
    /v1/products even though the implementing service is
    product-inventory-service.
servers:
  - url: https://api-1.test.logitrail.com
    description: Test / Development Server
  - url: https://api-1.logitrail.com
    description: Production Server
paths:
  /v1/products/{productId}/stock-rows:
    get:
      summary: List per-product stock rows
      description: Returns the in-warehouse stock of one of your products, aggregated
        into rows keyed on (arrival_batch_id, best_before_date, batch_number) —
        extended with the reserving order id when include_order_breakdown=true.
        Each row carries available_quantity / reserved_quantity /
        total_quantity, the earliest on-shelf timestamp, and references to the
        inbound shipment and (optionally) the reserving order. Articles that
        have already shipped are excluded.
      operationId: GetProductStockRowsPublic
      security:
        - idp:
            - products:read
      parameters:
        - in: path
          name: productId
          schema:
            type: string
            pattern: ^[a-f0-9]{24}$
            description: Logitrail's internal technical ID of the product (24-char hex).
          required: true
          description: Logitrail's internal technical ID of the product (24-char hex).
        - in: header
          name: x-logitrail-merchant-id
          schema:
            type: string
            minLength: 1
            description: Logitrail's Merchant ID.
          required: true
          description: Logitrail's Merchant ID.
        - in: query
          name: offset
          schema:
            default: 0
            description: Pagination offset into the post-aggregation row list.
            type: integer
            minimum: 0
            maximum: 9007199254740991
          description: Pagination offset into the post-aggregation row list.
        - in: query
          name: limit
          schema:
            default: 100
            description: Pagination page size (1–500). Default 100. Caps the
              post-aggregation row list.
            type: integer
            minimum: 1
            maximum: 500
          description: Pagination page size (1–500). Default 100. Caps the
            post-aggregation row list.
        - in: query
          name: include_order_breakdown
          schema:
            default: false
            description: When true, split rows by the reserving order id (`order.id`). When
              false (default), reserved articles are summed across all reserving
              orders and `order` is null on every row.
            type: boolean
          description: When true, split rows by the reserving order id (`order.id`). When
            false (default), reserved articles are summed across all reserving
            orders and `order` is null on every row.
      responses:
        "200":
          description: Per-product stock rows.
          content:
            application/json:
              schema:
                type: object
                properties:
                  stock_rows:
                    type: array
                    items:
                      type: object
                      properties:
                        arrival_batch_id:
                          anyOf:
                            - type: integer
                              minimum: -9007199254740991
                              maximum: 9007199254740991
                            - type: "null"
                          description: Internal arrival batch id. Null when the underlying article rows
                            have no arrival batch (e.g. legacy rows).
                        best_before_date:
                          anyOf:
                            - type: string
                              format: date
                              pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
                            - type: "null"
                          description: Best-before date (ISO 8601 YYYY-MM-DD) of the arrival batch. Null
                            when the batch carries no expiration date.
                        batch_number:
                          anyOf:
                            - type: string
                            - type: "null"
                          description: Lot / batch number from the arrival batch. Null when the batch
                            carries no batch number.
                        on_shelf_at:
                          anyOf:
                            - type: string
                              format: date-time
                              pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
                            - type: "null"
                          description: Earliest `to_shelf` timestamp for the row's articles, falling back
                            to the earliest `arrived` timestamp when no
                            `to_shelf` movement exists. Null when neither is
                            recorded.
                        inbound_shipment:
                          anyOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  minLength: 1
                                  description: Logitrail's internal inbound shipment ID this row's articles
                                    arrived on.
                              required:
                                - id
                              additionalProperties: false
                            - type: "null"
                          description: Inbound shipment reference. Null when the underlying article rows
                            have no inbound shipment recorded.
                        order:
                          anyOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  minLength: 1
                                  description: Logitrail's internal order ID the row is reserved against.
                              required:
                                - id
                              additionalProperties: false
                            - type: "null"
                          description: Reserving order reference. Null when the row represents
                            currently-available stock or when
                            `include_order_breakdown=false`. Object form leaves
                            room to enrich with merchant_order_id later.
                        total_quantity:
                          type: integer
                          minimum: 0
                          maximum: 9007199254740991
                          description: Total units in this row. Equals `available_quantity +
                            reserved_quantity`.
                        available_quantity:
                          type: integer
                          minimum: 0
                          maximum: 9007199254740991
                          description: Units in this row that are currently not reserved against any
                            order.
                        reserved_quantity:
                          type: integer
                          minimum: 0
                          maximum: 9007199254740991
                          description: Units in this row that are currently reserved. When
                            `include_order_breakdown=true` and the row carries
                            an `order.id`, every unit in the row is reserved
                            against that order.
                      required:
                        - arrival_batch_id
                        - best_before_date
                        - batch_number
                        - on_shelf_at
                        - inbound_shipment
                        - order
                        - total_quantity
                        - available_quantity
                        - reserved_quantity
                      additionalProperties: false
                      title: PublicProductStockRow
                      description: "One aggregated stock row for one of your products. Identity tuple:
                        (arrival_batch_id, best_before_date, batch_number) —
                        extended with the reserving order id when
                        include_order_breakdown=true."
                    description: Per-product stock rows. Sum of `total_quantity` across rows equals
                      the in-warehouse unit count for the product.
                required:
                  - stock_rows
                additionalProperties: false
        "400":
          description: Validation error, see response body for details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: A brief, human readable error message.
                  error_code:
                    type: string
                    const: VALIDATION_ERROR
                    description: Reason of the error.
                  validation_errors:
                    type: array
                    items: {}
                    description: An array of validation error details.
                required:
                  - message
                  - error_code
                  - validation_errors
                additionalProperties: false
  /v1/products/articles:
    get:
      summary: List articles for one of your inbound shipments
      description: Returns the units of one of your inbound shipments, aggregated into
        rows keyed on (product, in_warehouse_status, batch_number,
        best_before_date). in_warehouse_status tells you where each unit is in
        its lifecycle (inbound = expected, arrived = received but not yet
        shelved, on_shelf, picked, …). Each row carries the total_quantity and
        the product (id, sku, name). Filter with in_warehouse_status; paginated
        with offset/limit (up to 500 rows per page). Inbound shipments that do
        not belong to you return an empty list.
      operationId: GetProductArticlesPublic
      security:
        - idp:
            - products:read
      parameters:
        - in: header
          name: x-logitrail-merchant-id
          schema:
            type: string
            minLength: 1
            description: Logitrail's Merchant ID.
          required: true
          description: Logitrail's Merchant ID.
        - in: query
          name: inbound_shipment_id
          schema:
            type: string
            pattern: ^[a-f0-9]{24}$
            description: Inbound shipment ID (24-char hex) whose articles to list. Required.
              Shipments that do not belong to you return an empty list.
          required: true
          description: Inbound shipment ID (24-char hex) whose articles to list. Required.
            Shipments that do not belong to you return an empty list.
        - in: query
          name: in_warehouse_status
          schema:
            description: "Optional in-warehouse status filter — a single value or an array.
              When omitted, every status present on the shipment is returned.
              Allowed values: inbound, arrived, on_shelf, picked, packed,
              sent_out, dropshipped, incident."
            anyOf:
              - type: string
                enum:
                  - inbound
                  - arrived
                  - on_shelf
                  - picked
                  - packed
                  - sent_out
                  - dropshipped
                  - incident
              - minItems: 1
                type: array
                items:
                  type: string
                  enum:
                    - inbound
                    - arrived
                    - on_shelf
                    - picked
                    - packed
                    - sent_out
                    - dropshipped
                    - incident
          description: "Optional in-warehouse status filter — a single value or an array.
            When omitted, every status present on the shipment is returned.
            Allowed values: inbound, arrived, on_shelf, picked, packed,
            sent_out, dropshipped, incident."
        - in: query
          name: offset
          schema:
            default: 0
            description: Pagination offset into the post-aggregation row list.
            type: integer
            minimum: 0
            maximum: 9007199254740991
          description: Pagination offset into the post-aggregation row list.
        - in: query
          name: limit
          schema:
            default: 100
            description: Pagination page size (1–500). Default 100. Caps the
              post-aggregation row list.
            type: integer
            minimum: 1
            maximum: 500
          description: Pagination page size (1–500). Default 100. Caps the
            post-aggregation row list.
      responses:
        "200":
          description: Aggregated article rows for the inbound shipment.
          content:
            application/json:
              schema:
                type: object
                properties:
                  article_rows:
                    type: array
                    items:
                      type: object
                      properties:
                        product:
                          type: object
                          properties:
                            id:
                              type: string
                              pattern: ^[a-f0-9]{24}$
                              description: Product ID (24-char hex).
                            sku:
                              anyOf:
                                - type: string
                                - type: "null"
                              description: Your SKU for the product. Null when no SKU is recorded for the
                                product.
                            name:
                              type: string
                              description: Display name of the product.
                          required:
                            - id
                            - sku
                            - name
                          additionalProperties: false
                          title: PublicProductArticleProduct
                          description: Product identity (id, sku, name) for an aggregated row.
                        in_warehouse_status:
                          type: string
                          enum:
                            - inbound
                            - arrived
                            - on_shelf
                            - picked
                            - packed
                            - sent_out
                            - dropshipped
                            - incident
                          description: "In-warehouse lifecycle status of the units in this row: inbound
                            (expected), arrived (received, not yet shelved),
                            on_shelf, picked, packed, sent_out, dropshipped or
                            incident."
                        batch_number:
                          anyOf:
                            - type: string
                            - type: "null"
                          description: Lot / batch number of the units. Null when no batch number is
                            recorded.
                        best_before_date:
                          anyOf:
                            - type: string
                              format: date
                              pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
                            - type: "null"
                          description: Best-before date (ISO 8601 YYYY-MM-DD) of the units. Null when no
                            expiration date is recorded.
                        total_quantity:
                          type: integer
                          minimum: 0
                          maximum: 9007199254740991
                          description: Number of units aggregated into this row.
                        inbound_shipment:
                          anyOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  minLength: 1
                                  description: The inbound shipment ID the units arrived on.
                              required:
                                - id
                              additionalProperties: false
                            - type: "null"
                          description: Inbound shipment reference. Null when the units have no inbound
                            shipment recorded.
                      required:
                        - product
                        - in_warehouse_status
                        - batch_number
                        - best_before_date
                        - total_quantity
                        - inbound_shipment
                      additionalProperties: false
                      title: PublicProductArticleRow
                      description: "One aggregated article row for one of your inbound shipments.
                        Identity tuple: (product.id, in_warehouse_status,
                        batch_number, best_before_date)."
                    description: Aggregated article rows for the requested inbound shipment. Sum of
                      `total_quantity` across rows equals the shipment's unit
                      count for your merchant (after applying the request's
                      filters).
                required:
                  - article_rows
                additionalProperties: false
        "400":
          description: Validation error, see response body for details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: A brief, human readable error message.
                  error_code:
                    type: string
                    const: VALIDATION_ERROR
                    description: Reason of the error.
                  validation_errors:
                    type: array
                    items: {}
                    description: An array of validation error details.
                required:
                  - message
                  - error_code
                  - validation_errors
                additionalProperties: false
  /v1/products/{productId}/inventory-movements:
    get:
      summary: List inventory movement history for a product
      description: Returns the chronological inventory movement history for one of
        your products. Each entry records a timestamp, movement type (e.g.
        inbound_shipment.to_shelf, order_pickup.picked), article count, and
        references to the related inbound shipment or order when applicable.
        Supports time-range and type filters; paginated with offset/limit (up to
        500 rows per page). Products that do not belong to your merchant return
        an empty list.
      operationId: GetProductInventoryMovements
      security:
        - idp:
            - products:read
      parameters:
        - in: path
          name: productId
          schema:
            type: string
            pattern: ^[a-f0-9]{24}$
            description: Logitrail's internal technical ID of the product (24-char hex).
          required: true
          description: Logitrail's internal technical ID of the product (24-char hex).
        - in: header
          name: x-logitrail-merchant-id
          schema:
            type: string
            minLength: 1
            description: Logitrail's Merchant ID.
          required: true
          description: Logitrail's Merchant ID.
        - in: query
          name: offset
          schema:
            default: 0
            description: Pagination offset into the chronological movement list.
            type: integer
            minimum: 0
            maximum: 9007199254740991
          description: Pagination offset into the chronological movement list.
        - in: query
          name: limit
          schema:
            default: 100
            description: Pagination page size (1–500). Default 100.
            type: integer
            minimum: 1
            maximum: 500
          description: Pagination page size (1–500). Default 100.
        - in: query
          name: ts_min
          schema:
            description: Optional inclusive lower bound on movement `ts` (ISO 8601). Omit
              for "all history".
            type: string
            format: date-time
            pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
          description: Optional inclusive lower bound on movement `ts` (ISO 8601). Omit
            for "all history".
        - in: query
          name: ts_max
          schema:
            description: Optional inclusive upper bound on movement `ts` (ISO 8601). Omit
              for "up to now".
            type: string
            format: date-time
            pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
          description: Optional inclusive upper bound on movement `ts` (ISO 8601). Omit
            for "up to now".
        - in: query
          name: type
          schema:
            description: Optional movement-type filter. Repeat the parameter or supply a
              comma-separated value to filter to multiple types.
            anyOf:
              - type: string
                enum:
                  - custom
                  - dropshipping.received
                  - dropshipping.supplied
                  - inbound_shipment.arrived
                  - inbound_shipment.to_shelf
                  - incident
                  - internal_movement
                  - order_pickup.picked
                  - order.packed
                  - order.quick_handle
                  - product.split
              - minItems: 1
                type: array
                items:
                  type: string
                  enum:
                    - custom
                    - dropshipping.received
                    - dropshipping.supplied
                    - inbound_shipment.arrived
                    - inbound_shipment.to_shelf
                    - incident
                    - internal_movement
                    - order_pickup.picked
                    - order.packed
                    - order.quick_handle
                    - product.split
          description: Optional movement-type filter. Repeat the parameter or supply a
            comma-separated value to filter to multiple types.
        - in: query
          name: order
          schema:
            default: asc
            description: Chronological order. `asc` (default) returns oldest first.
            type: string
            enum:
              - asc
              - desc
          description: Chronological order. `asc` (default) returns oldest first.
      responses:
        "200":
          description: Per-product inventory movement history.
          content:
            application/json:
              schema:
                type: object
                properties:
                  movements:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Internal ProductArticleMovement id (auto-increment, exposed as
                            string).
                        ts:
                          type: string
                          format: date-time
                          pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
                          description: Movement timestamp (ISO 8601).
                        type:
                          type: string
                          enum:
                            - custom
                            - dropshipping.received
                            - dropshipping.supplied
                            - inbound_shipment.arrived
                            - inbound_shipment.to_shelf
                            - incident
                            - internal_movement
                            - order_pickup.picked
                            - order.packed
                            - order.quick_handle
                            - product.split
                          description: Movement type (e.g. inbound_shipment.to_shelf,
                            order_pickup.picked).
                        quantity:
                          type: integer
                          minimum: 0
                          maximum: 9007199254740991
                          description: Number of articles touched by this movement.
                        inbound_shipment:
                          anyOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  minLength: 1
                                  description: Logitrail's internal inbound shipment ID this row's articles
                                    arrived on.
                              required:
                                - id
                              additionalProperties: false
                            - type: "null"
                          description: Inbound shipment reference. Object form leaves room for sender_name
                            etc later.
                        order:
                          anyOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  minLength: 1
                                  description: Logitrail's internal order ID the row is reserved against.
                              required:
                                - id
                              additionalProperties: false
                            - type: "null"
                          description: Reserving order reference. Object form leaves room to enrich with
                            merchant_order_id / our_id later.
                      required:
                        - id
                        - ts
                        - type
                        - quantity
                        - inbound_shipment
                        - order
                      additionalProperties: false
                      description: A single inventory movement for the product.
                    description: Chronological list of movements for the product in the requested
                      `order`.
                required:
                  - movements
                additionalProperties: false
                description: Per-product inventory movement history (paginated).
        "400":
          description: Validation error, see response body for details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: A brief, human readable error message.
                  error_code:
                    type: string
                    const: VALIDATION_ERROR
                    description: Reason of the error.
                  validation_errors:
                    type: array
                    items: {}
                    description: An array of validation error details.
                required:
                  - message
                  - error_code
                  - validation_errors
                additionalProperties: false
components:
  securitySchemes:
    idp:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://idp.logitrail.com/realms/logitrail/protocol/openid-connect/token
          scopes:
            products:read: Read product data
