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

# Admin Categories API — Manage Product Categories

> Create and manage product categories including active/inactive status, slug management, and product assignment. Requires ADMIN role.

The Admin Categories API lets you build and maintain the taxonomy that organizes your product catalog. Use it to create categories, control their visibility with the `is_active` flag, assign products to categories, and soft-delete categories when they are no longer needed. Unlike the public catalog endpoint, the admin view surfaces inactive and optionally deleted categories so you have full visibility into every state.

<Note>
  All category endpoints require an authenticated session with the `ADMIN` or `SUPER_ADMIN` role. Fetch a CSRF token from `GET /auth/csrf-token` and pass it in the `x-csrf-token` header for every POST, PATCH, PUT, and DELETE request.
</Note>

***

## List Categories

Retrieve a paginated list of categories from the admin view. Unlike the public endpoint, inactive and soft-deleted categories are visible here depending on the filter flags you provide.

```
GET /admin/categories
```

### Query Parameters

<ParamField query="page" type="integer" default="1">
  Page number (1-based).
</ParamField>

<ParamField query="limit" type="integer" default="20">
  Items per page. Accepted range: 1–100.
</ParamField>

<ParamField query="search" type="string">
  Free-text search across category name and description.
</ParamField>

<ParamField query="sort" type="string" default="name">
  Sort field. Prefix with `-` for descending. Accepted values: `name`, `-name`, `created_at`, `-created_at`, `updated_at`, `-updated_at`.
</ParamField>

<ParamField query="is_active" type="string">
  Filter by active status. Pass `"true"` to return only active categories, `"false"` for inactive only. Omit to return all.
</ParamField>

<ParamField query="include_deleted" type="string" default="false">
  Pass `"true"` to include soft-deleted categories. Must be the string `"true"` or `"false"`.
</ParamField>

### Response

<ResponseField name="data" type="array">
  Array of category objects.

  <Expandable title="Category fields">
    <ResponseField name="public_id" type="string">Category public ID, prefixed `cat_`.</ResponseField>
    <ResponseField name="name" type="string">Display name.</ResponseField>
    <ResponseField name="slug" type="string">URL-safe slug.</ResponseField>
    <ResponseField name="description" type="string | null">Category description, or `null`.</ResponseField>
    <ResponseField name="is_active" type="boolean">Whether the category is visible to customers.</ResponseField>
    <ResponseField name="deleted_at" type="string | null">ISO 8601 UTC soft-deletion timestamp, or `null` if the category is not deleted.</ResponseField>
    <ResponseField name="created_at" type="string">ISO 8601 UTC creation timestamp.</ResponseField>
    <ResponseField name="updated_at" type="string">ISO 8601 UTC last-updated timestamp.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object">
  <Expandable title="Pagination fields">
    <ResponseField name="total" type="integer">Total matching records.</ResponseField>
    <ResponseField name="totalPages" type="integer">Total number of pages.</ResponseField>
    <ResponseField name="hasNext" type="boolean">Whether a next page exists.</ResponseField>
    <ResponseField name="hasPrev" type="boolean">Whether a previous page exists.</ResponseField>
  </Expandable>
</ResponseField>

***

## Create Category

Create a new product category. Only `name` is required. The API auto-generates a URL slug from the name if you do not supply one.

```
POST /admin/categories
```

### Request Body

<ParamField body="name" type="string" required>
  Category display name. Between 1 and 255 characters.
</ParamField>

<ParamField body="slug" type="string">
  URL-safe slug. Must match `^[a-z0-9]+(?:-[a-z0-9]+)*$` and be unique. Auto-generated from `name` if omitted. Maximum 255 characters.
</ParamField>

<ParamField body="description" type="string">
  Long-form category description. Maximum 10,000 characters. Optional.
</ParamField>

<ParamField body="is_active" type="boolean" default="true">
  Whether the category is immediately visible to customers. Defaults to `true`.
</ParamField>

<Note>
  When you omit `slug`, the API derives it from the `name` by lowercasing, replacing spaces with hyphens, and stripping unsupported characters. For example, `"Running Shoes"` becomes `"running-shoes"`. If the auto-generated slug conflicts with an existing one, the API appends a short unique suffix automatically. Provide an explicit `slug` when you need deterministic URLs.
</Note>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  # Step 1: get CSRF token
  TOKEN=$(curl -s -X GET https://api.example.com/api/v1/auth/csrf-token \
    -H "Cookie: session=<your-session-cookie>" \
    | jq -r '.data.csrf_token')

  # Step 2: create category
  curl -X POST https://api.example.com/api/v1/admin/categories \
    -H "Content-Type: application/json" \
    -H "Cookie: session=<your-session-cookie>" \
    -H "x-csrf-token: $TOKEN" \
    -d '{
      "name": "Sneakers",
      "slug": "sneakers",
      "description": "Athletic and casual footwear for everyday wear.",
      "is_active": true
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.example.com/api/v1/admin/categories', {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': csrfToken,
    },
    body: JSON.stringify({
      name: 'Sneakers',
      slug: 'sneakers',
      description: 'Athletic and casual footwear for everyday wear.',
      is_active: true,
    }),
  });
  const data = await response.json();
  ```
</CodeGroup>

### 201 Response

```json theme={null}
{
  "success": true,
  "data": {
    "public_id": "cat_01H",
    "name": "Sneakers",
    "slug": "sneakers",
    "description": "Athletic and casual footwear for everyday wear.",
    "is_active": true,
    "deleted_at": null,
    "created_at": "2024-06-01T09:00:00.000Z",
    "updated_at": "2024-06-01T09:00:00.000Z"
  }
}
```

### Error Responses

| Status | Meaning                                                           |
| ------ | ----------------------------------------------------------------- |
| `400`  | Validation error — invalid slug format or missing required field. |
| `401`  | No active session.                                                |
| `403`  | Insufficient role.                                                |
| `409`  | A category with the given slug already exists.                    |

***

## Get Category

Fetch the full admin projection of a single category by its public ID. The admin view returns the category regardless of `is_active` or soft-deletion state.

```
GET /admin/categories/{category_public_id}
```

### Path Parameters

<ParamField path="category_public_id" type="string" required>
  The category's public ID, prefixed `cat_`.
</ParamField>

Returns the full category object. Responds with `404` if the category does not exist.

***

## Update Category

Partially update a category's fields. Only the fields you supply are changed. Pass `null` for `description` to explicitly clear it.

```
PATCH /admin/categories/{category_public_id}
```

### Path Parameters

<ParamField path="category_public_id" type="string" required>
  The category's public ID, prefixed `cat_`.
</ParamField>

### Request Body

At least one field is required.

<ParamField body="name" type="string">
  Updated display name. Between 1 and 255 characters.
</ParamField>

<ParamField body="slug" type="string">
  Updated URL slug. Must match `^[a-z0-9]+(?:-[a-z0-9]+)*$`. Returns `409` on conflict.
</ParamField>

<ParamField body="description" type="string | null">
  Updated description. Pass `null` to clear the existing value.
</ParamField>

<ParamField body="is_active" type="boolean">
  Toggle customer visibility without deleting the category.
</ParamField>

<Tip>
  Use `is_active: false` to temporarily hide a category from customers — for example, while you reorganize its products or prepare a seasonal launch — without soft-deleting it. Toggle it back to `true` when you are ready to go live. This is much easier to reverse than a delete operation.
</Tip>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.example.com/api/v1/admin/categories/cat_01H \
    -H "Content-Type: application/json" \
    -H "Cookie: session=<your-session-cookie>" \
    -H "x-csrf-token: $TOKEN" \
    -d '{
      "name": "Sneakers & Training",
      "is_active": false
    }'
  ```

  ```javascript Node.js theme={null}
  await fetch('https://api.example.com/api/v1/admin/categories/cat_01H', {
    method: 'PATCH',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': csrfToken,
    },
    body: JSON.stringify({ name: 'Sneakers & Training', is_active: false }),
  });
  ```
</CodeGroup>

***

## Delete Category

Soft-delete a category. The category is immediately hidden from all customer-facing endpoints but retained in the database for referential integrity. Returns `204 No Content` on success.

```
DELETE /admin/categories/{category_public_id}
```

### Path Parameters

<ParamField path="category_public_id" type="string" required>
  The category's public ID, prefixed `cat_`.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.example.com/api/v1/admin/categories/cat_01H \
    -H "Cookie: session=<your-session-cookie>" \
    -H "x-csrf-token: $TOKEN"
  ```

  ```javascript Node.js theme={null}
  await fetch('https://api.example.com/api/v1/admin/categories/cat_01H', {
    method: 'DELETE',
    credentials: 'include',
    headers: { 'x-csrf-token': csrfToken },
  });
  ```
</CodeGroup>

### Error Responses

| Status | Meaning             |
| ------ | ------------------- |
| `401`  | No active session.  |
| `403`  | Insufficient role.  |
| `404`  | Category not found. |

***

## Assign Product to Category

Assign a single product to a category. The operation is idempotent — assigning a product that is already in the category returns `204` without error.

```
PUT /admin/categories/{category_public_id}/products/{product_public_id}
```

### Path Parameters

<ParamField path="category_public_id" type="string" required>
  The category's public ID, prefixed `cat_`.
</ParamField>

<ParamField path="product_public_id" type="string" required>
  The product's public ID, prefixed `prd_`.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT \
    https://api.example.com/api/v1/admin/categories/cat_01H/products/prd_01H \
    -H "Cookie: session=<your-session-cookie>" \
    -H "x-csrf-token: $TOKEN"
  ```

  ```javascript Node.js theme={null}
  await fetch(
    'https://api.example.com/api/v1/admin/categories/cat_01H/products/prd_01H',
    {
      method: 'PUT',
      credentials: 'include',
      headers: {
        'x-csrf-token': csrfToken,
      },
    }
  );
  ```
</CodeGroup>

### Response

Returns `204 No Content` on success. Returns `404` if the category or product does not exist.

***

## Remove Product from Category

Unassign a single product from a category. The operation succeeds with `204` even if the product was not assigned to the category. Returns `404` if the category or product does not exist.

```
DELETE /admin/categories/{category_public_id}/products/{product_public_id}
```

### Path Parameters

<ParamField path="category_public_id" type="string" required>
  The category's public ID, prefixed `cat_`.
</ParamField>

<ParamField path="product_public_id" type="string" required>
  The product's public ID, prefixed `prd_`.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE \
    https://api.example.com/api/v1/admin/categories/cat_01H/products/prd_01H \
    -H "Cookie: session=<your-session-cookie>" \
    -H "x-csrf-token: $TOKEN"
  ```

  ```javascript Node.js theme={null}
  await fetch(
    'https://api.example.com/api/v1/admin/categories/cat_01H/products/prd_01H',
    {
      method: 'DELETE',
      credentials: 'include',
      headers: { 'x-csrf-token': csrfToken },
    }
  );
  ```
</CodeGroup>

### Error Responses

| Status | Meaning                        |
| ------ | ------------------------------ |
| `401`  | No active session.             |
| `403`  | Insufficient role.             |
| `404`  | Category or product not found. |
