Install
openclaw skills install highlevel-apiGoHighLevel (Private Integration Token) API integration with managed auth. CRM, sales pipelines, calendars, conversations, payments, and marketing automation...
openclaw skills install highlevel-apiAccess the GoHighLevel API with managed Private Integration Token (PIT) authentication. Manage contacts, sales pipelines, calendars, conversations, invoices, products, businesses, and marketing automation.
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.
# 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"
https://api.maton.ai/highlevel-pit/{resource}
Maton proxies requests to services.leadconnectorhq.com and automatically injects your PIT token.
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"
Manage your GoHighLevel PIT connections at https://api.maton.ai.
curl -s -X GET "https://api.maton.ai/connections?app=highlevel-pit&status=ACTIVE" \
-H "Authorization: Bearer $MATON_API_KEY"
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.
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"
}
}
curl -s -X DELETE "https://api.maton.ai/connections/{connection_id}" \
-H "Authorization: Bearer $MATON_API_KEY"
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.
These endpoints require an Agency token.
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 /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": { ... }
}
}
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"
}
PUT /highlevel-pit/locations/{locationId}
Content-Type: application/json
{
"name": "Updated Name",
"city": "Los Angeles"
}
DELETE /highlevel-pit/locations/{locationId}
GET /highlevel-pit/snapshots/?companyId={companyId}
These endpoints require a Sub-Account token. Most endpoints require a locationId query parameter.
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 /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": []
}
}
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"]
}
PUT /highlevel-pit/contacts/{contactId}
Content-Type: application/json
{
"firstName": "Jane",
"companyName": "New Company"
}
DELETE /highlevel-pit/contacts/{contactId}
GET /highlevel-pit/contacts/?locationId={locationId}&query=john@example.com
POST /highlevel-pit/contacts/{contactId}/tags
Content-Type: application/json
{
"tags": ["vip", "priority"]
}
Response:
{
"tags": ["customer", "vip", "priority"],
"tagsAdded": ["vip", "priority"]
}
DELETE /highlevel-pit/contacts/{contactId}/tags
Content-Type: application/json
{
"tags": ["vip"]
}
Response:
{
"tags": ["customer", "priority"],
"tagsRemoved": ["vip"]
}
GET /highlevel-pit/contacts/{contactId}/notes
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"
}
}
PUT /highlevel-pit/contacts/{contactId}/notes/{noteId}
Content-Type: application/json
{
"body": "Updated note content"
}
DELETE /highlevel-pit/contacts/{contactId}/notes/{noteId}
GET /highlevel-pit/contacts/{contactId}/tasks
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"
}
}
PUT /highlevel-pit/contacts/{contactId}/tasks/{taskId}
Content-Type: application/json
{
"title": "Updated task",
"completed": true
}
DELETE /highlevel-pit/contacts/{contactId}/tasks/{taskId}
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 /highlevel-pit/opportunities/{opportunityId}
POST /highlevel-pit/opportunities/
Content-Type: application/json
{
"pipelineId": "{pipelineId}",
"locationId": "{locationId}",
"name": "Enterprise Deal",
"pipelineStageId": "{stageId}",
"status": "open",
"contactId": "{contactId}",
"monetaryValue": 50000
}
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 /highlevel-pit/opportunities/{opportunityId}
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
}
]
}
]
}
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 /highlevel-pit/calendars/{calendarId}
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
}
]
}
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 /highlevel-pit/calendars/{calendarId}
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 /highlevel-pit/calendars/{calendarId}/free-slots?startDate={epochMs}&endDate={epochMs}&timezone={timezone}
GET /highlevel-pit/calendars/groups?locationId={locationId}
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 /highlevel-pit/conversations/{conversationId}
GET /highlevel-pit/conversations/{conversationId}/messages
POST /highlevel-pit/conversations/
Content-Type: application/json
{
"locationId": "{locationId}",
"contactId": "{contactId}"
}
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"]
}
}
]
}
GET /highlevel-pit/locations/{locationId}/tags
Response:
{
"tags": [
{
"id": "tag123",
"name": "VIP Customer",
"locationId": "loc123"
}
]
}
POST /highlevel-pit/locations/{locationId}/tags
Content-Type: application/json
{
"name": "New Tag"
}
GET /highlevel-pit/locations/{locationId}/tags/{tagId}
PUT /highlevel-pit/locations/{locationId}/tags/{tagId}
Content-Type: application/json
{
"name": "Updated Tag"
}
DELETE /highlevel-pit/locations/{locationId}/tags/{tagId}
GET /highlevel-pit/locations/{locationId}/customFields
Response:
{
"customFields": [
{
"id": "cf123",
"name": "Customer ID",
"fieldKey": "contact.customer_id",
"dataType": "TEXT",
"model": "contact",
"position": 50
}
]
}
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 /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
PUT /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
Content-Type: application/json
{
"name": "Updated Field Name"
}
DELETE /highlevel-pit/locations/{locationId}/customFields/{customFieldId}
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"
}
]
}
POST /highlevel-pit/locations/{locationId}/customValues
Content-Type: application/json
{
"name": "Company Tagline",
"value": "We build great things"
}
GET /highlevel-pit/locations/{locationId}/customValues/{customValueId}
PUT /highlevel-pit/locations/{locationId}/customValues/{customValueId}
Content-Type: application/json
{
"name": "Updated Name",
"value": "Updated value"
}
DELETE /highlevel-pit/locations/{locationId}/customValues/{customValueId}
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 /highlevel-pit/businesses/{businessId}
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"
}
PUT /highlevel-pit/businesses/{businessId}
Content-Type: application/json
{
"name": "Updated Business",
"city": "Los Angeles"
}
DELETE /highlevel-pit/businesses/{businessId}
GET /highlevel-pit/products/?locationId={locationId}
GET /highlevel-pit/products/{productId}?locationId={locationId}
Note: locationId query parameter is required even for single product retrieval.
POST /highlevel-pit/products/
Content-Type: application/json
{
"locationId": "{locationId}",
"name": "Digital Course",
"description": "Online training program",
"productType": "DIGITAL"
}
DELETE /highlevel-pit/products/{productId}?locationId={locationId}
IMPORTANT: Both offset and altId/altType are required.
GET /highlevel-pit/invoices/?altId={locationId}&altType=location&limit=20&offset=0
GET /highlevel-pit/invoices/{invoiceId}?altId={locationId}&altType=location
GET /highlevel-pit/payments/orders?altId={locationId}&altType=location&limit=20
GET /highlevel-pit/payments/transactions?altId={locationId}&altType=location&limit=20
GET /highlevel-pit/payments/subscriptions?altId={locationId}&altType=location&limit=20
GET /highlevel-pit/links/?locationId={locationId}
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}}"
}
}
PUT /highlevel-pit/links/{linkId}
Content-Type: application/json
{
"name": "Updated Link",
"redirectTo": "https://updated.com"
}
DELETE /highlevel-pit/links/{linkId}
GET /highlevel-pit/workflows/?locationId={locationId}
GET /highlevel-pit/campaigns/?locationId={locationId}
GET /highlevel-pit/forms/?locationId={locationId}
GET /highlevel-pit/surveys/?locationId={locationId}
GET /highlevel-pit/funnels/funnel/list?locationId={locationId}
Response:
{
"funnels": [...],
"count": 5
}
GET /highlevel-pit/social-media-posting/{locationId}/accounts
Response:
{
"success": true,
"results": {
"accounts": [...],
"groups": [...]
}
}
GET /highlevel-pit/social-media-posting/{locationId}/categories
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
GoHighLevel uses different pagination styles depending on the endpoint:
GET /highlevel-pit/contacts/?locationId={locationId}&limit=20&startAfterId={lastContactId}
Response includes meta.startAfterId for the next page cursor.
GET /highlevel-pit/opportunities/search?location_id={locationId}&limit=20&page=2
GET /highlevel-pit/invoices/?altId={locationId}&altType=location&limit=20&offset=20
GET /highlevel-pit/locations/search?companyId={companyId}&limit=20&skip=20
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();
import os
import requests
response = 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()
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| 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 |
"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.