> ## 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 categories

> List, create, read, update, and delete product categories with a Bearer API key.

Send every request to `https://dash.joinmarkt.com` with:

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

The key must be allowed to access `{storeId}`. Category scopes also require the key owner to have `manageCategories` on that store.

Successful list, get, create, and update responses are the JSON objects below. They are not wrapped in a `data` envelope. Errors use this shape:

```json theme={null}
{ "success": false, "error": "Message", "code": "BAD_REQUEST" }
```

These apply to every method on this page:

* **401** `Unauthorized`, code `UNAUTHORIZED` — missing bearer token, unknown key, revoked key, or the account deletion has completed.
* **401** `API key expired`, code `UNAUTHORIZED`.
* **403** `Forbidden`, code `FORBIDDEN` — the key cannot access this store, the scope is missing, or the owner lacks `manageCategories`.
* **429** `Too many requests`, code `RATE_LIMITED` — more than 120 requests in 60 seconds.
* **500** code `INTERNAL_ERROR`, plus `errorId`. `error` is the fallback named on that method.

`{categoryId}` in a path is the category `id`, or a public id `CAT-` plus five groups of four characters from `23456789ABCDEFGHJKMNPQRSTUVWXYZ` (for example `CAT-2345-6789-ABCD-EFGH-JKMN`). An unknown public id is looked up as a normal id and then follows the not-found errors below.

A JSON body must be an object. `storeId` inside the body is removed before validation. The store in the path is the one that is written. Fields other than `name`, `description`, and `image` are dropped.

## List categories

```http theme={null}
GET https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories?page=1&limit=50
```

Scope: `category:read`

Query:

* `page` — integer, minimum 1, default 1
* `limit` — integer, 1–100, default 50

An invalid `page` or `limit` returns **400** with code `BAD_REQUEST`. The `error` text is the Zod issues joined with `; `.

Categories are ordered by `createdAt` descending. A page past the end returns an empty `categories` array and the real `total`.

```json theme={null}
{
  "categories": [
    {
      "id": "cm123category",
      "name": "Games",
      "description": null,
      "image": null,
      "storeId": "cm123store",
      "productCount": 3,
      "createdAt": "2026-09-21T00:00:00.000Z",
      "updatedAt": "2026-09-21T00:00:00.000Z"
    }
  ],
  "page": 1,
  "limit": 50,
  "total": 1
}
```

`productCount` is the number of products in the category.

Unexpected failures return **500** `Failed to list categories`.

## Create a category

```http theme={null}
POST https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories
```

Scope: `category:create` · **201**

Body:

* `name` — required string, trimmed, 1–200 characters. Unique per store
* `description` — optional string, max 5000 characters, or `null`. An empty string is stored as `null`
* `image` — optional string, max 500 characters, or `null`. An empty string is stored as `null`

```json theme={null}
{ "name": "Games", "description": "Digital game keys", "image": null }
```

```json theme={null}
{
  "id": "cm123category",
  "name": "Games",
  "description": "Digital game keys",
  "image": null,
  "storeId": "cm123store",
  "productCount": 0,
  "createdAt": "2026-09-21T00:00:00.000Z",
  "updatedAt": "2026-09-21T00:00:00.000Z"
}
```

`productCount` on create is `0`.

Errors:

* **400** code `BAD_REQUEST` — invalid JSON (`Invalid JSON`), a body that is not an object (`JSON body must be an object`), or Zod validation (`name: …` style, joined with `; `)
* **400** code `category_name_duplicate` — `Category name already exists for this store`
* **400** code `category_validation` — the category record fails the same name, description, and image checks after the merchant schema
* **500** `Failed to create category`

## Get a category

```http theme={null}
GET https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories/{categoryId}
```

Scope: `category:read`

The response uses the same fields as a category in [List categories](#list-categories). `productCount` is the current number of products in the category.

Errors:

* **404** `Category not found`, code `NOT_FOUND` — unknown id, or the category belongs to another store
* **500** `Failed to load category`

## Update a category (PATCH)

```http theme={null}
PATCH https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories/{categoryId}
```

Scope: `category:update` · **200**

Same fields as create. Every field is optional. Omitted fields stay as they are. `null` clears `description` or `image`. An empty string is stored as an empty string. Create is the only write that stores an empty `description` or `image` as `null`.

```json theme={null}
{ "name": "Game keys", "description": null }
```

The response uses the same fields as get. `productCount` is the count loaded before the update.

Errors:

* **400** code `BAD_REQUEST` — invalid JSON, a non-object body, or Zod validation
* **400** code `category_name_duplicate` — `Category name already exists for this store`
* **400** code `category_validation` — the saved name, description, or image fails validation
* **404** `Category not found`, code `NOT_FOUND`
* **500** `Failed to update category`

## Update a category (PUT)

```http theme={null}
PUT https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories/{categoryId}
```

Scope: `category:update` · **200**

PUT calls the same update as PATCH. Send the same partial object. Omitted fields stay as they are.

```json theme={null}
{ "image": "https://cdn.example.com/games.png" }
```

The response and errors match [Update a category (PATCH)](#update-a-category-patch), including **500** `Failed to update category`.

## Delete a category

```http theme={null}
DELETE https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/categories/{categoryId}
```

Scope: `category:delete` · **200**

No body. Delete succeeds only when the category has no products.

```json theme={null}
{ "success": true }
```

Errors:

* **400** code `category_has_products` — `Cannot delete category with products. Please remove products first.`
* **404** `Category not found`, code `NOT_FOUND`
* **500** `Failed to delete category`

## Related

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


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