https://dash.joinmarkt.com
{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 the dashboard ceiling in the table.
Limiter: 120 requests per 60 seconds per client IP (
merchant-api). A limited call returns 429 before the key is checked.
Successful bodies are the JSON objects below. Errors are:
code is BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, RATE_LIMITED, or INTERNAL_ERROR. A 500 also includes errorId. Unexpected failures use the route fallback string as error.
GET /api/merchant/v1/stores/{storeId}/orders
Scope: order:read
Query parameters this route reads:
Omitted
limit, cursor, status, from, and to are left unset (limit defaults to 50). An invalid value returns 400. The message is the Zod issues joined with ; (path: message when the issue has a path). An unreadable cursor returns 400 with error Invalid cursor.
Rows are newest first (createdAt descending, then id descending). The handler loads limit + 1 rows. hasMore is true when an extra row exists. nextCursor is set only then, and is the base64url of the last order in the returned orders array: {createdAt ISO}|{id}. The next page is rows older than that cursor (same createdAt and a smaller id still match).
List items do not include
note or customFieldResponses.
error on an unexpected failure: Failed to list orders.
GET /api/merchant/v1/stores/{storeId}/orders/{orderId}
Scope: order:read
No query parameters.
{orderId} is looked up as the row id or the stored orderId. The path value is trimmed. That string is tried, then the uppercased form when it differs. A missing order, or an order whose storeId is not this shop, returns 404 with error Order not found.
The body is one order object. It has every list field, plus:
ipAddress, userAgent, browser, operatingSystem, asn, ipIntel, stripeCheckoutUrl, cryptomusPaymentAddress, cryptomusQrCode, nowpaymentsPaymentAddress, paypalFfEmail, venmoFfEmail, cashAppEmail, customPaymentProofUrls, gatewayCheckoutUrl. Product-key strings are not included.
Fallback error: Failed to load order.
PATCH /api/merchant/v1/stores/{storeId}/orders/{orderId}
Scope: order:update
No query parameters. Content-Type: application/json.
The body must be a JSON object. Invalid JSON returns 400 Invalid JSON. An array or other non-object returns 400 JSON body must be an object. A body storeId is dropped before validation.
Allowed keys (unknown keys fail because the schema is strict):
At least one of these must be present:
note (including null), or markDelivered set to true. Otherwise 400 with Provide note and/or markDelivered: true. markDelivered: false by itself fails that check. markDelivered: false together with note updates the note and does not change delivery.
note is saved when the field is present. Blank text is stored as no human note. System lines already on the order are kept in the database. The response note is the human text only.
markDelivered: true runs after the note write. It only proceeds when the stored status is paid, processing, or completed. Any other status, including pending, returns 400 Order is not eligible for fulfillment updates. If the order is already marked delivered, 400 Order is already delivered. If there is no product key, manual message, download link, download file, or delivery instructions, 400 Add keys, a message, link, or files before marking delivered. When the status is not already completed, the order is completed. This route does not accept fulfillment content fields (manualMessage, downloadLink, downloadFiles, deliveryInstructions, status).
The response is the same object as GET one order, loaded again by the row id. If delivery fails after a note write, the note is already saved.
error: Failed to update order.

