Skip to main content
A category is a node in the tree that organizes articles inside a knowledge base. Categories nest through parent_id; each carries a name, a slug, an icon, and a sort order. An article points to a category through its category_id.

Anatomy

{
  "object": "article_category",
  "id": "5d694e14-5440-4f5d-9e8a-7c3b2a1d0f9e",
  "knowledge_base_id": "2c1b4a9f-7e8d-4a3c-9b1f-6e5d4c3b2a1f",
  "parent_id": null,
  "name": "Returns",
  "slug": "returns",
  "description": "Policies and processes for product returns.",
  "icon": "package",
  "sort_order": 0,
  "created_at": "2026-05-22T13:50:00+00:00"
}

Where categories live

Categories are scoped to an org and live inside a knowledge base — knowledge_base_id is required when you create one. The tree is built from parent_idnull means “root of the tree.” There is no enforced depth limit, but most teams keep it to two or three levels for usability.

Creating a category

POST /article-categories requires name, slug, and knowledge_base_id. The slug must be unique within the org. Pass parent_id to nest under another category.
Both knowledge_base_id and parent_id must reference resources that belong to your organization. A foreign or nonexistent id is rejected with 422"A referenced resource does not exist." sort_order, when supplied, must be an integer; a non-integer value returns invalid_payload.
curl https://api.awardee.dev/v1/article-categories \
  -X POST \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Returns",
    "slug": "returns",
    "description": "Policies and processes for product returns.",
    "icon": "package",
    "knowledge_base_id": "2c1b4a9f-7e8d-4a3c-9b1f-6e5d4c3b2a1f"
  }'

Listing

GET /article-categories returns a flat, paginated list. Use the query parameters to scope:
ParamEffect
knowledge_base_idReturn categories in this KB only
parent_idReturn direct children of this category
root_onlyReturn only categories with parent_id === null
page, page_sizeStandard pagination
The API does not return a tree — assemble it client-side from the flat list (see the object reference).

Deleting

DELETE /article-categories/{id} has two modes.

Default: refuse if non-empty

If the category has child categories or contains articles, the call returns 409 category_not_empty with counts of what’s in the way. Empty categories delete cleanly with a 204.
{
  "error": "category_not_empty",
  "message": "Category has children or articles. Pass ?cascade=true to delete recursively.",
  "child_category_count": 2,
  "article_count": 14,
  "request_id": "req_8fK2x9aLp0qR"
}
The counts are scoped to direct contents — a child category that itself has children only contributes 1 to child_category_count. Use them to decide whether a cascade is safe.

Cascade: delete everything

Pass ?cascade=true to delete the category, every descendant category, and every article inside them in one call. The response is a summary:
{
  "object": "article_category.delete_result",
  "cascade": true,
  "deleted_category_count": 3,
  "deleted_article_count": 14
}
Cascade is irreversible and destroys articles. The article.deleted webhook fires per article. There is no soft-delete fallback — for a recoverable removal, set article status to archived instead.
# Default: 409 if non-empty
curl https://api.awardee.dev/v1/article-categories/5d694e14-5440-4f5d-9e8a-7c3b2a1d0f9e \
  -X DELETE \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e"

# Cascade: delete everything inside
curl "https://api.awardee.dev/v1/article-categories/5d694e14-5440-4f5d-9e8a-7c3b2a1d0f9e?cascade=true" \
  -X DELETE \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e"

Webhook events

EventFires on
article_category.createdNew category written
article_category.updatedAny field changes
article_category.deletedCategory removed (one event per deleted category, including cascade descendants)