openapi: 3.0.0
info:
  title: Stock reservations API
  version: 0.1.0
  x-audience: internal-team
  x-api-id: stock-reservations-092026
  x-lint-ignore:
    - provide-domain-gateway-integration
  description: >
    Places the stock holds an order needs: a reservation against current

    [ATP](https://docs.newstore.com/docs/glossary#available-to-promise), and a
    future

    allocation for a pre-order item, which has no current stock to reserve.


    Both operations are idempotent per sales order item. An item that already
    holds a

    reservation or a future allocation keeps the one it has and is reported as
    placed,

    so a caller retrying after a timeout cannot double-place a hold.


    Neither operation falls back to the other. An item reported as not placed
    holds no

    stock, and the caller decides what that means for the order.
  contact:
    name: team-order-management
    url: https://goodscloud.atlassian.net/wiki/spaces/HQ/overview
    email: team-order-management@newstore.com
servers:
  - url: http://stock-http.team-order-management.svc.cluster.local:8000
    description: The stock service inside the team-order-management namespace.
tags:
  - name: future-allocations
    description: Holds placed against future stock for a sales order's pre-order items.
  - name: reservations
    description: Holds placed against current stock for a sales order's items.
paths:
  /reservations:
    post:
      summary: Reserve current stock for a sales order
      operationId: createReservations
      tags:
        - reservations
      x-lint-ignore:
        - secure-endpoints
        - owasp-protection-global-unsafe
      description: >
        Reserves current stock for the given items of one sales order, holding
        it until the

        reservation expires.


        Reserves as much as it can: an item with no current stock at its
        fulfillment node

        comes back in `non_reserved_item_ids` and the response is still a 200,
        because a

        partly reserved order is a normal outcome rather than a failure. Items
        already

        holding a reservation are returned in `reserved_items` without being
        reserved again.
      requestBody:
        required: true
        description: The sales order and the items to reserve stock for.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateReservationsRequest'
            examples:
              Reserve two items at one fulfillment node:
                value:
                  sales_order_uuid: 8e3ba7ef-b3d3-4c40-a05a-9a83f6f6b6a4
                  items:
                    - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
                      product_id: NS-SHIRT-BLUE-M
                      fulfillment_node_id: US01
                    - sales_order_item_id: b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
                      product_id: NS-SHIRT-BLUE-L
                      fulfillment_node_id: US01
      responses:
        '200':
          description: >
            The reservation was attempted for every item. Items that could be
            reserved are in

            `reserved_items`, the rest in `non_reserved_item_ids`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateReservationsResponse'
              example:
                reserved_items:
                  - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
                    reservation_id: 6f1c1f6e-9a4a-4a7e-b0a2-2f9c4d3e8b10
                non_reserved_item_ids:
                  - b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
        '400':
          description: The request body is malformed or fails validation.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                messages:
                  - 'sales_order_uuid: sales_order_uuid is required'
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        '409':
          description: >
            The reservation would have taken a product's availability at a
            fulfillment node

            below zero, so no hold was placed for any item. `error_code` is
            `negative_atp`.


            The fulfillment node the items were requested at decides which
            availability is

            checked, so the same items at another node, or with no node given,
            can succeed.

            Retrying is worthwhile: the usual cause is a concurrent change to
            that node's

            availability.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                message: Error while reserving items.
                error_code: negative_atp
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        '500':
          description: The reservation could not be attempted.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                message: Error while reserving items.
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        default:
          description: Unexpected Error
  /future-allocations:
    post:
      summary: Allocate a sales order's pre-order items against future stock
      operationId: createFutureAllocations
      tags:
        - future-allocations
      x-lint-ignore:
        - secure-endpoints
        - owasp-protection-global-unsafe
      description: >
        Allocates the given items of one sales order against future inventory,
        for items sold

        before the product is in stock.


        With `preferred_fulfillment_node_id`, allocates as much as that node's
        future

        availability covers and places the remaining items at other nodes.
        Without it, works

        through the nodes with the highest future availability first. An item
        that no node can

        cover comes back in `unallocated_item_ids`, and the response is still a
        200.


        Items already holding a future allocation keep it and are returned in
        `allocated_items`.
      requestBody:
        required: true
        description: The sales order and the items to allocate against future inventory.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFutureAllocationsRequest'
            examples:
              Allocate a pre-order item, preferring one node:
                value:
                  sales_order_uuid: 8e3ba7ef-b3d3-4c40-a05a-9a83f6f6b6a4
                  preferred_fulfillment_node_id: US01
                  items:
                    - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
                      product_id: NS-COAT-PREORDER-M
      responses:
        '200':
          description: >
            The allocation was attempted for every item. Items that could be
            allocated are in

            `allocated_items`, with the node holding each, and the rest in

            `unallocated_item_ids`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateFutureAllocationsResponse'
              example:
                allocated_items:
                  - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
                    fulfillment_node_id: DC01
                unallocated_item_ids: []
        '400':
          description: The request body is malformed or fails validation.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                messages:
                  - 'sales_order_uuid: sales_order_uuid is required'
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        '409':
          description: >
            The allocation would have taken a product's availability at a
            fulfillment node

            below zero, so no hold was placed for any item. `error_code` is
            `negative_atp`.


            The fulfillment node the items were requested at decides which
            availability is

            checked, so the same items at another node, or with no node given,
            can succeed.

            Retrying is worthwhile: the usual cause is a concurrent change to
            that node's

            availability.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                message: Error while allocating items against future inventory.
                error_code: negative_atp
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        '500':
          description: The allocation could not be attempted.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              example:
                message: Error while allocating items against future inventory.
                request_id: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        default:
          description: Unexpected Error
components:
  schemas:
    CreateReservationsResponse:
      title: CreateReservationsResponse
      description: What the reservation placed, and which items it could not place.
      type: object
      example:
        reserved_items:
          - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
            reservation_id: 6f1c1f6e-9a4a-4a7e-b0a2-2f9c4d3e8b10
        non_reserved_item_ids:
          - b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
      required:
        - reserved_items
        - non_reserved_item_ids
      properties:
        reserved_items:
          description: >-
            The items now holding a reservation, including any placed by an
            earlier call.
          type: array
          maxItems: 2000
          example:
            - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              reservation_id: 6f1c1f6e-9a4a-4a7e-b0a2-2f9c4d3e8b10
          items:
            $ref: '#/components/schemas/ReservedItem'
        non_reserved_item_ids:
          description: >
            The items no reservation was placed for, because their fulfillment
            node had no

            current stock. They hold no stock, and no future allocation is
            attempted for them.
          type: array
          maxItems: 2000
          example:
            - b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
          items:
            description: The uuid of a sales order item.
            type: string
            maxLength: 256
            example: b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
    ReservedItem:
      title: ReservedItem
      description: A sales order item holding a reservation.
      type: object
      required:
        - sales_order_item_id
        - reservation_id
      properties:
        sales_order_item_id:
          description: The uuid of the sales order item.
          type: string
          maxLength: 256
          example: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
        reservation_id:
          description: The id of the reservation holding the stock.
          type: string
          maxLength: 256
          example: 6f1c1f6e-9a4a-4a7e-b0a2-2f9c4d3e8b10
    CreateFutureAllocationsResponse:
      title: CreateFutureAllocationsResponse
      description: What the future allocation placed, and which items it could not place.
      type: object
      example:
        allocated_items:
          - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
            fulfillment_node_id: DC01
        unallocated_item_ids: []
      required:
        - allocated_items
        - unallocated_item_ids
      properties:
        allocated_items:
          description: >
            The items now holding a future allocation, including any placed by
            an earlier call,

            with the fulfillment node holding each.
          type: array
          maxItems: 2000
          example:
            - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              fulfillment_node_id: DC01
          items:
            $ref: '#/components/schemas/AllocatedItem'
        unallocated_item_ids:
          description: >-
            The items no node had enough future availability for. They hold no
            stock.
          type: array
          maxItems: 2000
          items:
            description: The uuid of a sales order item.
            type: string
            maxLength: 256
            example: b9f5c8c4-1c1e-4b2c-8d4b-7e3b2f0d5a22
    AllocatedItem:
      title: AllocatedItem
      description: A sales order item holding a future allocation, and where it is held.
      type: object
      required:
        - sales_order_item_id
        - fulfillment_node_id
      properties:
        sales_order_item_id:
          description: The uuid of the sales order item.
          type: string
          maxLength: 256
          example: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
        fulfillment_node_id:
          description: The fulfillment node holding the future allocation.
          type: string
          maxLength: 256
          example: US01
    Problem:
      title: Problem
      description: >
        An error, as Problem JSON. Both operations answer with the platform's
        legacy error

        fields (`message`, `messages`, `request_id`, `error_code`), which the
        Problem schema

        still carries as deprecated properties.
      type: object
      properties:
        message:
          description: A human readable description of the error.
          type: string
          maxLength: 2048
          deprecated: true
          example: Error while reserving items.
        messages:
          description: >-
            The individual validation failures, when the request body was
            rejected.
          type: array
          maxItems: 100
          deprecated: true
          example:
            - 'sales_order_uuid: sales_order_uuid is required'
          items:
            type: string
            maxLength: 2048
            example: 'sales_order_uuid: sales_order_uuid is required'
        request_id:
          description: >-
            The id of the request, for correlating with the service's logs and
            traces.
          type: string
          maxLength: 256
          deprecated: true
          example: 0f0ba7c1-3e2f-4d6f-8f0c-2f0a2b1d4c55
        error_code:
          description: >
            Identifies the failure where the API distinguishes one.
            `negative_atp`, on a 409,

            is the only value currently returned.
          type: string
          maxLength: 256
          deprecated: true
          example: negative_atp
        type:
          description: >-
            A URI reference identifying the problem type in the context of this
            API.
          type: string
          maxLength: 2048
          default: about:blank
          example: about:blank
        title:
          description: A short summary of the problem type.
          type: string
          maxLength: 2048
          example: Internal Server Error
        status:
          description: The HTTP status code generated for this occurrence of the problem.
          type: integer
          format: int32
          minimum: 100
          maximum: 599
          example: 500
        detail:
          description: >-
            A human readable explanation specific to this occurrence of the
            problem.
          type: string
          maxLength: 2048
          example: Error while reserving items.
        instance:
          description: A URI reference identifying the specific occurrence of the problem.
          type: string
          maxLength: 2048
          example: /reservations
    CreateReservationsRequest:
      type: object
      title: CreateReservationsRequest
      description: Reserve current stock for the items of one sales order.
      required:
        - sales_order_uuid
        - items
      properties:
        sales_order_uuid:
          type: string
          description: The uuid of the sales order the items belong to.
          minLength: 1
          maxLength: 256
          example: 8e3ba7ef-b3d3-4c40-a05a-9a83f6f6b6a4
        items:
          type: array
          minItems: 1
          maxItems: 2000
          items:
            type: object
            description: A sales order item to reserve stock for.
            required:
              - sales_order_item_id
              - product_id
            properties:
              sales_order_item_id:
                type: string
                description: The uuid of the sales order item.
                minLength: 1
                maxLength: 256
                example: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id:
                type: string
                description: The id of the product to reserve.
                minLength: 1
                maxLength: 256
                example: NS-SHIRT-BLUE-M
              fulfillment_node_id:
                type: string
                description: >-
                  The fulfillment node to reserve at. When omitted stock picks
                  one.
                maxLength: 256
                example: US01
            example:
              sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id: NS-SHIRT-BLUE-M
              fulfillment_node_id: US01
          example:
            - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id: NS-SHIRT-BLUE-M
              fulfillment_node_id: US01
    CreateFutureAllocationsRequest:
      type: object
      title: CreateFutureAllocationsRequest
      description: >-
        Allocate the items of one sales order against future inventory, for
        items sold before the product is in stock.
      required:
        - sales_order_uuid
        - items
      properties:
        sales_order_uuid:
          type: string
          description: The uuid of the sales order the items belong to.
          minLength: 1
          maxLength: 256
          example: 8e3ba7ef-b3d3-4c40-a05a-9a83f6f6b6a4
        preferred_fulfillment_node_id:
          type: string
          description: >-
            The fulfillment node to allocate at. Items it cannot cover are
            allocated elsewhere. When omitted stock picks the node with the
            highest future availability.
          maxLength: 256
          example: US01
        items:
          type: array
          minItems: 1
          maxItems: 2000
          items:
            type: object
            description: A sales order item to allocate against future inventory.
            required:
              - sales_order_item_id
              - product_id
            properties:
              sales_order_item_id:
                type: string
                description: The uuid of the sales order item.
                minLength: 1
                maxLength: 256
                example: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id:
                type: string
                description: The id of the product to allocate.
                minLength: 1
                maxLength: 256
                example: NS-SHIRT-BLUE-M
            example:
              sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id: NS-SHIRT-BLUE-M
          example:
            - sales_order_item_id: a7d4b7b3-0b0d-4a1b-9c3a-6d2a1e9c4f11
              product_id: NS-SHIRT-BLUE-M
x-lint-ignore:
  - owasp-security-hosts-https-oas3
