Skip to main content
Base URL: https://dash.joinmarkt.com
The user must be able to access {storeId}. Store scope SELECTED must include that shop. ALL_ACCESSIBLE does not add a shop list. Access mode RESTRICTED must include the route scope exactly. Access mode FULL skips the scope list. Both modes still require viewCustomers. Limiter: 120 requests per 60 seconds per client IP. 429 Too many requests is returned before the key is checked. {customerKey} is the key field: a public id CUS- plus five groups of four characters from 23456789ABCDEFGHJKMNPQRSTUVWXYZ (no 0, O, 1, I, or L). Lookup trims the path value and uppercases it. Any other shape, including a legacy base64 email key, returns 404 Customer not found. A CUS- id that is not stored for this shop, or a stored email with no @, returns the same 404. The id field is a separate legacy numeric string derived from the email. It is not accepted in the path. Successful bodies are the objects below. Errors:
code is BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, RATE_LIMITED, or INTERNAL_ERROR. A 500 also includes errorId.

GET /api/merchant/v1/stores/{storeId}/customers

Scope: customer:read Query parameters this route reads: Other query keys are ignored. An invalid page or limit returns 400. The message is the Zod issues joined with ; (path: message when the issue has a path). An empty page or limit is invalid ("" coerces to 0), not the default. The list is every customer derived for the shop, then sliced. total is the count before the slice. page and limit in the body are the values used. A page past the end returns "customers": [] and the same total. A row is included when the shop has an order, a customer auth session, a ticket, or a store-credit balance for that email. Sort is joinedAt descending.
status uses the first match: affiliate when referralCount > 0; else vip when totalSpent ≥ 200 or orderCount ≥ 8; else regular when orderCount ≥ 3; else active when orderCount > 0; else new.
Fallback error: Failed to list customers.

GET /api/merchant/v1/stores/{storeId}/customers/{customerKey}

Scope: customer:read No query parameters. The body is one customer. It includes every list field, then the fields below. integrationCount on this response is always 0. Detail does not load auth sessions. A list row that exists only because of an auth session (no orders, no tickets, and no store-credit balance row) returns 404 Customer not found. A balance of 0 still counts as a row. A key that does not resolve to an email, or resolves to a value with no @, also returns 404. orders is every order for the email in this shop, createdAt descending. publicId and orderId on each order are the two stored columns, returned as saved. tickets is every ticket for the lowercased email, createdAt descending.
Detail-only fields: network fields: ipAddress, country, browser, operatingSystem, userAgent, asn (string or null), seenAt (that order’s createdAt ISO-8601, or null), distinctIps, distinctCountries. The distinct counts are unique trimmed, lowercased values across the customer’s orders. When no order carries those session fields, the strings and seenAt are null and both counts are 0. On detail, joinedAt is the oldest order createdAt, otherwise the oldest ticket createdAt, otherwise the request time. totalSpent is the sum of those orders’ price. orderCount is that array’s length. ticketCount is tickets.length. ipAddress is the newest order IP, or null. balance is the stored store-credit balance, or 0. Fallback error: Failed to load customer.

PATCH /api/merchant/v1/stores/{storeId}/customers/{customerKey}/balance

Scope: customer:balance:write No query parameters. Content-Type: application/json. The body must be a JSON object. Invalid JSON returns 400 Invalid JSON. A non-object returns 400 JSON body must be an object. A body storeId is dropped. Unknown keys are ignored. The path storeId is the shop that is updated. A schema failure returns 400 with Zod issues joined by ; (path: message when the issue has a path).
add and subtract require amount > 0. Otherwise 400 Amount must be greater than zero. The current balance is rounded to 2 decimal places before the write. subtract returns 400 Cannot subtract more than the current balance. when amount is greater than that rounded balance, and nothing is written. An equal amount is allowed and stores 0. set may be 0. add stores the rounded sum, subtract stores the rounded difference, and set stores the rounded amount. Negative amount never reaches those rules: the schema rejects it. Unknown key, or an email that is not in this shop’s customer map, returns 404 Customer not found.
Fallback error: Failed to update customer balance.

Errors