GoHighLevel
by @byungkyu
GoHighLevel (Private Integration Token) API integration with managed auth. CRM, sales pipelines, calendars, conversations, payments, and marketing automation...
clawhub install highlevel-apiπ About This Skill
name: highlevel-pit description: | GoHighLevel (Private Integration Token) API integration with managed auth. CRM, sales pipelines, calendars, conversations, payments, and marketing automation. Use this skill when users want to manage contacts, opportunities, calendars, conversations, invoices, products, or workflows in GoHighLevel using a Private Integration Token (PIT). GoHighLevel has two token types: Agency tokens and Sub-Account tokens. Agency tokens manage locations (sub-accounts), while Sub-Account tokens access CRM, calendars, pipelines, and other location-scoped data. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key. metadata: author: maton version: "1.0" clawdbot: emoji: π§ requires: env: - MATON_API_KEY
GoHighLevel (Private Integration Token)
Access the GoHighLevel API with managed Private Integration Token (PIT) authentication. Manage contacts, sales pipelines, calendars, conversations, invoices, products, businesses, and marketing automation.
Important: Two Token Types
GoHighLevel uses two types of Private Integration Tokens with different scopes:
| Token Type | Purpose | Key Capabilities | |---|---|---| | Agency | Manage the agency and its sub-accounts (locations) | Search/create/update/delete locations, manage snapshots | | Sub-Account | Operate within a specific location | Contacts, calendars, pipelines, conversations, payments, custom fields, tags, workflows, campaigns |
You will typically need both connections β an agency token for location management and a sub-account token for CRM operations. Use the Maton-Connection header to specify which token to use for each request.
Quick Start
# List contacts (Sub-Account token)
curl -s -X GET "https://api.maton.ai/highlevel-pit/contacts/?locationId={locationId}&limit=10" \
-H "Authorization: Bearer $MATON_API_KEY"
Base URL
https://api.maton.ai/highlevel-pit/{resource}
Maton proxies requests to services.leadconnectorhq.com and automatically injects your PIT token.
Authentication
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
1. Sign in or create an account at maton.ai 2. Go to maton.ai/settings 3. Copy your API key
Connection Management
Manage your GoHighLevel PIT connections at https://api.maton.ai.
List Connections
curl -s -X GET "https://api.maton.ai/connections?app=highlevel-pit&status=ACTIVE" \
-H "Authorization: Bearer $MATON_API_KEY"
Create Connection
curl -s -X POST "https://api.maton.ai/connections" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MATON_API_KEY" \
-d '{"app": "highlevel-pit"}'
Open the returned url in a browser to enter your Private Integration Token.
Get Connection
curl -s -X GET "https://api.maton.ai/connections/{connection_id}" \
-H "Authorization: Bearer $MATON_API_KEY"
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"app": "highlevel-pit",
"method": "API_KEY"
}
}
Delete Connection
curl -s -X DELETE "https://api.maton.ai/connections/{connection_id}" \
-H "Authorization: Bearer $MATON_API_KEY"
Specifying Connection
Since you will typically have two connections (agency + sub-account), always specify which one to use:
curl -s -X GET "https://api.maton.ai/highlevel-pit/contacts/?locationId={locationId}" \
-H "Authorization: Bearer $MATON_API_KEY" \
-H "Maton-Connection: {connection_id}"
If you have multiple connections, always include this header to ensure requests go to the intended account.
Security & Permissions
API Reference β Agency Token
These endpoints require an Agency token.
Locations (Sub-Accounts)
#### Search Locations
GET /highlevel-pit/locations/search?companyId={companyId}
Query parameters:
companyId (required) - The agency's company IDlimit - Results per pageskip - Number to skip (offset)order - Sort orderemail - Filter by emailResponse:
{
"locations": [
{
"id": "abc123",
"companyId": "xyz789",
"name": "My Sub-Account",
"address": "123 Main St",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postalCode": "94105",
"timezone": "America/Los_Angeles",
"email": "admin@example.com",
"phone": "+15551234567"
}
]
}
#### Get Location
GET /highlevel-pit/locations/{locationId}
Response:
{
"location": {
"id": "abc123",
"name": "My Sub-Account",
"address": "123 Main St",
"city": "San Francisco",
"state": "CA",
"settings": {
"allowDuplicateContact": false,
"allowDuplicateOpportunity": false
},
"social": { ... },
"permissions": { ... }
}
}
#### Create Location
POST /highlevel-pit/locations/
Content-Type: application/json{
"companyId": "{companyId}",
"name": "New Sub-Account",
"address": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US",
"timezone": "America/Los_Angeles",
"email": "admin@example.com",
"phone": "+15551234567"
}
#### Update Location
PUT /highlevel-pit/locations/{locationId}
Content-Type: application/json{
"name": "Updated Name",
"city": "Los Angeles"
}
#### Delete Location
DELETE /highlevel-pit/locations/{locationId}
Snapshots
#### List Snapshots
GET /highlevel-pit/snapshots/?companyId={companyId}
API Reference β Sub-Account Token
These endpoints require a Sub-Account token. Most endpoints require a locationId query parameter.
Contacts
#### List Contacts
GET /highlevel-pit/contacts/?locationId={locationId}
Query parameters:
locationId (required)limit - Results per page (default 20)query - Search by name, email, or phonestartAfter - Cursor for pagination (contact ID)startAfterId - Cursor for paginationResponse:
{
"contacts": [
{
"id": "abc123",
"locationId": "loc123",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+15551234567",
"companyName": "Acme Inc",
"tags": ["customer", "vip"],
"type": "lead",
"dnd": false,
"dateAdded": "2026-04-28T07:34:32.829Z",
"customFields": []
}
],
"meta": {
"total": 150,
"startAfter": "abc123",
"startAfterId": "abc123"
}
}
#### Get Contact
GET /highlevel-pit/contacts/{contactId}
Response:
{
"contact": {
"id": "abc123",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+15551234567",
"tags": ["customer"],
"type": "lead",
"companyName": "Acme Inc",
"customFields": [],
"additionalEmails": [],
"additionalPhones": []
}
}
#### Create Contact
POST /highlevel-pit/contacts/
Content-Type: application/json{
"locationId": "{locationId}",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+15551234567",
"companyName": "Acme Inc",
"tags": ["customer"]
}
#### Update Contact
PUT /highlevel-pit/contacts/{contactId}
Content-Type: application/json{
"firstName": "Jane",
"companyName": "New Company"
}
#### Delete Contact
DELETE /highlevel-pit/contacts/{contactId}
#### Search Contacts by Email/Phone
GET /highlevel-pit/contacts/?locationId={locationId}&query=john@example.com
Contact Tags
#### Add Tags
POST /highlevel-pit/contacts/{contactId}/tags
Content-Type: application/json{
"tags": ["vip", "priority"]
}
Response:
{
"tags": ["customer", "vip", "priority"],
"tagsAdded": ["vip", "priority"]
}
#### Remove Tags
DELETE /highlevel-pit/contacts/{contactId}/tags
Content-Type: application/json{
"tags": ["vip"]
}
Response:
{
"tags": ["customer", "priority"],
"tagsRemoved": ["vip"]
}
Contact Notes
#### List Notes
GET /highlevel-pit/contacts/{contactId}/notes
#### Create Note
POST /highlevel-pit/contacts/{contactId}/notes
Content-Type: application/json{
"body": "Spoke with client about renewal"
}
Response:
{
"note": {
"id": "note123",
"body": "Spoke with client about renewal",
"dateAdded": "2026-04-30T10:22:47.934Z",
"contactId": "abc123"
}
}
#### Update Note
PUT /highlevel-pit/contacts/{contactId}/notes/{noteId}
Content-Type: application/json{
"body": "Updated note content"
}
#### Delete Note
DELETE /highlevel-pit/contacts/{contactId}/notes/{noteId}
Contact Tasks
#### List Tasks
GET /highlevel-pit/contacts/{contactId}/tasks
#### Create Task
IMPORTANT: The completed field is required.
POST /highlevel-pit/contacts/{contactId}/tasks
Content-Type: application/json{
"title": "Follow up call",
"body": "Discuss contract renewal",
"dueDate": "2026-06-01T10:00:00Z",
"completed": false
}
Response:
{
"task": {
"id": "task123",
"title": "Follow up call",
"body": "Discuss contract renewal",
"dueDate": "2026-06-01T10:00:00.000Z",
"completed": false,
"contactId": "abc123"
}
}
#### Update Task
PUT /highlevel-pit/contacts/{contactId}/tasks/{taskId}
Content-Type: application/json{
"title": "Updated task",
"completed": true
}
#### Delete Task
DELETE /highlevel-pit/contacts/{contactId}/tasks/{taskId}
Opportunities (Deals)
#### Search Opportunities
GET /highlevel-pit/opportunities/search?location_id={locationId}
Query parameters:
location_id (required)pipeline_id - Filter by pipelinepipeline_stage_id - Filter by stagestatus - open, won, lost, abandoned, allcontact_id - Filter by contactq - Search querylimit - Results per pagepage - Page numberResponse:
{
"opportunities": [
{
"id": "opp123",
"name": "Enterprise Deal",
"monetaryValue": 50000,
"pipelineId": "pipe123",
"pipelineStageId": "stage123",
"status": "open",
"contactId": "abc123",
"contact": {
"id": "abc123",
"name": "John Doe",
"email": "john@example.com"
}
}
],
"meta": {
"total": 25,
"currentPage": 1,
"nextPage": 2,
"prevPage": null
}
}
#### Get Opportunity
GET /highlevel-pit/opportunities/{opportunityId}
#### Create Opportunity
POST /highlevel-pit/opportunities/
Content-Type: application/json{
"pipelineId": "{pipelineId}",
"locationId": "{locationId}",
"name": "Enterprise Deal",
"pipelineStageId": "{stageId}",
"status": "open",
"contactId": "{contactId}",
"monetaryValue": 50000
}
#### Update Opportunity
IMPORTANT: pipelineId is required even when not changing it.
PUT /highlevel-pit/opportunities/{opportunityId}
Content-Type: application/json{
"pipelineId": "{pipelineId}",
"name": "Updated Deal",
"monetaryValue": 75000,
"status": "won"
}
#### Delete Opportunity
DELETE /highlevel-pit/opportunities/{opportunityId}
Pipelines
#### List Pipelines
GET /highlevel-pit/opportunities/pipelines?locationId={locationId}
Response:
{
"pipelines": [
{
"id": "pipe123",
"name": "Sales Pipeline",
"stages": [
{
"id": "stage-uuid",
"name": "New Lead",
"position": 0,
"stageWinProbability": 14.29
},
{
"id": "stage-uuid-2",
"name": "Contacted",
"position": 1,
"stageWinProbability": 28.57
}
]
}
]
}
Calendars
#### List Calendars
GET /highlevel-pit/calendars/?locationId={locationId}
Response:
{
"calendars": [
{
"id": "cal123",
"locationId": "loc123",
"name": "Personal Calendar",
"calendarType": "personal",
"eventType": "RoundRobin_OptimizeForAvailability",
"slotDuration": 30,
"teamMembers": [
{
"userId": "user123",
"selected": true,
"priority": 0.5
}
]
}
]
}
#### Get Calendar
GET /highlevel-pit/calendars/{calendarId}
#### Create Calendar
POST /highlevel-pit/calendars/
Content-Type: application/json{
"locationId": "{locationId}",
"name": "Team Calendar",
"calendarType": "personal",
"eventType": "RoundRobin_OptimizeForAvailability",
"teamMembers": [
{
"userId": "{userId}",
"priority": 0.5,
"selected": true
}
]
}
#### Update Calendar
Note: Do NOT include locationId in the update body.
PUT /highlevel-pit/calendars/{calendarId}
Content-Type: application/json{
"name": "Updated Calendar",
"calendarType": "personal",
"eventType": "RoundRobin_OptimizeForAvailability",
"teamMembers": [
{
"userId": "{userId}",
"priority": 0.5,
"selected": true
}
]
}
#### Delete Calendar
DELETE /highlevel-pit/calendars/{calendarId}
#### Get Calendar Events
Requires at least one of calendarId, userId, or groupId.
GET /highlevel-pit/calendars/events?locationId={locationId}&calendarId={calendarId}&startTime={epochMs}&endTime={epochMs}
Query parameters:
locationId (required)calendarId, userId, or groupId (at least one required)startTime - Start of range (epoch milliseconds)endTime - End of range (epoch milliseconds)#### Get Free Slots
GET /highlevel-pit/calendars/{calendarId}/free-slots?startDate={epochMs}&endDate={epochMs}&timezone={timezone}
#### Calendar Groups
GET /highlevel-pit/calendars/groups?locationId={locationId}
Conversations
#### Search Conversations
GET /highlevel-pit/conversations/search?locationId={locationId}
Query parameters:
locationId (required)limit - Results per pagecontactId - Filter by contactassignedTo - Filter by assigned userstatus - Filter by statusResponse:
{
"conversations": [
{
"id": "conv123",
"locationId": "loc123",
"contactId": "abc123",
"fullName": "John Doe",
"type": "TYPE_PHONE",
"lastMessageDate": 1777361673411,
"lastMessageType": "TYPE_NO_SHOW",
"unreadCount": 0
}
],
"total": 5
}
#### Get Conversation
GET /highlevel-pit/conversations/{conversationId}
#### Get Conversation Messages
GET /highlevel-pit/conversations/{conversationId}/messages
#### Create Conversation
POST /highlevel-pit/conversations/
Content-Type: application/json{
"locationId": "{locationId}",
"contactId": "{contactId}"
}
Users
#### List Users
GET /highlevel-pit/users/?locationId={locationId}
Response:
{
"users": [
{
"id": "user123",
"name": "Admin User",
"firstName": "Admin",
"lastName": "User",
"email": "admin@example.com",
"phone": "+15551234567",
"roles": {
"type": "admin",
"role": "admin",
"locationIds": ["loc123"]
}
}
]
}
Location Tags
#### List Tags
GET /highlevel-pit/locations/{locationId}/tags
Response:
{
"tags": [
{
"id": "tag123",
"name": "VIP Customer",
"locationId": "loc123"
}
]
}
#### Create Tag
POST /highlevel-pit/locations/{locationId}/tags
Content-Type: application/json{
"name": "New Tag"
}
#### Get Tag
GET /highlevel-pit/locations/{locationId}/tags/{tagId}
#### Update Tag
PUT /highlevel-pit/locations/{locationId}/tags/{tagId}
Content-Type: application/json{
"name": "Updated Tag"
}
#### Delete Tag
DELETE /highlevel-pit/locations/{locationId}/tags/{tagId}
Custom Fields
#### List Custom Fields
GET /highlevel-pit/locations/{locationId}/customFields
Response:
{
"customFields": [
{
"id": "cf123",
"name": "Customer ID",
"fieldKey": "contact.customer_id",
"dataType": "TEXT",
"model": "contact",
"position": 50
}
]
}
#### Create Custom Field
POST /highlevel-pit/locations/{locationId}/customFields
Content-Type: application/json{
"name": "Customer ID",
"dataType": "TEXT",
"model": "contact"
}
Valid dataType values: TEXT, LARGE_TEXT, NUMERICAL, PHONE, MONETORY, CHECKBOX, SINGLE_OPTIONS, MULTIPLE_OPTIONS, FLOAT, DATE, TEXTBOX_LIST, FILE_UPLOAD, SIGNATURE
Valid model values: contact, opportunity
#### Get Custom Field
GET /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
#### Update Custom Field
PUT /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
Content-Type: application/json{
"name": "Updated Field Name"
}
#### Delete Custom Field
DELETE /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
Custom Values
#### List Custom Values
GET /highlevel-pit/locations/{locationId}/customValues
Response:
{
"customValues": [
{
"id": "cv123",
"name": "Company Tagline",
"fieldKey": "{{ custom_values.company_tagline }}",
"value": "We build great things",
"locationId": "loc123"
}
]
}
#### Create Custom Value
POST /highlevel-pit/locations/{locationId}/customValues
Content-Type: application/json{
"name": "Company Tagline",
"value": "We build great things"
}
#### Get Custom Value
GET /highlevel-pit/locations/{locationId}/customValues/{customValueId}
#### Update Custom Value
PUT /highlevel-pit/locations/{locationId}/customValues/{customValueId}
Content-Type: application/json{
"name": "Updated Name",
"value": "Updated value"
}
#### Delete Custom Value
DELETE /highlevel-pit/locations/{locationId}/customValues/{customValueId}
Businesses
#### List Businesses
GET /highlevel-pit/businesses/?locationId={locationId}
Response:
{
"success": true,
"businesses": [
{
"id": "biz123",
"name": "Acme Inc",
"locationId": "loc123",
"city": "Los Angeles",
"website": "www.acme.com",
"phone": "+15551234567",
"email": "info@acme.com"
}
]
}
#### Get Business
GET /highlevel-pit/businesses/{businessId}
#### Create Business
POST /highlevel-pit/businesses/
Content-Type: application/json{
"locationId": "{locationId}",
"name": "New Business",
"city": "San Francisco",
"phone": "+15551234567",
"email": "info@newbiz.com",
"website": "www.newbiz.com"
}
#### Update Business
PUT /highlevel-pit/businesses/{businessId}
Content-Type: application/json{
"name": "Updated Business",
"city": "Los Angeles"
}
#### Delete Business
DELETE /highlevel-pit/businesses/{businessId}
Products
#### List Products
GET /highlevel-pit/products/?locationId={locationId}
#### Get Product
GET /highlevel-pit/products/{productId}?locationId={locationId}
Note: locationId query parameter is required even for single product retrieval.
#### Create Product
POST /highlevel-pit/products/
Content-Type: application/json{
"locationId": "{locationId}",
"name": "Digital Course",
"description": "Online training program",
"productType": "DIGITAL"
}
#### Delete Product
DELETE /highlevel-pit/products/{productId}?locationId={locationId}
Invoices
#### List Invoices
IMPORTANT: Both offset and altId/altType are required.
GET /highlevel-pit/invoices/?altId={locationId}&altType=location&limit=20&offset=0
#### Get Invoice
GET /highlevel-pit/invoices/{invoiceId}?altId={locationId}&altType=location
Payments
#### List Orders
GET /highlevel-pit/payments/orders?altId={locationId}&altType=location&limit=20
#### List Transactions
GET /highlevel-pit/payments/transactions?altId={locationId}&altType=location&limit=20
#### List Subscriptions
GET /highlevel-pit/payments/subscriptions?altId={locationId}&altType=location&limit=20
Trigger Links
#### List Links
GET /highlevel-pit/links/?locationId={locationId}
#### Create Link
POST /highlevel-pit/links/
Content-Type: application/json{
"locationId": "{locationId}",
"name": "Survey Link",
"redirectTo": "https://example.com/survey"
}
Response:
{
"link": {
"id": "link123",
"name": "Survey Link",
"redirectTo": "https://example.com/survey",
"fieldKey": "{{trigger_link.link123}}"
}
}
#### Update Link
PUT /highlevel-pit/links/{linkId}
Content-Type: application/json{
"name": "Updated Link",
"redirectTo": "https://updated.com"
}
#### Delete Link
DELETE /highlevel-pit/links/{linkId}
Workflows
#### List Workflows
GET /highlevel-pit/workflows/?locationId={locationId}
Campaigns
#### List Campaigns
GET /highlevel-pit/campaigns/?locationId={locationId}
Forms
#### List Forms
GET /highlevel-pit/forms/?locationId={locationId}
Surveys
#### List Surveys
GET /highlevel-pit/surveys/?locationId={locationId}
Funnels
#### List Funnels
GET /highlevel-pit/funnels/funnel/list?locationId={locationId}
Response:
{
"funnels": [...],
"count": 5
}
Social Media
#### List Accounts
GET /highlevel-pit/social-media-posting/{locationId}/accounts
Response:
{
"success": true,
"results": {
"accounts": [...],
"groups": [...]
}
}
#### List Categories
GET /highlevel-pit/social-media-posting/{locationId}/categories
Media Files
#### List Files
IMPORTANT: The type parameter is required.
GET /highlevel-pit/medias/files?altId={locationId}&altType=location&type=file&limit=20
Valid type values: file, image, video, audio
Pagination
GoHighLevel uses different pagination styles depending on the endpoint:
Cursor-Based (Contacts)
GET /highlevel-pit/contacts/?locationId={locationId}&limit=20&startAfterId={lastContactId}
Response includes meta.startAfterId for the next page cursor.
Offset-Based (Opportunities, Invoices)
GET /highlevel-pit/opportunities/search?location_id={locationId}&limit=20&page=2
GET /highlevel-pit/invoices/?altId={locationId}&altType=location&limit=20&offset=20
Skip-Based (Locations)
GET /highlevel-pit/locations/search?companyId={companyId}&limit=20&skip=20
Code Examples
JavaScript
const response = await fetch(
'https://api.maton.ai/highlevel-pit/contacts/?locationId={locationId}&limit=20',
{
headers: {
'Authorization': Bearer ${process.env.MATON_API_KEY},
'Maton-Connection': '{sub-account-connection-id}'
}
}
);
const data = await response.json();
Python
import os
import requestsresponse = requests.get(
'https://api.maton.ai/highlevel-pit/contacts/',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Maton-Connection': '{sub-account-connection-id}'
},
params={'locationId': '{locationId}', 'limit': 20}
)
data = response.json()
Notes
Maton-Connection header to pick the right one.locationId query parameteraltId and altType=location instead of locationIdlocationId in the URL path, not as a query paramstartTime/endTime as epoch milliseconds, not ISO-8601calendarId, userId, or groupIdlocationId in the body (returns 422)locationId as a query parametercompleted field (boolean)pipelineId even when not changing itoffset parameter (use 0 for first page)type parametercurl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environmentsError Handling
| Status | Meaning | |--------|---------| | 400 | Bad request or invalid parameters | | 401 | Invalid/missing Maton API key, token not authorized for scope, or user type mismatch | | 403 | Token does not have access to this location | | 404 | Resource not found | | 422 | Validation error β check required fields | | 429 | Rate limited | | 4xx/5xx | Passthrough error from GoHighLevel API |
Common Errors
"The token does not have access to this location" (403): You're using an Agency token for an endpoint that requires a Sub-Account token, or vice versa.
"The token is not authorized for this scope" (401): The token doesn't have the required scope. Agency tokens can't access CRM data; Sub-Account tokens can't manage locations.
"Token's user type mismatch" (401): You're using the wrong token type for this endpoint (e.g., Agency token on a Sub-Account-only endpoint).
"LocationId can't be undefined" (422): You forgot the locationId query parameter.
Resources
π‘ Examples
# List contacts (Sub-Account token)
curl -s -X GET "https://api.maton.ai/highlevel-pit/contacts/?locationId={locationId}&limit=10" \
-H "Authorization: Bearer $MATON_API_KEY"
π Tips & Best Practices
Maton-Connection header to pick the right one.locationId query parameteraltId and altType=location instead of locationIdlocationId in the URL path, not as a query paramstartTime/endTime as epoch milliseconds, not ISO-8601calendarId, userId, or groupIdlocationId in the body (returns 422)locationId as a query parametercompleted field (boolean)pipelineId even when not changing itoffset parameter (use 0 for first page)type parametercurl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments