API reference
Promotions
Management API: GET /sites/{id}/promotions, POST /sites/{id}/promotions, GET /sites/{id}/promotions/{promotionId}, …
List promotions (automatic discounts and discount codes), newest first. Archived promotions are left out unless includeArchived is true. Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
includeArchivedboolean · queryAlso return archived promotions.
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[].namestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
data[].kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
data[].method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
data[].discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
data[].codestring | nullrequiredDiscount code (uppercase) when method is CODE.
data[].percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
data[].amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
data[].amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
data[].amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
data[].minQuantityinteger | nullrequiredMinimum quantity across matching lines.
data[].minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
data[].startsAtstring | nullrequiredISO 8601 start; null = already started.
data[].endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
data[].dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
data[].dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
data[].usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
data[].perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
data[].usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
data[].selectorobject | nullrequireddata[].selector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]data[].selector.refsobject[]data[].selector.categoryIdsstring[]data[].selector.tagsstring[]data[].selector.priceRangeobjectdata[].marketIdsstring[]requiredOnly these markets; empty = every market.
data[].audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
data[].minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
data[].badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
data[].isActivebooleanrequireddata[].archivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
data[].createdAtstringrequireddata[].updatedAtstringrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool promotions_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/promotions" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "prm_example01",
"name": "Autumn 10% Off",
"kind": "DISCOUNT_CODE",
"method": "CODE",
"discountType": "PERCENTAGE",
"code": "AUTUMN10",
"percentage": 10,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": "2026-10-01T02:30:00.000Z",
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 100,
"perCustomerLimit": 1,
"usageCount": 14,
"selector": null,
"marketIds": [],
"audienceType": "ALL",
"minPriorPurchases": null,
"badgeOverride": null,
"isActive": true,
"archivedAt": null,
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Create a promotion
/sites/{id}/promotionsCreate a promotion. It applies at Kanorio's hosted checkout only; a headless storefront can read it to show badges but cannot apply it. Same rules as the dashboard (code uniqueness, FLASH_SALE needs endsAt, selector references must belong to this site). Errors: FORBIDDEN, VALIDATION (details.code: CODE_REQUIRED, KIND_METHOD_MISMATCH, SCOPE_REQUIRED, START_MUST_BE_BEFORE_END, FLASH_SALE_ENDS_AT_REQUIRED, DAILY_WINDOW_INCOMPLETE, INVALID_PRODUCT, INVALID_VARIANT, INVALID_CATEGORY, INVALID_RESOURCE, INVALID_MARKET, UNSUPPORTED_LINE_KIND, TAGS_NOT_SUPPORTED), PROMOTION_CODE_CONFLICT, SITE_SUSPENDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Idempotency-Keystring · headerClient-chosen key (1-200 chars). Retrying with the same key and the same body returns the original result; the same key with a different body is rejected with IDEMPOTENCY_CONFLICT (409). Keys expire after 24 hours.
Request body
namestringrequiredkind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredDISCOUNT_CODE must use method CODE; the other kinds use AUTOMATIC.
method"CODE" | "AUTOMATIC"requireddiscountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredcodestringRequired when method is CODE; stored uppercase; unique per site.
percentageintegeramountsobject[]Required for FIXED_AMOUNT / OVERRIDE_PRICE; one entry per currency.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityintegerminOrderAmountintegerMinor units.
startsAtstringISO 8601 with offset.
endsAtstringISO 8601 with offset; required for FLASH_SALE.
dailyStartMinuteintegerdailyEndMinuteintegerMust be set together with dailyStartMinute.
usageLimitintegerperCustomerLimitintegerselectorobject | nullselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]Market ids of this site; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"minPriorPurchasesintegerOnly with audienceType RETURNING.
badgeOverridestringisActivebooleanDefault true on create.
Response 200
idstringrequirednamestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
codestring | nullrequiredDiscount code (uppercase) when method is CODE.
percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityinteger | nullrequiredMinimum quantity across matching lines.
minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
startsAtstring | nullrequiredISO 8601 start; null = already started.
endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
selectorobject | nullrequiredselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]requiredOnly these markets; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
isActivebooleanrequiredarchivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403VALIDATION400 · details.code: CODE_REQUIRED, KIND_METHOD_MISMATCH, SCOPE_REQUIRED, START_MUST_BE_BEFORE_END, FLASH_SALE_ENDS_AT_REQUIRED, DAILY_WINDOW_INCOMPLETE, INVALID_PRODUCT, INVALID_VARIANT, INVALID_CATEGORY, INVALID_RESOURCE, INVALID_MARKET, UNSUPPORTED_LINE_KIND, TAGS_NOT_SUPPORTEDPROMOTION_CODE_CONFLICT409SITE_SUSPENDED403
MCP tool promotions_create
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/promotions" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Studio bookings 20% off","kind":"PRODUCT_DISCOUNT","method":"AUTOMATIC","discountType":"PERCENTAGE","percentage":20,"selector":{"kinds":["BOOKING"],"refs":[{"kind":"BOOKING","id":"res_example01"}]},"audienceType":"RETURNING","minPriorPurchases":2}'{
"id": "prm_example02",
"name": "Studio bookings 20% off",
"kind": "PRODUCT_DISCOUNT",
"method": "AUTOMATIC",
"discountType": "PERCENTAGE",
"code": null,
"percentage": 20,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": null,
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 100,
"perCustomerLimit": 1,
"usageCount": 0,
"selector": {
"kinds": [
"BOOKING"
],
"refs": [
{
"kind": "BOOKING",
"id": "res_example01"
}
]
},
"marketIds": [],
"audienceType": "RETURNING",
"minPriorPurchases": 2,
"badgeOverride": null,
"isActive": true,
"archivedAt": null,
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Retrieve a promotion
/sites/{id}/promotions/{promotionId}Read one promotion, including its selector and per-currency amounts. Errors: FORBIDDEN, PROMOTION_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
promotionIdstring · pathrequiredPromotion id from promotions.list.
Response 200
idstringrequirednamestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
codestring | nullrequiredDiscount code (uppercase) when method is CODE.
percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityinteger | nullrequiredMinimum quantity across matching lines.
minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
startsAtstring | nullrequiredISO 8601 start; null = already started.
endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
selectorobject | nullrequiredselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]requiredOnly these markets; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
isActivebooleanrequiredarchivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PROMOTION_NOT_FOUND404
MCP tool promotions_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/promotions/{promotionId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"id": "prm_example01",
"name": "Autumn 10% Off",
"kind": "DISCOUNT_CODE",
"method": "CODE",
"discountType": "PERCENTAGE",
"code": "AUTUMN10",
"percentage": 10,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": "2026-10-01T02:30:00.000Z",
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 100,
"perCustomerLimit": 1,
"usageCount": 14,
"selector": null,
"marketIds": [],
"audienceType": "ALL",
"minPriorPurchases": null,
"badgeOverride": null,
"isActive": true,
"archivedAt": null,
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Update a promotion
/sites/{id}/promotions/{promotionId}Update a promotion (PATCH: fields left out stay as they are; selector: null means "all lines"; isActive toggles it). Changing a promotion does not affect orders already placed. Errors: FORBIDDEN, VALIDATION (details.code: CODE_REQUIRED, KIND_METHOD_MISMATCH, SCOPE_REQUIRED, START_MUST_BE_BEFORE_END, FLASH_SALE_ENDS_AT_REQUIRED, DAILY_WINDOW_INCOMPLETE, INVALID_PRODUCT, INVALID_VARIANT, INVALID_CATEGORY, INVALID_RESOURCE, INVALID_MARKET, UNSUPPORTED_LINE_KIND, TAGS_NOT_SUPPORTED), PROMOTION_CODE_CONFLICT, SITE_SUSPENDED, PROMOTION_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
promotionIdstring · pathrequiredPromotion id from promotions.list.
Request body
namestringkind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"DISCOUNT_CODE must use method CODE; the other kinds use AUTOMATIC.
method"CODE" | "AUTOMATIC"discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"codestringRequired when method is CODE; stored uppercase; unique per site.
percentageintegeramountsobject[]Required for FIXED_AMOUNT / OVERRIDE_PRICE; one entry per currency.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityintegerminOrderAmountintegerMinor units.
startsAtstringISO 8601 with offset.
endsAtstringISO 8601 with offset; required for FLASH_SALE.
dailyStartMinuteintegerdailyEndMinuteintegerMust be set together with dailyStartMinute.
usageLimitintegerperCustomerLimitintegerselectorobject | nullselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]Market ids of this site; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"minPriorPurchasesintegerOnly with audienceType RETURNING.
badgeOverridestringisActivebooleanDefault true on create.
Response 200
idstringrequirednamestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
codestring | nullrequiredDiscount code (uppercase) when method is CODE.
percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityinteger | nullrequiredMinimum quantity across matching lines.
minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
startsAtstring | nullrequiredISO 8601 start; null = already started.
endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
selectorobject | nullrequiredselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]requiredOnly these markets; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
isActivebooleanrequiredarchivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403VALIDATION400 · details.code: CODE_REQUIRED, KIND_METHOD_MISMATCH, SCOPE_REQUIRED, START_MUST_BE_BEFORE_END, FLASH_SALE_ENDS_AT_REQUIRED, DAILY_WINDOW_INCOMPLETE, INVALID_PRODUCT, INVALID_VARIANT, INVALID_CATEGORY, INVALID_RESOURCE, INVALID_MARKET, UNSUPPORTED_LINE_KIND, TAGS_NOT_SUPPORTEDPROMOTION_CODE_CONFLICT409SITE_SUSPENDED403PROMOTION_NOT_FOUND404
MCP tool promotions_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/promotions/{promotionId}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"selector":{"refs":[{"kind":"PRODUCT","id":"prod_example01","variantId":"tier_example01"}],"priceRange":{"min":50000}},"usageLimit":200}'{
"id": "prm_example01",
"name": "Autumn 10% Off",
"kind": "DISCOUNT_CODE",
"method": "CODE",
"discountType": "PERCENTAGE",
"code": "AUTUMN10",
"percentage": 10,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": "2026-10-01T02:30:00.000Z",
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 200,
"perCustomerLimit": 1,
"usageCount": 14,
"selector": {
"refs": [
{
"kind": "PRODUCT",
"id": "prod_example01",
"variantId": "tier_example01"
}
],
"priceRange": {
"min": 50000
}
},
"marketIds": [],
"audienceType": "ALL",
"minPriorPurchases": null,
"badgeOverride": null,
"isActive": true,
"archivedAt": null,
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Delete a promotion
/sites/{id}/promotions/{promotionId}Delete a promotion that was never redeemed. Once a paid order used it the redemption history is an accounting trail, so archive it instead. Errors: FORBIDDEN, PROMOTION_NOT_FOUND, PROMOTION_HAS_REDEMPTIONS.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
promotionIdstring · pathrequiredPromotion id from promotions.list.
Response 200
idstringrequireddeletedtruerequired
Errors
FORBIDDEN403PROMOTION_NOT_FOUND404PROMOTION_HAS_REDEMPTIONS409
MCP tool promotions_delete
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/promotions/{promotionId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"id": "prm_example02",
"deleted": true
}Archive a promotion
/sites/{id}/promotions/{promotionId}/archiveArchive a promotion: it stops applying, is hidden from the dashboard list and keeps its redemption history. Idempotent. Errors: FORBIDDEN, PROMOTION_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
promotionIdstring · pathrequiredPromotion id from promotions.list.
Response 200
idstringrequirednamestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
codestring | nullrequiredDiscount code (uppercase) when method is CODE.
percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityinteger | nullrequiredMinimum quantity across matching lines.
minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
startsAtstring | nullrequiredISO 8601 start; null = already started.
endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
selectorobject | nullrequiredselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]requiredOnly these markets; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
isActivebooleanrequiredarchivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PROMOTION_NOT_FOUND404
MCP tool promotions_archive
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/promotions/{promotionId}/archive" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "prm_example01",
"name": "Autumn 10% Off",
"kind": "DISCOUNT_CODE",
"method": "CODE",
"discountType": "PERCENTAGE",
"code": "AUTUMN10",
"percentage": 10,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": "2026-10-01T02:30:00.000Z",
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 100,
"perCustomerLimit": 1,
"usageCount": 14,
"selector": null,
"marketIds": [],
"audienceType": "ALL",
"minPriorPurchases": null,
"badgeOverride": null,
"isActive": false,
"archivedAt": "2026-10-06T08:00:00.000Z",
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}Promotion unarchive
/sites/{id}/promotions/{promotionId}/unarchiveRestore an archived promotion and enable it again. Errors: FORBIDDEN, PROMOTION_NOT_FOUND, SITE_SUSPENDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
promotionIdstring · pathrequiredPromotion id from promotions.list.
Response 200
idstringrequirednamestringrequiredShown to the seller and, for automatic promotions, as the line on the checkout page.
kind"PRODUCT_DISCOUNT" | "DISCOUNT_CODE" | "QUANTITY_DISCOUNT" | "FLASH_SALE" | "MIN_ORDER"requiredWizard type; drives the storefront badge and which conditions apply.
method"CODE" | "AUTOMATIC"requiredCODE = the buyer enters a code at checkout; AUTOMATIC = applied when conditions match.
discountType"PERCENTAGE" | "FIXED_AMOUNT" | "OVERRIDE_PRICE"requiredPERCENTAGE of the matching lines, FIXED_AMOUNT off the order once, or OVERRIDE_PRICE as the per-unit price of matching lines.
codestring | nullrequiredDiscount code (uppercase) when method is CODE.
percentageinteger | nullrequired1–100 when discountType is PERCENTAGE.
amountsobject[]requiredPer-currency amount for FIXED_AMOUNT / OVERRIDE_PRICE.
amounts[].currencystringrequiredISO 4217, case-insensitive; stored lowercase.
amounts[].amountintegerrequiredMinor units (cents; TWD uses 100 = NT$1).
minQuantityinteger | nullrequiredMinimum quantity across matching lines.
minOrderAmountinteger | nullrequiredMinimum discountable subtotal of the order, minor units.
startsAtstring | nullrequiredISO 8601 start; null = already started.
endsAtstring | nullrequiredISO 8601 end; null = no end. Required for FLASH_SALE.
dailyStartMinuteinteger | nullrequiredDaily window start, minutes from midnight in the site timezone.
dailyEndMinuteinteger | nullrequiredDaily window end, minutes from midnight in the site timezone.
usageLimitinteger | nullrequiredTotal paid orders allowed; null = unlimited.
perCustomerLimitinteger | nullrequiredPaid orders per customer; null = unlimited.
usageCountintegerrequiredPaid orders that redeemed this promotion (refunds are not subtracted).
selectorobject | nullrequiredselector.kinds"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"[]selector.refsobject[]selector.refs[].kind"PRODUCT" | "BOOKING" | "PREPAID" | "CUSTOM"requiredselector.refs[].idstringrequiredselector.refs[].variantIdstringselector.categoryIdsstring[]selector.tagsstring[]selector.priceRangeobjectselector.priceRange.minintegerselector.priceRange.maxintegermarketIdsstring[]requiredOnly these markets; empty = every market.
audienceType"ALL" | "FIRST_PURCHASE" | "RETURNING"requiredFIRST_PURCHASE = no paid order yet; RETURNING = at least minPriorPurchases paid orders.
minPriorPurchasesinteger | nullrequiredRETURNING threshold; null = 1.
badgeOverridestring | nullrequiredCustom storefront badge text (automatic promotions only).
isActivebooleanrequiredarchivedAtstring | nullrequiredArchived promotions are hidden from the dashboard list and never apply.
createdAtstringrequiredupdatedAtstringrequired
Errors
FORBIDDEN403PROMOTION_NOT_FOUND404SITE_SUSPENDED403
MCP tool promotions_unarchive
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/promotions/{promotionId}/unarchive" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "prm_example01",
"name": "Autumn 10% Off",
"kind": "DISCOUNT_CODE",
"method": "CODE",
"discountType": "PERCENTAGE",
"code": "AUTUMN10",
"percentage": 10,
"amounts": [],
"minQuantity": null,
"minOrderAmount": null,
"startsAt": "2026-10-01T02:30:00.000Z",
"endsAt": null,
"dailyStartMinute": null,
"dailyEndMinute": null,
"usageLimit": 100,
"perCustomerLimit": 1,
"usageCount": 14,
"selector": null,
"marketIds": [],
"audienceType": "ALL",
"minPriorPurchases": null,
"badgeOverride": null,
"isActive": true,
"archivedAt": null,
"createdAt": "2026-10-01T02:30:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z"
}