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),
});Response200JSON response with data and pagination envelopes.
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 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(),
billableAmount: z.number().optional(),
status: expenseStatusSchema.optional(),
tags: z.array(z.string()).optional(),
});Response201JSON response with a data envelope.
DELETE
/api/v1/expenses/:idDelete an expense
Path parametersid: string
Response204No response body.
GET
/api/v1/expenses/:idGet a single expense
Path parametersid: string
Response200JSON response with a data envelope.
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 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(),
billableAmount: z.number().nullable().optional(),
status: expenseStatusSchema.optional(),
tags: z.array(z.string()).optional(),
});Response200JSON response with a data envelope.
POST
/api/v1/expenses/:id/approveApprove an expense
Path parametersid: string
Response200JSON response with a data envelope.
POST
/api/v1/expenses/:id/rejectReject an expense
Path parametersid: string
JSON body schema
const bodySchema = z.object({ reason: z.string().optional() });Response200JSON response with a data envelope.