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
Client name (1-255 characters)
Tax identification number (max 50 characters)
Contact email (max 320 characters)
Contact phone number (max 20 characters)
Physical or mailing address (max 1000 characters)
Internal notes (max 2000 characters)
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
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.
Set as default clientWhen 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
Request Body
Site name (1-255 characters)
Site address (max 1000 characters)
City name (max 100 characters)
Internal notes (max 2000 characters)
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
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
Request Body
Rate rule name (1-100 characters)
Base hourly rate (used when not overtime)Can be null if using only resources with individual rates
Overtime hourly rate (min: 0)
Three-letter currency code (e.g., “EUR”, “USD”, “GBP”)
Array of overtime trigger types:
WEEKEND: Saturdays and Sundays
AFTER_HOURS: Outside workday hours
MANUAL: User can manually mark as overtime
Workday start time in HH:mm format (e.g., “09:00”)Required if using AFTER_HOURS trigger
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]
Whether rate rule is active
Date when rate rule becomes effective (ISO 8601)
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
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
Request Body
Resource name (1-100 characters)
Base hourly rate for this resource (min: 0)
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
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:
-
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)
-
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
-
Applies Rate
- If overtime: uses
overtimeRatePerHour
- If not overtime: uses
baseRatePerHour (can be null)
- Sets
appliedRatePerHour on the time entry
Example: Weekend Overtime
{
"overtimeTriggers": ["WEEKEND"],
"overtimeRatePerHour": 112.50
}
{
"date": "2026-03-08", // Saturday
"clientId": "770e8400..."
}
// Result: isOvertime = true, appliedRatePerHour = 112.50
Example: After-Hours Overtime
{
"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
{
"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