---
title: "Create a product"
method: POST
path: "/v1/commerce/products"
tags: ["Commerce"]
---

# Create a product

`POST /v1/commerce/products`

Creates a product with its options and variants. `status` defaults to
`draft`: no platform offers a sandbox for product writes, so nothing
goes on sale unless you ask for `active`. A product without `options`
has exactly one variant. Images are fetched by the platform from the
given URLs and may appear on the product a few seconds later.

## Request body

- object
  - `accountId` string, required
  - `title` string, required
  - `descriptionHtml` string
  - `handle` string
  - `vendor` string
  - `productType` string
  - `tags` string[]
  - `seo` object
    - `title` string
    - `description` string
  - `status` 'draft' | 'active'
  - `images` object[]
    - `url` string, uri, required
    - `altText` string
  - `options` object[]
    - `name` string, required
    - `values` string[], required
  - `variants` object[], required
    - `sku` string
    - `price` union, required — Decimal amount in the store currency.
      - number
      - string
    - `compareAtPrice` union
      - number
      - string
    - `options` object[] — One value per product option, e.g. [{ name: Size, value: M }].
      - `name` string, required
      - `value` string, required

## Response `201`

Product created

- object
  - `product` CommerceProduct — A product on a connected store, in the platform-neutral shape.
    - `id` string — Platform-native product id.
    - `accountId` string
    - `platform` 'shopify'
    - `title` string
    - `descriptionHtml` string, nullable
    - `handle` string, nullable — URL slug of the product.
    - `vendor` string, nullable
    - `productType` string, nullable
    - `tags` string[]
    - `status` 'active' | 'draft' | 'pending_review' | 'rejected' | 'inactive' | 'archived' | 'deleted' — Zernio product status. The platform value is returned as platformStatus. Shopify: ACTIVE, DRAFT and ARCHIVED map to the same names; UNLISTED maps to inactive.
    - `platformStatus` string — The raw status on the platform, e.g. ACTIVE on Shopify.
    - `featuredImage` CommerceImage
      - `id` string, nullable — Media id, used to reorder or remove images. Null for collection images.
      - `url` string
      - `altText` string, nullable
    - `images` CommerceImage[] — First 20 images, in store order.
      - `id` string, nullable — Media id, used to reorder or remove images. Null for collection images.
      - `url` string
      - `altText` string, nullable
    - `options` object[] — Option axes (e.g. Size, Color) and their values.
      - `name` string
      - `values` string[]
    - `variants` CommerceVariant[] — First 100 variants.
      - `id` string — Platform-native variant id.
      - `title` string — Option combination label, e.g. "S / Blue".
      - `sku` string, nullable
      - `barcode` string, nullable
      - `price` CommerceMoney — An exact amount of money. The amount is a decimal string so cents are never lost to floating point.
        - `amount` string, required
        - `currency` string, required — ISO 4217 code. Never null: the store currency is filled in when the platform omits it.
      - `compareAtPrice` CommerceMoney — An exact amount of money. The amount is a decimal string so cents are never lost to floating point.
        - `amount` string, required
        - `currency` string, required — ISO 4217 code. Never null: the store currency is filled in when the platform omits it.
      - `inventoryQuantity` integer, nullable — Units on hand; null when inventory is not tracked.
      - `availableForSale` boolean, nullable
      - `options` object[]
        - `name` string
        - `value` string
    - `totalInventory` integer, nullable
    - `url` string, nullable — Public storefront URL; null while the product is not published.
    - `seo` object
      - `title` string, nullable
      - `description` string, nullable
    - `createdAt` string, date-time, nullable
    - `updatedAt` string, date-time, nullable
    - `publishedAt` string, date-time, nullable
    - `platformData` object, nullable — Platform-only fields. Null when the platform has none.

## Other responses

- `400` — Invalid request
- `401` — Unauthorized
- `403` — The platform rejected the request (code insufficient_permissions). Reconnect the store.
- `404` — Account not found or not accessible (code account_not_found).
- `429` — Rate limited, either by Zernio or by the platform. Retry later.

## Changes

- **2026-09-30** `16a7b9d5373e` — 1 info
  - removed the `platform` enum value from the `details/budgetScope` response property for the response status `400`
- **2026-09-29** `69bbe3da3a18` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/commerce/products/post.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/16a7b9d5373e?raw)
