> ## Documentation Index
> Fetch the complete documentation index at: https://dev.puppetvendors.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update a product

> Update any product in your store, straight to Shopify. Send `vendorId` to move the product to another vendor. Send the full set of metafields: any left out are deleted. A live product with a vendor edit waiting for review is refused with 409 `PRODUCT_PENDING_REVIEW`; approve or reject the edit first. Requires the `admin:products:write` permission.



## OpenAPI

````yaml openapi-merchant.json PUT /products/{productId}
openapi: 3.0.0
info:
  title: PuppetVendors Merchant API V2
  version: 2.0.0
  description: >-
    The endpoints reachable with a merchant API key (`mk_live_…`), minted from
    Settings → API Access in the PuppetVendors admin. A merchant key administers
    users and vendors, and renews its own token. Everything else in the V2 API,
    including orders, products, payouts, fulfillments and documents, stays on
    the v1 API token and is refused here with a 403. Keys are revealed once at
    creation; rotating one leaves the previous key working for a 24 hour grace
    window.
  contact:
    name: PuppetVendors Support
    url: https://puppetvendors.com
  license:
    name: Proprietary
servers:
  - url: https://production-api.puppetvendors.com
    description: Production
  - url: http://localhost:8082
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Auth
    description: Authentication endpoints for obtaining and refreshing API tokens
  - name: Portal - Auth
    description: Vendor portal authentication endpoints (vendor user login, token refresh)
  - name: Products
    description: Product management and synchronization
  - name: Merchant Products
    description: >-
      Every vendor's products in your store, read and edited with a merchant API
      key, plus bulk approval and rejection of pending products. A missing
      product returns 404 `PRODUCT_NOT_FOUND`.
  - name: Orders
    description: Order retrieval and management
  - name: Payouts
    description: Vendor payout information
  - name: Fulfillments
    description: Order fulfillment operations
  - name: Reports
    description: Vendor sales reports and analytics
  - name: Vendors
    description: Vendor management operations
  - name: Settings
    description: Settings endpoints for vendor configuration and management
  - name: Users
    description: User account management
  - name: Shop
    description: Shop configuration and settings
  - name: Commissions
    description: Commission rate management
  - name: Integrations
    description: Vendor integration configuration endpoints
  - name: Line Items
    description: Order line item operations
paths:
  /products/{productId}:
    put:
      tags:
        - Merchant Products
      summary: Update a product
      description: >-
        Update any product in your store, straight to Shopify. Send `vendorId`
        to move the product to another vendor. Send the full set of metafields:
        any left out are deleted. A live product with a vendor edit waiting for
        review is refused with 409 `PRODUCT_PENDING_REVIEW`; approve or reject
        the edit first. Requires the `admin:products:write` permission.
      operationId: merchantUpdateProduct
      parameters:
        - in: path
          name: productId
          required: true
          schema:
            type: string
          description: >-
            A product ObjectId, a Shopify product GID or a numeric Shopify
            product id. Malformed is 400.
          example: 507f1f77bcf86cd799439021
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductUpdateRequest'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    $ref: '#/components/schemas/Product'
        '400':
          description: >-
            Malformed productId or vendorId, PRODUCT_VALIDATION_FAILED, or
            VALIDATION_ERROR for an invalid body or an unsupported field:
            `error.details` lists each unsupported field with the code
            `unrecognized_keys` and a message naming the field to use instead.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing, invalid or revoked token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            Not a merchant API key, the key lacks admin:products:write, or
            vendorId is not a vendor of this shop
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >-
            PRODUCT_NOT_FOUND, no product with this id in the shop. The product
            is looked up across the whole shop, and a vendorId does not narrow
            single-product calls (it is only checked to be a vendor of this
            shop, 403 otherwise; in the body it reassigns the product). A
            soft-deleted product (status deleted) answers 404 too
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            PRODUCT_PENDING_REVIEW, MAGIC_APPROVAL_PUBLISHING,
            PRODUCT_HANDLE_TAKEN
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: SHOPIFY_OPERATION_FAILED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
components:
  schemas:
    ProductUpdateRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 255
          description: Product title
          example: Widget Pro Deluxe v2
        descriptionHtml:
          type: string
          description: >-
            HTML description. Send an empty string to clear it; omit it to keep
            the current value.
        vendorId:
          type: string
          description: >-
            Move the product to this vendor. Must be a vendor of your shop (403
            otherwise); a malformed id is 400.
          example: 507f1f77bcf86cd799439011
        productType:
          type: string
          maxLength: 255
          description: Product type/category
        tags:
          type: array
          items:
            type: string
            maxLength: 255
          maxItems: 250
          description: Product tags (replaces existing)
        status:
          type: string
          enum:
            - ACTIVE
            - DRAFT
            - ARCHIVED
          description: Product status
        handle:
          type: string
          maxLength: 255
          description: URL-friendly product handle
        options:
          type: array
          items:
            $ref: '#/components/schemas/ProductOptionInput'
          maxItems: 3
          description: Product options (replaces existing)
        variants:
          type: array
          items:
            $ref: '#/components/schemas/ProductVariantInput'
          maxItems: 100
          description: Product variants (replaces existing)
        metafields:
          type: array
          items:
            $ref: '#/components/schemas/ProductMetafieldInput'
          description: Custom metafields (replaces existing)
        seo:
          $ref: '#/components/schemas/SeoInput'
          description: SEO settings
        category:
          type: string
          description: Product category/collection
        templateSuffix:
          type: string
          description: Custom template suffix
        images:
          type: array
          maxItems: 250
          description: Product images (replaces existing on update)
          items:
            type: object
            required:
              - src
            properties:
              src:
                type: string
                format: uri
                example: https://cdn.example.com/widget.jpg
              altText:
                type: string
                maxLength: 255
        medias:
          type: array
          maxItems: 250
          description: >-
            Digital files for download after purchase. On update, omit to keep
            existing files.
          items:
            type: object
            required:
              - file
              - name
            properties:
              file:
                type: string
                format: uri
                description: https URL of the uploaded file
              name:
                type: string
                minLength: 1
              size:
                type: integer
                minimum: 0
              type:
                type: string
                maxLength: 255
              variant:
                type: string
                maxLength: 255
              variantId:
                type: string
                maxLength: 255
              assignedTo:
                type: string
                maxLength: 255
        vendor:
          type: string
          description: Vendor display name on the Shopify product
        brandName:
          type: string
          description: Brand name
        collections:
          type: array
          items:
            type: string
            minLength: 1
          description: >-
            Shopify collection ids. The list replaces the product's collections:
            any collection not in it is removed. Omit the field to keep the
            current collections, or send [] to remove the product from all of
            them.
          maxItems: 250
        isDigital:
          type: boolean
          description: Marks the product as a digital product (use with medias)
      additionalProperties: false
    Product:
      type: object
      properties:
        _id:
          type: string
          description: Product ObjectId
          example: 507f1f77bcf86cd799439013
        title:
          type: string
          description: Product title
          example: Widget Pro
        handle:
          type: string
          description: URL-friendly product handle
          example: widget-pro
        status:
          type: string
          enum:
            - active
            - draft
            - archived
          description: Product status
          example: active
        vendorId:
          type: string
          description: Associated vendor ObjectId
          example: 507f1f77bcf86cd799439011
        variants:
          type: array
          items:
            type: object
            properties:
              _id:
                type: string
                description: Mongo subdocument id
              id:
                type: string
                description: Shopify variant id (numeric) once synced
              admin_graphql_api_id:
                type: string
                description: Shopify variant GID once synced
              title:
                type: string
                description: Variant title (e.g. "Red / Large")
              sku:
                type: string
              price:
                type: number
              inventory:
                type: number
              optionValues:
                type: array
                description: Resolved option name/value pairs
                items:
                  type: object
                  properties:
                    name:
                      type: string
                    value:
                      type: string
              selectedOptions:
                type: array
                description: Shopify-shaped option name/value pairs
                items:
                  type: object
                  properties:
                    name:
                      type: string
                    value:
                      type: string
              inventoryQuantities:
                type: array
                description: Per-location stock for this variant
                items:
                  type: object
                  properties:
                    locationId:
                      type: string
                    name:
                      type: string
                    quantity:
                      type: integer
                    availableQuantity:
                      type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Indicates the request failed
        error:
          type: object
          required:
            - message
          properties:
            message:
              type: string
              description: Human-readable error message
              example: Resource not found
            code:
              type: string
              description: >-
                Stable machine-readable error code. The frontend uses this as
                its localisation key (e.g. errors.api.<code>). Server errors
                always report INTERNAL_SERVER_ERROR.
              example: NOT_FOUND
            errorId:
              type: string
              description: >-
                Correlation id for support/log lookup. Present on opaque 5xx
                responses so a user can quote a reference for the underlying
                (server-side logged) error.
              example: err_1783365360839_j281w7b
            details:
              type: object
              description: >-
                Additional client-error context (e.g. field-level validation
                errors). Never populated for server errors.
    ProductOptionInput:
      type: object
      required:
        - name
        - values
      properties:
        name:
          type: string
          description: Option name (e.g., Color, Size)
          example: Color
        values:
          type: array
          items:
            type: string
          description: Possible values for this option
          example:
            - Red
            - Blue
            - Green
    ProductVariantInput:
      type: object
      properties:
        optionValues:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                example: Color
              value:
                type: string
                example: Red
          description: Option values for this variant
        price:
          type: string
          pattern: ^\d+(\.\d{1,2})?$
          description: Variant price
          example: '29.99'
        compareAtPrice:
          type: string
          pattern: ^\d+(\.\d{1,2})?$
          description: Original/compare price
          example: '39.99'
        sku:
          type: string
          maxLength: 255
          description: Stock keeping unit
          example: WIDGET-RED-L
        barcode:
          type: string
          maxLength: 255
          description: EAN/barcode
        inventoryQuantity:
          type: integer
          minimum: -1000000000
          maximum: 1000000000
          description: >-
            Inventory quantity at location. May be negative for oversold or
            backordered stock, which only actually sells when the variant
            inventoryPolicy is CONTINUE.
          example: 100
        locationId:
          type: string
          description: Shopify location ID (required if inventoryQuantity is provided)
          example: gid://shopify/Location/123456
        requiresShipping:
          type: boolean
          description: Whether variant requires shipping
          example: true
        taxable:
          type: boolean
          description: Whether variant is taxable
          example: true
        inventoryPolicy:
          type: string
          enum:
            - DENY
            - CONTINUE
          description: Behavior when out of stock
          example: DENY
    ProductMetafieldInput:
      type: object
      required:
        - namespace
        - key
        - value
      properties:
        namespace:
          type: string
          description: Metafield namespace
          example: custom
        key:
          type: string
          description: Metafield key
          example: color_family
        value:
          type: string
          description: Metafield value
          example: red
        type:
          type: string
          description: Metafield type
          example: single_line_text_field
        description:
          type: string
          description: Metafield description
    SeoInput:
      type: object
      properties:
        title:
          type: string
          maxLength: 255
          description: SEO title tag
          example: Premium Widget - Best Price Online
        description:
          type: string
          maxLength: 320
          description: SEO meta description
          example: >-
            High-quality widgets at competitive prices. Free shipping on orders
            over $50.
  securitySchemes:
    bearerAuth:
      type: apiKey
      in: header
      name: x-access-token
      description: >-
        JWT minted from a merchant API key (`mk_live_…`) by POST /authenticate.
        Send the token value directly, with no "Bearer" prefix. Tokens last 14
        days and can be renewed at POST /refresh-token; revoking the key refuses
        the next request made with any token minted from it.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.