> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinmarkt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant feedback

> List store reviews and write or clear the seller reply with a Bearer API key.

Prefix: `/api/merchant/v1`. Send:

```txt theme={null}
Authorization: Bearer mk_live_<your_key>
```

The key must be allowed to access `{storeId}`. When its store scope is `SELECTED`, `{storeId}` must be in that list.

`RESTRICTED` keys must include the scope named on the method (`feedback:read` or `feedback:reply:write`). `FULL` keys skip that scope list. Either way the user must still have dashboard permission `viewFeedbacks`.

Success JSON is the payload below. It is not wrapped in `data`. Errors are `{ "success": false, "error", "code" }`. A `500` also includes `errorId`.

A `storeId` property in a JSON body is ignored. The path store id is the one that is used.

`{feedbackId}` may be the internal id or the public id (`FBK-XXXX-XXXX-XXXX-XXXX-XXXX`). The response `id` is always the internal id. `publicId` is not returned. A review that belongs to another shop returns `404` with error `Feedback not found`.

```http theme={null}
GET    /api/merchant/v1/stores/{storeId}/feedback
GET    /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}
POST   /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
PUT    /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
DELETE /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
```

## List feedback

```http theme={null}
GET /api/merchant/v1/stores/{storeId}/feedback
```

Scope: `feedback:read`

Reviews are ordered by `createdAt` descending. `page` and `limit` paginate in the database. `total` is the count for the current `rating` filter. `ratingCounts` is always the full store, including reviews outside that filter.

| Query | Rules |
| - | - |
| `page` | Integer ≥ 1. Default `1`. |
| `limit` | Integer from 1 to 100. Default `50`. |
| `rating` | Optional. Read with `Number.parseInt` (base 10). The integer must be from 1 to 5. Otherwise `400` and error `rating must be an integer from 1 to 5`. |

```json theme={null}
{
  "feedbacks": [
    {
      "id": "clxyz",
      "storeId": "cmstore",
      "rating": 5,
      "comment": "Fast delivery",
      "customerName": "Alex",
      "customerEmail": "buyer@example.com",
      "productName": "Premium key",
      "productImage": null,
      "reply": null,
      "repliedAt": null,
      "createdAt": "2026-09-21T10:00:00.000Z",
      "updatedAt": "2026-09-21T10:00:00.000Z"
    }
  ],
  "ratingCounts": { "1": 0, "2": 0, "3": 0, "4": 0, "5": 1, "all": 1 },
  "page": 1,
  "limit": 50,
  "total": 1
}
```

Each feedback object:

| Field | Type |
| - | - |
| `id` | string. Internal id. |
| `storeId` | string |
| `rating` | number |
| `comment` | string |
| `customerName` | string |
| `customerEmail` | string |
| `productName` | string or null |
| `productImage` | string or null |
| `reply` | string or null |
| `repliedAt` | ISO 8601 string or null |
| `createdAt` | ISO 8601 string |
| `updatedAt` | ISO 8601 string |

`ratingCounts` keys are `"1"`, `"2"`, `"3"`, `"4"`, `"5"`, and `"all"`. Each value is a number. `all` is the sum of those five counts.

```bash theme={null}
curl "https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/feedback?page=1&limit=50&rating=5" \
  -H "Authorization: Bearer mk_live_<your_key>" \
  -H "Accept: application/json"
```

## Get feedback

```http theme={null}
GET /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}
```

Scope: `feedback:read`

Returns one feedback object with the fields in the list above. Status `200`.

## Reply to feedback

```http theme={null}
POST /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
PUT  /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
```

Scope: `feedback:reply:write`

`PUT` calls the same handler as `POST`. Both return `200` and the updated feedback object.

Body must be a JSON object:

| Field | Rules |
| - | - |
| `reply` | String, trimmed, 1–5000 characters. Required. |

```json theme={null}
{ "reply": "Thanks for the review" }
```

```bash theme={null}
curl -X POST "https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply" \
  -H "Authorization: Bearer mk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d "{\"reply\":\"Thanks for the review\"}"
```

## Clear a reply

```http theme={null}
DELETE /api/merchant/v1/stores/{storeId}/feedback/{feedbackId}/reply
```

Scope: `feedback:reply:write`

No body. Sets `reply` and `repliedAt` to null and returns the feedback object. Status `200`.

## Errors

| Status | `code` | When |
| - | - | - |
| 400 | `BAD_REQUEST` | Invalid JSON (`Invalid JSON`), body is not an object (`JSON body must be an object`), `reply` fails validation, or `page`, `limit`, or `rating` is invalid |
| 401 | `UNAUTHORIZED` | Missing, malformed, unknown, or revoked token (`Unauthorized`), completed account deletion (`Unauthorized`), or expired key (`API key expired`). The token must start with `mk_live_`. |
| 403 | `FORBIDDEN` | Scope, shop, or `viewFeedbacks` not allowed (`Forbidden`) |
| 404 | `NOT_FOUND` | `Feedback not found` |
| 429 | `RATE_LIMITED` | 120 requests per 60 seconds for this client IP (`Too many requests`) |
| 500 | `INTERNAL_ERROR` | Unexpected failure. `error` is `Failed to list feedback`, `Failed to load feedback`, `Failed to reply to feedback`, or `Failed to clear feedback reply`. `errorId` is set. |

## Related

* [API keys](https://docs.joinmarkt.com/developers/api-keys.md)
* [Merchant tickets](https://docs.joinmarkt.com/api-reference/merchant-tickets.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.