Skip to content
Cook
Documentation Browse

Move from another tool

GoHighLevel's API on Cook

Keep your GoHighLevel scripts. Point them at Cook's GoHighLevel address with a Cook key, and your contacts, opportunities, conversations, calendars and users answer the way GoHighLevel's API does.

Cook answers GoHighLevel’s API v2 (LeadConnector, services.leadconnectorhq.com). A script that creates contacts, moves opportunities, logs messages, books appointments or reads your team keeps working when you point it at Cook. A GoHighLevel location is your Cook workspace, contacts are Cook leads, opportunities are a lead’s place in a pipeline, calendars are Cook booking pages, appointments are Cook meetings and users are the people on your team.

The switch: two lines

Change the address and the key. Nothing else changes.

Base URL:       https://api.trycook.ai/compat/ghl
Authorization:  Bearer ck_live_…
Version:        2021-07-28

Make the key in Cook under Settings → API keys. The key decides the workspace, and its permissions decide what the script may do (see the compat overview). Use an account key (one made for a person) for the team calls; see Approvals.

  • Version header. Required, as on GoHighLevel. Cook accepts 2021-07-28, 2021-04-15, 2023-02-21, v3 and legacy. A missing one answers 401 “version header was not found.”; any other value answers 401 “version header is invalid”.
  • Your locationId. Your workspace has a GoHighLevel-shaped location id (20 letters and digits) and a company id. Read them once with GET /locations/search: locations[0].id is your locationId, locations[0].companyId your companyId. Pass them wherever GoHighLevel asks for them. Another location’s id answers 403 “The token does not have access to this location.”; another company’s id answers 401 “The token does not have access to this company.”.

Everything after the address is as GoHighLevel documents it: the paths, query parameters, request and response bodies, succeded spelled as GoHighLevel spells it, and the error body ({ "statusCode", "message", "error" }; a 422 carries message as a list of reasons). A path Cook doesn’t answer is a 404 "Cannot <METHOD> /<path>".

What Cook answers

Supported means it behaves as GoHighLevel documents it. Partial says what differs. Not supported says why.

Locations

GoHighLevel callOn CookNotes
GET /locations/searchSupportedYour one location. email filters on the workspace’s email.
GET /locations/{locationId}PartialName, email, website, logo and timezone: your default availability schedule’s, else the time zone Cook last saw you chat from, else UTC. settings says one contact per email or phone and one opportunity per contact.
POST /locations/, PUT and DELETE /locations/{locationId}Not supported404. Make and remove workspaces in Cook.
/locations/{locationId}/timezones, /templates, /tasks/search, /recurring-tasksNot supported404.

Contacts

GoHighLevel callOn CookNotes
POST /contacts/Supported201. Name, email, phone, company, source, timezone, address fields, website, date of birth, tags, dnd (or dndSettings), assignedTo and customFields. Fields Cook has no place for (gender, attribution and the like) are not kept. An email or phone another contact already has answers 400 “This location does not allow duplicated contacts.” with meta.contactId and meta.matchingField, as GoHighLevel does with duplicates off.
GET, PUT, DELETE /contacts/{contactId}SupportedDelete removes the lead and what it owns in Cook.
POST /contacts/upsertSupportedMatches on email, then phone. new: true when it made one.
POST /contacts/searchSupportedfilters (eq, not_eq, contains, not_contains, exists, not_exists and ranges on dates) on name fields, email, phone, company, source, timezone, type, tags, assignedTo, dnd, dateAdded and customFields.<id>; up to two sort fields; page + pageLimit (up to 500, first 10,000 rows) or searchAfter.
GET /contacts/search/duplicateSupportedemail or number. { "contact": null } when none.
GET /contacts/Supportedquery, limit (up to 100), startAfter, startAfterId.
/contacts/{contactId}/notes, /notes/{id}SupportedNotes on the lead’s timeline in Cook.
/contacts/{contactId}/tasks, /tasks/{taskId}, /tasks/{taskId}/completedPartialTasks on Cook’s task board, linked to the lead. Only tasks made through this API list on a contact (see Gotchas). dueDate keeps the day and reads back at T00:00:00.000Z.
GET /contacts/{contactId}/appointmentsSupportedThe lead’s meetings in Cook.
POST, DELETE /contacts/{contactId}/tagsSupported
POST /contacts/bulk/tags/update/{type}Supportedadd or remove (with removeAllTags), up to 500 contacts.
POST, DELETE /contacts/{contactId}/workflow/{workflowId}PartialSee Workflows.
/contacts/{contactId}/campaigns/…, /followers, /contacts/bulk/business, /contacts/business/{businessId}Not supported404. Cook has no campaigns, followers or businesses; a lead has one owner (assignedTo).
GET /contacts/lookupNot supported404. It is not in GoHighLevel’s published API v2; use GET /contacts/search/duplicate.

Tags, custom fields and custom values

GoHighLevel callOn CookNotes
GET, POST /locations/{locationId}/tags, GET, PUT, DELETE /locations/{locationId}/tags/{tagId}SupportedSee Tags.
GET, POST /locations/{locationId}/customFields, GET, PUT, DELETE /customFields/{id}PartialContact fields only. See Custom fields and values.
POST /locations/{locationId}/customFields/uploadNot supported404.
GET, POST /locations/{locationId}/customValues, GET, PUT, DELETE /customValues/{id}SupportedSee Custom fields and values.

Opportunities and pipelines

GoHighLevel callOn CookNotes
GET /opportunities/pipelinesSupportedYour Cook pipelines and their stages.
POST /opportunities/Partial201. One opportunity per contact: see Opportunities.
GET, PUT, DELETE /opportunities/{id}SupportedPUT moves the lead to another stage or pipeline. DELETE takes the lead out of the pipeline; the contact stays.
PUT /opportunities/{id}/statusSupportedopen, won, lost, abandoned.
POST /opportunities/upsertSupportedUpdates the contact’s opportunity, moving it if pipelineId differs.
GET /opportunities/searchSupportedlocation_id, pipeline_id, pipeline_stage_id, contact_id, assigned_to, status, q, date, endDate, order, limit, page, startAfter + startAfterId.
POST /opportunities/searchPartialquery, limit, page, searchAfter, and filters with eq on pipeline_id, pipeline_stage_id, contact_id, assigned_to, status, id. aggregations is empty.
Pipeline create, edit, deleteNot supportedGoHighLevel’s API v2 has no such calls. Make pipelines in Cook under Pipelines, or with Cook’s own API (/v1 crm.pipelineCreate).
/opportunities/{id}/followers, /opportunities/lost-reasonNot supported404.

Conversations and messages

GoHighLevel callOn CookNotes
POST /conversations/Supported201. A contact that already has a conversation answers 400 “Conversation already exists” with its conversationId.
GET, PUT, DELETE /conversations/{conversationId}PartialPUT: starred and unreadCount (0 marks it read in Cook too); feedback answers 422. DELETE removes the conversation and its messages.
GET /conversations/searchPartialcontactId, id, query, assignedTo, sort, startAfterDate, limit (up to 100), lastMessageType, lastMessageDirection, lastMessageAction, status, startDate, endDate. followers, mentions, the score profile filters and any sortBy but last_message_date answer 422.
GET /conversations/{conversationId}/messagesSupported{ "messages": { "lastMessageId", "nextPage", "messages": [...] } }, as GoHighLevel’s live API answers. limit up to 100, lastMessageId, type.
GET /conversations/messages/{id}SupportedAlso reads a send that waits for approval.
POST /conversations/messagesPartialSMS and Email only. See Sending messages.
POST /conversations/messages/inboundSupportedLogs a message the contact sent, without sending anything. See Logging messages.
POST /conversations/messages/outboundSupportedLogs a call (type: "Call", as GoHighLevel allows).
PUT /conversations/messages/{messageId}/statusPartialFor messages Cook’s own carrier didn’t send. A text Cook sent answers 400 “This message’s delivery status is reported by the carrier that sent it”.
Attachments, uploads, recordings, transcriptions, export, email scheduling, custom subtypes, unsubscribes, live-chat typing, review repliesNot supported404. Cook keeps call recordings and transcripts on the meeting or call in Cook.

Calendars and appointments

GoHighLevel callOn CookNotes
GET /calendars/SupportedYour booking pages. showDrafted=false lists live ones only. groupId answers an empty list.
POST /calendars/PartialMakes a booking page and publishes it (unless isActive: false) in the location’s timezone. Kept: name, description, eventColor, meetingLocation, slotDuration (+ slotDurationUnit), formSubmitRedirectURL, teamMembers. Other fields are not kept.
GET /calendars/{calendarId}Supported
PUT /calendars/{calendarId}PartialName, description, length, location, redirect or thank-you message and team members, through the workspace’s approval rule (see Approvals). isActive publishes or unpublishes at once.
DELETE /calendars/{calendarId}SupportedA routine’s test page answers 400.
GET /calendars/{calendarId}/free-slotsSupportedstartDate, endDate (ms), timezone, userId (a user who isn’t a host gets no slots). Uses the page’s hours, connected calendar, buffers and notice.
GET /calendars/eventsSupportedlocationId, startTime, endTime (required), calendarId, userId. groupId answers an empty list.
GET /calendars/events/appointments/{eventId}Supported
POST /calendars/events/appointmentsPartialBooks through the booking page itself (it must be live, the contact needs an email, the time must be free). ignoreFreeSlotValidation is not honoured. A status other than confirmed, or an endTime that changes the page’s length, answers 422.
PUT /calendars/events/appointments/{eventId}PartialstartTime reschedules; appointmentStatus cancelled cancels; showed and noshow set the meeting’s outcome. Title, address, assigned user, description, location, rrule, another calendar or another length answer 422.
DELETE /calendars/events/{eventId}Partial201 { "succeeded": true }. Cancels the meeting; Cook keeps it, and it reads back as cancelled.
Groups, block slots, appointment notes, resources, notifications, schedulesNot supported404.

Workflows and users

GoHighLevel callOn CookNotes
GET /workflows/SupportedYour Cook routines. status is published when the routine is on, else draft.
GET /users/, GET /users/{userId}SupportedThe people on your team. Owners and admins read as admin, members as user.
GET /users/searchSupportedcompanyId (yours), query, role, ids, sort, sortDirection, skip, limit.
POST /users/, PUT, DELETE /users/{userId}PartialThrough an approval card. See Approvals.
POST /users/search/filter-by-emailNot supported404. Use GET /users/search with query.

Everything else

Forms and surveys, campaigns, custom objects and associations, invoices, payments, products, funnels, blogs, social posting, SaaS and agency calls and the rest of GoHighLevel’s API answer 404. Marketplace OAuth is served; see Marketplace apps (OAuth).

Opportunities

GoHighLevel keeps opportunities apart from contacts, and a contact can have several. In Cook an opportunity is the lead’s place in a pipeline: its pipeline, stage, status and value. So:

  • One opportunity per contact. A second POST /opportunities/ for the same contact answers 400 and names the one it has:

    {
      "statusCode": 400,
      "message": "This contact already has an opportunity in this pipeline.",
      "error": "OPPORTUNITY_NO_DUPLICATE",
      "meta": { "existingId": "…" }
    }

    In another pipeline the message is “This contact already has an opportunity in another pipeline. Cook keeps one opportunity per contact: move it with PUT /opportunities/{id} (pipelineId, pipelineStageId).”

  • POST /opportunities/upsert updates the contact’s opportunity, and moves it when you give another pipelineId.

  • Moving is PUT /opportunities/{id} with pipelineId and pipelineStageId. A new pipeline without a stage starts at its first stage.

  • Deleting an opportunity takes the lead out of the pipeline. The contact stays.

  • Imported contacts. A GoHighLevel contact you imported with several opportunities is several leads in Cook that share its contact id. Each is its own opportunity, and contact edits reach all of them.

  • The opportunity’s name is kept on the lead. Its custom fields are not: Cook keeps custom fields on contacts (see below).

  • createdAt is when the opportunity was made, not when its contact was: a deal opened today on a contact from last month is today’s deal. GET /opportunities/search sorts by it (order) and filters on it (date, endDate). An imported opportunity keeps GoHighLevel’s own createdAt.

  • From an AI agent, the Cook MCP’s lead_set_opportunity (or /v1 crm.setOpportunity) makes and moves the same opportunities.

Approvals

Some calls change things a person in your workspace may want to see first. Cook decides them with your workspace’s approval rules, the same ones that decide what Cook’s AI may do, under Settings → Approvals. When a card is filed, nothing happens until a person approves it in Cook.

  • Sending a message (POST /conversations/messages) follows the rules for texting and emailing a lead. When a card is filed the answer is still 200, with status: "pending":

    {
      "conversationId": "…",
      "messageId": "…",
      "emailMessageId": "…",
      "messageIds": ["…"],
      "msg": "Message is waiting for approval: nothing is sent until a person approves it in Cook.",
      "status": "pending"
    }

    emailMessageId is there for an Email. Read the message again with GET /conversations/messages/{id}: it reads pending while the card waits, then the carrier’s status once it is sent, or failed when the card is turned down, expires or fails.

  • Changing a calendar (PUT /calendars/{calendarId}) follows the rule for booking pages. When a card is filed: 403 “Cook changes a calendar through an approval card: card … waits for a person in Cook, and nothing changes until it is approved.” When the rule lets the change through, Cook waits for it (up to about 10 seconds) and answers 200 with the calendar.

  • Rescheduling or cancelling an appointment (PUT and DELETE on events) follows your AI setter’s settings. When they ask for a card: 403 “Nothing was moved yet: approval card … waits for a person in Cook. …” (or “Nothing was cancelled yet: …”), ending with the note the card carries.

  • The team always waits for a person.

    • POST /users/ answers 403 “Cook does not change the team directly: it filed approval card … (”…”). … is invited when a person approves it in Cook.”
    • PUT /users/{userId} with a new role answers 403 with the same start, ending ”…‘s role changes when a person approves it in Cook.” Other fields (name, email, password, phone, permissions) answer 422: people change their own profile in Cook. An owner’s role answers 422.
    • DELETE /users/{userId} answers 200 { "succeded": false, "message": "Cook does not change the team directly: it filed approval card … (\"…\"). … is removed when a person approves it in Cook." }.
    • These three need an account key; a workspace key is refused.

Everything else (contacts, notes, tasks, tags, fields, values, opportunities, conversations, logging messages, booking, enrolling in a workflow) applies at once.

Tags

GoHighLevel’s location tags are the tags on your Cook leads.

  • GET /locations/{locationId}/tags lists every tag in use on a lead, plus the ones made with POST and not used yet.
  • PUT /locations/{locationId}/tags/{tagId} renames the tag on every lead that has it.
  • DELETE /locations/{locationId}/tags/{tagId} takes it off every lead.

Custom fields and values

  • Custom fields are Cook’s lead fields. dataType TEXT, LARGE_TEXT, PHONE and TEXTBOX_LIST are text; NUMERICAL and MONETORY are numbers; DATE is a date; SINGLE_OPTIONS, RADIO, MULTIPLE_OPTIONS and CHECKBOX are a pick list (give options). Any other dataType answers 422. A field reads back with the nearest of these types and fieldKey contact.<key>.
  • Set a field on a contact with customFields: [{ "id": "…", "field_value": "…" }] (or key instead of id, value instead of field_value). A list value is stored as one comma-separated text.
  • Opportunity fields. model: "opportunity" answers 400, and ?model=opportunity lists none: Cook keeps custom fields on contacts.
  • Restricted fields you made private in Cook are left out of the list and can’t be read, searched or set through this API (setting one answers 422).
  • Custom values are your workspace’s custom values, the same ones Cook’s templates use. fieldKey is {{ custom_values.<key> }}, and that tag works in Cook’s emails, texts and routines. Renaming a value keeps its key.

Sending messages

POST /conversations/messages sends through Cook, from the contact’s client’s own number or mailbox, under your approval rules.

  • type: "SMS" sends a text (message). type: "Email" sends an email (subject, html or message).
  • Other types answer 400 “<type> messages cannot be sent through this location: Cook sends SMS and Email through the API”. Log messages on other channels with /inbound instead.
  • When Cook refuses, the answer is 400 with Cook’s reason, for example “Not sent: this lead’s client has no live texting number of its own, and a client’s leads are never texted from another number.”
  • attachments, scheduledTimestamp, templateId, emailCc, emailBcc, an emailTo other than the contact’s email and a toNumber other than the contact’s phone answer 422. Cook picks the sender and the thread, so fromNumber, emailFrom, threadId and conversationProviderId are not used.

Logging messages

POST /conversations/messages/inbound and /outbound write a message on the conversation without sending anything.

  • Conversations and channels. One GoHighLevel conversation holds a contact’s texts and calls together, and Cook files them in the conversation you name. Email (and Facebook and Instagram) have their own conversations; a message of that kind goes on the contact’s conversation for its channel, and the answer gives that conversation’s id.
  • What an inbound message does in Cook: it updates the conversation and the lead’s timeline; “STOP” turns on do-not-disturb and “START” turns it off (no reply text is sent); an ordinary reply counts as the lead replying, so routines that start or stop on a reply do. Cook’s AI setter does not answer it, because the message didn’t come through Cook.
  • altId makes a repeat of the same message a no-op.

Workflows

Cook routines take the place of GoHighLevel workflows.

  • GET /workflows/ lists your routines with UUID ids.
  • POST /contacts/{contactId}/workflow/{workflowId} starts a run of that routine for the lead, with the checks Cook’s own Run button makes. A routine that is off answers 400 “This workflow is a draft. Cook enrolls contacts only in a published (switched on) workflow”. A routine for one client refuses a lead of another. eventStartTime is accepted, but Cook’s appointment waits use the lead’s next meeting in Cook.
  • DELETE /contacts/{contactId}/workflow/{workflowId} stops the lead’s runs of that routine. They read as canceled in the routine’s enrollment list.

Calendars

A GoHighLevel calendar is a Cook booking page; an appointment is a Cook meeting.

  • Ids. Calendars, appointments, contacts and users have 20-character ids; pipeline stages and workflows have UUIDs. An object gets its id the first time Cook returns it, and keeps it. Objects you imported from GoHighLevel keep their GoHighLevel ids.
  • Times are ISO 8601 with the offset of the timezone you ask for, else the page’s.
  • Booking goes through the booking page, so the lead, the meeting, the calendar event and reminders are what a booking on the page makes.

Webhooks

None yet. GoHighLevel’s webhooks belong to a Marketplace app (an app subscribes, rather than a URL you register through the API), and Cook does not send Marketplace webhook events (INSTALL, ContactCreate, …) to apps. To act when something changes in Cook (a new lead, a booking, a reply), use a Cook routine that starts on that event.

Marketplace apps (OAuth)

An app built on GoHighLevel’s Marketplace OAuth (a setter bot, a dialer, a reporting tool) moves to Cook by changing its OAuth and API addresses. Cook serves GoHighLevel’s OAuth 2.0 authorization-code flow: the choose-location page, POST /oauth/token, refresh, POST /oauth/locationToken and GET /oauth/installedLocations, with GoHighLevel’s request and response bodies.

1. Register the app in Cook

GoHighLevel registers apps in its Marketplace; Cook registers them through the API. Any Cook workspace’s owner or admin can do it, with their own full-access account key (Settings → API keys, made for a person, no narrowed areas):

POST https://api.trycook.ai/compat/ghl/oauth/clients
Authorization: Bearer ck_live_…
Version: 2021-07-28
Content-Type: application/json

{
  "name": "Example Setter",
  "redirectUris": ["https://setter.example.com/oauth/callback/highlevel"],
  "scopes": ["contacts.readonly", "contacts.write", "conversations.write",
             "conversations/message.write", "calendars/events.write",
             "locations.readonly", "oauth.readonly", "oauth.write"]
}

The answer (201) carries clientId (GoHighLevel’s shape, <appId>-<8 characters>), appId, clientSecret and installUrl, the choose-location link to send people to. The client secret is shown once: Cook keeps only its hash. Lost it? Delete the app and register it again.

  • redirectUris: up to 10, https (or http on localhost / 127.0.0.1), matched exactly.
  • scopes: the GoHighLevel scopes the app may ask for. An unknown scope is refused (422).
  • GET /oauth/clients lists your apps (no secrets) and how many workspaces installed each. DELETE /oauth/clients/{clientId} deletes an app and ends every token it holds, in every workspace, at once.

2. Swap the OAuth addresses

GoHighLevelCook
https://marketplace.gohighlevel.com/oauth/chooselocationhttps://app.trycook.ai/compat/ghl/oauth/chooselocation (the installUrl host)
https://services.leadconnectorhq.com/oauth/tokenhttps://api.trycook.ai/compat/ghl/oauth/token
https://services.leadconnectorhq.com/oauth/locationTokenhttps://api.trycook.ai/compat/ghl/oauth/locationToken
https://services.leadconnectorhq.com/oauth/installedLocationshttps://api.trycook.ai/compat/ghl/oauth/installedLocations
https://services.leadconnectorhq.com (the API)https://api.trycook.ai/compat/ghl

The app sends people to the choose-location page with response_type=code, client_id, redirect_uri and scope (and state, returned untouched). The person signs in to Cook, picks the workspace (GoHighLevel’s location), sees what the app would reach in plain words and installs or denies. Cook then sends the browser to the redirect URI with ?code=… (or ?error=access_denied). Only an owner or admin of a paid workspace can install an app.

The token calls are GoHighLevel’s, form-encoded (JSON is accepted too):

POST https://api.trycook.ai/compat/ghl/oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=…&client_secret=…&grant_type=authorization_code&code=…&user_type=Location&redirect_uri=…

The answer is GoHighLevel’s token body: access_token, token_type (Bearer), expires_in (86399), refresh_token, scope, refreshTokenId, userType, locationId (or approvedLocations for user_type=Company), companyId, userId and traceId. Call the API with Authorization: Bearer <access_token> and the Version header, as on GoHighLevel.

Tokens

  • Codes are good once, for ten minutes, for the redirect URI they were issued for. A code presented again more than a minute after it was used ends what it was exchanged for.
  • Access tokens last a day (expires_in: 86399). Each one is a Cook key acting as the person who installed the app, in the workspace they picked, narrowed to what the approved scopes reach (below). Cook re-checks that person’s role and the workspace’s plan on every call.
  • Refresh tokens last a year and rotate: grant_type=refresh_token&refresh_token=… returns a new pair, and the refresh token used is refused from then on (400 “Invalid grant: refresh token is invalid”). The access token it replaced keeps working for five minutes. A used refresh token presented again after a minute ends the install, so store the new one before using it.
  • Company tokens (user_type=Company) carry oauth.readonly / oauth.write routes: GET /oauth/installedLocations lists your one location; POST /oauth/locationToken (companyId, locationId) returns a location token.

Scopes

Cook’s permissions are coarser than GoHighLevel’s scopes, so a scope reaches the whole Cook area its calls use:

GoHighLevel scopeReaches in Cook
contacts.readonly / .writeCRM and board, read / write (contacts, notes, tasks)
conversations.*, conversations/message.*, opportunities.*, locations.readonly, locations/tags.*, locations/customFields.*, locations/customValues.*, calendars/events.*CRM, read / write
calendars.readonly / .writeBooking pages, read / write
users.readonly / .writeTeam, read / write
workflows.readonlyRoutines, read
oauth.readonly / oauth.writeThe OAuth routes above; no Cook data

Any other documented GoHighLevel scope is accepted and reaches nothing in Cook. A token never gets full access, never registers apps and never decides approval cards. Adding a contact to a workflow needs a Cook key with routines write; no scope grants it.

Removing an app

  • The workspace that installed it: revoke one of its keys (named “OAuth · ”) in Settings → API keys. The install ends: every token it holds stops and its refresh token is refused.
  • The app itself: DELETE /marketplace/app/{appId}/installations with its token.
  • The app’s maker: DELETE /oauth/clients/{clientId} ends it everywhere.

Not served: Marketplace webhooks, grant_type=client_credentials, bulk installs across several locations (a Cook company has one location), and the Marketplace listing itself.

Gotchas

  • GET /contacts/lookup isn’t served. Use GET /contacts/search/duplicate.
  • Pipelines are read-only here. Make and change them in Cook.
  • Calendar groups don’t exist in Cook: groupId filters answer empty lists.
  • DELETE /calendars/events/{eventId} cancels. The meeting stays in Cook and reads back as cancelled.
  • Tasks on a contact. GET /contacts/{contactId}/tasks lists the tasks made through this API. Tasks people add on Cook’s board are not linked to a lead, so they don’t show there.
  • One contact per email or phone. Creating a duplicate answers 400; use POST /contacts/upsert.
  • Limits. Cook’s own limit applies: 300 calls a minute per key, then 429 with Retry-After and GoHighLevel’s X-RateLimit-* headers.
  • Plan. A workspace without a paid plan gets 402.

What can’t move

  • Workflow graphs. GoHighLevel has no API to read them. Rebuild them as Cook routines (Cook’s AI can do this from a description).
  • Funnels, websites and snapshots. Not readable through GoHighLevel’s API; rebuild them in Cook.
  • A2P 10DLC registration. It belongs to the carrier. Register your business again for texting in Cook.
  • LC Phone numbers. They stay with GoHighLevel until you port them.
  • The white-label mobile app stays with GoHighLevel.