// Shapes follow the HighLevel API responses (spec GoHighLevel/highlevel-api-docs, commit 0af86a4, 19/06/2026). // Only the fields the check reads are typed. See src/lib/register.ts for the source of each field. export type IsoDate = string; export interface Contact { id: string; firstName?: string; lastName?: string; name?: string; dateAdded: IsoDate; source?: string; tags: string[]; dnd?: boolean; dndSettings?: Partial>; customFields?: { id: string; value: string }[]; } export interface FormSubmission { id: string; contactId: string; formId: string; createdAt: IsoDate; name?: string; } export type MessageType = | "TYPE_SMS" | "TYPE_EMAIL" | "TYPE_CALL" | "TYPE_ACTIVITY_OPPORTUNITY" | "TYPE_ACTIVITY_APPOINTMENT" | "TYPE_FORM_SUBMISSION" // The spec's example shows "SMS" where its enum says "TYPE_SMS": both forms accepted, to confirm on a real response. | "SMS" | "Email" | "Call"; export type MessageStatus = | "connected" | "delivered" | "failed" | "opened" | "pending" | "read" | "scheduled" | "sent" | "undelivered" | "clicked" | "opt_out" | "queued"; export type MessageSource = "workflow" | "bulk_actions" | "campaign" | "api" | "app"; export interface Message { id: string; messageType: MessageType; contactId: string; conversationId: string; dateAdded: IsoDate; body?: string; // optional in the schema: a missing body never means a clean message direction: "inbound" | "outbound"; status?: MessageStatus; source?: MessageSource; userId?: string; meta?: { callStatus?: "pending" | "completed" | "answered" | "busy" | "no-answer" | "failed" | "canceled" | "voicemail" }; } export type AppointmentStatus = "new" | "confirmed" | "cancelled" | "showed" | "noshow" | "invalid"; export interface CalendarEvent { id: string; calendarId: string; contactId: string; title: string; appointmentStatus: AppointmentStatus; startTime: IsoDate; endTime: IsoDate; dateAdded: IsoDate; dateUpdated: IsoDate; } export interface CalendarNotification { _id: string; receiverType: "contact" | "guest" | "assignedUser" | "emails" | "phoneNumbers" | "business"; channel: "email" | "inApp" | "sms" | "whatsapp"; notificationType: "booked" | "confirmation" | "cancellation" | "reminder" | "followup" | "reschedule"; isActive: boolean; deleted?: boolean; beforeTime?: { timeOffset: number; unit: "minutes" | "hours" | "days" }[]; } export interface Opportunity { id: string; name: string; contactId: string; pipelineId: string; pipelineStageId: string; status: "open" | "won" | "lost" | "abandoned"; createdAt: IsoDate; lastStageChangeAt: IsoDate; } export interface Workflow { id: string; name: string; status: string; // the spec types it as a free string, example "draft" version: number; createdAt: IsoDate; updatedAt: IsoDate; } /** What the collector could read. The check refuses to conclude on anything it did not read. */ export type SourceKey = | "contacts" | "forms.submissions" | "messages.nonEmail" | "messages.email" | "calendars.events" | "calendars.notifications" | "opportunities" | "workflows"; export type SourceStatus = "complete" | "partial" | "not_read" | "forbidden"; export interface SourceRead { source: SourceKey; status: SourceStatus; note?: string; } export interface Snapshot { locationId: string; collectedAt: IsoDate; sources: SourceRead[]; contacts: Contact[]; submissions: FormSubmission[]; messages: Message[]; events: CalendarEvent[]; notifications: Record; // by calendarId opportunities: Opportunity[]; workflows: Workflow[]; } /** Everything in here is defined for the demonstration, never discovered from an account. */ export interface CheckConfig { timezone: string; inquiryFormIds: string[]; firstReplyWithinMinutes: number; holdUntilFieldId: string; expectedAutoReplyMarkers: string[]; // text of the automated first reply; any other automated send is not a reply personalizedTemplates: { id: string; name: string; marker: string; // text that identifies the template in a sent body greetingPrefix: string; // e.g. "Hi " : the name is expected between this and the next comma requiredContactField: "firstName"; atRiskStageIds: string[]; // open opportunities in these stages will receive the template next }[]; bookingAsk: { marker: string; calendarId: string }; stageLimitsDays: Record; reminderMatchToleranceMinutes: number; maxAutomatedPer24h: number; expectedActiveWorkflows: { name: string; expectedStatus: string }[]; staleRunAfterHours: number; }