> 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
