curl --request PATCH \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'cloudchat-instance: <cloudchat-instance>' \
--data '
{
"category": {
"name": "Getting Started",
"description": "First steps with the product."
}
}
'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
payload = { "category": {
"name": "Getting Started",
"description": "First steps with the product."
} }
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {
'cloudchat-instance': '<cloudchat-instance>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
category: {name: 'Getting Started', description: 'First steps with the product.'}
})
};
fetch('https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'category' => [
'name' => 'Getting Started',
'description' => 'First steps with the product.'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"cloudchat-instance: <cloudchat-instance>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
payload := strings.NewReader("{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": 12,
"slug": "getting-started",
"name": "Getting Started",
"description": "First steps with the product.",
"locale": "en",
"position": 10,
"parent_category_id": null,
"associated_category_id": null,
"created_at": "2026-05-02T11:04:17.000Z",
"updated_at": "2026-08-19T18:44:03.771Z"
}{
"error": {
"code": "bad_request",
"message": "The request is malformed."
}
}{
"error": {
"code": "unauthorized",
"message": "Authentication is required. Send a valid Bearer token in the Authorization header."
}
}{
"error": {
"code": "forbidden",
"message": "You are not allowed to perform this action."
}
}{
"error": {
"code": "not_found",
"message": "Resource could not be found."
}
}{
"error": {
"code": "validation_failed",
"message": "The request payload is invalid.",
"details": [
{
"field": "slug",
"code": "taken",
"message": "Another category of this portal already uses this slug in this locale. Omit the slug to have one derived from the name."
}
]
}
}{
"message": "API rate limit exceeded"
}{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later."
}
}Update a category
Change a category’s name, description, URL handle or translation link.
Send only the fields you are changing; a body with none of them is a 400, and a field sent as null is cleared. This is how a naming mistake gets fixed after the fact — including the slug, which is exactly why the category is addressed here by numeric id and not by slug.
locale is not writable. An article inherits the language of its category, so changing it would move the language of every article inside without touching one of them; move an article to a category of the other language with update an article instead. position and parent_category_id are not writable here either — ordering and nesting stay in the dashboard.
Requires being an administrator of the account or a member of the portal.
curl --request PATCH \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'cloudchat-instance: <cloudchat-instance>' \
--data '
{
"category": {
"name": "Getting Started",
"description": "First steps with the product."
}
}
'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
payload = { "category": {
"name": "Getting Started",
"description": "First steps with the product."
} }
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {
'cloudchat-instance': '<cloudchat-instance>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
category: {name: 'Getting Started', description: 'First steps with the product.'}
})
};
fetch('https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'category' => [
'name' => 'Getting Started',
'description' => 'First steps with the product.'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"cloudchat-instance: <cloudchat-instance>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
payload := strings.NewReader("{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"category\": {\n \"name\": \"Getting Started\",\n \"description\": \"First steps with the product.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": 12,
"slug": "getting-started",
"name": "Getting Started",
"description": "First steps with the product.",
"locale": "en",
"position": 10,
"parent_category_id": null,
"associated_category_id": null,
"created_at": "2026-05-02T11:04:17.000Z",
"updated_at": "2026-08-19T18:44:03.771Z"
}{
"error": {
"code": "bad_request",
"message": "The request is malformed."
}
}{
"error": {
"code": "unauthorized",
"message": "Authentication is required. Send a valid Bearer token in the Authorization header."
}
}{
"error": {
"code": "forbidden",
"message": "You are not allowed to perform this action."
}
}{
"error": {
"code": "not_found",
"message": "Resource could not be found."
}
}{
"error": {
"code": "validation_failed",
"message": "The request payload is invalid.",
"details": [
{
"field": "slug",
"code": "taken",
"message": "Another category of this portal already uses this slug in this locale. Omit the slug to have one derived from the name."
}
]
}
}{
"message": "API rate limit exceeded"
}{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later."
}
}Authorizations
The id_token from POST /auth/v1/signin, sent as Authorization: Bearer <id_token>. Not the access_token — that one does not carry the identity Cloud Chat authorizes on.
Headers
Your Cloud Chat instance ID — an integer, fixed for your company, told at onboarding. The API overview explains how instances work, how to find yours, and the errors a wrong or missing value produces.
1
Path Parameters
Your Cloud Chat account. It has to be an account your token grants membership on, and it has to live on the instance in the cloudchat-instance header — the two travel together. Account numbers are only unique within an instance, so the same number is a different company on another instance. Usually a mismatched pair fails closed with a 401, because your user does not exist on the other instance — but if your identity happens to exist on both, the call succeeds against the other company's data, silently. Read it and you are looking at the wrong help center; write it and you have stored into the wrong account. Send the two values that were given to you together, and never try a number to see what answers.
1
The portal's slug — the URL-safe handle you see in its public address (/hc/<slug>/...), not a numeric id. List portals returns every slug on the account. Note the asymmetry: portals are addressed by slug, articles by numeric id.
"acme-help"
The id returned when the category was created or listed. Numeric — never the category slug, and that is on purpose: updating a category can rewrite the slug, so a slug would be an address the endpoint itself can invalidate.
12
Body
The fields to change go under a category wrapper. At least one of them is required; locale is absent on purpose, since articles inherit the language of their category.
Show child attributes
Show child attributes
Response
The category as stored, after the change.
A category of one help center portal.
What goes in category_id when creating or updating an article.
12
"getting-started"
"Getting Started"
An article created in this category inherits this locale, and translate translates into it. Older categories can carry a locale that is no longer among the portal's allowed_locales: they are still listed and still render, but any write to them is refused until that locale is allowed again through updating the portal.
"en"
The order the help center renders, ascending. Null on a category that never got one — which is most of them, because nothing assigns a position automatically. The listing puts those last.
10
"2026-05-02T11:04:17.000Z"
"2026-08-10T14:32:05.123Z"
"First steps with the product."
Set when the category is nested under another.
null
Root category linking the translations of the same category across locales.
null