# GHL Voice AI Agents — API reference (verified 2026-08-07)

Base: `https://services.leadconnectorhq.com`. All calls need headers:
`Authorization: Bearer <LOCATION token>`, `Version: 2021-07-28`, `Accept: application/json`.

**Auth rule:** location-level token ONLY. The V3 agency token (168 scopes incl. `voice-ai-agents.readonly/write`, `voice-ai-agent-goals.*`, `voice-ai-dashboard.readonly`) still 401s `authClass type is not allowed` on every endpoint below. Docs index: `https://marketplace.gohighlevel.com/docs/ghl/voice-ai/*` (JS-rendered; endpoint paths confirmed via sitemap + live probing).

## Endpoints (all verified live against RRR 2026-08-07)

| Call | Notes |
|---|---|
| `GET /voice-ai/agents?locationId=<loc>` | List agents. Returns `{"agents": [...]}`. |
| `POST /voice-ai/agents` (body has locationId) | Create. Returns full agent with new `id`. |
| `GET /voice-ai/agents/{id}?locationId=<loc>` | Fetch one agent, full shape. |
| `PATCH /voice-ai/agents/{id}?locationId=<loc>` | Update. **locationId in QUERY, not body** (body → 403 "LocationId is required"). |
| `DELETE /voice-ai/agents/{id}?locationId=<loc>` | Delete agent. |
| `POST /voice-ai/actions?locationId=<loc>` | Create action on an agent. Body: `{locationId, agentId, actionType, name, actionParameters}`. 201 on success. |
| `DELETE /voice-ai/actions/{actionId}?locationId=<loc>&agentId=<agentId>` | Delete action. **agentId REQUIRED in query** — without it, 403 "AgentId is required". 204 on success. |
| `GET /voice-ai/dashboard`, `/voice-ai/agent-goals` | 404 as probed — likely different paths; not needed for the create/configure flow. |

Other documented-but-unprobed paths (from docs sitemap): `voice-ai/get-call-log`, `get-call-logs`, `patch-agent`, `get-agent`, `get-agents`, `create-agent`, `delete-agent`, `create-action`, `update-action`, `get-action`, `delete-action`.

## Agent object shape (from live GET on Connie-RRR)

```json
{
  "id": "...", "locationId": "...",
  "agentName": "...", "businessName": "...",
  "welcomeMessage": "...",
  "agentPrompt": "...(multi-KB string OK — Connie's is 6136 chars)...",
  "voiceId": "g6xIsTj2HwM6VR4iXFCw",
  "language": "en-US",
  "responsiveness": 0.7,
  "maxCallDuration": 600,
  "sendUserIdleReminders": true,
  "reminderAfterIdleTimeSeconds": 8,
  "inboundNumber": "+172****6868",
  "inboundNumbers": ["+172****6868"],
  "callEndWorkflowIds": ["uuid", "..."],
  "sendPostCallNotificationTo": {"admins": true, "allUsers": false, "contactAssignedUser": false, "specificUsers": [], "customEmails": []},
  "agentWorkingHours": [],
  "timezone": "America/Denver",
  "isAgentAsBackupDisabled": true,
  "translation": {"enabled": false, "language": null},
  "toolCallStrictMode": true,
  "actions": [...]
}
```

Create payload that worked (2026-08-07): `locationId, agentName, businessName, welcomeMessage, agentPrompt, voiceId, language, responsiveness, maxCallDuration, sendUserIdleReminders, reminderAfterIdleTimeSeconds, timezone, sendPostCallNotificationTo`. Phone attach via follow-up PATCH with `{"inboundNumber": "+1XXXXXXXXXX", "inboundNumbers": ["+1XXXXXXXXXX"]}` (full unmasked digits in the request; API masks in responses).

## Action types (seen live)

`CALL_TRANSFER` (transferToType: number, transferToValue, triggerPrompt/Message, hearWhisperMessage), `APPOINTMENT_BOOKING` (calendarId, daysOfOfferingDates, slotsPerDay, hoursBetweenSlots), `DATA_EXTRACTION` (contactFieldId — REQUIRES a custom field to exist; customFields scope needed), `SMS` (triggerPrompt, triggerMessage, messageBody with `{{contact.first_name}}` etc.).

**Action `name` charset:** letters, numbers, spaces, underscores, hyphens ONLY — em-dashes 400 ("Action name can only contain letters, numbers, spaces, underscores, and hyphens").

**SMS action recipient = THE CALLER.** No destination field exists. To text Rob (or any third party) after a call: build a GHL workflow triggered on call-end and reference it in `callEndWorkflowIds` (workflow construction is UI-only as of 2026-08-07; `workflows.readonly` was not on the RRR location token's scope list — add `workflows.readonly workflows.write` to the scope= list on a future reinstall if API workflow reads are ever needed).

**Workflow route specifics (verified 2026-08-07):** the workflow AI builder scaffolds this acceptably from a pasted prompt — it picked the **"Transcript generated"** trigger (not "call ended"), which is fine: the transcript is guaranteed to exist when the SMS fires, landing ~30-60s after hangup. The trigger has TWO prerequisites, both UI-only toggles — no agent-schema field exposes them: (1) account-level **Phone System → Voice → Call Recording & Transcription → Call Transcription = ON**; (2) per-number **Phone numbers → the number → Basic Details → Call Recording = ON**. Missing either shows an orange "Call Transcription is currently disabled" warning on the trigger block. **Filter dropdown contents (observed live 2026-08-07):** the "Transcript generated" trigger's filter picker lists CONTACT CUSTOM FIELDS only (every qualification-survey field in the sub-account) — the ONLY call-native entries are "Duration (seconds)" and the "Has AI Agent?" boolean. There is NO agent-name, agent-id, or phone-number filter field, and saving the trigger first does not unlock more (Rob tested). Consequence: a "Transcript generated" trigger CANNOT be scoped to one agent via its own filter UI — use `callEndWorkflowIds` on the agent instead (PATCH the agent with the workflow's UUID; Connie-RRR carries 3) and leave the workflow trigger unfiltered. That wiring fires the workflow for that agent's calls only, no filter needed.

**Workflow AI builder overclaims (2026-08-07):** it reported "filtered so it only triggers for the Voice AI agent named …" — no such filter field exists; nothing was actually added. After ANY AI-builder run, verify the filter row and the SMS action's recipient (hardcoded number vs `{{contact.phone}}`) in the raw config panels before publishing.

## Voice IDs

No voice-list endpoint verified. Practical method: GET an existing production agent and copy its `voiceId`. Connie-RRR's voice: `g6xIsTj2HwM6VR4iXFCw` (en-US).

## RRR agent inventory (2026-08-07)

| id | agentName | notes |
|---|---|---|
| 6a0f6a221c9ebcf26dbe92ee | Connie - RRR | production receptionist; on +172****6868; transfer→Google Voice; booking+extraction+SMS actions |
| 6a30532c15f478f4f40727fd | Connie - Pipeline Layer | PL prompt, lives in RRR subaccount (matches 877-LAN-EXIT stranding) |
| 6a30330d9dbd1578a80b0245 | Connie - Pipeline Layer copy | duplicate |
| 6a1e1a8a4f726442bcbecf2e | 18 Local Biz Live Demo | demo |
| 6a610d8416889858eac754e0 | 18 Local Biz Live Demo - Copy | demo |
| 69d44ed04419210f7ae24f23 | AI Sells Itself | third-party template ("Easy AI Agency"), not ours |
| **6a75d6414ee3e02404bb0127** | **Missed Call Only Agent (Bail Bonds)** | **built 2026-08-07; on 877-LAN-EXIT (PN698e609037d81adb69ea1d6ce9054e4a); Connie's voice; prompt spec at /root/.hermes/work/ghl/voice-agent-bail-bonds.json; call-end SMS-to-Rob workflow = pending UI build by Rob** |

## Bail-bonds pilot specifics (2026-08-07)

- Rob's script, verbatim in agentPrompt: missed-call backup for a bail bonds business; captures defendant name → facility → bond status → relationship → caller name/callback → urgency notes; 15-min callback window baked in; hard rules against quoting fees / legal advice. Spec JSON: `/root/.hermes/work/ghl/voice-agent-bail-bonds.json`.
- Purpose: DEMO prop — Rob stands in front of a bail bondsman and shows the post-call text landing on HIS cell (303-828-7753, direct cell — Google Voice +130****7628 exists but not preferred for this).
- Custom contact fields (BB — Defendant Name, Facility, Bond Status, Caller Relationship, Callback Number, Urgency Notes, Call Summary) were DESIGNED but creation 401d — the 2026-08-07 location install's scope list didn't include `locations/customFields.write`. Deferred; DATA_EXTRACTION actions need those field IDs. Reinstall with wider scope= to complete the full-data-capture build.
- RRR location token scope list used (worked for the agent build): `voice-ai-agents.readonly voice-ai-agents.write voice-ai-agent-goals.readonly voice-ai-agent-goals.write voice-ai-dashboard.readonly contacts.readonly contacts.write conversations.readonly conversations.write conversations/message.readonly conversations/message.write calendars.readonly calendars.write calendars/events.readonly calendars/events.write locations.readonly phonenumbers.read users.readonly` — add `locations/customFields.readonly locations/customFields.write workflows.readonly workflows.write` for the full build.
