Skip to main content
Base URL: https://dash.joinmarkt.com Paths use the prefix /api/merchant/v1. Every method calls requireMerchantAuth before the handler.
The token must start with 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:
An unexpected failure returns 500 with 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
Query (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
Body (createCouponBodySchema). The path storeId is attached after this parse.
201 returns the coupon object from GET one coupon. A duplicate code in the store returns 400:
When the store is already at its plan coupon cap, create returns 403. 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
200 returns one coupon object. Fields are the coupon object in the list section. 500 fallback: Failed to load coupon.

PATCH /api/merchant/v1/stores/{storeId}/coupons/{couponId}

Scope: coupon:update
Body (updateCouponBodySchema): every create field is optional, with the same types and limits. Omitted fields stay as stored.
200 returns the coupon object from GET one. A duplicate code returns 400 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
PUT calls the same handler as PATCH: same scope, same partial body, same 200 coupon object, same duplicate-code error. The 500 fallback is Failed to update coupon.

DELETE /api/merchant/v1/stores/{storeId}/coupons/{couponId}

Scope: coupon:delete
No body. 200:
500 fallback: Failed to delete coupon.