API Reference
Expenses
Manage operational and project expense records.
REST /api/v1API key authentication7 endpoints
GET
/api/v1/expensesList expenses with optional filters
Query schema
const expenseTypeSchema = z.enum(['supplier_invoice', 'staff_claim', 'direct_cost']);
const expenseStatusSchema = z.enum(['draft', 'pending', 'approved', 'rejected', 'paid']);
const listQuerySchema = z.object({
rootTaskId: z.string().uuid().optional(),
taskId: z.string().uuid().optional(),
supplierId: z.string().uuid().optional(),
claimedByResourceId: z.string().uuid().optional(),
expenseType: expenseTypeSchema.optional(),
category: z.string().optional(),
status: expenseStatusSchema.optional(),
dateFrom: z.string().optional(),
dateTo: z.string().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),
});Response200Returns results in data and page totals, limit and offset in pagination.
POST
/api/v1/expensesCreate a new expense
JSON body schema
const expenseTypeSchema = z.enum(['supplier_invoice', 'staff_claim', 'direct_cost']);
const expenseStatusSchema = z.enum(['draft', 'pending', 'approved', 'rejected', 'paid']);
const financialModifierSchema = z.object({
id: z.string().uuid(),
name: z.string(),
type: z.enum(['percentage', 'amount', 'multiplier']),
value: z.string(),
});
const createBodySchema = z.object({
rootTaskId: z.string().uuid().optional(),
taskId: z.string().uuid().optional(),
supplierId: z.string().uuid().optional(),
claimedByResourceId: z.string().uuid().optional(),
purchaseOrderId: z.string().uuid().optional(),
purchaseOrderLineId: z.string().uuid().optional(),
expenseType: expenseTypeSchema,
category: z.string().optional(),
amount: z.number().min(0),
currency: z.string().length(3).optional(),
date: z.string(),
description: z.string().optional(),
referenceNumber: z.string().optional(),
reimbursementStatus: expenseStatusSchema.optional(),
isBillable: z.boolean().optional(),
markupPercentage: z.number().optional(),
billableModifiers: z.array(financialModifierSchema).optional(),
billableAmount: z.number().optional(),
status: expenseStatusSchema.optional(),
tags: z.array(z.string()).optional(),
});Response201Returns the result in the data field.
DELETE
/api/v1/expenses/:idDelete an expense
Path parametersid: string
Response204No response body.
GET
/api/v1/expenses/:idGet a single expense
Path parametersid: string
Response200Returns the result in the data field.
PATCH
/api/v1/expenses/:idUpdate an expense
Path parametersid: string
JSON body schema
const expenseTypeSchema = z.enum(['supplier_invoice', 'staff_claim', 'direct_cost']);
const expenseStatusSchema = z.enum(['draft', 'pending', 'approved', 'rejected', 'paid']);
const financialModifierSchema = z.object({
id: z.string().uuid(),
name: z.string(),
type: z.enum(['percentage', 'amount', 'multiplier']),
value: z.string(),
});
const updateBodySchema = z.object({
rootTaskId: z.string().uuid().nullable().optional(),
taskId: z.string().uuid().nullable().optional(),
supplierId: z.string().uuid().nullable().optional(),
claimedByResourceId: z.string().uuid().nullable().optional(),
purchaseOrderId: z.string().uuid().nullable().optional(),
purchaseOrderLineId: z.string().uuid().nullable().optional(),
expenseType: expenseTypeSchema.optional(),
category: z.string().nullable().optional(),
amount: z.number().min(0).optional(),
currency: z.string().length(3).optional(),
date: z.string().optional(),
description: z.string().nullable().optional(),
referenceNumber: z.string().nullable().optional(),
reimbursementStatus: expenseStatusSchema.nullable().optional(),
isBillable: z.boolean().optional(),
markupPercentage: z.number().optional(),
billableModifiers: z.array(financialModifierSchema).optional(),
billableAmount: z.number().nullable().optional(),
status: expenseStatusSchema.optional(),
tags: z.array(z.string()).optional(),
});Response200Returns the result in the data field.
POST
/api/v1/expenses/:id/approveApprove an expense
Path parametersid: string
Response200Returns the result in the data field.
POST
/api/v1/expenses/:id/rejectReject an expense
Path parametersid: string
JSON body schema
const bodySchema = z.object({ reason: z.string().optional() });Response200Returns the result in the data field.