API Reference
Contacts
Manage client, consultant, and supplier contacts.
REST /api/v1API key authentication5 endpoints
GET
/api/v1/contactsList contact records with optional filters and pagination.
Query schema
const contactTypeSchema = z.enum(['company', 'individual']);
const listQuerySchema = z.object({
contactType: contactTypeSchema.optional(),
contactTypeKey: z.string().min(1).max(30).optional(),
parentContactId: z.string().uuid().optional(),
isActive: z
.string()
.transform((v) => v === 'true')
.optional(),
isBillable: z
.string()
.transform((v) => v === 'true')
.optional(),
search: z.string().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/contactsCreate a new contact.
JSON body schema
const contactTypeSchema = z.enum(['company', 'individual']);
const createBodySchema = z.object({
contactType: contactTypeSchema,
parentContactId: z.string().uuid().optional(),
name: z.string().min(1),
email: z.string().email().optional(),
phone: z.string().optional(),
jobTitle: z.string().optional(),
billingEmail: z.string().email().optional(),
billingAddress: z.string().optional(),
paymentTermsDays: z.number().int().min(0).optional(),
isBillable: z.boolean().optional(),
isActive: z.boolean().optional(),
website: z.string().url().optional(),
taxNumber: z.string().optional(),
notes: z.string().optional(),
tags: z.array(z.string()).optional(),
});Response201JSON response with a data envelope.
DELETE
/api/v1/contacts/:idDelete a contact.
Path parametersid: string
Response204No response body.
GET
/api/v1/contacts/:idGet a single contact by ID.
Path parametersid: string
Response200JSON response with a data envelope.
PATCH
/api/v1/contacts/:idUpdate a contact.
Path parametersid: string
JSON body schema
const contactTypeSchema = z.enum(['company', 'individual']);
const updateBodySchema = z.object({
contactType: contactTypeSchema.optional(),
parentContactId: z.string().uuid().nullable().optional(),
name: z.string().min(1).optional(),
email: z.string().email().nullable().optional(),
phone: z.string().nullable().optional(),
jobTitle: z.string().nullable().optional(),
billingEmail: z.string().email().nullable().optional(),
billingAddress: z.string().nullable().optional(),
paymentTermsDays: z.number().int().min(0).nullable().optional(),
isBillable: z.boolean().optional(),
isActive: z.boolean().optional(),
website: z.string().url().nullable().optional(),
taxNumber: z.string().nullable().optional(),
notes: z.string().nullable().optional(),
tags: z.array(z.string()).optional(),
});Response200JSON response with a data envelope.