# Update Route

> PUT /v1/kb/routes/{id} · Knowledge base API

Source: https://developer.kustomer.com/api/knowledge-base/update-route

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

## Authorization

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

Updates requested route.

## About Knowledge base routes

A route maps a URL path on your help center to an article or category, so old and custom links keep working. When a visitor opens a path that matches a route, the help center redirects them to the content's current address.

Every article and category already has a canonical help center address built from its slug and `hash`, such as `/categories/shipping-rJ5Dz7bLL`. A route adds another path for the same content. Routes are most useful after a migration: create one for each URL from your old help center, and existing bookmarks and search results still land on the right page. When the help center gets a path it doesn't recognize, it looks the path up and sends a `301` redirect to the canonical address. Routes belong to the org rather than to a single knowledge base. Deleting a route only removes the alias, not the content.

#### Fields

| Field | Meaning |
| --- | --- |
| `url` | The path to match, such as `/hc/en-us/articles/360001234`. Up to 1024 characters. |
| `routableType` | `article` or `category`. |
| `routableId` | The ID of the article or category. It must exist. |
| `pattern` | Optional. A regular expression that lets several routes share one path. |
| `position` | Optional. The order in which routes that share a path are tried. |

#### How a URL is matched

Kustomer normalizes the URL, then looks for a route with that exact `url` and no `pattern`. If there isn't one, it removes the query string and collects up to 100 routes for that base path, sorted by `position`.

- If the only route for the path has no `pattern`, it matches whatever the query string is.
- Otherwise, Kustomer tries each route's `pattern` in order against the path and query string, ignoring case. The first one that matches wins.

For example, an old help center that used `/article.php?id=12` and `/article.php?id=34` needs two routes with `url` `/article.php`, with patterns `id=12$` and `id=34$`. Each combination of `url` and `pattern` must be unique, so a path can have only one route without a pattern. Kustomer rejects a duplicate.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Find out where a URL goes, with the target article or category in `included` | [`GET /v1/kb/route`](https://developer.kustomer.com/api/knowledge-base/get-route-by-url-public), with `url` |
| List all routes, or the routes for one base path in match order | [`GET /v1/kb/routes`](https://developer.kustomer.com/api/knowledge-base/get-routes), with `url` to filter |
| List the routes that point at an article or a category | [`GET /v1/kb/articles/{id}/routes`](https://developer.kustomer.com/api/knowledge-base/get-routes-by-article-id) or [`GET /v1/kb/categories/{id}/routes`](https://developer.kustomer.com/api/knowledge-base/get-routes-by-category-id) |
| Create a route to an article or category | [`POST /v1/kb/routes`](https://developer.kustomer.com/api/knowledge-base/create-a-route) |
| Create a route to a specific category | [`POST /v1/kb/categories/{id}/routes`](https://developer.kustomer.com/api/knowledge-base/create-a-category-route) |
| Read, change or delete a route | [`GET`](https://developer.kustomer.com/api/knowledge-base/get-route-by-id), [`PUT`](https://developer.kustomer.com/api/knowledge-base/update-route) or [`DELETE /v1/kb/routes/{id}`](https://developer.kustomer.com/api/knowledge-base/delete-route-by-id) |

Creating a route fails with `404` if `routableId` doesn't match an existing article or category of `routableType`. 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 base categories](https://developer.kustomer.com/api/knowledge-base#categories), [Knowledge bases](https://developer.kustomer.com/api/knowledge-base#knowledge-bases)

### Recipes

#### 1. Keep old help center links working after a migration

After you import articles from another help center, add a redirect from each old URL to the new article so bookmarks and search results still land on the right page.

**Step 1.** [`POST /v1/kb/routes`](https://developer.kustomer.com/api/knowledge-base/create-a-route.md). For each old URL, create a route with the old path as `url`, `routableType` `article` and the new article's ID as `routableId`. Keep a mapping of old IDs to new ones while you import; an article's `externalId` is a good place to store the old ID. Creating a second route for the same `url` without a `pattern` is rejected as a duplicate, so a rerun doesn't create copies.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/kb/routes" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"url\": \"/hc/en-us/articles/360001234\", \"routableType\": \"article\", \"routableId\": \"$ARTICLE_ID\"}"
```

```bash title="kusty"
while IFS=, read -r old_url article_id; do
  kusty api POST /v1/kb/routes --field url="$old_url" \
    --field routableType=article --field routableId=$article_id --fields id
done < redirects.csv
```

**Step 2.** [`GET /v1/kb/route`](https://developer.kustomer.com/api/knowledge-base/get-route-by-url-public.md). Check a few old URLs the way the help center does. The response is the matching route, with the target article or category in `included`. A `404` means no route matches.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/route?url=/hc/en-us/articles/360001234" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/kb/route --query url=/hc/en-us/articles/360001234 \
  --fields attributes.routableType,attributes.routableId
```

> Old category and section pages need routes too: use `routableType` `category`, or [`POST /v1/kb/categories/{id}/routes`](https://developer.kustomer.com/api/knowledge-base/create-a-category-route), which takes the category from the path.

#### 2. Redirect URLs that differ only in their query string

Send `/article.php?id=12` and `/article.php?id=34` to different articles, as old help centers that used query-string IDs need.

**Step 1.** [`POST /v1/kb/routes`](https://developer.kustomer.com/api/knowledge-base/create-a-route.md). Create one route per target, all with the same base `url` and a `pattern` that matches the query string. Patterns are regular expressions, matched against the path and query string, ignoring case. `position` sets the order they're tried in, and the first match wins, so put specific patterns before broad ones.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/kb/routes" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"url\": \"/article.php\", \"pattern\": \"id=12$\", \"position\": 1, \"routableType\": \"article\", \"routableId\": \"$ARTICLE_12_ID\"}"
```

```bash title="kusty"
kusty api POST /v1/kb/routes --field url=/article.php --field 'pattern=id=12$' \
  --field position:=1 --field routableType=article --field routableId=$ARTICLE_12_ID
kusty api POST /v1/kb/routes --field url=/article.php --field 'pattern=id=34$' \
  --field position:=2 --field routableType=article --field routableId=$ARTICLE_34_ID
```

**Step 2.** [`GET /v1/kb/route`](https://developer.kustomer.com/api/knowledge-base/get-route-by-url-public.md). Look up each full old URL, query string included, to check that it reaches the right article.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/route?url=%2Farticle.php%3Fid%3D34" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/kb/route --query 'url=/article.php?id=34' --fields attributes.routableId
```

> If a base path has exactly one route and it has no `pattern`, it matches whatever the query string is. Anchor patterns with `$` so `id=1` doesn't also match `id=12`.

#### 3. Point an article's old links at its replacement

Before you retire an article, move the redirects that point at it to the article that replaces it, so old links don't break.

**Step 1.** [`GET /v1/kb/articles/{id}/routes`](https://developer.kustomer.com/api/knowledge-base/get-routes-by-article-id.md). List the routes that point at the article you're retiring.

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

```bash title="kusty"
kusty api GET /v1/kb/articles/$OLD_ARTICLE_ID/routes --all | jq -r .data.id > routes.txt
```

**Step 2.** [`PUT /v1/kb/routes/{id}`](https://developer.kustomer.com/api/knowledge-base/update-route.md). Point each route at the replacement. Send `routableType` and `routableId` together: Kustomer only checks that the target exists when both are present. The URL and pattern stay the same. To redirect to a category instead, send `routableType` `category` with the category's ID.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/routes/$ROUTE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"routableType\": \"article\", \"routableId\": \"$NEW_ARTICLE_ID\"}"
```

```bash title="kusty"
while read -r id; do
  kusty api PUT /v1/kb/routes/$id --field routableType=article --field routableId=$NEW_ARTICLE_ID
done < routes.txt
```

**Step 3.** [`PUT /v1/kb/articles/{id}`](https://developer.kustomer.com/api/knowledge-base/update-article.md). Then delete the old article with `deleted: true`. There is no `DELETE` endpoint for articles.

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

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

> Routes keep pointing at an article after it's deleted, because deleting only removes the article from the help center. Moving them first is what keeps those old links useful. To drop a redirect you no longer need, use [`DELETE /v1/kb/routes/{id}`](https://developer.kustomer.com/api/knowledge-base/delete-route-by-id).

### Example object

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

```json
{
  "type": "route",
  "id": "5de87213da2862001aefe5a6",
  "attributes": {
    "url": "https://foo.com/foo/bar",
    "routableType": "article",
    "routableId": "5de25efeffe329001a9b5e93",
    "createdAt": "2019-12-05T02:57:23.929Z",
    "updatedAt": "2019-12-05T02:57:23.929Z",
    "modifiedAt": "2019-12-05T02:57:23.929Z"
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57504f64d76808100089eb44"
      },
      "data": {
        "type": "org",
        "id": "57504f64d76808100089eb44"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/57504f644c80120f00f3c378"
      },
      "data": {
        "type": "user",
        "id": "57504f644c80120f00f3c378"
      }
    },
    "modifiedBy": {
      "links": {
        "self": "/v1/users/57504f644c80120f00f3c378"
      },
      "data": {
        "type": "user",
        "id": "57504f644c80120f00f3c378"
      }
    },
    "article": {
      "links": {
        "self": "/v1/kb/articles/5de25efeffe329001a9b5e93"
      },
      "data": {
        "type": "article",
        "id": "5de25efeffe329001a9b5e93"
      }
    }
  },
  "links": {
    "self": "/v1/kb/routes/5de87213da2862001aefe5a6"
  }
}
```

Fields:

- `attributes.url` (string): The URL to match, usually an old path such as `/hc/en-us/articles/360001234`, at most 1024 characters. Kustomer normalizes it before matching. Each `url` can have only one route without a `pattern`.
- `attributes.routableType` (string): `article` or `category`: what the route points at. It also names the relationship that holds the target, `article` or `category`.
- `attributes.routableId` (string): The ID of the article or category the route redirects to. It must exist, or creating the route fails with `404`.
- `relationships.article` (object): The article this route sends visitors to, the same ID as `routableId`. A route to a category has a `category` relationship instead.

### Relationships

- Points to **Knowledge base articles** via `relationships.article`: The knowledge base article a route redirects to, when `routableType` is `article`. An article can have many routes; list them with `GET /v1/kb/articles/{id}/routes`.
- Points to **Knowledge base categories** 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.delete`, `org.permission.kb.update`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Resource ID |

## Request body

Content type: `application/json` (required)

- Any of:
  - any
  - any
  - any
  - any
  - any
- `url` (string<url>): (max length 1024)
- `routableType` (string): One of: `article`, `category`. (max length 128)
- `routableId` (string<object-id>)
- `pattern` (string)
- `position` (number)

Example:

```json
null
```

## Responses

### 200

OK

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `url` (string, required)
    - `routableType` (string, required)
    - `routableId` (string, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `modifiedAt` (string, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `createdBy` (object, required)
    - `modifiedBy` (object, required)
    - `article` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "route",
    "id": "5de87213da2862001aefe5a6",
    "attributes": {
      "url": "https://foo.com/foo/bar2",
      "routableType": "article",
      "routableId": "5de25efeffe329001a9b5e93",
      "createdAt": "2019-12-05T02:57:23.929Z",
      "updatedAt": "2019-12-05T02:58:23.929Z",
      "modifiedAt": "2019-12-05T02:58:23.929Z"
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/57504f64d76808100089eb44"
        },
        "data": {
          "type": "org",
          "id": "57504f64d76808100089eb44"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/57504f644c80120f00f3c378"
        },
        "data": {
          "type": "user",
          "id": "57504f644c80120f00f3c378"
        }
      },
      "modifiedBy": {
        "links": {
          "self": "/v1/users/57504f644c80120f00f3c378"
        },
        "data": {
          "type": "user",
          "id": "57504f644c80120f00f3c378"
        }
      },
      "article": {
        "links": {
          "self": "/v1/kb/articles/5de25efeffe329001a9b5e93"
        },
        "data": {
          "type": "article",
          "id": "5de25efeffe329001a9b5e93"
        }
      }
    },
    "links": {
      "self": "/v1/kb/routes/5de87213da2862001aefe5a6"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/kb/routes/6026aba3fcca5f03e5541cd8' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data 'null'
```
