https://dash.joinmarkt.com
Paths use the prefix /api/merchant/v1. Every method calls requireMerchantAuth before the handler.
mk_live_.
The rate-limit IP is
cf-connecting-ip, then x-real-ip, then the last address in x-forwarded-for. When none of those headers is present, the bucket id is unknown.
A full-access key skips the scope-list check and still requires manageCoupons. A restricted key needs the method scope on the key and manageCoupons.
Success bodies are the JSON objects shown on each method. Failures look like this:
code INTERNAL_ERROR, error set to the method fallback below, and an errorId.
storeId inside a JSON body is discarded before validation. The store is the {storeId} path parameter. The body must be a JSON object. Invalid JSON returns 400 Invalid JSON. A body that is not an object returns 400 JSON body must be an object. Both use code BAD_REQUEST.
{couponId} is the coupon id, or a public id shaped CPN-XXXX-XXXX-XXXX-XXXX-XXXX (matched case-insensitively). A missing coupon, or a coupon in another store, returns 404 Coupon not found with code NOT_FOUND.
Validation failures return 400 BAD_REQUEST. The error string is Zod issues joined as field: message, separated by ; .
GET /api/merchant/v1/stores/{storeId}/coupons
Scope: coupon:read
merchantListQuerySchema in the merchant HTTP helper). Other query keys are ignored.
Coupons for the store are loaded newest
createdAt first, then sliced for page and limit.
200:
A page past the end returns
"coupons": [] with that page, limit, and total.
Coupon object (serializeCoupon):
500 fallback:
Failed to list coupons.
POST /api/merchant/v1/stores/{storeId}/coupons
Scope: coupon:create
createCouponBodySchema). The path storeId is attached after this parse.
error is Your <planId> plan allows up to <cap> coupons. Upgrade to add more. The body also includes:
500 fallback:
Failed to create coupon.
GET /api/merchant/v1/stores/{storeId}/coupons/{couponId}
Scope: coupon:read
Failed to load coupon.
PATCH /api/merchant/v1/stores/{storeId}/coupons/{couponId}
Scope: coupon:update
updateCouponBodySchema): every create field is optional, with the same types and limits.
Omitted fields stay as stored.
Coupon code already exists for this store with code coupon_code_duplicate.
500 fallback: Failed to update coupon.
PUT /api/merchant/v1/stores/{storeId}/coupons/{couponId}
Scope: coupon:update
Failed to update coupon.
DELETE /api/merchant/v1/stores/{storeId}/coupons/{couponId}
Scope: coupon:delete
Failed to delete coupon.

