// Reference inventory: the HighLevel documentation each check relies on, where it was read, and its status. // It lists references, not behaviours verified in a live integration. // Read on 25/09/2026. Spec = github.com/GoHighLevel/highlevel-api-docs, commit 0af86a4 (19/06/2026). // Status: // documented read in an official source; the example data follows it // not documented no official source found; the check does not rely on it as a fact // conflict two official sources disagree; to confirm on the first real call // Nothing here has been tested against a live HighLevel account. export type RegisterStatus = "documented" | "not documented" | "conflict"; export interface RegisterEntry { id: string; behaviour: string; route?: string; version?: string; status: RegisterStatus; howTheCheckUsesIt: string; sources: { label: string; url: string }[]; } const SPEC = (app: string) => ({ label: `Spec: apps/${app}.json`, url: `https://github.com/GoHighLevel/highlevel-api-docs/blob/0af86a4/apps/${app}.json` }); const MP = (path: string, label: string) => ({ label, url: `https://marketplace.gohighlevel.com/docs/${path}` }); const HELP = (id: string, label: string) => ({ label, url: `https://help.gohighlevel.com/support/solutions/articles/${id}` }); export const register: RegisterEntry[] = [ { id: "export-channel", behaviour: "Without a channel parameter, the message export returns every non-email type, including activity entries. Email needs channel=Email.", route: "GET /conversations/messages/export", status: "documented", howTheCheckUsesIt: "Email is read in its own pass. If that pass is missing, no absence of reply is concluded for a contact who allows email.", sources: [MP("2021-07-28/ghl/conversations/export-messages-by-location/", "Marketplace: Export messages by location ID"), SPEC("conversations")], }, { id: "export-cursor", behaviour: "The export cursor is valid for two minutes from the last request.", route: "GET /conversations/messages/export", status: "documented", howTheCheckUsesIt: "An expired cursor marks the pass as partial; nothing is concluded from a partial pass.", sources: [MP("2021-07-28/ghl/conversations/export-messages-by-location/", "Marketplace: Export messages by location ID")], }, { id: "export-excluded", behaviour: "Group chat and SMS review request types are not supported by the export.", route: "GET /conversations/messages/export", status: "documented", howTheCheckUsesIt: "Review requests are never counted as replies, and their absence is never read as a failure.", sources: [MP("2021-07-28/ghl/conversations/export-messages-by-location/", "Marketplace: Export messages by location ID")], }, { id: "message-fields", behaviour: "A message carries source (workflow, bulk_actions, campaign, api, app), status (including scheduled, pending, failed, undelivered) and direction. Body is optional.", route: "GET /conversations/messages/:id", status: "documented", howTheCheckUsesIt: "Failed, undelivered, scheduled and pending messages are not replies. Bulk and campaign sends are not replies. A missing body is never read as a clean message.", sources: [MP("2021-07-28/ghl/conversations/get-message/", "Marketplace: Get message by message id"), SPEC("conversations")], }, { id: "version-header", behaviour: "Version header for conversations and calendars routes: the spec lists 2021-04-15, the marketplace page lists 2021-07-28 and marks that documentation as no longer actively maintained (v3 is current; the current Get Workflow page also lists v3). Contacts, opportunities, forms and workflows list 2021-07-28 in both.", route: "Version header", version: "2021-04-15 or 2021-07-28", status: "conflict", howTheCheckUsesIt: "Versions here describe the documentation read, not a validated contract. The version and a raw response get checked on the first authorised read.", sources: [SPEC("conversations"), SPEC("calendars"), MP("2021-07-28/ghl/conversations/export-messages-by-location/", "Marketplace page, version banner")], }, { id: "form-submissions", behaviour: "Form submissions can be listed by location, form and date range.", route: "GET /forms/submissions", version: "2021-07-28", status: "documented", howTheCheckUsesIt: "The list of expected inquiries comes from here (the normal entry), never from the workflow being watched.", sources: [SPEC("forms")], }, { id: "contact-dateadded", behaviour: "A contact has dateAdded, source, tags, dnd and dndSettings.", route: "GET /contacts/:contactId", version: "2021-07-28", status: "documented", howTheCheckUsesIt: "dateAdded is not used to define an inquiry: imports, returning clients and recruiting all break it. DND decides which channels count.", sources: [SPEC("contacts")], }, { id: "opp-stage", behaviour: "An opportunity has lastStageChangeAt, distinct from lastStatusChangeAt and updatedAt.", route: "GET /opportunities/:id, /opportunities/search", version: "2021-07-28", status: "documented", howTheCheckUsesIt: "Time in stage is measured from lastStageChangeAt, then weighed against any wait agreed with the client.", sources: [SPEC("opportunities")], }, { id: "calendar-events", behaviour: "Calendar events carry appointmentStatus (new, confirmed, cancelled, showed, noshow, invalid on appointment objects; the event list types it as a free string), startTime, dateAdded, dateUpdated.", route: "GET /calendars/events", status: "documented", howTheCheckUsesIt: "Cancelled appointments are set aside. A booking after the reminder was due is set aside.", sources: [MP("2021-07-28/ghl/calendars/get-calendar-events/", "Marketplace: Get Calendar Events"), SPEC("calendars")], }, { id: "calendar-notifications", behaviour: "A calendar's notifications list type (reminder, confirmation...), channel, receiver, isActive and beforeTime offsets.", route: "GET /calendars/:calendarId/notifications", status: "documented", howTheCheckUsesIt: "A reminder is expected only where the calendar has an active contact reminder, at its configured offset.", sources: [MP("2021-07-28/ghl/calendars/get-event-notification/", "Marketplace: Get notifications"), SPEC("calendars")], }, { id: "native-reminder-source", behaviour: "Which source value a native calendar reminder carries in the message export.", status: "not documented", howTheCheckUsesIt: "Any outbound message on the reminder's channel near the due time is accepted as a match. A missing match is a clue, not proof.", sources: [], }, { id: "workflows-list", behaviour: "The workflow list exposes id, name, status, version, createdAt, updatedAt. No triggers, branches, delays or actions.", route: "GET /workflows/", version: "2021-07-28", status: "documented", howTheCheckUsesIt: "Only a named, expected workflow is compared with its expected status. Workflow logic is reviewed in GHL itself.", sources: [SPEC("workflows")], }, { id: "workflow-status-values", behaviour: "The status values of a workflow. The spec gives only \"draft\" as an example; \"published\" and \"paused\" are not documented.", route: "GET /workflows/", status: "not documented", howTheCheckUsesIt: "The expected status is a setting of the check (\"published\" in the example), to confirm against a real response. No paused check exists.", sources: [SPEC("workflows")], }, { id: "enrolment-write-only", behaviour: "The API can add a contact to a workflow and remove it. It offers no read of a contact's enrolments.", route: "POST / DELETE /contacts/:contactId/workflow/:workflowId", version: "2021-07-28", status: "documented", howTheCheckUsesIt: "The check never claims a contact did or did not enter a workflow. It states that an expected result is missing.", sources: [SPEC("contacts")], }, { id: "stale-trigger", behaviour: "The native Stale Opportunities trigger fires after a duration in a stage, set in whole days. It does not work retroactively.", status: "documented", howTheCheckUsesIt: "Time in stage is a secondary check; the native trigger is the recommended fix going forward.", sources: [HELP("155000002492-workflow-trigger-stale-opportunities", "Help: Workflow Trigger, Stale Opportunities (27/04/2026)")], }, { id: "error-notifications", behaviour: "Workflow error emails are off by default and send at most one email every 24 hours. This documentation (phase 1, 2024) lists webhooks, premium actions, and Facebook and Instagram actions; it does not list texts or emails.", status: "documented", howTheCheckUsesIt: "Execution-error notifications do not establish that every expected inquiry entered the workflow. Worth turning on for what they cover.", sources: [HELP("155000003065", "Help: Error Notifications in workflows (11/09/2024)")], }, { id: "tag-trigger", behaviour: "A tag added or removed by hand or by a workflow, or added by a third-party integration such as Zapier or by a CSV import, can fire a workflow with the Contact Tag trigger.", status: "documented", howTheCheckUsesIt: "Why write access is restricted by token scopes, not by a promise to only touch tags.", sources: [HELP("48001213546-workflow-trigger-contact-tag", "Help: Workflow Trigger, Contact Tag (09/06/2025)")], }, { id: "empty-merge-field", behaviour: "A merge field with no data is replaced with a blank. There is no fallback value.", status: "documented", howTheCheckUsesIt: "A blank greeting in a sent message is read as a defect; a missing first name before a send is a risk.", sources: [HELP("155000004390-overview-of-merge-fields-custom-variables", "Help: Overview of Merge Fields (01/09/2026)")], }, { id: "unknown-merge-field", behaviour: "How a misspelled or unknown merge field renders.", status: "not documented", howTheCheckUsesIt: "Not coded. To test in the account with one message to an internal contact.", sources: [], }, { id: "if-else-empty", behaviour: "If/Else routes contacts by conditions; when a value is unavailable the field may be empty and the workflow keeps running.", status: "documented", howTheCheckUsesIt: "The prevention for a blank name is a condition placed before the send, not a later scan.", sources: [HELP("155000002471-workflow-action-if-else", "Help: Workflow Action, If/Else (01/09/2026)")], }, { id: "goal-event", behaviour: "A Goal Event can watch Appointment Status. When the goal is met, the contact jumps to the Goal Event step and skips the steps before it. End this workflow, Continue anyway and Wait until the goal is met set what happens to a contact who reaches the step without meeting the goal.", status: "documented", howTheCheckUsesIt: "The native fix proposed for a follow-up that keeps asking a booked client to book: the goal placed after the last booking request.", sources: [HELP("155000003328-workflow-action-goal-event", "Help: Workflow Action, Goal Event (08/07/2026)")], }, { id: "user-reply-wait", behaviour: "The Wait action has “A user to reply”: it holds the contact until a team member replies on a chosen channel, with an optional timeout whose branch is used to escalate. It resolves when the reply is delivered. The article suggests placing it after the contact has messaged in.", status: "documented", howTheCheckUsesIt: "The native option proposed for a missed first reply, for human text or email replies only. To test on a form inquiry.", sources: [HELP("155000002470-workflow-action-wait", "Help: Workflow Action, Wait (07/09/2026)")], }, { id: "user-replied-trigger", behaviour: "The User Replied trigger fires on messages a user sends from the conversation view, once delivered. Messages sent by workflow actions or Conversation AI do not fire it.", status: "documented", howTheCheckUsesIt: "Why automated acknowledgements are not assumed to satisfy a human-reply rule.", sources: [HELP("155000008196-workflow-trigger-user-replied", "Help: Workflow Trigger, User Replied (10/07/2026)")], }, { id: "config-errors", behaviour: "Workflows highlight configuration errors (missing settings in actions, invalid If/Else or Wait configurations) before publishing.", status: "documented", howTheCheckUsesIt: "Mentioned as a native layer to look at during the review; it is not a no-reply alert.", sources: [HELP("155000004872-highlighting-resolving-errors-in-a-workflow", "Help: Highlighting and Resolving Errors in a Workflow (15/09/2026)")], }, { id: "call-status", behaviour: "A call message carries meta.callStatus: pending, completed, answered, busy, no-answer, failed, canceled or voicemail.", status: "documented", howTheCheckUsesIt: "Our rule: a call counts as a reply only when completed or answered. A missed call or voicemail is an attempt.", sources: [SPEC("conversations")], }, { id: "message-type-format", behaviour: "messageType: the spec's enum lists TYPE_SMS, TYPE_EMAIL, TYPE_CALL; the example of the same property shows SMS.", status: "conflict", howTheCheckUsesIt: "Both forms are accepted. To confirm on a real response.", sources: [SPEC("conversations")], }, { id: "remove-from-workflow", behaviour: "The Remove from Workflow action takes a contact out of chosen workflows.", status: "documented", howTheCheckUsesIt: "Alternative fix: the booking workflow removes the contact from the nurture.", sources: [HELP("155000002553-workflow-action-remove-from-workflow", "Help: Workflow Action, Remove from Workflow (08/04/2025)")], }, { id: "execution-deep-link", behaviour: "From a message's details in Conversations, a link opens the exact workflow execution that sent it.", status: "documented", howTheCheckUsesIt: "The first place to look for any message a finding points to.", sources: [HELP("155000003992", "Help: Execution Logs and Enrollment History (15/04/2026)")], }, { id: "private-integration", behaviour: "Private Integration tokens carry the scopes the owner chooses and stay valid until rotated.", status: "documented", howTheCheckUsesIt: "Connecting a real account would use a read-only token chosen by the owner, rotated or deleted when no longer needed.", sources: [HELP("155000003054-private-integrations-everything-you-need-to-know", "Help: Private Integrations (24/06/2026)")], }, { id: "mcp", behaviour: "HighLevel's MCP server discovers, describes and executes API operations under the connection's scopes.", status: "documented", howTheCheckUsesIt: "Claude can read and explain this check's report. It does not run the check's tested functions through MCP.", sources: [MP("other/mcp", "Marketplace: MCP Server")], }, ]; export const registerCounts = { documented: register.filter((r) => r.status === "documented").length, notDocumented: register.filter((r) => r.status === "not documented").length, conflict: register.filter((r) => r.status === "conflict").length, testedLive: 0, };