API reference
Products
Management API: GET /sites/{id}/products, POST /sites/{id}/products, GET /sites/{id}/products/{productId}, …
List products (ACTIVE first, then ARCHIVED; excludes sample template products by default unless includeTemplates=true) with images, variants and paid order counts; storeCurrency is the ISO 4217 code all amounts use. Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
status"DRAFT" | "ACTIVE" | "ARCHIVED" · queryincludeTemplatesboolean | "true" | "false" · queryInclude sample template products seeded for new stores (defaults to false).
limitinteger · queryMaximum number of items to return, 1–100. Defaults to 20.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
Response 200
dataobject[]requireddata[].idstringrequireddata[].titlestringrequireddata[].descriptionstring | nullrequireddata[].slugstring | nullrequiredLast URL segment of the product page.
data[].status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
data[].seoobjectrequiredSearch and share fields stored on the product.
data[].seo.titlestring | nullrequireddata[].seo.descriptionstring | nullrequireddata[].seo.keywordsstring[]requireddata[].seo.ogTitlestring | nullrequireddata[].seo.ogDescriptionstring | nullrequireddata[].seo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
data[].seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
data[].seo.canonicalUrlstring | nullrequireddata[].seo.robotsIndex"index" | "noindex"requireddata[].seo.robotsFollow"follow" | "nofollow"requireddata[].seo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
data[].type"DIGITAL" | "PHYSICAL" | "SERVICE"requireddata[].isTemplatebooleanrequiredSample product seeded for new sellers; not public.
data[].imagesobject[]requireddata[].images[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
data[].images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
data[].images[].altstringdata[].images[].widthintegerdata[].images[].heightintegerdata[].imageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
data[].imageFramings[].focusobjectdata[].imageFramings[].screensobjectdata[].imageFramings[].fitMode"contain" | "cover"data[].imageFramings[].padColorstringdata[].purchaseNotesstring | nullrequireddata[].allowCustomAmountbooleanrequireddata[].reviewsEnabledbooleanrequireddata[].metadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
data[].privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
data[].primaryCategoryIdstring | nullrequireddata[].optionsobject[]requireddata[].options[].idstringrequireddata[].options[].namestringrequireddata[].options[].positionintegerrequireddata[].options[].valuesstring[]requireddata[].pricingTiersobject[]requiredPurchasable variants; manage them in the console.
data[].pricingTiers[].idstringrequireddata[].pricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
data[].pricingTiers[].optionValuesstring[]requireddata[].pricingTiers[].amountintegerrequiredMinor units in the store currency.
data[].pricingTiers[].compareAtAmountinteger | nullrequireddata[].pricingTiers[].isPublishedbooleanrequireddata[].pricingTiers[].sortOrderintegerrequireddata[].pricingTiers[].highlightBadgestring | nullrequireddata[].pricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
data[].pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
data[].deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
data[].deliverables[].deliverableIdstringrequireddata[].deliverables[].tierIdstring | nullrequireddata[].orderCountintegerPaid orders (list only).
data[].createdAtstringrequireddata[].updatedAtstringrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
storeCurrencystringrequiredLowercase ISO 4217, e.g. twd, usd.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool products_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/products" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}
],
"storeCurrency": "TWD",
"hasMore": false,
"nextCursor": null
}Create a DRAFT product. It stays off the public shop until products.publish. Add variants and prices in the console; a product with no published variant still cannot be purchased after it is published. detailMarkdown and faq are optional and render on the product detail page. Images and ogImageAssetId are media-library assets: pass their assetId (upload with media.upload or media.import_stock first). Slug is generated from the title when omitted. SEO fields are optional and only the ones you send are stored. productType only decides whether the product is shipped; link deliverables (courses, downloads) afterwards with products.update { deliverables } on either type. Errors: FORBIDDEN, VALIDATION (details.reason), SITE_SUSPENDED, ASSET_NOT_FOUND (details.assetIds: not in this site's media library, in the trash, or its file is missing), ASSET_NOT_ALLOWED (not an image, or a product file).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
titlestringrequireddescriptionstringPlain text description shown on the store card, checkout and product page. Optional; omit or send an empty string for none. Markdown markers are removed.
imagesobject[]requiredUp to 10; the first is the cover image. May be empty: the product page then renders without a gallery and listings show a placeholder.
images[].assetIdstringrequiredassetId of an image in the media library (media.upload, media.import_stock, media.list).
images[].altstringimageFramingsobject | null[]Optional framing per image (parallel to images; omit or pass null for automatic presentation).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullallowCustomAmountbooleanreviewsEnabledbooleanslugstringproductType"DIGITAL" | "PHYSICAL" | "SERVICE"Only about shipping, and only the default: DIGITAL (default) has nothing to ship, PHYSICAL is shipped and needs delivery settings. Each variant can override it (pricingTiers[].requiresShipping, set in the console). Either type can grant deliverables on purchase (products.update { deliverables }).
detailMarkdownstring | nullMarkdown shown under the product images on the detail page. Null or an empty string clears it. Omit on update to leave it unchanged.
faqobject[]On update, replaces the whole FAQ list. Omit to leave it unchanged. Pass [] to clear.
faq[].idstringfaq[].questionstringrequiredfaq[].answerstringrequiredMarkdown subset, same as the FAQ module.
metadataobject | nullReplace all public integration fields (flat key/value, up to 50 keys, 16 KB); null clears them.
privateMetadataobject | nullReplace all private integration fields; null clears them.
seoTitlestring | nullSearch title. Null or empty clears it.
seoDescriptionstring | nullseoKeywordsstring[] | nullNull or [] clears keywords.
ogImageAssetIdstring | nullShare image assetId. Null removes it. Without one, an image may be generated automatically.
ogImageAltstring | nullogImageFramingobject | nullogImageFraming.focusobjectogImageFraming.focus.xnumberrequiredogImageFraming.focus.ynumberrequiredogImageFraming.screensobjectogImageFraming.screens.desktopobjectogImageFraming.screens.tabletobjectogImageFraming.screens.mobileobjectogImageFraming.fitMode"contain" | "cover"ogImageFraming.padColorstringogTitlestring | nullogDescriptionstring | nullcanonicalUrlstring | nullrobotsIndex"index" | "noindex"robotsFollow"follow" | "nofollow"twitterCard"summary" | "summary_large_image"autoOgEnabledbooleanexcludeFromSiteSearchbooleanHide from visitor site search. Does not change robotsIndex.
Response 201
idstringrequiredtitlestringrequireddescriptionstring | nullrequiredslugstring | nullrequiredLast URL segment of the product page.
status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
seoobjectrequiredSearch and share fields stored on the product.
seo.titlestring | nullrequiredseo.descriptionstring | nullrequiredseo.keywordsstring[]requiredseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.canonicalUrlstring | nullrequiredseo.robotsIndex"index" | "noindex"requiredseo.robotsFollow"follow" | "nofollow"requiredseo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
type"DIGITAL" | "PHYSICAL" | "SERVICE"requiredisTemplatebooleanrequiredSample product seeded for new sellers; not public.
imagesobject[]requiredimages[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
images[].altstringimages[].widthintegerimages[].heightintegerimageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullrequiredallowCustomAmountbooleanrequiredreviewsEnabledbooleanrequiredmetadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
primaryCategoryIdstring | nullrequiredoptionsobject[]requiredoptions[].idstringrequiredoptions[].namestringrequiredoptions[].positionintegerrequiredoptions[].valuesstring[]requiredpricingTiersobject[]requiredPurchasable variants; manage them in the console.
pricingTiers[].idstringrequiredpricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
pricingTiers[].optionValuesstring[]requiredpricingTiers[].amountintegerrequiredMinor units in the store currency.
pricingTiers[].compareAtAmountinteger | nullrequiredpricingTiers[].isPublishedbooleanrequiredpricingTiers[].sortOrderintegerrequiredpricingTiers[].highlightBadgestring | nullrequiredpricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nullrequiredorderCountintegerPaid orders (list only).
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403VALIDATION400 · details.reasonSITE_SUSPENDED403ASSET_NOT_FOUND404 · details.assetIds: not in this site's media library, in the trash, or its file is missingASSET_NOT_ALLOWED422 · not an image, or a product file
MCP tool products_create
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/products" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Strawberry Birthday Cake","productType":"PHYSICAL","description":"Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.","images":[{"assetId":"asset_example01","alt":"Strawberry birthday cake"}]}'{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Get one product with images, options and variants. Errors: FORBIDDEN, PRODUCT_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
productIdstring · pathrequiredProduct id. Get it from products.list.
Response 200
idstringrequiredtitlestringrequireddescriptionstring | nullrequiredslugstring | nullrequiredLast URL segment of the product page.
status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
seoobjectrequiredSearch and share fields stored on the product.
seo.titlestring | nullrequiredseo.descriptionstring | nullrequiredseo.keywordsstring[]requiredseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.canonicalUrlstring | nullrequiredseo.robotsIndex"index" | "noindex"requiredseo.robotsFollow"follow" | "nofollow"requiredseo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
type"DIGITAL" | "PHYSICAL" | "SERVICE"requiredisTemplatebooleanrequiredSample product seeded for new sellers; not public.
imagesobject[]requiredimages[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
images[].altstringimages[].widthintegerimages[].heightintegerimageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullrequiredallowCustomAmountbooleanrequiredreviewsEnabledbooleanrequiredmetadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
primaryCategoryIdstring | nullrequiredoptionsobject[]requiredoptions[].idstringrequiredoptions[].namestringrequiredoptions[].positionintegerrequiredoptions[].valuesstring[]requiredpricingTiersobject[]requiredPurchasable variants; manage them in the console.
pricingTiers[].idstringrequiredpricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
pricingTiers[].optionValuesstring[]requiredpricingTiers[].amountintegerrequiredMinor units in the store currency.
pricingTiers[].compareAtAmountinteger | nullrequiredpricingTiers[].isPublishedbooleanrequiredpricingTiers[].sortOrderintegerrequiredpricingTiers[].highlightBadgestring | nullrequiredpricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nullrequiredorderCountintegerPaid orders (list only).
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PRODUCT_NOT_FOUND404
MCP tool products_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/products/{productId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Update a product
/sites/{id}/products/{productId}Partially update a product (only provided fields change; images replace the whole list). SEO fields are the same: omitted fields stay as they are, and null clears a text or share image. Publishing is products.publish: a DRAFT product stays off the shop. Changes to an ACTIVE product appear on the public store immediately. Errors: FORBIDDEN, PRODUCT_NOT_FOUND, VALIDATION (details.reason), SITE_SUSPENDED, ASSET_NOT_FOUND (details.assetIds: not in this site's media library, in the trash, or its file is missing), ASSET_NOT_ALLOWED (not an image, or a product file).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
productIdstring · pathrequiredProduct id. Get it from products.list.
Request body
titlestringdescriptionstringPlain text description shown on the store card, checkout and product page. Optional; omit or send an empty string for none. Markdown markers are removed.
imagesobject[]Up to 10; the first is the cover image. May be empty: the product page then renders without a gallery and listings show a placeholder.
images[].assetIdstringrequiredassetId of an image in the media library (media.upload, media.import_stock, media.list).
images[].altstringimageFramingsobject | null[]Optional framing per image (parallel to images; omit or pass null for automatic presentation).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullallowCustomAmountbooleanreviewsEnabledbooleanslugstringproductType"DIGITAL" | "PHYSICAL" | "SERVICE"Default shipping for variants without their own setting. Changing it affects future checkouts only; orders keep what was decided at checkout. Omit to leave it unchanged.
detailMarkdownstring | nullMarkdown shown under the product images on the detail page. Null or an empty string clears it. Omit on update to leave it unchanged.
faqobject[]On update, replaces the whole FAQ list. Omit to leave it unchanged. Pass [] to clear.
faq[].idstringfaq[].questionstringrequiredfaq[].answerstringrequiredMarkdown subset, same as the FAQ module.
metadataobject | nullReplace all public integration fields (flat key/value, up to 50 keys, 16 KB); null clears them.
privateMetadataobject | nullReplace all private integration fields; null clears them.
seoTitlestring | nullSearch title. Null or empty clears it.
seoDescriptionstring | nullseoKeywordsstring[] | nullNull or [] clears keywords.
ogImageAssetIdstring | nullShare image assetId. Null removes it. Without one, an image may be generated automatically.
ogImageAltstring | nullogImageFramingobject | nullogImageFraming.focusobjectogImageFraming.focus.xnumberrequiredogImageFraming.focus.ynumberrequiredogImageFraming.screensobjectogImageFraming.screens.desktopobjectogImageFraming.screens.tabletobjectogImageFraming.screens.mobileobjectogImageFraming.fitMode"contain" | "cover"ogImageFraming.padColorstringogTitlestring | nullogDescriptionstring | nullcanonicalUrlstring | nullrobotsIndex"index" | "noindex"robotsFollow"follow" | "nofollow"twitterCard"summary" | "summary_large_image"autoOgEnabledbooleanexcludeFromSiteSearchbooleanHide from visitor site search. Does not change robotsIndex.
deliverablesobject[]Replace the whole list of deliverables granted on purchase. Create deliverables first with deliverables.create. Pass [] to unlink all. Access duration is not set here: use pricingTiers[].accessDays.
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nulldeliverables[].accessDaysanypricingTiersobject[]Set access duration on existing variants by id: [{ id, accessDays }], accessDays null = permanent. It applies to every deliverable the variant grants. Only accessDays can be changed here; other variant fields are managed in the console. Variants not listed are left unchanged.
pricingTiers[].idstringrequiredpricingTiers[].accessDaysinteger | nullrequired
Response 200
idstringrequiredtitlestringrequireddescriptionstring | nullrequiredslugstring | nullrequiredLast URL segment of the product page.
status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
seoobjectrequiredSearch and share fields stored on the product.
seo.titlestring | nullrequiredseo.descriptionstring | nullrequiredseo.keywordsstring[]requiredseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.canonicalUrlstring | nullrequiredseo.robotsIndex"index" | "noindex"requiredseo.robotsFollow"follow" | "nofollow"requiredseo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
type"DIGITAL" | "PHYSICAL" | "SERVICE"requiredisTemplatebooleanrequiredSample product seeded for new sellers; not public.
imagesobject[]requiredimages[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
images[].altstringimages[].widthintegerimages[].heightintegerimageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullrequiredallowCustomAmountbooleanrequiredreviewsEnabledbooleanrequiredmetadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
primaryCategoryIdstring | nullrequiredoptionsobject[]requiredoptions[].idstringrequiredoptions[].namestringrequiredoptions[].positionintegerrequiredoptions[].valuesstring[]requiredpricingTiersobject[]requiredPurchasable variants; manage them in the console.
pricingTiers[].idstringrequiredpricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
pricingTiers[].optionValuesstring[]requiredpricingTiers[].amountintegerrequiredMinor units in the store currency.
pricingTiers[].compareAtAmountinteger | nullrequiredpricingTiers[].isPublishedbooleanrequiredpricingTiers[].sortOrderintegerrequiredpricingTiers[].highlightBadgestring | nullrequiredpricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nullrequiredorderCountintegerPaid orders (list only).
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PRODUCT_NOT_FOUND404VALIDATION400 · details.reasonSITE_SUSPENDED403ASSET_NOT_FOUND404 · details.assetIds: not in this site's media library, in the trash, or its file is missingASSET_NOT_ALLOWED422 · not an image, or a product file
MCP tool products_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/products/{productId}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"purchaseNotes":"Please leave your preferred pickup time in the order note."}'{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Publish a product
/sites/{id}/products/{productId}/publishPublish a product (status ACTIVE) so it appears on the public shop and its detail page. Sample products and archived products cannot be published. Idempotent when already ACTIVE. Errors: FORBIDDEN, PRODUCT_NOT_FOUND, PRODUCT_NOT_LISTABLE, SITE_SUSPENDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
productIdstring · pathrequiredProduct id. Get it from products.list.
Response 200
idstringrequiredtitlestringrequireddescriptionstring | nullrequiredslugstring | nullrequiredLast URL segment of the product page.
status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
seoobjectrequiredSearch and share fields stored on the product.
seo.titlestring | nullrequiredseo.descriptionstring | nullrequiredseo.keywordsstring[]requiredseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.canonicalUrlstring | nullrequiredseo.robotsIndex"index" | "noindex"requiredseo.robotsFollow"follow" | "nofollow"requiredseo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
type"DIGITAL" | "PHYSICAL" | "SERVICE"requiredisTemplatebooleanrequiredSample product seeded for new sellers; not public.
imagesobject[]requiredimages[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
images[].altstringimages[].widthintegerimages[].heightintegerimageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullrequiredallowCustomAmountbooleanrequiredreviewsEnabledbooleanrequiredmetadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
primaryCategoryIdstring | nullrequiredoptionsobject[]requiredoptions[].idstringrequiredoptions[].namestringrequiredoptions[].positionintegerrequiredoptions[].valuesstring[]requiredpricingTiersobject[]requiredPurchasable variants; manage them in the console.
pricingTiers[].idstringrequiredpricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
pricingTiers[].optionValuesstring[]requiredpricingTiers[].amountintegerrequiredMinor units in the store currency.
pricingTiers[].compareAtAmountinteger | nullrequiredpricingTiers[].isPublishedbooleanrequiredpricingTiers[].sortOrderintegerrequiredpricingTiers[].highlightBadgestring | nullrequiredpricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nullrequiredorderCountintegerPaid orders (list only).
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PRODUCT_NOT_FOUND404PRODUCT_NOT_LISTABLE422SITE_SUSPENDED403
MCP tool products_publish
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/products/{productId}/publish" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Unpublish a product
/sites/{id}/products/{productId}/unpublishUnpublish a product (status DRAFT). It leaves the public shop and its detail page returns not found. Sample products and archived products cannot be unpublished; restore an archived product in the console first. Idempotent when already DRAFT. Errors: FORBIDDEN, PRODUCT_NOT_FOUND, PRODUCT_NOT_LISTABLE, SITE_SUSPENDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
productIdstring · pathrequiredProduct id. Get it from products.list.
Response 200
idstringrequiredtitlestringrequireddescriptionstring | nullrequiredslugstring | nullrequiredLast URL segment of the product page.
status"DRAFT" | "ACTIVE" | "ARCHIVED"requiredDRAFT is not on the public shop. ACTIVE is published. ARCHIVED is retired.
seoobjectrequiredSearch and share fields stored on the product.
seo.titlestring | nullrequiredseo.descriptionstring | nullrequiredseo.keywordsstring[]requiredseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.ogImagestring | nullrequiredShare image display URL. Changes with versions; do not store it.
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.canonicalUrlstring | nullrequiredseo.robotsIndex"index" | "noindex"requiredseo.robotsFollow"follow" | "nofollow"requiredseo.excludeFromSiteSearchbooleanrequiredHide from visitor site search. Does not change robotsIndex.
type"DIGITAL" | "PHYSICAL" | "SERVICE"requiredisTemplatebooleanrequiredSample product seeded for new sellers; not public.
imagesobject[]requiredimages[].assetIdstring | nullrequiredassetId in the media library; null for older images that are not in it.
images[].urlstringrequiredDisplay URL (changes with versions; do not store it).
images[].altstringimages[].widthintegerimages[].heightintegerimageFramingsobject | null[]Optional presentation framing per image (parallel to images; omitted when all images use automatic mode).
imageFramings[].focusobjectimageFramings[].focus.xnumberrequiredimageFramings[].focus.ynumberrequiredimageFramings[].screensobjectimageFramings[].screens.desktopobjectimageFramings[].screens.tabletobjectimageFramings[].screens.mobileobjectimageFramings[].fitMode"contain" | "cover"imageFramings[].padColorstringpurchaseNotesstring | nullrequiredallowCustomAmountbooleanrequiredreviewsEnabledbooleanrequiredmetadataobjectrequiredPublic integration fields (flat key/value, up to 50 keys, 16 KB). Visible to the storefront and Delivery API.
privateMetadataobjectrequiredPrivate integration fields; only the console, members and service accounts can read them.
primaryCategoryIdstring | nullrequiredoptionsobject[]requiredoptions[].idstringrequiredoptions[].namestringrequiredoptions[].positionintegerrequiredoptions[].valuesstring[]requiredpricingTiersobject[]requiredPurchasable variants; manage them in the console.
pricingTiers[].idstringrequiredpricingTiers[].titlestringrequiredVariant title derived from option values (or the legacy name).
pricingTiers[].optionValuesstring[]requiredpricingTiers[].amountintegerrequiredMinor units in the store currency.
pricingTiers[].compareAtAmountinteger | nullrequiredpricingTiers[].isPublishedbooleanrequiredpricingTiers[].sortOrderintegerrequiredpricingTiers[].highlightBadgestring | nullrequiredpricingTiers[].requiresShippingbooleanrequiredWhether buyers of this variant enter a delivery address and pay shipping. Variants without their own setting follow productType, so one product can mix a course-only variant and a course + printed book variant. Set per variant in the console.
pricingTiers[].accessDaysinteger | nullrequiredDays buyers of this variant can access every deliverable it grants, counted from payment; null = permanent. One product can sell a 3-month variant and a lifetime variant. Set it with products.update({ pricingTiers: [{ id, accessDays }] }).
deliverablesobject[]requiredDeliverables granted on purchase (any product type). tierId null = every variant. How long buyers keep access is pricingTiers[].accessDays. Manage with products.update({ deliverables }).
deliverables[].deliverableIdstringrequireddeliverables[].tierIdstring | nullrequiredorderCountintegerPaid orders (list only).
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PRODUCT_NOT_FOUND404PRODUCT_NOT_LISTABLE422SITE_SUSPENDED403
MCP tool products_unpublish
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/products/{productId}/unpublish" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "prod_example01",
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream. Pre-order at least one day ahead.",
"slug": "strawberry-birthday-cake",
"status": "ACTIVE",
"seo": {
"title": "Strawberry Birthday Cake",
"description": "Seasonal strawberries and fresh whipped cream.",
"keywords": [
"cake",
"birthday"
],
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"ogImageAssetId": null,
"excludeFromSiteSearch": false,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow"
},
"type": "PHYSICAL",
"isTemplate": false,
"images": [
{
"assetId": "asset_example01",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"alt": "Strawberry birthday cake",
"width": 2400,
"height": 1600
}
],
"purchaseNotes": "Please leave your preferred pickup time in the order note.",
"metadata": {},
"privateMetadata": {},
"allowCustomAmount": false,
"reviewsEnabled": true,
"primaryCategoryId": "pcat_example01",
"options": [
{
"id": "opt_example01",
"name": "Size",
"position": 0,
"values": [
"6-inch",
"8-inch"
]
}
],
"pricingTiers": [
{
"id": "tier_example01",
"title": "6-inch",
"optionValues": [
"6-inch"
],
"amount": 880,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 0,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
},
{
"id": "tier_example02",
"title": "8-inch",
"optionValues": [
"8-inch"
],
"amount": 1280,
"compareAtAmount": null,
"isPublished": true,
"sortOrder": 1,
"highlightBadge": null,
"requiresShipping": true,
"accessDays": null
}
],
"deliverables": [],
"orderCount": 18,
"createdAt": "2026-08-20T06:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Archive a product
/sites/{id}/products/{productId}/archiveArchive a product: it disappears from the store but keeps its orders and can be restored in the console. Deleting products is not available to agents. Errors: FORBIDDEN, PRODUCT_NOT_FOUND, SITE_SUSPENDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
productIdstring · pathrequiredProduct id. Get it from products.list.
Response 200
idstringrequiredstatus"ARCHIVED"required
Errors
FORBIDDEN403PRODUCT_NOT_FOUND404SITE_SUSPENDED403
MCP tool products_archive
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/products/{productId}/archive" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "prod_example01",
"status": "ARCHIVED"
}