Commerce & Products
Manage a Shopify store's catalog through the Commerce API and the deprecated Products API.
Commerce
A connected store is a Commerce store. Pass this account's id as accountId to the Commerce API at /v1/commerce.
It creates and edits products, variants, images, stock, collections, discounts, sales channel publishing, market prices, metafields, metaobjects, pages, menus, redirects and marketing activities. It can also sync the store into a Meta catalog for catalog ads.
Products, collections, metafields and pages work on every install.
Stock, sales channels, discounts, navigation, metaobjects, markets, marketing activities and image removal need optional scopes.
GET /v1/commerce/store lists them in missingCapabilities and returns grantPermissionsUrl, where the merchant approves them without reinstalling. See Capabilities and extra permissions.
curl "https://zernio.com/api/v1/commerce/store?accountId=66b2e19d8c3f5a7e9d0b1c2d" \
-H "Authorization: Bearer $ZERNIO_API_KEY"Products
The Products API below is deprecated in favor of the Commerce API, which returns the same catalog in the platform-neutral shape.
It keeps working for existing integrations. List and read products with their variants, options and images, and edit a product's own fields and variant prices.
All data lives on Shopify. Zernio proxies it and stores nothing.
List products
Call GET /v1/accounts/{accountId}/products. Filter with status (active, draft or archived) and query, Shopify's product search syntax, which is passed through verbatim:
const { data: page } = await zernio.products.listProducts({
path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
query: { status: 'active', query: 'vendor:Acme tag:summer', limit: 20 }
});
for (const product of page.products) {
console.log(product.id, product.title, product.variants[0]?.price);
}Response (200):
{
"platform": "shopify",
"products": [
{
"id": "8214569812345",
"platform": "shopify",
"title": "Linen shirt",
"handle": "linen-shirt",
"descriptionHtml": "<p>Breathable linen for warm days.</p>",
"vendor": "Acme",
"productType": "Shirts",
"tags": ["summer", "linen"],
"status": "active",
"featuredImage": { "url": "https://cdn.shopify.com/s/files/1/.../linen-front.jpg", "altText": "Linen shirt, front" },
"images": [{ "url": "https://cdn.shopify.com/s/files/1/.../linen-front.jpg", "altText": "Linen shirt, front" }],
"options": [{ "name": "Size", "values": ["S", "M", "L"] }],
"variants": [
{
"id": "45123456789012",
"title": "S",
"sku": "LINEN-S",
"barcode": null,
"price": "59.00",
"compareAtPrice": "79.00",
"inventoryQuantity": 12,
"availableForSale": true,
"selectedOptions": [{ "name": "Size", "value": "S" }]
}
],
"seo": { "title": "Linen shirt | Acme", "description": null },
"totalInventory": 31,
"onlineStoreUrl": "https://acme.com/products/linen-shirt",
"createdAt": "2026-05-02T10:12:00Z",
"updatedAt": "2026-09-01T08:30:00Z",
"publishedAt": "2026-05-02T10:15:00Z"
}
],
"nextCursor": "eyJsYXN0X2lkIjo4MjE0NTY5ODEyMzQ1fQ=="
}Pagination is the same cursor scheme as the Blogs API: limit 1 to 50 (default 20) and nextCursor, null on the last page.
The response carries the first 100 variants and the first 20 images of each product.
Update a product
Call PATCH /v1/accounts/{accountId}/products/{productId} with any subset of title, descriptionHtml, handle, vendor, productType, tags, status, seo and variants.
tagsreplaces the whole list.variantschanges the price and compare-at price of the listed variant ids only. A variant that belongs to another product is a400.
The response is the product as it reads after the update.
const { data: updated } = await zernio.products.updateProduct({
path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', productId: '8214569812345' },
body: {
descriptionHtml: '<p>Breathable linen for warm days. Now in three sizes.</p>',
tags: ['summer', 'linen', 'new'],
seo: { title: 'Linen shirt | Acme', description: 'A breathable linen shirt in S, M and L.' },
variants: [{ id: '45123456789012', price: 54.0, compareAtPrice: 79.0 }]
}
});
console.log(updated.product.variants[0].price); // "54.00"Prices are decimal strings in the store currency. A number is accepted and formatted to two decimals.
Send "compareAtPrice": null to take a variant off sale.
status: "draft" unpublishes a product without deleting it, and status: "archived" hides it everywhere. Both are reversible with status: "active".
Every product operation
| Operation | Endpoint |
|---|---|
| List products | GET /v1/accounts/{accountId}/products |
| Get a product | GET /v1/accounts/{accountId}/products/{productId} |
| Update a product | PATCH /v1/accounts/{accountId}/products/{productId} |