---
title: "Update a product"
method: PATCH
path: "/v1/accounts/{accountId}/products/{productId}"
tags: ["Products"]
---

# Update a product

`PATCH /v1/accounts/{accountId}/products/{productId}`

Partial-updates a product. Send any subset of `title`,
`descriptionHtml`, `handle`, `vendor`, `productType`, `tags`, `status`,
`seo` and `variants`; at least one field is required (an empty body
returns 400). `tags` replaces the full tag list. `variants` updates
the price and compare-at price of the listed variant ids only; other
variants are untouched, and a variant id that does not belong to the
product is a 400. Responds with the product as it is after the update.

Supported on Shopify (platform `shopify`); accounts on other
platforms return 400. A store connected before product access was
added answers 403 insufficient_permissions until the merchant
reconnects it through `GET /v1/connect/shopify`.

## Path parameters

- `accountId` string, required
- `productId` string, required

## Request body

- object — At least one field is required.
  - `title` string
  - `descriptionHtml` string — Product description as HTML.
  - `handle` string — URL slug of the product.
  - `vendor` string
  - `productType` string
  - `tags` string[] — Replaces the full tag list.
  - `status` 'active' | 'draft' | 'archived' — archived hides the product everywhere; draft keeps it editable but unpublished.
  - `seo` object — Search-engine title and description overrides.
    - `title` string
    - `description` string
  - `variants` object[] — Price changes per variant. Only the listed variants change.
    - `id` string, required — Variant id from the product response.
    - `price` union — Decimal amount in the store currency. Numbers are formatted to two decimals.
      - number
      - string
    - `compareAtPrice` union — Strike-through price. Send null to remove it.
      - number
      - string

## Response `200`

Product updated

- object
  - `platform` 'shopify'
  - `product` Product — A product on the connected platform with its variants, options and images. All data lives on the platform; Zernio proxies it and stores nothing.
    - `id` string — Platform-native product id (numeric string for Shopify).
    - `platform` 'shopify'
    - `title` string
    - `handle` string — URL slug of the product.
    - `descriptionHtml` string, nullable — Product description as HTML.
    - `vendor` string, nullable
    - `productType` string, nullable — Free-text product type as set on the store.
    - `tags` string[]
    - `status` 'active' | 'draft' | 'archived'
    - `featuredImage` ProductImage
      - `url` string
      - `altText` string, nullable
    - `images` ProductImage[] — First 20 images in the product media, in store order.
      - `url` string
      - `altText` string, nullable
    - `options` object[] — Option axes (e.g. Size, Color) and their values.
      - `name` string
      - `values` string[]
    - `variants` ProductVariant[] — First 100 variants.
      - `id` string — Platform-native variant id (numeric string for Shopify).
      - `title` string — Option combination label, e.g. "S / Blue".
      - `sku` string, nullable
      - `barcode` string, nullable
      - `price` string — Decimal amount in the store currency, e.g. "19.90".
      - `compareAtPrice` string, nullable — Strike-through price; null when the variant is not on sale.
      - `inventoryQuantity` integer, nullable — Units on hand across locations; null when inventory is not tracked.
      - `availableForSale` boolean
      - `selectedOptions` object[]
        - `name` string
        - `value` string
    - `seo` object
      - `title` string, nullable
      - `description` string, nullable
    - `totalInventory` integer, nullable
    - `onlineStoreUrl` string, nullable — Public storefront URL; null while the product is not published to the online store.
    - `createdAt` string, date-time, nullable
    - `updatedAt` string, date-time, nullable
    - `publishedAt` string, date-time, nullable

## Other responses

- `400` — Invalid request
- `401` — Unauthorized
- `403` — The platform rejected the request (code insufficient_permissions). The store lacks the product scopes or the token was revoked; reconnect the Shopify account.
- `404` — Account not found or not accessible (code account_not_found), or product not found (code product_not_found).
- `429` — Rate limited, either by Zernio or by Shopify. Retry later.

## Changes

- **2026-09-25** `2c04683ce694` — 4 info
  - added the optional property `details/adAccountId` to the response with the `400` status
  - added the optional property `details/createdObjects` to the response with the `400` status
  - added the optional property `details/stage` to the response with the `400` status
  - added the optional property `details/unconfirmedWrite` to the response with the `400` status
- **2026-09-17** `be448f13ecdc` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/accounts/:accountId/products/:productId/patch.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/b2325332041a?raw)
