Skip to main content

Overview

Time entry categories provide a way to classify the type of work being performed. Categories are company-specific and can be used to distinguish between billable work, training, travel, meetings, and other work types.

Key Features

  • Company-scoped: Each category belongs to a single company
  • Color Coding: Visual identification with hex color codes
  • Default Category: Mark one category as default for quick entry creation
  • Active/Inactive Status: Control which categories are available for selection
  • Unique Names: Category names must be unique within a company

Authentication

All endpoints require:
  • Valid JWT authentication token
  • Platform admin role (checked via middleware)

Create Category

curl -X POST https://api.example.com/categories \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Development",
    "color": "#6366F1",
    "isDefault": false,
    "isActive": true
  }'
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Development",
    "color": "#6366F1",
    "isDefault": false,
    "isActive": true,
    "createdAt": "2026-03-04T15:30:00.000Z",
    "updatedAt": "2026-03-04T15:30:00.000Z",
    "createdBy": "440e8400-e29b-41d4-a716-446655440000"
  }
}

Request Body

companyId
string
required
UUID of the company this category belongs to
name
string
required
Category name (1-100 characters)Must be unique within the company
color
string
default:"#6366F1"
Hex color code for visual identification (e.g., “#6366F1”)Must match pattern: ^#[0-9A-Fa-f]{6}$
isDefault
boolean
default:"false"
Whether this category should be pre-selected when creating new time entries
Only one category per company can be marked as default. Application logic should enforce this in the UI.
isActive
boolean
default:"true"
Whether the category is active and available for selection

Response Fields

All request fields plus:
  • id: Generated UUID
  • createdAt, updatedAt: Timestamps
  • createdBy: User ID who created the category

List Categories

curl -X GET "https://api.example.com/categories?companyId=550e8400-e29b-41d4-a716-446655440000&isActive=true" \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "success": true,
  "data": [
    {
      "id": "880e8400-e29b-41d4-a716-446655440000",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Development",
      "color": "#6366F1",
      "isDefault": true,
      "isActive": true,
      "createdAt": "2026-03-04T15:30:00.000Z",
      "updatedAt": "2026-03-04T15:30:00.000Z"
    },
    {
      "id": "881e8400-e29b-41d4-a716-446655440000",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Training",
      "color": "#F59E0B",
      "isDefault": false,
      "isActive": true,
      "createdAt": "2026-03-04T15:31:00.000Z",
      "updatedAt": "2026-03-04T15:31:00.000Z"
    },
    {
      "id": "882e8400-e29b-41d4-a716-446655440000",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Travel",
      "color": "#8B5CF6",
      "isDefault": false,
      "isActive": true,
      "createdAt": "2026-03-04T15:32:00.000Z",
      "updatedAt": "2026-03-04T15:32:00.000Z"
    }
  ]
}

Query Parameters

companyId
string
required
Filter by company UUID
isActive
boolean
Filter by active status
  • true: Only active categories
  • false: Only inactive categories
  • Omit: All categories
Unlike other list endpoints, categories do not support pagination. All categories for the company are returned in a single response.

Update Category

curl -X PATCH https://api.example.com/categories/880e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Software Development",
    "color": "#3B82F6",
    "isDefault": true
  }'
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Software Development",
    "color": "#3B82F6",
    "isDefault": true,
    "isActive": true,
    "updatedAt": "2026-03-04T16:00:00.000Z"
  }
}

Path Parameters

id
string
required
UUID of the category to update

Request Body

All fields are optional. Only provided fields will be updated.
name
string
Update category name (1-100 characters)Must remain unique within the company
color
string
Update hex color code (must match ^#[0-9A-Fa-f]{6}$)
isDefault
boolean
Set as default category
When setting isDefault: true, you should ensure only one category per company has this flag. Implement application logic to clear the flag from other categories.
isActive
boolean
Update active status

Delete Category

curl -X DELETE https://api.example.com/categories/880e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "success": true,
  "message": "Category deleted successfully"
}

Path Parameters

id
string
required
UUID of the category to delete
When a category is deleted:
  • All time entries linked to this category will have their categoryId set to null (cascade: SetNull)
  • This is a permanent operation and cannot be undone
  • Consider marking the category as inactive instead if you want to preserve the relationship

Category Colors

Default Color

The default category color is #6366F1 (indigo).
Category TypeSuggested ColorHex Code
DevelopmentIndigo#6366F1
TrainingAmber#F59E0B
TravelPurple#8B5CF6
MeetingsBlue#3B82F6
MaintenanceGreen#10B981
SupportRed#EF4444
PlanningPink#EC4899
DocumentationCyan#06B6D4

Common Category Setups

Standard Work Categories

[
  { "name": "Development", "color": "#6366F1", "isDefault": true },
  { "name": "Code Review", "color": "#8B5CF6" },
  { "name": "Testing", "color": "#10B981" },
  { "name": "Documentation", "color": "#06B6D4" },
  { "name": "Meetings", "color": "#3B82F6" }
]

Professional Services Categories

[
  { "name": "Billable Work", "color": "#10B981", "isDefault": true },
  { "name": "Internal", "color": "#6366F1" },
  { "name": "Training", "color": "#F59E0B" },
  { "name": "Travel", "color": "#8B5CF6" },
  { "name": "Admin", "color": "#64748B" }
]

Consulting Categories

[
  { "name": "Client Consulting", "color": "#3B82F6", "isDefault": true },
  { "name": "Research", "color": "#8B5CF6" },
  { "name": "Proposal Writing", "color": "#EC4899" },
  { "name": "Client Meetings", "color": "#10B981" },
  { "name": "Travel", "color": "#F97316" }
]

Best Practices

Naming Conventions

  1. Be Specific: Use clear names like “Client Meetings” instead of just “Meetings”
  2. Keep It Short: Aim for 1-2 words when possible
  3. Be Consistent: Use similar naming patterns across categories (e.g., all verbs or all nouns)
  4. Avoid Abbreviations: Use “Development” instead of “Dev” for clarity

Default Category Strategy

// Set the most commonly used category as default
await updateCategory(developmentCategoryId, {
  isDefault: true
});

// This will be pre-selected in time entry forms

Active Status Management

// Archive old categories instead of deleting
await updateCategory(oldCategoryId, {
  isActive: false,
  name: "[ARCHIVED] Old Category Name"
});

Unique Name Constraint

The database enforces unique category names per company:
@@unique([companyId, name])
If you attempt to create a duplicate:
Error Response
{
  "success": false,
  "error": "A category with this name already exists in this company"
}

Integration Examples

Creating categories for a new company

const categories = [
  { name: "Development", color: "#6366F1", isDefault: true },
  { name: "Training", color: "#F59E0B" },
  { name: "Travel", color: "#8B5CF6" },
  { name: "Meetings", color: "#3B82F6" },
];

for (const category of categories) {
  await fetch('https://api.example.com/categories', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      companyId: newCompany.id,
      ...category,
      isActive: true
    })
  });
}

Using categories in time entry forms

// Fetch active categories for dropdown
const response = await fetch(
  `https://api.example.com/categories?companyId=${companyId}&isActive=true`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);

const { data: categories } = await response.json();

// Find default category
const defaultCategory = categories.find(c => c.isDefault);

// Pre-populate form
setFormData({
  ...formData,
  categoryId: defaultCategory?.id || categories[0]?.id
});

Color-coded time reporting

// Group time entries by category for visual report
const entriesByCategory = timeEntries.reduce((acc, entry) => {
  const categoryName = entry.category?.name || 'Uncategorized';
  const categoryColor = entry.category?.color || '#64748B';
  
  if (!acc[categoryName]) {
    acc[categoryName] = {
      color: categoryColor,
      hours: 0,
      entries: []
    };
  }
  
  acc[categoryName].hours += parseFloat(entry.hours);
  acc[categoryName].entries.push(entry);
  
  return acc;
}, {});

Build docs developers (and LLMs) love