curl --request DELETE \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId} \
--header 'Authorization: Bearer <token>' \
--header 'cloudchat-instance: <cloudchat-instance>'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>"
}
response = requests.delete(url, headers=headers)
print(response.text)const options = {
method: 'DELETE',
headers: {'cloudchat-instance': '<cloudchat-instance>', Authorization: 'Bearer <token>'}
};
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 => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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"
"net/http"
"io"
)
func main() {
url := "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.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::Delete.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"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": "categoryId",
"code": "has_articles",
"message": "Cannot delete this category: 4 articles still belong to it, archived ones included. Deleting it would leave them without the category their language comes from. Move them with updateHelpCenterArticle, or delete them, and try again."
}
]
}
}{
"message": "API rate limit exceeded"
}{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later."
}
}Delete a category
Permanently remove an empty category.
Expect the refusal to be the normal answer. A category that still holds articles is refused with 422 and the number of articles; clearing it first — move them with update an article category_id, or delete them — is the main path, not an edge case. Retrying the call unchanged will not start working.
The refusal exists because deleting a full category would not fail: it would silently detach its contents, and an article with no category has lost where its language comes from. Categories nested under it and categories linked to it as translations block it the same way, each reported separately under error.details with its own count. Archived articles count too — archived is content you can publish again, not deleted content.
The response has no body, and there is nothing to restore from afterwards. Requires being an administrator of the account or a member of the portal.
curl --request DELETE \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId} \
--header 'Authorization: Bearer <token>' \
--header 'cloudchat-instance: <cloudchat-instance>'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>"
}
response = requests.delete(url, headers=headers)
print(response.text)const options = {
method: 'DELETE',
headers: {'cloudchat-instance': '<cloudchat-instance>', Authorization: 'Bearer <token>'}
};
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 => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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"
"net/http"
"io"
)
func main() {
url := "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/portals/{portalSlug}/categories/{categoryId}")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.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::Delete.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"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": "categoryId",
"code": "has_articles",
"message": "Cannot delete this category: 4 articles still belong to it, archived ones included. Deleting it would leave them without the category their language comes from. Move them with updateHelpCenterArticle, or delete them, and try again."
}
]
}
}{
"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
Response
The category is gone. No body is returned.