API Reference
Time Entries
Create, update, list, and remove organization time entries.
REST /api/v1API key authentication5 endpoints
GET
/api/v1/time-entriesList time entries with optional filters
Query schema
const dateSchema = z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.refine((value) => {
const parsed = parseDateOnlyLocal(value);
return parsed !== null && formatDateLocal(parsed) === value;
}, 'Date must be a valid YYYY-MM-DD value');
const listQuerySchema = z.object({
taskId: z.string().uuid().optional(),
resourceId: z.string().uuid().optional(),
projectId: z.string().uuid().optional(),
dateFrom: dateSchema.optional(),
dateTo: dateSchema.optional(),
isBillable: z
.string()
.transform((v) => v === 'true')
.optional(),
isInvoiced: z
.string()
.transform((v) => v === 'true')
.optional(),
limit: z.coerce.number().int().min(1).max(100).default(50),
offset: z.coerce.number().int().min(0).default(0),
});Response200JSON response with data and pagination envelopes.
POST
/api/v1/time-entriesCreate a new time entry
JSON body schema
const dateSchema = z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.refine((value) => {
const parsed = parseDateOnlyLocal(value);
return parsed !== null && formatDateLocal(parsed) === value;
}, 'Date must be a valid YYYY-MM-DD value');
const timeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, 'Time must be in HH:MM format');
const entrySourceSchema = z.enum(['manual', 'timer', 'copied', 'suggested']);
const createBodySchema = z
.object({
taskId: z.string().uuid(),
resourceId: z.string().uuid(),
date: dateSchema,
hours: z.number().positive().max(24),
durationSeconds: z
.number()
.int()
.positive()
.max(24 * 60 * 60)
.optional(),
startTime: timeSchema.optional(),
entrySource: entrySourceSchema.optional(),
description: z.string().nullable().optional(),
isBillable: z.boolean().default(true),
billableRate: z.number().optional(),
tags: z.array(z.string()).optional(),
})
.superRefine((value, ctx) => {
if ((value.entrySource ?? 'manual') !== 'timer' && value.hours < 0.25) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Hours must be at least 0.25 for non-timer entries',
path: ['hours'],
});
}
});Response201JSON response.
DELETE
/api/v1/time-entries/:idDelete a time entry
Path parametersid: string
Response204No response body.
GET
/api/v1/time-entries/:idGet a single time entry
Path parametersid: string
Response200JSON response with a data envelope.
PATCH
/api/v1/time-entries/:idUpdate a time entry
Path parametersid: string
JSON body schema
const dateSchema = z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.refine((value) => {
const parsed = parseDateOnlyLocal(value);
return parsed !== null && formatDateLocal(parsed) === value;
}, 'Date must be a valid YYYY-MM-DD value');
const timeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, 'Time must be in HH:MM format');
const updateBodySchema = z.object({
taskId: z.string().uuid().optional(),
resourceId: z.string().uuid().optional(),
date: dateSchema.optional(),
hours: z.number().min(0.25).max(24).optional(),
durationSeconds: z
.number()
.int()
.positive()
.max(24 * 60 * 60)
.optional(),
startTime: timeSchema.nullable().optional(),
description: z.string().nullable().optional(),
isBillable: z.boolean().optional(),
billableRate: z.number().nullable().optional(),
tags: z.array(z.string()).optional(),
});Response200JSON response.