Zernio
Zernio
Overview

Guides

ProfilesConnecting accountsMedia UploadsQueue SchedulingTimezones & SchedulingIdempotency & Safe RetriesPost LifecycleError HandlingRate LimitsPixels and Tracking TagsCommerceReport Bugs & RequestsPlatform Settings
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Guides

Commerce

One API over Shopify and WooCommerce stores: products, variants, inventory, collections, discounts, channels, markets, metafields, pages and menus, plus syncing a store into a Meta catalog.


The Commerce API is one platform-neutral API at /v1/commerce over every connected store. Write an integration once and it works on Shopify and on WooCommerce (self-hosted WordPress). Pick the store with accountId: a query parameter on reads and a body field on writes. It is the _id of the connected shopify or wordpress account.

All data lives on the store. Zernio proxies each call and stores nothing, except the catalog syncs you create.

Conventions

ConventionDetail
StoreaccountId of a connected shopify account, or of a wordpress account whose site runs WooCommerce. Commerce objects report platform: "shopify" or platform: "woocommerce".
IdsProducts, variants, collections and the rest use the platform's own ids (numeric strings). Read them from responses; never construct them.
MoneyResponses return { "amount": "24.00", "currency": "USD" } with the amount as a decimal string. Write bodies take a bare decimal amount in the store's currency.
Statusstatus uses Zernio's vocabulary (active, draft, pending_review, rejected, inactive, archived, deleted). The platform's raw value is always next to it as platformStatus.
TimestampsISO 8601, UTC.
PaginationLists return { <resource>, nextCursor }. Pass limit and the previous nextCursor as cursor; nextCursor is null on the last page. The cursor is opaque.
Unsupported operationAn operation the store's platform cannot serve answers 400 platform_not_supported. Check capabilities on Get a store first.

Connect a store

A Commerce store is an account you already connect for blogs:

  • Shopify: the OAuth flow with the store's myshopify.com domain, an install from the Shopify App Store, or a custom-app Admin token. See Shopify.
  • WooCommerce: connect the self-hosted WordPress site with a username and an Application Password. See WordPress. The user must be allowed to manage WooCommerce. WordPress.com sites cannot serve WooCommerce through Zernio: the WordPress.com token reaches no WooCommerce API.

Capabilities and extra permissions

Call Get a store before anything else. It returns the store's currency and country, the capabilities it can serve now, the missingCapabilities the platform supports but this store has not granted, and grantPermissionsUrl.

curl "https://zernio.com/api/v1/commerce/store?accountId=66b2e19d8c3f5a7e9d0b1c2d" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
{
  "store": {
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "platform": "shopify",
    "name": "Acme",
    "domain": "acme.myshopify.com",
    "url": "https://acme.com",
    "currency": "USD",
    "country": "US",
    "capabilities": ["products.read", "products.create", "products.update", "products.status", "products.price", "products.variants", "products.images", "collections.read", "collections.write", "collections.metafields", "metafields.read", "metafields.write", "pages.read", "pages.write"],
    "missingCapabilities": ["products.images_remove", "inventory.read", "inventory.write", "channels.read", "channels.write", "discounts.read", "discounts.write", "discounts.codes", "navigation.read", "navigation.write", "metaobjects.read", "metaobjects.write", "markets.read", "markets.write", "marketing.write"],
    "grantPermissionsUrl": "https://admin.shopify.com/store/acme/oauth/install?client_id=...&optional_scopes=write_files%2Cwrite_inventory%2Cread_locations%2C..."
  }
}

Shopify

Every install grants the products, collections, metafields and pages capabilities. The rest need optional scopes that the store owner approves separately, so a store that only uses blogs is never asked for them. Send the owner to grantPermissionsUrl: it opens the Shopify admin and approves the missing scopes on the existing install, with no reinstall, and the owner can revoke them later. Call Get a store again afterwards to read the new capabilities.

CapabilityShopify scope
products.read, collections.read, metafields.readread_products (always granted)
products.create, products.update, products.status, products.price, products.variants, products.images, collections.write, collections.metafields, metafields.writewrite_products (always granted)
pages.read / pages.writeread_content / write_content (always granted)
products.images_removewrite_files
inventory.readwrite_inventory and read_locations
inventory.writewrite_inventory and read_locations
channels.read, channels.writewrite_publications
discounts.read, discounts.write, discounts.codeswrite_discounts
navigation.read, navigation.writewrite_online_store_navigation
metaobjects.read, metaobjects.writewrite_metaobjects
markets.readwrite_markets
markets.writewrite_markets and write_products
marketing.writewrite_marketing_events

grantPermissionsUrl is null when nothing is missing, and on a store connected with a custom-app token: that token carries the custom app's scopes, not Zernio's, so the merchant adds the scopes to their custom app instead. A store connected before product access existed lacks the required product scopes, which the link does not cover: reconnect it.

Calling an operation whose scope is missing answers 403 insufficient_permissions, and the message points to grantPermissionsUrl.

WooCommerce

WooCommerce has no optional permissions, so grantPermissionsUrl is always null. The store capabilities appear when the site runs WooCommerce and the connected user can manage it; pages.read and pages.write appear when the user can edit pages, even on a site without WooCommerce. See WooCommerce differences for what the platform does not serve.

Endpoint map

Every operation also lists its platforms on its reference page.

Products

OperationEndpointPlatforms
Get a storeGET /v1/commerce/storeBoth
List productsGET /v1/commerce/productsBoth
Get a productGET /v1/commerce/products/{productId}Both
Create a productPOST /v1/commerce/productsBoth
Update a productPATCH /v1/commerce/products/{productId}Both
Duplicate a productPOST /v1/commerce/products/{productId}/duplicateBoth
Activate, deactivate, archive or delete productsPOST /v1/commerce/products/stateBoth
Add or remove tags in bulkPOST /v1/commerce/products/tagsBoth
Update variant pricesPOST /v1/commerce/products/{productId}/priceBoth

Variants, options and images

OperationEndpointPlatforms
Add variantsPOST /v1/commerce/products/{productId}/variantsBoth
Delete variantsDELETE /v1/commerce/products/{productId}/variantsBoth
Add optionsPOST /v1/commerce/products/{productId}/optionsBoth
Delete optionsDELETE /v1/commerce/products/{productId}/optionsBoth
Add imagesPOST /v1/commerce/products/{productId}/imagesBoth
Remove imagesDELETE /v1/commerce/products/{productId}/imagesBoth
Reorder imagesPOST /v1/commerce/products/{productId}/images/reorderBoth

Inventory and locations

OperationEndpointPlatforms
List locationsGET /v1/commerce/locationsBoth
Get a product's stockGET /v1/commerce/inventoryBoth
Set or adjust stockPOST /v1/commerce/products/{productId}/inventoryBoth

Collections

On WooCommerce, collections are product categories.

OperationEndpointPlatforms
List collectionsGET /v1/commerce/collectionsBoth
Get a collectionGET /v1/commerce/collections/{collectionId}Both
Create a collectionPOST /v1/commerce/collectionsBoth
Update a collectionPATCH /v1/commerce/collections/{collectionId}Both
Delete a collectionDELETE /v1/commerce/collections/{collectionId}Both
Add or remove productsPOST /v1/commerce/collections/{collectionId}/productsBoth
Reorder productsPOST /v1/commerce/collections/{collectionId}/reorderShopify

Discounts

On WooCommerce, discounts are coupons.

OperationEndpointPlatforms
List discountsGET /v1/commerce/discountsBoth
Get a discountGET /v1/commerce/discounts/{discountId}Both
Create a discountPOST /v1/commerce/discountsBoth
Update a discountPATCH /v1/commerce/discounts/{discountId}Both
Activate or deactivatePOST /v1/commerce/discounts/{discountId}/stateBoth
Add codesPOST /v1/commerce/discounts/{discountId}/codesShopify
Delete a discountDELETE /v1/commerce/discounts/{discountId}Both

Sales channels

OperationEndpointPlatforms
List sales channelsGET /v1/commerce/channelsShopify
Publish or unpublish a productPOST /v1/commerce/products/{productId}/channelsShopify
Publish or unpublish a collectionPOST /v1/commerce/collections/{collectionId}/channelsShopify

Markets and price lists

OperationEndpointPlatforms
List marketsGET /v1/commerce/marketsShopify
List price listsGET /v1/commerce/price-listsShopify
Set fixed pricesPUT /v1/commerce/price-lists/{priceListId}/pricesShopify
Remove fixed pricesDELETE /v1/commerce/price-lists/{priceListId}/pricesShopify

Metafields and metaobjects

OperationEndpointPlatforms
List product metafieldsGET /v1/commerce/products/{productId}/metafieldsBoth
Set product metafieldsPUT /v1/commerce/products/{productId}/metafieldsBoth
Delete product metafieldsDELETE /v1/commerce/products/{productId}/metafieldsBoth
List collection metafieldsGET /v1/commerce/collections/{collectionId}/metafieldsShopify
Set collection metafieldsPUT /v1/commerce/collections/{collectionId}/metafieldsShopify
Delete collection metafieldsDELETE /v1/commerce/collections/{collectionId}/metafieldsShopify
List metaobject definitionsGET /v1/commerce/metaobject-definitionsShopify
List metaobjects of a typeGET /v1/commerce/metaobjectsShopify
Get a metaobjectGET /v1/commerce/metaobjects/{metaobjectId}Shopify
Create a metaobjectPOST /v1/commerce/metaobjectsShopify
Update a metaobjectPATCH /v1/commerce/metaobjects/{metaobjectId}Shopify
Delete a metaobjectDELETE /v1/commerce/metaobjects/{metaobjectId}Shopify

Pages, menus and redirects

On WooCommerce, pages are the WordPress site's pages.

OperationEndpointPlatforms
List pagesGET /v1/commerce/pagesBoth
Get a pageGET /v1/commerce/pages/{pageId}Both
Create a pagePOST /v1/commerce/pagesBoth
Update a pagePATCH /v1/commerce/pages/{pageId}Both
Delete a pageDELETE /v1/commerce/pages/{pageId}Both
List navigation menusGET /v1/commerce/menusShopify
Get a navigation menuGET /v1/commerce/menus/{menuId}Shopify
Create a navigation menuPOST /v1/commerce/menusShopify
Replace a navigation menuPUT /v1/commerce/menus/{menuId}Shopify
Delete a navigation menuDELETE /v1/commerce/menus/{menuId}Shopify
List URL redirectsGET /v1/commerce/redirectsShopify
Create a URL redirectPOST /v1/commerce/redirectsShopify
Update a URL redirectPATCH /v1/commerce/redirects/{redirectId}Shopify
Delete a URL redirectDELETE /v1/commerce/redirects/{redirectId}Shopify

Marketing activities

Record a post, ad or message you ran for the merchant in the store's Marketing section, with its link and UTM parameters for attribution. Use your own id (for example the Zernio post id) as remoteId.

OperationEndpointPlatforms
Record a marketing activityPUT /v1/commerce/marketing-activitiesShopify
Delete a marketing activityDELETE /v1/commerce/marketing-activities/{remoteId}Shopify
Report daily engagementPOST /v1/commerce/marketing-activities/{remoteId}/engagementsShopify

Examples

List and create products

curl "https://zernio.com/api/v1/commerce/products?accountId=66b2e19d8c3f5a7e9d0b1c2d&status=active&limit=50" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

query is passed to the platform's product search as is, and collectionId narrows the list to one collection. A status the platform has no equivalent of returns an empty page.

A new product starts as draft unless you send status: "active". It takes up to 3 options, 100 variants and 20 images:

curl -X POST "https://zernio.com/api/v1/commerce/products" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "title": "Linen Shirt",
    "descriptionHtml": "<p>Breathable linen, cut for summer.</p>",
    "status": "active",
    "images": [{ "url": "https://cdn.example.com/linen-shirt.jpg", "altText": "Linen shirt" }],
    "options": [{ "name": "Size", "values": ["M", "L"] }],
    "variants": [
      { "sku": "LS-M", "price": "49.00", "options": [{ "name": "Size", "value": "M" }] },
      { "sku": "LS-L", "price": "49.00", "options": [{ "name": "Size", "value": "L" }] }
    ]
  }'

Activate, deactivate, archive or delete products takes up to 50 product ids and reports each product's outcome, so one failure does not abort the rest. A delete is permanent on both platforms.

Set stock

Read the location ids with List locations, then set or adjust per variant. mode: "set" makes quantity the new available count; mode: "adjust" adds it (negative to subtract). The response is the product's stock after the change.

curl -X POST "https://zernio.com/api/v1/commerce/products/8214569812345/inventory" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "mode": "adjust",
    "changes": [{ "variantId": "44021234567890", "locationId": "71234567890", "quantity": -2 }]
  }'

Sync a store to a Meta catalog

A catalog sync keeps a Meta product catalog in step with a store, for catalog ads (goal: catalog_sales) and Shops. It works on both platforms.

You need:

  • accountId: the store.
  • catalogAccountId: a connected facebook, instagram or metaads account whose Meta login can manage the catalog (the catalog_management permission). A WhatsApp account cannot hold a sync, because background runs refresh the catalog token through a Meta login.
  • catalogId: the numeric Meta catalog id. Find it with List catalogs.
curl -X POST "https://zernio.com/api/v1/commerce/catalog-syncs" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "catalogAccountId": "66c3f2a0b1d4e5f6a7b8c9d0",
    "catalogId": "1234567890123456"
  }'

The call answers 202 with the sync in runStatus: "pending", and the first full run starts in the background. One store syncs to a given catalog once: a second create for the same pair is 409 catalog_sync_conflict. A catalog can take several stores.

Once the run has finished, Get a catalog sync reads:

{
  "catalogSync": {
    "id": "66d4a1b2c3d4e5f6a7b8c9d0",
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "catalogPlatform": "meta",
    "catalogAccountId": "66c3f2a0b1d4e5f6a7b8c9d0",
    "catalogId": "1234567890123456",
    "runStatus": "succeeded",
    "lastRunStartedAt": "2026-09-29T05:20:04.000Z",
    "lastRunFinishedAt": "2026-09-29T05:21:37.000Z",
    "lastError": null,
    "itemsSent": 412,
    "itemsSkipped": 9,
    "itemsDeleted": 3,
    "createdAt": "2026-09-20T10:02:11.000Z"
  }
}

What a run does

Each product variant becomes one catalog item, grouped by product (item_group_id). A product is listed only when it is active, published to the online store (it has a storefront url) and has at least one image. Item ids are namespaced to the store (<platform>:<store>:<variantId>), so a run only ever touches items this store put in the catalog.

A full run pushes every listable variant, then deletes this store's items that are no longer listable or no longer exist. The counters describe the last run:

FieldMeaning
itemsSentVariant items Meta accepted in the last run (items Meta rejected are not counted).
itemsSkippedActive products left out because they are not published or have no image.
itemsDeletedItems of this store removed from the catalog because their product or variant is gone or no longer listable.
lastErrorWhy the last run failed, or a note when Meta rejected some items or had not finished processing a batch.

runStatus is pending until the first run starts, running during a run and succeeded or failed after it. A run in which Meta rejected every item is failed. A run that has not finished after 30 minutes reads as failed and is retried on the next daily pass.

Keeping it current

  • Product changes: a product created, updated or deleted on the store refreshes its items within minutes. Shopify notifies Zernio through its product webhooks; WooCommerce through webhooks Zernio registers on the site when a self-hosted site is connected and again when a sync is created. A deleted product queues a full run of the sync, since its variant ids are gone with it.
  • Daily pass: every sync that has not run in the last 20 hours runs once a day, which catches missed deliveries and deleted variants. Syncs of a disconnected store are skipped until it is reconnected.
  • On demand: Run a catalog sync now (POST /v1/commerce/catalog-syncs/{syncId}/run) queues a full run and answers 202. It is 409 catalog_sync_conflict while a run is in progress.

The same product changes also reach your own webhooks as commerce.product.created, commerce.product.updated and commerce.product.deleted, with the store and product id only; read the product with Get a product.

Stop a sync

Stop a catalog sync (DELETE /v1/commerce/catalog-syncs/{syncId}) stops syncing. Items already in the catalog stay there. List catalog syncs and Get a catalog sync read the state; reading and stopping keep working after the store is disconnected.

WooCommerce differences

WooCommerce is reached through the self-hosted site's REST API with the connected user's Application Password. What differs from Shopify:

AreaOn WooCommerce
Not served (400 platform_not_supported)Reordering collection products, adding codes to a discount (no discounts.codes), sales channels, navigation menus, URL redirects, metaobjects, markets and price lists, marketing activities.
Productsvendor, productType and seo are not supported (400). deactivate sets the product to draft and archive to private (inactive). A WooCommerce pending product reads as pending_review and a trashed one as deleted.
CollectionsProduct categories. They have no SEO fields and no sort order (400).
DiscountsCoupons, so code discounts only (no automatic discounts). A coupon cannot start in the future or require a minimum quantity (400). Deactivating one sets its expiry to now.
InventoryOne location, with the id default. An adjust reads the count and writes the sum, since WooCommerce has no atomic increment: a sale landing in between can be overwritten.
MetafieldsProduct custom fields (meta data) only: stores do not list collections.metafields, and collection metafields answer 400 platform_not_supported. A key without a dot is returned under the namespace woocommerce; namespace.key round-trips.
PaginationThe cursor wraps a page number and a page holds at most 100 rows.
PagesWordPress pages, available whenever the user can edit pages, with or without WooCommerce.

Common errors

ErrorCauseFix
400 platform_not_supportedThe account is not a store, or the store's platform does not serve this operationCheck capabilities on Get a store.
400 invalid_field_valueThe platform rejected a field, or the field does not exist on this platformRead the message; see WooCommerce differences.
403 insufficient_permissionsThe store has not granted the scope the operation needsSend the store owner to grantPermissionsUrl, or reconnect the store.
404 product_not_found / 404 resource_not_foundThe id was deleted or belongs to another storeList the resource to find a current id.
409 catalog_sync_conflictThe store already syncs to that catalog, or a run is already in progressRun the existing sync, or wait for the run to finish.
429Rate limited by Zernio or by the platformRetry later. See rate limits.
502The platform failed or was unavailableRetry once the earlier request's outcome is known.

Related

  • Commerce API reference: every endpoint with its fields.
  • Shopify: connecting a store, blogs and tracking pixels.
  • WordPress: connecting a site, blogs and tracking tags.
  • Product catalogs: the Meta catalogs a sync writes to.
  • Pixels and tracking tags: installing ad pixels on a store.
Was this page helpful?

Pixels and Tracking Tags

One API for every ad platform's pixel or site tag: list, create, update, read stats, share, manage users and conversion events, and install on Shopify or WordPress.

Report Bugs & Requests

Send a structured bug report or feature request from your code or AI agent with POST /v1/feedback. Every report is read by the Zernio team.

On this page

ConventionsConnect a storeCapabilities and extra permissionsShopifyWooCommerceEndpoint mapProductsVariants, options and imagesInventory and locationsCollectionsDiscountsSales channelsMarkets and price listsMetafields and metaobjectsPages, menus and redirectsMarketing activitiesExamplesList and create productsSet stockSync a store to a Meta catalogWhat a run doesKeeping it currentStop a syncWooCommerce differencesCommon errorsRelated