Categories (Semantic Module)
The Categories semantic module annotates an association between a source entity and a taxonomy entity to provide formal, structured classification.
While informal tagging modules like Tags are designed for lightweight keywords (such as featured or bestseller) and default to open user-generated creation, Categories represents structured classification where records belong to defined organizational groupings:
- E-Commerce Catalogs: Products classified under
["Electronics", "Audio", "Accessories"]. - Help Centers & Knowledge Bases: Support tickets categorized as
["Billing", "Invoicing"]. - Corporate Asset Management: Documents grouped under
["Legal", "Contracts"].
Decorating an association with Categories enables automated name-based lookups, configurable inline response formatting (flat string labels or rich object summaries), and strict curated taxonomy enforcement by default.
Technical Specifications
Section titled “Technical Specifications”| Attribute | Specification |
|---|---|
| Scope | Association (Applied to a relationship between entities) |
| Relationship Cardinality | Many-to-Many (multiple: true, join table) or Many-to-One (multiple: false, foreign key column) |
| Target Entity | Exactly one target entity (union targets are not supported) with exactly one property annotated with SemanticType.Name |
| Hierarchy Status | Flat taxonomy in current iteration (hierarchy nesting and depth limits are not supported) |
| Default Custom Categories | Disabled (allowCustomCategories: false — strict curated catalog by default) |
| Default Case Sensitivity | Case-insensitive (caseSensitive: false) |
| Default Character Set | ASCII only (allowUnicode: false) |
| Default Response Style | 'labels' (flat string array, e.g., ["Electronics", "Gadgets"]) |
| Supported Response Styles | 'labels', 'summary' (returns [{ id, name }]), or 'none' (omits inline) |
| Database Architecture | Automatic join table for Many-to-Many (_source_assoc) or foreign key column for Many-to-One |
Key Capabilities
Section titled “Key Capabilities”1. Curated Taxonomy by Default (allowCustomCategories: false)
Section titled “1. Curated Taxonomy by Default (allowCustomCategories: false)”Unlike open-ended tags, categorization typically requires strict administrative curation. For this reason, allowCustomCategories defaults to false:
- Callers may only link records to categories that already exist in the database catalog for the active organization.
- Submitting an unrecognized category label immediately rejects the mutation with a
422 Unprocessable Entitystatus. - When open categorization is needed, setting
allowCustomCategories: trueenables automatic find-or-create provisioning.
2. Configurable Inline Exposition (responseStyle)
Section titled “2. Configurable Inline Exposition (responseStyle)”Categories allows domain modelers to control how category data is returned in parent resource responses (e.g., GET /products/:id):
'labels'(default): Returns a flat array of category names:["Electronics", "Audio"]. Minimizes response payload size while delivering immediate readability.'summary': Returns an array of lightweight summary objects containing both the primary key and the name property:[{"id": "cat_01", "name": "Electronics"}]. Ideal when frontend clients need IDs to power navigation links or filter state.'none': Excludes category associations from the parent response payload entirely. Clients retrieve category details on demand through sub-resource endpoints (GET /products/:id/categories).
3. Multi-Valued and Single-Valued Cardinality
Section titled “3. Multi-Valued and Single-Valued Cardinality”Categories supports two architectural relationship types:
- Many-to-Many (
multiple: true): Used when a record can belong to multiple categories simultaneously. The Serverless Engine automatically manages the underlying join table, performing transactional replace-all synchronization on parent writes. - Many-to-One (
multiple: false): Used when each record belongs to exactly one category. The engine manages foreign key assignment natively.
4. Atomic Replace-All Synchronization
Section titled “4. Atomic Replace-All Synchronization”When mutating a Many-to-Many category association via parent POST or PATCH requests:
- The engine synchronizes associations in a single database transaction, clearing existing pivot rows and linking the newly specified categories.
- Duplicate entries in the submitted array are automatically deduplicated.
- Partial Updates: Omitting the
categoriesproperty in aPATCHpayload preserves existing category assignments without alteration.
5. Label-First Sub-Resource Operations (Link & Unlink)
Section titled “5. Label-First Sub-Resource Operations (Link & Unlink)”API consumers do not need internal primary keys to manage category links:
- Append Category (
POST /products/:id/categories): Accepts{"name": "Gadgets"}and attaches the category without affecting existing links. - Remove Category (
DELETE /products/:id/categories/:name): The path parameter is the category name string (e.g.,/categories/Gadgets). The engine resolves the name to the target foreign key and removes the pivot row, returning204 No Content.
6. Multi-Tenant Organization Scoping
Section titled “6. Multi-Tenant Organization Scoping”In APIs utilizing organization authorization strategies (EnterpriseRBAC or OrganizationRBAC), the target category entity must carry an association annotated with SemanticType.OrganizationResource. Category lookups, insertions, and uniqueness checks are automatically scoped to the calling organization.
Configuration Options
Section titled “Configuration Options”When configuring the Categories semantic on an association in the Domain Modeler, the following parameters are available:
| Option | Type | Default | Description |
|---|---|---|---|
allowCustomCategories | boolean | false | When false (default), only existing database category records can be assigned. When true, new categories are created on the fly. |
caseSensitive | boolean | false | When false, normalizes category casing so casing variations share the same record. When true, matches exact casing. |
allowUnicode | boolean | false | When false, restricts category names to ASCII. When true, permits international scripts and special symbols. |
responseStyle | 'labels' | 'summary' | 'none' | 'labels' | Controls how categories appear inline in parent GET responses. 'labels' emits string arrays, 'summary' emits object arrays with IDs, and 'none' omits the field. |
API Lifecycle Examples
Section titled “API Lifecycle Examples”1. Assigning Categories on Record Creation (responseStyle: 'labels')
Section titled “1. Assigning Categories on Record Creation (responseStyle: 'labels')”With the default 'labels' response style, categories serialize as a flat string array:
POST /productsContent-Type: application/json
{ "title": "Noise-Cancelling Studio Headphones", "price": 299.99, "categories": ["Electronics", "Audio"]}Server Response (201 Created):
{ "data": { "id": "prod_502", "title": "Noise-Cancelling Studio Headphones", "price": 299.99, "categories": ["Electronics", "Audio"] }}2. Category Summary Response (responseStyle: 'summary')
Section titled “2. Category Summary Response (responseStyle: 'summary')”When the association is configured with responseStyle: 'summary', parent GET responses include category IDs alongside names:
GET /products/prod_502Server Response (200 OK):
{ "data": { "id": "prod_502", "title": "Noise-Cancelling Studio Headphones", "price": 299.99, "categories": [ { "id": "cat_101", "name": "Electronics" }, { "id": "cat_102", "name": "Audio" } ] }}3. Rejection on Unauthorized Custom Category
Section titled “3. Rejection on Unauthorized Custom Category”Because allowCustomCategories defaults to false, attempting to assign a category that does not already exist in the database fails validation:
POST /productsContent-Type: application/json
{ "title": "Gardening Shovel", "categories": ["Outdoor & Gardening"]}Server Response (422 Unprocessable Entity):
{ "errors": [ { "field": "categories", "message": "Category \"Outdoor & Gardening\" does not exist and custom categories are not allowed.", "code": "E_VALIDATION_ERROR" } ]}4. Appending a Category via Sub-Resource
Section titled “4. Appending a Category via Sub-Resource”To attach an additional category to a product without overwriting existing assignments:
POST /products/prod_502/categoriesContent-Type: application/json
{ "name": "Accessories"}Server Response (201 Created):
{ "data": { "id": "cat_103", "name": "Accessories" }}5. Removing a Category by Name
Section titled “5. Removing a Category by Name”To unlink a category, provide the category label in the URL path:
DELETE /products/prod_502/categories/AccessoriesServer Response (204 No Content):
(Empty response body confirming the association was removed)
If the category label is not found in the database, the API responds with:
Server Response (404 Not Found):
{ "error": "Relation record not found"}6. Listing Full Category Definitions via Sub-Resource
Section titled “6. Listing Full Category Definitions via Sub-Resource”If the categories association is exposed with a list action in the API Modeler, clients can query the sub-resource endpoint to inspect full Category entity records (including primary keys and any additional metadata properties):
GET /products/prod_502/categoriesServer Response (200 OK):
{ "data": [ { "id": "cat_101", "name": "Electronics" }, { "id": "cat_102", "name": "Audio" } ], "meta": { "total": 2, "per_page": 20, "current_page": 1, "last_page": 1, "has_more_pages": false }}Domain Modeler Best Practices
Section titled “Domain Modeler Best Practices”- Categories vs. Tags:
- Use
Categoriesfor formal, controlled classification schemes where categories are managed centrally by administrators (allowCustomCategories: false) and where clients may require IDs in responses (responseStyle: 'summary'). - Use
Tagsfor user-generated, multi-faceted keywords where open creation (allowCustomTags: true) and flat string arrays are standard.
- Use
- Pre-Seeding Curated Categories: Since custom categories are blocked by default, pre-populate your taxonomy table using administrative API endpoints (
POST /categories) or data migrations before opening client creation endpoints. - Target Entity Requirements (What is the “Target”?):
- When you create an association in the Domain Modeler, it connects two entities:
- The Source Entity: The resource being categorized (for example,
ProductorSupportTicket). - The Target Entity: The entity that stores the category taxonomy records (for example,
Category,Department, orGenre).
- The Source Entity: The resource being categorized (for example,
- Single
NameProperty: The target entity (Category) must define exactly one property annotated with theNamesemantic module. The engine uses this property automatically as the category label. - Multi-Tenant Organization Scoping: When organization-level authorization is enabled, the target entity (
Category) must include an association decorated withSemanticType.OrganizationResourceto ensure category records are isolated per organization.
- When you create an association in the Domain Modeler, it connects two entities:
- Choosing Cardinality:
- Use
multiple: true(Many-to-Many) if products or records belong to multiple functional categories. - Use
multiple: false(Many-to-One) when business logic enforces a single primary category per item.
- Use