openapi: 3.1.0
info:
  title: Inventories API
  description: |-
    API for managing inventories.

    Inventory records are identified by a product variant and a location. You can
    address a record with Loop global IDs (`productVariantId` and `locationId`) or
    with your platform's external IDs (`variantExternalId` and `locationExternalId`).

    ### Resource identifier format

    Resource identifiers (the `id` field and related global-identifier fields) are returned as JSON
    integers by default. To receive them as strings instead, send the
    `X-Loop-Global-Identifier-Type: string` request header.
  version: "v1"
tags:
  - name: Inventories
    description: Operations related to managing inventories
servers:
  - url: https://api.loopreturns.com/api/2026-10
    description: 2026-10 (Stable)
  - url: https://api.loopreturns.com/api/2026-07
    description: 2026-07
  - url: https://api.loopreturns.com/api/unstable
    description: Unstable (Preview)
paths:
  /inventories:
    get:
      summary: List Inventories
      description: >-
        Retrieve a list of inventories. The list can be filtered, sorted, and
        paginated using the parameters below.
      operationId: listInventories
      tags:
        - Inventories
      parameters:
        - name: location_id
          in: query
          description: The list returned will contain only inventories associated with the specified location.
          required: false
          schema:
            type: integer
        - name: product_variant_id
          in: query
          description: The list returned will contain only inventories containing the specified product variant.
          required: false
          schema:
            type: integer
        - name: limit
          in: query
          description: The maximum number of inventories returned.
          required: false
          schema:
            type: integer
            maximum: 250
            default: 50
        - name: sort_order
          in: query
          description: The sort order for the results.
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: asc
        - name: cursor
          in: query
          description: The cursor to return the next page of results.
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                type: object
                required:
                  - inventories
                properties:
                  inventories:
                    type: array
                    items:
                      $ref: "#/components/schemas/InventoryResponse"
                    description: An array of inventories.
                  next_page_url:
                    type: [string, "null"]
                  previous_page_url:
                    type: [string, "null"]
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
      security:
        - read: []
  /inventories/{productVariantId}/{locationId}:
    post:
      summary: Upsert Inventory
      description: >-
        Upsert inventory details for a product variant by location. If the
        inventory exists, it will be updated; if it doesn't exist, it will be
        created.
      operationId: upsertInventoryViaPost
      tags:
        - Inventories
      parameters:
        - in: path
          name: productVariantId
          schema:
            type: integer
            examples:
              - 653985747439023552
          required: true
          description: The unique identifier for the product variant, created by Loop.
        - in: path
          name: locationId
          schema:
            type: integer
            examples:
              - 631923350119788400
          required: true
          description: The unique identifier for the location, created by Loop.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateInventoryRequest"
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: >-
            The product variant or location could not be found. If no inventory
            record exists for the product variant and location pair, one is
            created and a 200 response is returned instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "422":
          description: "Unprocessable Entity"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
      security:
        - write: []
    get:
      summary: Get Inventory
      description: Get inventory details for a product variant by location.
      operationId: getInventory
      tags:
        - Inventories
      parameters:
        - in: path
          name: productVariantId
          schema:
            type: integer
            examples:
              - 653985747439023552
          required: true
          description: The unique identifier for the product variant, created by Loop.
        - in: path
          name: locationId
          schema:
            type: integer
            examples:
              - 631923350119788400
          required: true
          description: The unique identifier for the location, created by Loop.
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
      security:
        - read: []
    put:
      summary: Upsert Inventory
      description: >-
        Upsert inventory details for a product variant by location. If the
        inventory exists, it will be updated; if it doesn't exist, it will be
        created.
      operationId: upsertInventory
      tags:
        - Inventories
      parameters:
        - in: path
          name: productVariantId
          schema:
            type: integer
            examples:
              - 653985747439023552
          required: true
          description: The unique identifier for the product variant, created by Loop.
        - in: path
          name: locationId
          schema:
            type: integer
            examples:
              - 631923350119788400
          required: true
          description: The unique identifier for the location, created by Loop.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateInventoryRequest"
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: >-
            The product variant or location could not be found. If no inventory
            record exists for the product variant and location pair, one is
            created and a 200 response is returned instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "422":
          description: "Unprocessable Entity"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
      security:
        - write: []
    patch:
      summary: Upsert Inventory
      description: >-
        Upsert inventory details for a product variant by location. If the
        inventory exists, it will be updated; if it doesn't exist, it will be
        created.
      operationId: upsertInventoryViaPatch
      tags:
        - Inventories
      parameters:
        - in: path
          name: productVariantId
          schema:
            type: integer
            examples:
              - 653985747439023552
          required: true
          description: The unique identifier for the product variant, created by Loop.
        - in: path
          name: locationId
          schema:
            type: integer
            examples:
              - 631923350119788400
          required: true
          description: The unique identifier for the location, created by Loop.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateInventoryRequest"
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: >-
            The product variant or location could not be found. If no inventory
            record exists for the product variant and location pair, one is
            created and a 200 response is returned instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "422":
          description: "Unprocessable Entity"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
      security:
        - write: []
    delete:
      summary: Delete Inventory
      description: Delete an inventory.
      operationId: deleteInventory
      tags:
        - Inventories
      parameters:
        - in: path
          name: productVariantId
          schema:
            type: integer
            examples:
              - 653985747439023552
          required: true
          description: The unique identifier for the product variant, created by Loop.
        - in: path
          name: locationId
          schema:
            type: integer
            examples:
              - 631923350119788400
          required: true
          description: The unique identifier for the location, created by Loop.
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryDeleteResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
      security:
        - write: []
  /inventories/external/{variantExternalId}/{locationExternalId}:
    get:
      summary: Get Inventory by External IDs
      description: >-
        Get inventory details for a product variant at a location, identified by
        the variant's `external_id` and the location's `external_id`. If more
        than one product variant in the shop shares the
        same `external_id`, this request returns `409 Conflict`.
      operationId: getInventoryByExternalIds
      tags:
        - Inventories
      parameters:
        - in: path
          name: variantExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - "4006381333931"
          required: true
          description: The identifier provided as the `external_id` for this variant.
        - in: path
          name: locationExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - main-warehouse
          required: true
          description: The identifier provided as the `external_id` for this location.
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "409":
          description: >-
            More than one product variant in the shop matches the provided
            `variantExternalId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConflictResponse"
      security:
        - read: []
    put:
      summary: Upsert Inventory by External IDs
      description: >-
        Upsert inventory details for a product variant at a location, identified
        by the variant's `external_id` and the location's `external_id`. If the
        inventory exists, it will be updated; if it
        doesn't exist, it will be created. The product variant and location must
        already exist. If more than one product variant in the shop shares the
        same `external_id`, this request returns `409 Conflict`.
      operationId: upsertInventoryByExternalIds
      tags:
        - Inventories
      parameters:
        - in: path
          name: variantExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - "4006381333931"
          required: true
          description: The identifier provided as the `external_id` for this variant.
        - in: path
          name: locationExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - main-warehouse
          required: true
          description: The identifier provided as the `external_id` for this location.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateInventoryRequest"
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryWrappedResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: >-
            The product variant or location could not be found. If no inventory
            record exists for the resolved product variant and location pair,
            one is created and a 200 response is returned instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "409":
          description: >-
            More than one product variant in the shop matches the provided
            `variantExternalId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConflictResponse"
        "422":
          description: "Unprocessable Entity"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
      security:
        - write: []
    delete:
      summary: Delete Inventory by External IDs
      description: >-
        Delete an inventory record identified by the product variant's
        `external_id` and the location's `external_id`. If more than one
        product variant in the shop shares the same `external_id`, this request
        returns `409 Conflict`.
      operationId: deleteInventoryByExternalIds
      tags:
        - Inventories
      parameters:
        - in: path
          name: variantExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - "4006381333931"
          required: true
          description: The identifier provided as the `external_id` for this variant.
        - in: path
          name: locationExternalId
          schema:
            type: string
            maxLength: 64
            examples:
              - main-warehouse
          required: true
          description: The identifier provided as the `external_id` for this location.
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryDeleteByExternalIdsResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnauthorizedResponse"
        "404":
          description: "Not Found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFoundResponse"
        "409":
          description: >-
            More than one product variant in the shop matches the provided
            `variantExternalId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConflictResponse"
      security:
        - write: []
components:
  schemas:
    UpdateInventoryRequest:
      type: object
      properties:
        available_count:
          type: integer
          format: int64
          description: The number of `product_variant`s available at `location`.
          examples:
            - 42
      required:
        - available_count
    InventoryWrappedResponse:
      type: object
      required:
        - inventory
      properties:
        inventory:
          $ref: "#/components/schemas/InventoryResponse"
    InventoryResponse:
      type: object
      properties:
        available_count:
          type: [integer, "null"]
          format: int64
          description: The number of `product_variant`s available at `location`.
          examples:
            - 42
        product_variant:
          description: The details associated with the inventory's `product_variant`.
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/ProductVariantResponseData"
        location:
          description: The details associated with the inventory's `location`.
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/LocationResponseData"
    InventoryDeleteResponse:
      type: object
      required:
        - product_variant_id
        - location_id
        - success
      properties:
        product_variant_id:
          type: integer
          format: int64
          description: The unique identifier for the product variant, created by Loop.
          examples:
            - 674353902539857452
        location_id:
          type: integer
          format: int64
          description: The unique identifier for the location, created by Loop.
          examples:
            - 631923350119788400
        success:
          type: boolean
          description: Whether the inventory was successfully deleted.
          examples:
            - true
    InventoryDeleteByExternalIdsResponse:
      type: object
      required:
        - variant_external_id
        - location_external_id
        - product_variant_id
        - location_id
        - success
      properties:
        variant_external_id:
          type: string
          description: The identifier provided as the `external_id` for this variant.
          examples:
            - "4006381333931"
        location_external_id:
          type: string
          description: The identifier provided as the `external_id` for this location.
          examples:
            - main-warehouse
        product_variant_id:
          type: integer
          format: int64
          description: The unique identifier for the product variant, created by Loop.
          examples:
            - 674353902539857452
        location_id:
          type: integer
          format: int64
          description: The unique identifier for the location, created by Loop.
          examples:
            - 631923350119788400
        success:
          type: boolean
          description: Whether the inventory was successfully deleted.
          examples:
            - true
    ProductVariantResponseData:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
          description: The unique identifier for the product variant, created by Loop.
          examples:
            - 674353902539857452
        external_id:
          type: [string, "null"]
          maxLength: 100
          description: The identifier used by an external source to identify the product variant.
          examples:
            - "4006381333931"
        sku:
          type: [string, "null"]
          maxLength: 255
          description: The SKU of the product variant.
          examples:
            - EXAMPLEPRODUCT-VAR-1
        name:
          type: string
          maxLength: 255
          description: The name of the product variant.
          examples:
            - Example Product
        weight_grams:
          type: [integer, "null"]
          description: The weight of the product variant in grams.
          examples:
            - 444
        barcode:
          type: [string, "null"]
          maxLength: 255
          description: The barcode of the product variant.
          examples:
            - 23141001200513
    LocationResponseData:
      type: object
      required:
        - id
        - name
        - status
        - address
      properties:
        id:
          type: integer
          format: int64
          description: The unique identifier for the location, created by Loop.
          examples:
            - 680118439701923350
        external_id:
          type: [string, "null"]
          maxLength: 100
          description: The identifier used by an external source to identify the location.
          examples:
            - main-warehouse
        name:
          type: string
          maxLength: 45
          description: The name of the location.
          examples:
            - Main Warehouse
        status:
          type: string
          description: The status of the location.
          enum:
            - active
            - inactive
        address:
          description: The address of the location.
          $ref: "#/components/schemas/AddressResponseData"
    AddressResponseData:
      type: object
      properties:
        name:
          type: [string, "null"]
          maxLength: 100
          description: "The name of the address."
          examples:
            - Main Warehouse
        company:
          type: [string, "null"]
          maxLength: 100
          description: "The name of the company residing at the address."
          examples:
            - Universal Exports
        address1:
          type: [string, "null"]
          maxLength: 125
          description: "Line 1 of the address."
          examples:
            - 1234 Example Street
        address2:
          type: [string, "null"]
          maxLength: 125
          description: "Line 2 of the address."
          examples:
            - Unit 19
        city:
          type: [string, "null"]
          maxLength: 100
          description: "The city in which the address is located."
          examples:
            - Townsville
        region:
          type: [string, "null"]
          maxLength: 100
          description: "The state or region in which the address is located."
          examples:
            - Louisiana
        postal_code:
          type: [string, "null"]
          maxLength: 16
          description: "The postal code associated with the address."
          examples:
            - 90210
        country_code:
          type: [string, "null"]
          maxLength: 2
          description: "The country code associated with the address."
          examples:
            - US
    ValidationErrorResponse:
      type: object
      description: >-
        Returned when the request body fails validation. Uses Laravel's standard
        validation error shape.
      properties:
        message:
          type: string
          examples:
            - "The available count field is required."
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          examples:
            - available_count:
                - "The available count field is required."
    UnauthorizedResponse:
      type: object
      required:
        - errors
      properties:
        errors:
          type: string
          description: The message describing why the request was not authorized.
          examples:
            - "Unauthorized."
    NotFoundResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: The message describing that the requested resource was not found.
          examples:
            - "Resource not found."
    ConflictResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: >-
            The message describing that more than one product variant matches
            the provided `variantExternalId`.
          examples:
            - "Multiple product variants match the provided external_id."
  securitySchemes:
    read:
      type: apiKey
      in: header
      name: X-Authorization
      description: 'API Scope: "Inventory (read)"'
      x-scope: Inventory (Read)
    write:
      name: X-Authorization
      type: apiKey
      in: header
      description: 'API Scope: "Inventory (write)"'
