# Get Categories

> GET /v1/kb/categories · Knowledge base API

Source: https://developer.kustomer.com/api/knowledge-base/get-categories

```http
GET https://{orgname}.api.kustomerapp.com/v1/kb/categories
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.kb.read`; or legacy `org.user.content.kb.read`, `org.user.kb.read`, `org.admin.content.kb.read`, `org.admin.kb.read`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Gets all categories.

## About Knowledge base categories

Categories are the folders that organize a knowledge base's articles into a tree you can browse, such as "Orders" containing "Returns". Each knowledge base has its own tree, and the help center builds its navigation from it.

Customers see categories as the sections of the help center home page and as category pages. Admins manage them in the knowledge base settings. Every knowledge base starts with a hidden root category (`root: true`), which Kustomer creates along with the knowledge base. Top-level categories are children of the root, and categories nest through `parent`. An article joins a category through the article's own `categories` list, so one article can appear in several categories, even in different knowledge bases.

#### Placement in the tree

A category always belongs to the same knowledge base as its parent, and its `knowledgeBase` is set from the parent. If you create a category without `parent`, it goes under the root of the org's default knowledge base. To add a category to another knowledge base, set `parent` to that knowledge base's root category or to a category inside it. Kustomer rejects a parent in a different knowledge base, a category as its own parent, and any parent for a root category.

#### Key fields

- `langs` holds the text for each language, keyed by language code (for example `en_us`): `title`, `slug`, `description` and `disabled`. If you leave out `slug`, Kustomer builds it from `title`.
- `hash` is the short ID in the category's URL, as in `/categories/shipping-rJ5Dz7bLL`. Kustomer generates it unless you send one.
- `positions` is the ordered list of article IDs in the category, and `categoryPositions` is the ordered list of its subcategories. Both set display order. Kustomer adds an article to `positions` when the article joins the category, so you only need to send these fields to reorder.
- `icon` is the image shown with the category, and `externalId` is your own unique ID for syncing from another system.

Send `deleted: true` to `PUT /v1/kb/categories/{id}` to soft-delete a category. You can't delete a root category. Deleting a category doesn't change its articles, so update any article that still lists it in `categories`.

#### Public and internal categories

The `/p/v3` category endpoints serve the help center. They need no API key and return each category's text flattened for the requested `lang`. Like the other public knowledge base endpoints, they work out the knowledge base from the host, so call your org host for the default knowledge base. Fetching a category by ID with `includeChildren=true` also returns its published articles and child categories in `included`. `childrenLimit` caps the number of child categories. `includeRoot` adds the root category to the list.

Internal categories are a separate, org-wide tree for organizing internal content. They don't belong to any knowledge base, and the public endpoints never return them. Listing them returns every internal category except the root.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List categories, or read one | [`GET /v1/kb/categories`](https://developer.kustomer.com/api/knowledge-base/get-categories) or [`GET /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-category-by-id) |
| Create a category | [`POST /v1/kb/categories`](https://developer.kustomer.com/api/knowledge-base/create-a-category) |
| Rename, move, reorder or delete a category | [`PUT /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/update-category) |
| Show the help center's categories, without an API key | [`GET /p/v3/kb/categories`](https://developer.kustomer.com/api/knowledge-base/get-categories-public) or [`GET /p/v3/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-category-by-id-public) |
| List or read internal categories | [`GET /v1/kb/internal/categories`](https://developer.kustomer.com/api/knowledge-base/get-internal-categories) or [`GET /v1/kb/internal/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-internal-category-by-id) |
| Create an internal category | [`POST /v1/kb/internal/categories`](https://developer.kustomer.com/api/knowledge-base/create-an-internal-category) |

Called with an API key, `GET /v1/kb/categories` returns only categories that hold a published public article, plus the categories above them, and leaves out the default knowledge base's root. A new or empty category doesn't appear there until it has content, so keep the IDs you create and read them by ID. `externalId` and `hash` must be unique in the org, so Kustomer rejects a duplicate. Reads need a knowledge base read role, and writes need `org.admin.kb.write` or the matching permission.

Related: [Knowledge base articles](https://developer.kustomer.com/api/knowledge-base#articles), [Knowledge bases](https://developer.kustomer.com/api/knowledge-base#knowledge-bases), [Routes](https://developer.kustomer.com/api/knowledge-base#routes)

### Recipes

#### 1. Import a category tree from another help center

Recreate your old help center's sections and subsections in a Kustomer knowledge base, in the same order, before you move the articles over.

**Step 1.** [`GET /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-category-by-id.md). Find the knowledge base's root category, the hidden parent of every top-level category. Start from any category in that knowledge base and follow `relationships.parent` until you reach one with `root: true`. For the default knowledge base you can skip this when creating top-level categories, because a category created without `parent` goes under its root, but you still need the root's ID to reorder them.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/categories/$CATEGORY_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
ID=$CATEGORY_ID
until [ "$(kusty api GET /v1/kb/categories/$ID --fields attributes.root | jq -r .data.attributes.root)" = true ]; do
  ID=$(kusty api GET /v1/kb/categories/$ID --fields relationships.parent | jq -r .data.relationships.parent.data.id)
done
ROOT_ID=$ID
```

**Step 2.** [`POST /v1/kb/categories`](https://developer.kustomer.com/api/knowledge-base/create-a-category.md). Create each top-level section with the root as `parent`, its text in `langs`, and the old system's ID as `externalId`. `externalId` must be unique in the org, so a rerun fails instead of creating duplicates. Keep each new `data.id` next to the old ID; you need the mapping for subcategories, articles and redirects.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/kb/categories" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"parent\": \"$ROOT_ID\", \"externalId\": \"zd-section-101\", \"langs\": {\"en_us\": {\"title\": \"Orders\", \"description\": \"Tracking, changes and cancellations\"}}}"
```

```bash title="kusty"
kusty api POST /v1/kb/categories \
  --field parent=$ROOT_ID --field externalId=zd-section-101 \
  --field langs.en_us.title=Orders \
  --field langs.en_us.description="Tracking, changes and cancellations" \
  --fields id
```

**Step 3.** [`POST /v1/kb/categories`](https://developer.kustomer.com/api/knowledge-base/create-a-category.md). Create the subcategories the same way, with their parent's new ID as `parent`. They land in the same knowledge base as the parent.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/kb/categories" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"parent\": \"$ORDERS_ID\", \"externalId\": \"zd-section-102\", \"langs\": {\"en_us\": {\"title\": \"Returns\"}}}"
```

```bash title="kusty"
kusty api POST /v1/kb/categories \
  --field parent=$ORDERS_ID --field externalId=zd-section-102 \
  --field langs.en_us.title=Returns --fields id
```

**Step 4.** [`PUT /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/update-category.md). Set the display order of the top-level sections by sending their IDs, in order, as the root's `categoryPositions`. Do the same on each section for its subcategories. Every ID must be an existing category.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/categories/$ROOT_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"categoryPositions\": [\"$ORDERS_ID\", \"$SHIPPING_ID\", \"$ACCOUNT_ID\"]}"
```

```bash title="kusty"
kusty api PUT /v1/kb/categories/$ROOT_ID \
  --field categoryPositions:="[\"$ORDERS_ID\", \"$SHIPPING_ID\", \"$ACCOUNT_ID\"]"
```

> Articles join categories through their own `categories`, so import articles after the tree exists; see [knowledge base articles](https://developer.kustomer.com/api/knowledge-base#articles). To keep the old section URLs working, add a [route](https://developer.kustomer.com/api/knowledge-base#routes) from each one to its new category.

#### 2. Retire a category without stranding its articles

Move a category's articles somewhere else, then delete it, so no article still points at a deleted category.

**Step 1.** [`GET /v1/kb/articles`](https://developer.kustomer.com/api/knowledge-base/get-articles.md). Deleting a category doesn't update its articles, so find them first. Page through the articles and keep the ones whose `categories` include the category's ID.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/articles?page=1&pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/kb/articles --all \
  | jq -c --arg c "$CATEGORY_ID" 'select(any(.data.attributes.categories[]?; .id == $c))
      | {id: .data.id, categories: [.data.attributes.categories[] | select(.id != $c) | {id}]}' > moves.jsonl
```

**Step 2.** [`PUT /v1/kb/articles/{id}`](https://developer.kustomer.com/api/knowledge-base/update-article.md). For each article, send its new `categories` list: the old list without the retired category, plus a replacement if it was the only one. `categories` replaces the whole list and also updates the article's live and draft versions.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/articles/$ARTICLE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"categories\": [{\"id\": \"$NEW_CATEGORY_ID\"}]}"
```

```bash title="kusty"
jq -c --arg n "$NEW_CATEGORY_ID" '.categories |= (if length == 0 then [{id: $n}] else . end)' moves.jsonl \
  | while read -r m; do
      kusty api PUT /v1/kb/articles/$(echo "$m" | jq -r .id) --data "$(echo "$m" | jq -c '{categories}')"
    done
```

**Step 3.** [`PUT /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/update-category.md). Soft-delete the category with `deleted: true`. Root categories can't be deleted. Subcategories aren't moved for you, so give them a new `parent` first if you want to keep them.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/categories/$CATEGORY_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"deleted": true}'
```

```bash title="kusty"
kusty api PUT /v1/kb/categories/$CATEGORY_ID --field deleted:=true
```

#### 3. Show the help center's sections on your own site

Build a "Browse help topics" menu in your app or website from the knowledge base's categories, without an API key.

**Step 1.** [`GET /p/v3/kb/categories`](https://developer.kustomer.com/api/knowledge-base/get-categories-public.md). Call your org host with the visitor's `lang`. You get the categories that hold published public articles in that language, plus the categories above them, with `title`, `description`, `slug` and `hash` flattened for that language and the parent's ID in `attributes.parent`. The org host serves the default knowledge base.

```bash title="cURL"
curl "https://$ORG.api.kustomerapp.com/p/v3/kb/categories?lang=en_us&pageSize=100"
```

```bash title="kusty"
kusty api GET /p/v3/kb/categories --base-url "https://$ORG.api.kustomerapp.com" \
  --query lang=en_us --all | jq -r '[.data.id, .data.attributes.title] | @tsv'
```

**Step 2.** [`GET /p/v3/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-category-by-id-public.md). When a visitor opens a section, fetch it with `includeChildren=true`. Its published articles and child categories come back in `included`, and `childrenLimit` caps how many child categories you get.

```bash title="cURL"
curl "https://$ORG.api.kustomerapp.com/p/v3/kb/categories/$CATEGORY_ID?lang=en_us&includeChildren=true"
```

> Use `positions` and `categoryPositions` to show articles and subcategories in the order admins set. Internal categories never appear on these endpoints.

### Example object

A real record from [`GET /v1/kb/categories/{id}`](https://developer.kustomer.com/api/knowledge-base/get-category-by-id.md):

```json
{
  "type": "category_v2",
  "id": "5de864c97a12680019a34cfd",
  "attributes": {
    "hash": "SkGRE1UTB",
    "langs": {
      "en_us": {
        "title": "My Amazing Category",
        "slug": "my-amazing-category",
        "description": "Knowledge Base articles related to my amazing category",
        "disabled": false
      }
    },
    "createdAt": "2019-12-05T02:00:41.607Z",
    "updatedAt": "2019-12-05T02:00:41.607Z",
    "published": true,
    "positions": [],
    "categoryPositions": [],
    "root": false
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57504f64d76808100089eb44"
      },
      "data": {
        "type": "org",
        "id": "57504f64d76808100089eb44"
      }
    },
    "knowledgeBase": {
      "data": {
        "type": "knowledge_base",
        "id": "5daf785c124be4f2960c9a4b"
      },
      "links": {
        "self": "/v1/kb/knowledge_bases/5daf785c124be4f2960c9a4b"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/57504f644c80120f00f3c378"
      },
      "data": {
        "type": "user",
        "id": "57504f644c80120f00f3c378"
      }
    },
    "parent": {
      "data": {
        "type": "category_v2",
        "id": "5d66d8a02b22ca00f1a5aab6"
      },
      "links": {
        "self": "/v1/kb/categories/5d66d8a02b22ca00f1a5aab6"
      }
    }
  },
  "links": {
    "self": "/v1/kb/categories/5de864c97a12680019a34cfd"
  }
}
```

Fields:

- `attributes.hash` (string): The short ID at the end of the category's URL, as in `/categories/shipping-rJ5Dz7bLL`. Kustomer generates it unless you send one, and it must be unique in the org.
- `attributes.langs` (object): The category's text for each language, keyed by language code. On `PUT`, languages you leave out are kept, but each language you send is replaced as a whole, so send its `title`, `description` and `disabled` again.
- `attributes.langs.en_us.title` (string): The category's name in this language, shown on the help center home page and category page.
- `attributes.langs.en_us.slug` (string): The readable part of the category's URL in this language. If you leave it out, Kustomer builds it from `title`.
- `attributes.langs.en_us.description` (string): The short description shown with the category in this language.
- `attributes.langs.en_us.disabled` (boolean): A per-language flag that the public category endpoints pass to the help center theme with the text. The endpoints still return a disabled category.
- `attributes.published` (boolean): Defaults to `true`. The category endpoints don't take it in a request body.
- `attributes.positions` (array): The display order of the category's articles, as article IDs. Kustomer adds an article when it joins the category, so send this only to reorder. At most 100.
- `attributes.categoryPositions` (array): The display order of the category's subcategories, as category IDs. Send it on `PUT` to reorder. At most 100.
- `attributes.root` (boolean): `true` for the hidden root category Kustomer creates with each knowledge base. Top-level categories are its children. A root category can't be moved or deleted.
- `relationships.knowledgeBase` (object): The knowledge base whose tree this category is in. It's always the parent's knowledge base, so you choose it by choosing `parent`.
- `relationships.parent` (object): The category this one sits under. Send a category ID as `parent` on `POST` or `PUT` to place or move it; it must be in the same knowledge base.

### Relationships

- Points to **Knowledge bases** via `relationships.knowledgeBase`: Every knowledge base category belongs to one knowledge base, the same as its parent's. Each knowledge base has its own tree, starting from a root category Kustomer creates with the knowledge base.
- Points to **Knowledge base categories** via `relationships.parent`: A category's parent category, which places it in the tree. Top-level categories are children of the knowledge base's root category, and a parent must be in the same knowledge base.
- Referenced by **Knowledge base articles** via `relationships.categories` (list): The categories an article appears in, up to 15, possibly in different knowledge bases. Categories don't list their articles except as display order in `positions`; set membership on the article with `PUT /v1/kb/articles/{id}`.
- Referenced by **Knowledge base routes** via `relationships.category`: The knowledge base category a route redirects to, when `routableType` is `category`. A category can have many routes; list them with `GET /v1/kb/categories/{id}/routes`.

### Permissions

Fewest roles for a read-only integration: `org.permission.kb.read`. For every endpoint: `org.permission.kb.read`, `org.permission.kb.create`, `org.permission.kb.update`.

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Page number of results to return |
| `pageSize` | integer | no | Number of results to return |

## Responses

### 200

OK

- `meta` (object, required)
  - `pageSize` (integer, required)
  - `page` (integer, required)
- `links` (object, required)
  - `self` (string, required)
  - `first` (string, required)
  - `prev` (string, required, nullable)
  - `next` (string, required, nullable)
- `data` (array<object>, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `hash` (string, required)
    - `langs` (object, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `published` (boolean, required)
    - `positions` (array<any>, required)
    - `categoryPositions` (array<any>, required)
    - `root` (boolean, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `knowledgeBase` (object, required)
    - `createdBy` (object, required)
    - `parent` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "meta": {
    "pageSize": 1000,
    "page": 1
  },
  "links": {
    "self": "/v2/kb/categories?pageSize=1000&page=1&client-request-id=dea541c4-c76b-4cc5-92e0-e44035b0b8f4",
    "first": "/v2/kb/categories?pageSize=1000&page=1&client-request-id=dea541c4-c76b-4cc5-92e0-e44035b0b8f4",
    "prev": null,
    "next": null
  },
  "data": [
    {
      "type": "category_v2",
      "id": "5de864c97a12680019a34cfd",
      "attributes": {
        "hash": "SkGRE1UTB",
        "langs": {
          "en_us": {
            "disabled": false,
            "description": "Knowledge Base articles related to my amazing category",
            "slug": "my-amazing-category",
            "title": "My Amazing Category"
          }
        },
        "createdAt": "2019-12-05T02:00:41.607Z",
        "updatedAt": "2019-12-05T02:00:41.607Z",
        "published": true,
        "positions": [],
        "categoryPositions": [],
        "root": false
      },
      "relationships": {
        "org": {
          "links": {
            "self": "/v1/orgs/57504f64d76808100089eb44"
          },
          "data": {
            "type": "org",
            "id": "57504f64d76808100089eb44"
          }
        },
        "knowledgeBase": {
          "data": {
            "type": "knowledge_base",
            "id": "5daf785c124be4f2960c9a4b"
          },
          "links": {
            "self": "/v1/kb/knowledge_bases/5daf785c124be4f2960c9a4b"
          }
        },
        "createdBy": {
          "links": {
            "self": "/v1/users/57504f644c80120f00f3c378"
          },
          "data": {
            "type": "user",
            "id": "57504f644c80120f00f3c378"
          }
        },
        "parent": {
          "data": {
            "type": "category_v2",
            "id": "5d66d8a02b22ca00f1a5aab6"
          },
          "links": {
            "self": "/v1/kb/categories/5d66d8a02b22ca00f1a5aab6"
          }
        }
      },
      "links": {
        "self": "/v1/kb/categories/5de864c97a12680019a34cfd"
      }
    }
  ]
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request GET \
  --url 'https://api.kustomerapp.com/v1/kb/categories' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
