> For the complete documentation index, see [llms.txt](https://docs.hesh.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hesh.app/manuals/public-api/product-categories.md).

# Product Categories

## List product categories

> \
> Returns categories in the tenant.\
> \
> \### Query\
> \
> \- \`parent\_id\` (optional) — when provided, returns children of that node. When omitted, returns roots.\
> \- \`tree\` (optional, default false) — when true, returns the full subtree rooted at \`parent\_id\` (or all roots + descendants when omitted) nested via \`children\[]\`. Pagination is ignored in this mode.\
> \- \`search\` (optional) — case-insensitive substring match on \`name\`.\
> \- \`page\` / \`limit\` (optional) — page-based pagination for the flat mode. Defaults: \`page = 1\`, \`limit = 25\`, max \`limit = 100\`.\
> \- \`include\_deleted\` (optional, default false) — include soft-deleted categories in the response.\
> \
> Each item includes \`id\`, \`name\`, \`parent\_id\`, \`path\` (root→parent breadcrumbs), and \`is\_active\` (computed as \`deleted\_at IS NULL\`).<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"paths":{"/api/v1/public/product-categories":{"get":{"description":"\nReturns categories in the tenant.\n\n### Query\n\n- `parent_id` (optional) — when provided, returns children of that node. When omitted, returns roots.\n- `tree` (optional, default false) — when true, returns the full subtree rooted at `parent_id` (or all roots + descendants when omitted) nested via `children[]`. Pagination is ignored in this mode.\n- `search` (optional) — case-insensitive substring match on `name`.\n- `page` / `limit` (optional) — page-based pagination for the flat mode. Defaults: `page = 1`, `limit = 25`, max `limit = 100`.\n- `include_deleted` (optional, default false) — include soft-deleted categories in the response.\n\nEach item includes `id`, `name`, `parent_id`, `path` (root→parent breadcrumbs), and `is_active` (computed as `deleted_at IS NULL`).\n","operationId":"PublicProductCategoriesController_list_v1","parameters":[{"name":"parent_id","required":false,"in":"query","description":"Return children of this parent id. Omit to return roots.","schema":{"format":"uuid","type":"string"}},{"name":"tree","required":false,"in":"query","description":"When true, return the full subtree as a nested tree.","schema":{"default":false,"type":"boolean"}},{"name":"search","required":false,"in":"query","description":"Case-insensitive substring match on name.","schema":{"maxLength":100,"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":25,"type":"number"}},{"name":"include_deleted","required":false,"in":"query","schema":{"default":false,"type":"boolean"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"List product categories","tags":["Product Categories"]}}}}
```

## Create category

> \
> Creates a new category.\
> \
> \### Notes\
> \
> \- \`name\` is required (1..255 chars).\
> \- \`parent\_id\` — when omitted, creates a root category. When provided, the parent must exist and be active (soft-deleted parents → 404).\
> \- \`name\` must be unique (case-insensitive) among active siblings under the same parent — \`409\` on collision.\
> \
> Tag storage is deliberately not yet modelled — do not send \`tags\`; the tag endpoints are stubbed.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PublicCreateCategoryDto":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"parent_id":{"type":"string","format":"uuid","description":"Parent id. Omit for root category."}},"required":["name"]},"PublicCategoryDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"path":{"description":"Root→parent breadcrumbs; empty for root categories.","type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}},"is_active":{"type":"boolean","description":"Computed from deleted_at IS NULL."},"created_at":{"type":"string","nullable":true}},"required":["id","name","parent_id","path","is_active","created_at"]},"IdNameDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"paths":{"/api/v1/public/product-categories":{"post":{"description":"\nCreates a new category.\n\n### Notes\n\n- `name` is required (1..255 chars).\n- `parent_id` — when omitted, creates a root category. When provided, the parent must exist and be active (soft-deleted parents → 404).\n- `name` must be unique (case-insensitive) among active siblings under the same parent — `409` on collision.\n\nTag storage is deliberately not yet modelled — do not send `tags`; the tag endpoints are stubbed.\n","operationId":"PublicProductCategoriesController_create_v1","parameters":[{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCreateCategoryDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Create category","tags":["Product Categories"]}}}}
```

## Get category by id

> \
> Returns a single category with optional expansions.\
> \
> \### Query\
> \
> \- \`include\` (optional, CSV) — one or more of \`products\`, \`children\`.\
> \- \`products\_page\`, \`products\_limit\` — pagination for the \`products\` include (defaults 1 / 25, max 100).\
> \
> Soft-deleted categories return 404 unless the caller can prove the id exists — call \`GET /?include\_deleted=true\` to find them.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PublicCategoryWithIncludesDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"path":{"description":"Root→parent breadcrumbs; empty for root categories.","type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}},"is_active":{"type":"boolean","description":"Computed from deleted_at IS NULL."},"created_at":{"type":"string","nullable":true},"products":{"$ref":"#/components/schemas/PublicCategoryProductsPageDto"},"children":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategoryDto"}}},"required":["id","name","parent_id","path","is_active","created_at"]},"IdNameDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"PublicCategoryProductsPageDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCategoryProductDto"}},"meta":{"$ref":"#/components/schemas/PaginationMetadata"}},"required":["data","meta"]},"PublicCategoryProductDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"PaginationMetadata":{"type":"object","properties":{"total":{"type":"number","description":"Total number of items"},"lastPage":{"type":"number","description":"Last page number"},"currentPage":{"type":"number","description":"Current page number"},"perPage":{"type":"number","description":"Items per page"},"prev":{"type":"number","description":"Previous page number","nullable":true},"next":{"type":"number","description":"Next page number","nullable":true}},"required":["total","lastPage","currentPage","perPage","prev","next"]},"PublicCategoryDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"path":{"description":"Root→parent breadcrumbs; empty for root categories.","type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}},"is_active":{"type":"boolean","description":"Computed from deleted_at IS NULL."},"created_at":{"type":"string","nullable":true}},"required":["id","name","parent_id","path","is_active","created_at"]}}},"paths":{"/api/v1/public/product-categories/{id}":{"get":{"description":"\nReturns a single category with optional expansions.\n\n### Query\n\n- `include` (optional, CSV) — one or more of `products`, `children`.\n- `products_page`, `products_limit` — pagination for the `products` include (defaults 1 / 25, max 100).\n\nSoft-deleted categories return 404 unless the caller can prove the id exists — call `GET /?include_deleted=true` to find them.\n","operationId":"PublicProductCategoriesController_getById_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"include","required":false,"in":"query","description":"Comma-separated list. Supported: products, children.","schema":{"type":"string"}},{"name":"products_page","required":false,"in":"query","description":"Page for the products include.","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"products_limit","required":false,"in":"query","description":"Page size for the products include.","schema":{"minimum":1,"maximum":100,"default":25,"type":"number"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryWithIncludesDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Get category by id","tags":["Product Categories"]}}}}
```

## Soft-delete category

> \
> Marks the category as deleted. Non-cascading.\
> \
> \### Rejected with 409 when\
> \
> \- The category has any non-deleted children.\
> \- The category has any \`product\_meta\` referencing it.\
> \- The category is already soft-deleted.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"paths":{"/api/v1/public/product-categories/{id}":{"delete":{"description":"\nMarks the category as deleted. Non-cascading.\n\n### Rejected with 409 when\n\n- The category has any non-deleted children.\n- The category has any `product_meta` referencing it.\n- The category is already soft-deleted.\n","operationId":"PublicProductCategoriesController_softDelete_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":""},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Soft-delete category","tags":["Product Categories"]}}}}
```

## Update category

> \
> Updates a category. All fields are optional.\
> \
> \### Rules\
> \
> \- \`name\` — must not collide with an active sibling under the current or new parent (\`409\`).\
> \- \`parent\_id\` — pass \`null\` to promote to root. Cannot equal \`:id\` and cannot equal any descendant of \`:id\` (would create a cycle → \`400\`). New parent must be active.\
> \- \`is\_active\` — \`true\` restores a soft-deleted category, \`false\` soft-deletes it. Rejected (\`409\`) if the current state already matches.\
> \- Rejected with \`409\` if the record is soft-deleted and you send a non-\`is\_active\` field — restore it first.\
> \- Successful \`parent\_id\` moves recompute the materialized \`path\` for the moved node and all its descendants.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PublicUpdateCategoryDto":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"parent_id":{"type":"string","nullable":true,"description":"New parent. Pass null to promote to root. Rejected if it would create a cycle."},"is_active":{"type":"boolean","description":"true → restore soft-deleted, false → soft-delete. Rejected if state is already what you set."}}},"PublicCategoryDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"path":{"description":"Root→parent breadcrumbs; empty for root categories.","type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}},"is_active":{"type":"boolean","description":"Computed from deleted_at IS NULL."},"created_at":{"type":"string","nullable":true}},"required":["id","name","parent_id","path","is_active","created_at"]},"IdNameDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"paths":{"/api/v1/public/product-categories/{id}":{"patch":{"description":"\nUpdates a category. All fields are optional.\n\n### Rules\n\n- `name` — must not collide with an active sibling under the current or new parent (`409`).\n- `parent_id` — pass `null` to promote to root. Cannot equal `:id` and cannot equal any descendant of `:id` (would create a cycle → `400`). New parent must be active.\n- `is_active` — `true` restores a soft-deleted category, `false` soft-deletes it. Rejected (`409`) if the current state already matches.\n- Rejected with `409` if the record is soft-deleted and you send a non-`is_active` field — restore it first.\n- Successful `parent_id` moves recompute the materialized `path` for the moved node and all its descendants.\n","operationId":"PublicProductCategoriesController_update_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUpdateCategoryDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Update category","tags":["Product Categories"]}}}}
```

## Restore soft-deleted category

> \
> Clears \`deleted\_at\` and \`deleted\_by\`. Returns \`409\` if the category is not currently deleted.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PublicCategoryDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"path":{"description":"Root→parent breadcrumbs; empty for root categories.","type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}},"is_active":{"type":"boolean","description":"Computed from deleted_at IS NULL."},"created_at":{"type":"string","nullable":true}},"required":["id","name","parent_id","path","is_active","created_at"]},"IdNameDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"paths":{"/api/v1/public/product-categories/{id}/restore":{"post":{"description":"\nClears `deleted_at` and `deleted_by`. Returns `409` if the category is not currently deleted.\n","operationId":"PublicProductCategoriesController_restore_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Restore soft-deleted category","tags":["Product Categories"]}}}}
```

## Get breadcrumbs for a category

> \
> Returns \`{ path: \[{ id, name }] }\` — the root→parent breadcrumb chain for the given category. Empty array for root categories.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Product Categories"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PublicCategoryPathDto":{"type":"object","properties":{"path":{"type":"array","items":{"$ref":"#/components/schemas/IdNameDto"}}},"required":["path"]},"IdNameDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"paths":{"/api/v1/public/product-categories/{id}/path":{"get":{"description":"\nReturns `{ path: [{ id, name }] }` — the root→parent breadcrumb chain for the given category. Empty array for root categories.\n","operationId":"PublicProductCategoriesController_getPath_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryPathDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Get breadcrumbs for a category","tags":["Product Categories"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hesh.app/manuals/public-api/product-categories.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
