.claude/skills/ghl-automation/SKILL.md
Automate GoHighLevel (GHL) operations -- manage contacts, pipelines, opportunities, tags, custom fields, calendars, appointments, conversations, workflows, and voice AI -- using the GHL REST API v2. This skill should be used when performing CRM operations, lead management, appointment scheduling, or voice AI configuration in GoHighLevel.
npx skillsauth add wallacedobbs428/thecalltaker ghl-automationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage your GoHighLevel CRM -- create and update contacts, move opportunities through pipelines, schedule appointments, send messages via conversations, configure workflows, and manage voice AI agents -- all through the GHL REST API v2.
API Base URL: https://services.leadconnectorhq.com
API Docs: highlevel.stoplight.io
All requests require a Bearer token in the Authorization header and a Version header specifying the API version for each endpoint.
curl -X GET "https://services.leadconnectorhq.com/contacts/{contactId}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Version: 2021-07-28" \
-H "Accept: application/json"
| Header | Value | Notes |
|--------|-------|-------|
| Authorization | Bearer {api_key} | Required on every request |
| Version | Endpoint-specific | See per-endpoint version below |
| Content-Type | application/json | Required for POST/PUT/PATCH/DELETE |
| Accept | application/json | Recommended |
| User-Agent | Any non-empty string | MUST be set to avoid Cloudflare 403 |
Most endpoints require a locationId parameter (query or body). This identifies the GHL sub-account.
Create, update, search, and manage CRM contacts.
API Version: 2021-07-28
Endpoint: GET /contacts/
Example:
curl -X GET "https://services.leadconnectorhq.com/contacts/?locationId={locationId}&[email protected]&limit=20" \
-H "Authorization: Bearer {api_key}" \
-H "Version: 2021-07-28"
Key parameters:
locationId (required) -- Sub-account IDquery -- Search by name, email, phone, or companylimit -- Results per page (default 20, max 100)page -- Page number for pagination (NOT offset)Endpoint: GET /contacts/{contactId}
Endpoint: POST /contacts/
Key parameters:
locationId (required)firstName, lastName -- Contact nameemail -- Email addressphone -- Phone in +1XXXXXXXXXX formatcompanyName -- Business nametags -- Array of tag stringssource -- Lead sourcecustomFields -- Array of {id, field_value} objectsExample body:
{
"locationId": "your_location_id",
"firstName": "John",
"lastName": "Smith",
"email": "[email protected]",
"phone": "+16155551234",
"companyName": "Acme HVAC",
"tags": ["pilot-candidate", "hvac"],
"source": "cold-email"
}
Endpoint: PUT /contacts/{contactId}
Same body fields as create. Omitted fields are not changed.
Endpoint: DELETE /contacts/{contactId}
Endpoint: POST /contacts/{contactId}/tags
{
"tags": ["hot-lead", "demo-caller"]
}
Endpoint: DELETE /contacts/{contactId}/tags
{
"tags": ["cold-lead"]
}
Endpoint: POST /contacts/{contactId}/notes
{
"body": "Called 3/14, interested in pilot program."
}
Manage sales pipelines and their stages.
API Version: 2021-07-28
Endpoint: GET /opportunities/pipelines?locationId={locationId}
Returns all pipelines with their stages.
Endpoint: GET /opportunities/pipelines/{pipelineId}?locationId={locationId}
Track deals through pipeline stages.
API Version: 2021-07-28
Endpoint: GET /opportunities/search
Key parameters:
locationId (required)pipelineId -- Filter by pipelinestageId -- Filter by stagestatus -- open, won, lost, abandoned, allq -- Search querylimit -- Results per page (max 100)page -- Page numberEndpoint: POST /opportunities/
Key parameters:
pipelineId (required)locationId (required)name (required) -- Deal namestageId (required) -- Pipeline stage to place incontactId (required) -- Associated contactstatus -- open (default), won, lost, abandonedmonetaryValue -- Deal value in dollarsExample body:
{
"pipelineId": "pipeline_id",
"locationId": "location_id",
"name": "Acme HVAC - Pro Plan",
"stageId": "stage_id",
"contactId": "contact_id",
"status": "open",
"monetaryValue": 297
}
Endpoint: PUT /opportunities/{opportunityId}
Update stage, status, monetary value, or other fields.
Endpoint: DELETE /opportunities/{opportunityId}
Endpoint: PUT /opportunities/{opportunityId}/status
{
"status": "won"
}
Manage tags at the location level.
API Version: 2021-07-28
Endpoint: GET /locations/{locationId}/tags
Returns all tags for the sub-account.
Endpoint: POST /locations/{locationId}/tags
{
"name": "pilot-active"
}
Endpoint: GET /locations/{locationId}/tags/{tagId}
Endpoint: PUT /locations/{locationId}/tags/{tagId}
{
"name": "pilot-converted"
}
Endpoint: DELETE /locations/{locationId}/tags/{tagId}
Manage custom data fields on contacts.
API Version: 2021-07-28
Endpoint: GET /locations/{locationId}/customFields
Returns all custom field definitions with their IDs, names, field types, and accepted values.
Endpoint: POST /locations/{locationId}/customFields
{
"name": "Lead Score",
"dataType": "NUMBER",
"placeholder": "0-100"
}
Supported data types: TEXT, LARGE_TEXT, NUMBER, MONETARY, PHONE, EMAIL, DATE, CHECKBOX, SINGLE_OPTIONS, MULTIPLE_OPTIONS, FILE_UPLOAD, SIGNATURE
Endpoint: PUT /locations/{locationId}/customFields/{customFieldId}
Endpoint: DELETE /locations/{locationId}/customFields/{customFieldId}
Use the contact update endpoint with customFields array:
{
"customFields": [
{
"id": "custom_field_id",
"field_value": "85"
}
]
}
Manage booking calendars and availability.
API Version: 2021-04-15
Endpoint: GET /calendars/?locationId={locationId}
Returns all calendars with their IDs, names, and settings.
Endpoint: GET /calendars/{calendarId}
Endpoint: POST /calendars/
Key parameters:
locationId (required)name (required)descriptionslug -- URL-friendly identifierwidgetType -- default, classic, neocalendarType -- RoundRobin, event, class_booking, collective, service_bookingslotDuration -- Duration in minutesavailabilities -- Array of day/time availability windowsEndpoint: PUT /calendars/{calendarId}
Endpoint: DELETE /calendars/{calendarId}
Endpoint: GET /calendars/{calendarId}/free-slots?startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}
Returns available time slots for a date range.
Schedule and manage calendar appointments.
API Version: 2021-04-15
Endpoint: GET /calendars/events?locationId={locationId}&calendarId={calendarId}&startTime={epoch_ms}&endTime={epoch_ms}
Key parameters:
locationId (required)calendarId -- Filter by specific calendarstartTime -- Start of range (epoch milliseconds)endTime -- End of range (epoch milliseconds)Endpoint: GET /calendars/events/appointments/{eventId}
Endpoint: POST /calendars/events/appointments
Key parameters:
calendarId (required)locationId (required)contactId (required)startTime (required) -- ISO 8601 datetimeendTime (required) -- ISO 8601 datetimetitle -- Appointment titleappointmentStatus -- confirmed, new, cancelled, showed, noshow, invalidnotes -- Additional notesExample body:
{
"calendarId": "calendar_id",
"locationId": "location_id",
"contactId": "contact_id",
"startTime": "2026-03-15T10:00:00-05:00",
"endTime": "2026-03-15T10:30:00-05:00",
"title": "Demo Call - Acme HVAC",
"appointmentStatus": "confirmed"
}
Endpoint: PUT /calendars/events/appointments/{eventId}
Endpoint: DELETE /calendars/events/appointments/{eventId}
Send and receive messages (SMS, email) through the conversations API.
API Version: 2021-04-15
Endpoint: GET /conversations/search?locationId={locationId}
Key parameters:
locationId (required)contactId -- Filter by contactlimit -- Max results (default 20)Endpoint: GET /conversations/{conversationId}
Endpoint: POST /conversations/
{
"locationId": "location_id",
"contactId": "contact_id"
}
Endpoint: POST /conversations/messages
{
"type": "SMS",
"contactId": "contact_id",
"message": "Hey! Just following up on the demo you heard."
}
Key fields:
type -- SMS, Email, WhatsApp, GMB, IG, FB, Custom, Live_ChatcontactId (required)message -- SMS/chat body textEndpoint: POST /conversations/messages
{
"type": "Email",
"contactId": "contact_id",
"subject": "Your AI Receptionist Demo Results",
"html": "<h1>Here are your results</h1><p>Your demo call lasted 2 minutes...</p>",
"emailFrom": "The Call Taker <[email protected]>"
}
Key fields for email:
type -- EmailcontactId (required)subject -- Email subject linehtml -- Email body (NOT message -- use html for email body content)emailFrom -- Sender name and addressEndpoint: GET /conversations/{conversationId}/messages
Manage automation workflows.
API Version: 2021-07-28
Endpoint: GET /workflows/?locationId={locationId}
Returns all workflows with their IDs, names, and statuses.
Endpoint: GET /workflows/{workflowId}?locationId={locationId}
Configure and manage Voice AI agents for phone handling.
API Version: 2021-04-15
Endpoint: GET /voice-ai/agents?locationId={locationId}
Returns all voice agents with their IDs, names, and configurations.
Endpoint: GET /voice-ai/agents/{agentId}?locationId={locationId}
Endpoint: PATCH /voice-ai/agents/{agentId}?locationId={locationId}
Key parameters (in body):
name -- Agent display nameprompt -- System prompt / instructionswelcomeMessage -- Greeting when call connectsvoiceId -- Text-to-speech voice IDresponsiveness -- Float 0-1 (1.0 = fastest response)Example:
{
"name": "After-Hours AI",
"prompt": "You are an AI receptionist for {business}...",
"welcomeMessage": "Thanks for calling! How can I help?",
"responsiveness": 1.0
}
Important: The locationId goes in the query string, NOT the request body.
POST /contacts/ with name, email, phone, tagsPOST /opportunities/ with contactId, pipelineId, stageIdPOST /conversations/messages with type SMSPOST /calendars/events/appointments with contactId, calendarIdGET /opportunities/search?q={name}PUT /opportunities/{id} with new stageIdPOST /conversations/messages to associated contactGET /contacts/?query={phone}POST /contacts/ with demo-caller tagsPOST /contacts/{id}/tags with ["demo-caller", "hot-demo"]POST /contacts/{id}/notes with call detailsPOST /opportunities/ in demo pipelineGET /voice-ai/agents?locationId={id}PATCH /voice-ai/agents/{agentId}?locationId={id} with industry-specific promptvoiceIdresponsiveness: 1.0html, NOT message: When sending email via conversations, the body content goes in the html field. The message field is for SMS/chat only. Using message for email results in blank emails.message: For SMS, use the message field. Do NOT use html.+1XXXXXXXXXX: Always include country code. Omitting it causes delivery failures.page, NOT offset: GHL uses page-based pagination. Do not use offset or skip parameters.User-Agent header receive Cloudflare 403 errors.2021-07-28. Conversations, calendars, and voice AI use 2021-04-15. Always set the correct Version header.locationId is a query param: When PATCHing voice AI agents, locationId goes in the URL query string, not the request body. Putting it in the body causes 400 errors.isinstance(msg, dict) before accessing keys.2021-04-15 for conversations endpoints, not 2021-07-28.["tag1", "tag2"], not objects.id not name: When setting custom field values on contacts, reference the field by its id, not its name. List custom fields first to get IDs.startDate and endDate in YYYY-MM-DD format.2026-03-15T10:00:00-05:00), not epoch timestamps.startTime/endTime as epoch milliseconds, not ISO 8601./voice-ai/agents/{id} (plural "agents"), not /voice-ai/agent/{id}.| Action | Method | Endpoint | Version | Required Params |
|--------|--------|----------|---------|-----------------|
| Search contacts | GET | /contacts/ | 2021-07-28 | locationId |
| Get contact | GET | /contacts/{contactId} | 2021-07-28 | contactId |
| Create contact | POST | /contacts/ | 2021-07-28 | locationId, firstName or email or phone |
| Update contact | PUT | /contacts/{contactId} | 2021-07-28 | contactId |
| Delete contact | DELETE | /contacts/{contactId} | 2021-07-28 | contactId |
| Add tags | POST | /contacts/{contactId}/tags | 2021-07-28 | contactId, tags[] |
| Remove tags | DELETE | /contacts/{contactId}/tags | 2021-07-28 | contactId, tags[] |
| Add note | POST | /contacts/{contactId}/notes | 2021-07-28 | contactId, body |
| List pipelines | GET | /opportunities/pipelines | 2021-07-28 | locationId |
| Search opportunities | GET | /opportunities/search | 2021-07-28 | locationId |
| Create opportunity | POST | /opportunities/ | 2021-07-28 | pipelineId, locationId, name, stageId, contactId |
| Update opportunity | PUT | /opportunities/{id} | 2021-07-28 | opportunityId |
| Delete opportunity | DELETE | /opportunities/{id} | 2021-07-28 | opportunityId |
| List tags | GET | /locations/{locationId}/tags | 2021-07-28 | locationId |
| Create tag | POST | /locations/{locationId}/tags | 2021-07-28 | locationId, name |
| List custom fields | GET | /locations/{locationId}/customFields | 2021-07-28 | locationId |
| Create custom field | POST | /locations/{locationId}/customFields | 2021-07-28 | locationId, name, dataType |
| List calendars | GET | /calendars/ | 2021-04-15 | locationId |
| Get free slots | GET | /calendars/{id}/free-slots | 2021-04-15 | calendarId, startDate, endDate |
| List appointments | GET | /calendars/events | 2021-04-15 | locationId |
| Create appointment | POST | /calendars/events/appointments | 2021-04-15 | calendarId, locationId, contactId, startTime, endTime |
| Update appointment | PUT | /calendars/events/appointments/{id} | 2021-04-15 | eventId |
| Delete appointment | DELETE | /calendars/events/appointments/{id} | 2021-04-15 | eventId |
| Search conversations | GET | /conversations/search | 2021-04-15 | locationId |
| Send SMS | POST | /conversations/messages | 2021-04-15 | contactId, type: SMS, message |
| Send email | POST | /conversations/messages | 2021-04-15 | contactId, type: Email, html, subject |
| Get messages | GET | /conversations/{id}/messages | 2021-04-15 | conversationId |
| List workflows | GET | /workflows/ | 2021-07-28 | locationId |
| List voice agents | GET | /voice-ai/agents | 2021-04-15 | locationId |
| Update voice agent | PATCH | /voice-ai/agents/{id} | 2021-04-15 | agentId, locationId (query) |
| Endpoint Group | Version Header |
|----------------|---------------|
| Contacts | 2021-07-28 |
| Opportunities | 2021-07-28 |
| Pipelines | 2021-07-28 |
| Tags | 2021-07-28 |
| Custom Fields | 2021-07-28 |
| Workflows | 2021-07-28 |
| Calendars | 2021-04-15 |
| Appointments | 2021-04-15 |
| Conversations | 2021-04-15 |
| Voice AI | 2021-04-15 |
GHL uses page-based pagination. Always check if more pages exist:
page = 1
all_contacts = []
while True:
resp = requests.get(
f"{BASE_URL}/contacts/",
headers=headers,
params={"locationId": location_id, "page": page, "limit": 100}
)
data = resp.json()
contacts = data.get("contacts", [])
all_contacts.extend(contacts)
if len(contacts) < 100:
break
page += 1
resp = requests.post(url, headers=headers, json=body)
if resp.status_code == 429:
# Rate limited — back off 30s, 60s, 120s
time.sleep(30)
resp = requests.post(url, headers=headers, json=body)
elif resp.status_code >= 500:
# Server error — retry with backoff 5s, 15s, 30s
time.sleep(5)
resp = requests.post(url, headers=headers, json=body)
elif resp.status_code == 400:
# Bad request — check field names, Version header, and locationId placement
print(resp.json())
elif resp.status_code == 403:
# Likely missing User-Agent header or invalid API key
pass
documentation
Agentic memory system for writers - track characters, relationships, scenes, and themes
tools
Automate repetitive development tasks and workflows. Use when creating build scripts, automating deployments, or setting up development workflows. Handles npm scripts, Makefile, GitHub Actions workflows, and task automation.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices". Fetches latest Vercel guidelines and checks files against all rules.
development
Implement web accessibility (a11y) standards following WCAG 2.1 guidelines. Use when building accessible UIs, fixing accessibility issues, or ensuring compliance with disability standards. Handles ARIA attributes, keyboard navigation, screen readers, semantic HTML, and accessibility testing.