Skip to main content

Overview

The Client Management API provides comprehensive endpoints for managing clients, their sites, rate rules, and billing resources. This system enables sophisticated overtime calculation and rate application for time entries.

Key Concepts

  • Clients: Companies or organizations you provide services to
  • Client Sites: Physical or logical locations where work is performed
  • Rate Rules: Define billing rates and overtime triggers for a client
  • Resources: Named billing rates within a rate rule (e.g., “Senior Developer”, “Junior Developer”)

Architecture

Client
├── Sites (multiple)
├── Rate Rules (multiple)
│   ├── Resources (multiple)
│   └── Overtime Triggers
└── Time Entries (multiple)

Authentication

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

Clients

Create Client

curl -X POST https://api.example.com/clients \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Big Client Inc",
    "taxId": "12-3456789",
    "email": "[email protected]",
    "phone": "+1-555-123-4567",
    "address": "123 Business St, Suite 100, City, ST 12345",
    "notes": "Primary client for Q1 2026",
    "isActive": true
  }'
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Big Client Inc",
    "taxId": "12-3456789",
    "email": "[email protected]",
    "phone": "+1-555-123-4567",
    "address": "123 Business St, Suite 100, City, ST 12345",
    "notes": "Primary client for Q1 2026",
    "isActive": true,
    "isDefault": false,
    "sites": [],
    "rateRules": [],
    "_count": {
      "timeEntries": 0
    },
    "createdAt": "2026-03-04T15:30:00.000Z",
    "updatedAt": "2026-03-04T15:30:00.000Z",
    "createdBy": "440e8400-e29b-41d4-a716-446655440000",
    "updatedBy": "440e8400-e29b-41d4-a716-446655440000"
  }
}

Request Body

companyId
string
required
UUID of your company
name
string
required
Client name (1-255 characters)
taxId
string
Tax identification number (max 50 characters)
email
string
Contact email (max 320 characters)
phone
string
Contact phone number (max 20 characters)
address
string
Physical or mailing address (max 1000 characters)
notes
string
Internal notes (max 2000 characters)
isActive
boolean
default:"true"
Whether client is active

Access Control

Only company owners, admins, and platform admins can create clients.

List Clients

curl -X GET "https://api.example.com/clients?companyId=550e8400-e29b-41d4-a716-446655440000&page=1&limit=50&isActive=true&search=Big" \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "success": true,
  "data": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "name": "Big Client Inc",
      "email": "[email protected]",
      "isActive": true,
      "sites": [
        {
          "id": "771e8400-e29b-41d4-a716-446655440000",
          "name": "Main Office",
          "city": "New York",
          "isActive": true
        }
      ],
      "rateRules": [
        {
          "id": "772e8400-e29b-41d4-a716-446655440000",
          "name": "Standard Rates 2026",
          "baseRatePerHour": "75.00",
          "overtimeRatePerHour": "112.50",
          "currency": "EUR",
          "isActive": true,
          "resources": [
            {
              "id": "773e8400-e29b-41d4-a716-446655440000",
              "name": "Senior Developer",
              "baseRatePerHour": "100.00",
              "isActive": true
            }
          ]
        }
      ],
      "_count": {
        "timeEntries": 45
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1,
    "totalPages": 1
  }
}

Query Parameters

companyId
string
required
Filter by company UUID
page
number
default:"1"
Page number (min: 1)
limit
number
default:"50"
Items per page (1-200)
isActive
boolean
Filter by active status
Search by client name (case-insensitive)

Get Client by ID

curl -X GET https://api.example.com/clients/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"
Returns full client details including all sites and rate rules.

Update Client

curl -X PATCH https://api.example.com/clients/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Big Client Corporation",
    "isDefault": true,
    "notes": "Upgraded to primary client status"
  }'

Request Body

All fields are optional.
name
string
Update client name
taxId
string
Update tax ID
email
string
Update email
phone
string
Update phone
address
string
Update address
notes
string
Update notes
isActive
boolean
Update active status
isDefault
boolean
Set as default client
When set to true, all other clients in the company are automatically set to isDefault: false via transaction.

Delete Client

curl -X DELETE https://api.example.com/clients/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"
Deleting a client cascades to all sites and rate rules. Time entries linked to the client will have clientId set to null.

Client Sites

Sites represent physical or logical locations where work is performed for a client.

Create Client Site

curl -X POST https://api.example.com/clients/770e8400-e29b-41d4-a716-446655440000/sites \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Main Office",
    "address": "456 Corporate Blvd",
    "city": "New York",
    "notes": "Primary work location",
    "isActive": true
  }'
{
  "success": true,
  "data": {
    "id": "771e8400-e29b-41d4-a716-446655440000",
    "clientId": "770e8400-e29b-41d4-a716-446655440000",
    "name": "Main Office",
    "address": "456 Corporate Blvd",
    "city": "New York",
    "notes": "Primary work location",
    "isActive": true,
    "isDefault": false,
    "createdAt": "2026-03-04T15:30:00.000Z",
    "updatedAt": "2026-03-04T15:30:00.000Z"
  }
}

Path Parameters

clientId
string
required
UUID of the client

Request Body

name
string
required
Site name (1-255 characters)
address
string
Site address (max 1000 characters)
city
string
City name (max 100 characters)
notes
string
Internal notes (max 2000 characters)
isActive
boolean
default:"true"
Whether site is active

Update Client Site

curl -X PATCH https://api.example.com/clients/sites/771e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "isDefault": true
  }'

Path Parameters

siteId
string
required
UUID of the site
When isDefault: true is set, all other sites for the same client are automatically set to isDefault: false.

Delete Client Site

curl -X DELETE https://api.example.com/clients/sites/771e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"

Rate Rules

Rate rules define billing rates and overtime calculation logic for a client.

Create Rate Rule

curl -X POST https://api.example.com/clients/770e8400-e29b-41d4-a716-446655440000/rates \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Standard Rates 2026",
    "baseRatePerHour": 75.00,
    "overtimeRatePerHour": 112.50,
    "currency": "EUR",
    "overtimeTriggers": ["WEEKEND", "AFTER_HOURS"],
    "workdayStartTime": "09:00",
    "workdayEndTime": "17:00",
    "workdays": [1, 2, 3, 4, 5],
    "isActive": true,
    "effectiveFrom": "2026-01-01",
    "effectiveTo": "2026-12-31"
  }'
{
  "success": true,
  "data": {
    "id": "772e8400-e29b-41d4-a716-446655440000",
    "clientId": "770e8400-e29b-41d4-a716-446655440000",
    "name": "Standard Rates 2026",
    "baseRatePerHour": "75.00",
    "overtimeRatePerHour": "112.50",
    "currency": "EUR",
    "overtimeTriggers": ["WEEKEND", "AFTER_HOURS"],
    "workdayStartTime": "09:00",
    "workdayEndTime": "17:00",
    "workdays": [1, 2, 3, 4, 5],
    "isActive": true,
    "effectiveFrom": "2026-01-01T00:00:00.000Z",
    "effectiveTo": "2026-12-31T00:00:00.000Z",
    "resources": [],
    "createdAt": "2026-03-04T15:30:00.000Z",
    "updatedAt": "2026-03-04T15:30:00.000Z",
    "createdBy": "440e8400-e29b-41d4-a716-446655440000"
  }
}

Path Parameters

clientId
string
required
UUID of the client

Request Body

name
string
required
Rate rule name (1-100 characters)
baseRatePerHour
number
Base hourly rate (used when not overtime)Can be null if using only resources with individual rates
overtimeRatePerHour
number
required
Overtime hourly rate (min: 0)
currency
string
default:"EUR"
Three-letter currency code (e.g., “EUR”, “USD”, “GBP”)
overtimeTriggers
array
default:"[]"
Array of overtime trigger types:
  • WEEKEND: Saturdays and Sundays
  • AFTER_HOURS: Outside workday hours
  • MANUAL: User can manually mark as overtime
workdayStartTime
string
Workday start time in HH:mm format (e.g., “09:00”)Required if using AFTER_HOURS trigger
workdayEndTime
string
Workday end time in HH:mm format (e.g., “17:00”)Required if using AFTER_HOURS trigger
workdays
array
default:"[1,2,3,4,5]"
Array of workday numbers (0=Sunday, 1=Monday, …, 6=Saturday)Default is Monday-Friday [1,2,3,4,5]
isActive
boolean
default:"true"
Whether rate rule is active
effectiveFrom
string
required
Date when rate rule becomes effective (ISO 8601)
effectiveTo
string
Date when rate rule expires (ISO 8601, optional)If null, the rule remains active indefinitely

Update Rate Rule

curl -X PATCH https://api.example.com/clients/rates/772e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "overtimeRatePerHour": 125.00,
    "effectiveTo": "2027-12-31"
  }'

Path Parameters

ruleId
string
required
UUID of the rate rule

Delete Rate Rule

curl -X DELETE https://api.example.com/clients/rates/772e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"
Deleting a rate rule cascades to all resources under it.

Rate Rule Resources

Resources define named billing rates within a rate rule (e.g., different rates for different skill levels).

Create Resource

curl -X POST https://api.example.com/clients/rates/772e8400-e29b-41d4-a716-446655440000/resources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Senior Developer",
    "baseRatePerHour": 100.00,
    "isActive": true
  }'
{
  "success": true,
  "data": {
    "id": "773e8400-e29b-41d4-a716-446655440000",
    "rateRuleId": "772e8400-e29b-41d4-a716-446655440000",
    "name": "Senior Developer",
    "baseRatePerHour": "100.00",
    "isActive": true,
    "createdAt": "2026-03-04T15:30:00.000Z",
    "updatedAt": "2026-03-04T15:30:00.000Z"
  }
}

Path Parameters

ruleId
string
required
UUID of the rate rule

Request Body

name
string
required
Resource name (1-100 characters)
baseRatePerHour
number
required
Base hourly rate for this resource (min: 0)
isActive
boolean
default:"true"
Whether resource is active
Resources inherit the overtime rate from their parent rate rule. Only the base rate is defined per resource.

Update Resource

curl -X PATCH https://api.example.com/clients/resources/773e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "baseRatePerHour": 110.00
  }'

Path Parameters

resourceId
string
required
UUID of the resource

Delete Resource

curl -X DELETE https://api.example.com/clients/resources/773e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"

Overtime Calculation Logic

How It Works

When a time entry is created or updated with a clientId, the system automatically:
  1. Finds Active Rate Rule
    • Matches clientId
    • isActive = true
    • effectiveFrom <= entry.date
    • effectiveTo is null OR effectiveTo >= entry.date
    • Orders by effectiveFrom DESC (most recent first)
  2. Evaluates Overtime Triggers (in order)
    • MANUAL: Uses isOvertime from request (defaults to false)
    • WEEKEND: Checks if entry.date.getDay() is 0 (Sunday) or 6 (Saturday)
    • AFTER_HOURS: Compares entry.startTime and entry.endTime against workdayStartTime and workdayEndTime
  3. Applies Rate
    • If overtime: uses overtimeRatePerHour
    • If not overtime: uses baseRatePerHour (can be null)
    • Sets appliedRatePerHour on the time entry

Example: Weekend Overtime

Rate Rule
{
  "overtimeTriggers": ["WEEKEND"],
  "overtimeRatePerHour": 112.50
}
Time Entry on Saturday
{
  "date": "2026-03-08",  // Saturday
  "clientId": "770e8400..."
}
// Result: isOvertime = true, appliedRatePerHour = 112.50

Example: After-Hours Overtime

Rate Rule
{
  "overtimeTriggers": ["AFTER_HOURS"],
  "workdayStartTime": "09:00",
  "workdayEndTime": "17:00",
  "baseRatePerHour": 75.00,
  "overtimeRatePerHour": 112.50
}
Time Entry Starting Early
{
  "date": "2026-03-04",  // Tuesday
  "startTime": "07:00",  // Before 09:00
  "endTime": "15:00",
  "clientId": "770e8400..."
}
// Result: isOvertime = true, appliedRatePerHour = 112.50

Example: Manual Overtime

Rate Rule
{
  "overtimeTriggers": ["MANUAL"],
  "overtimeRatePerHour": 112.50
}
Time Entry with Manual Flag
{
  "date": "2026-03-04",
  "isOvertime": true,  // User specifies
  "clientId": "770e8400..."
}
// Result: isOvertime = true, appliedRatePerHour = 112.50

Best Practices

Rate Rule Versioning

// Create new rate rule for 2027 while keeping 2026 active
await createRateRule(clientId, {
  name: "Standard Rates 2027",
  baseRatePerHour: 80.00,
  overtimeRatePerHour: 120.00,
  effectiveFrom: "2027-01-01",
  effectiveTo: null
});

// Update 2026 rule to expire
await updateRateRule(rule2026Id, {
  effectiveTo: "2026-12-31"
});

Default Client Setup

// Set most common client as default
await updateClient(primaryClientId, {
  isDefault: true
});

// This will automatically pre-select in time entry forms

Multiple Overtime Triggers

{
  "overtimeTriggers": ["WEEKEND", "AFTER_HOURS"],
  "workdayStartTime": "09:00",
  "workdayEndTime": "17:00"
}
This configuration marks entries as overtime if:
  • Work is on Saturday or Sunday, OR
  • Work starts before 09:00 or ends after 17:00

Build docs developers (and LLMs) love