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 manageGroups. A restricted key needs the method scope on the key and manageGroups.
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.
{groupId} is the group id, or a public id shaped GRP-XXXX-XXXX-XXXX-XXXX-XXXX (matched case-insensitively). The group object does not include publicId. A missing group, or a group in another store, returns 404 Group 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}/groups
Scope: group:read
merchantListQuerySchema). Other query keys are ignored.
200:
Each list row is a group object with
products set to []. productCount is the number of linked products. Rows are ordered by sortOrder ascending, then createdAt descending. sortOrder is not in the response. A page past the end returns "groups": [] with that page, limit, and total.
Group object:
500 fallback:
Failed to list groups.
POST /api/merchant/v1/stores/{storeId}/groups
Scope: group:create
merchantCreateGroupSchema):
products lists numeric product ids in this store. An id that is not in the store returns 400 One or more products were not found in this store (BAD_REQUEST). products: [] or an omitted products creates the group with no members. A duplicate name returns 400 Group name already exists for this store (BAD_REQUEST).
500 fallback: Failed to create group.
GET /api/merchant/v1/stores/{storeId}/groups/{groupId}
Scope: group:read
products lists members:
products:
productCount is _count.products when the row has it, otherwise the length of products.
500 fallback: Failed to load group.
PATCH /api/merchant/v1/stores/{storeId}/groups/{groupId}
Scope: group:update
merchantPatchGroupSchema, every create field optional):
products leaves membership unchanged. Sending products unlinks every current member, then links the given numeric product ids. products: [] leaves the group with no members. Product ids must belong to this store (same 400 as create). A duplicate name returns 400 Group name already exists for this store (BAD_REQUEST).
500 fallback: Failed to update group.
PUT /api/merchant/v1/stores/{storeId}/groups/{groupId}
Scope: group:update
Failed to update group.
DELETE /api/merchant/v1/stores/{storeId}/groups/{groupId}
Scope: group:delete
Failed to delete group.

