799 lines
24 KiB
Markdown
799 lines
24 KiB
Markdown
# GoHighLevel Users API — Documentación Completa por Endpoint
|
|
|
|
> **Fuentes:** [GitHub OpenAPI spec](https://github.com/GoHighLevel/highlevel-api-docs/blob/main/apps/users.json) · [Marketplace Docs](https://marketplace.gohighlevel.com/docs/ghl/users/) · Perplexity Search
|
|
> **Base URL:** `https://services.leadconnectorhq.com`
|
|
> **Auth:** `Authorization: Bearer {TOKEN}` | `Version: 2021-07-28` | `User-Agent: Mozilla/5.0`
|
|
|
|
---
|
|
|
|
## 1. POST /users/ — Create User
|
|
|
|
Crea un nuevo usuario en la agencia y lo asigna a una o más locations.
|
|
|
|
### cURL
|
|
|
|
```bash
|
|
curl -X POST https://services.leadconnectorhq.com/users/ \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0" \
|
|
-d '{
|
|
"companyId": "ve9EPM428h8vShlRW1KT",
|
|
"firstName": "John",
|
|
"lastName": "Deo",
|
|
"email": "john@deo.com",
|
|
"password": "*******",
|
|
"phone": "+18832327657",
|
|
"type": "account",
|
|
"role": "admin",
|
|
"locationIds": ["C2QujeCh8ZnC7al2InWR"],
|
|
"permissions": {
|
|
"contactsEnabled": true,
|
|
"opportunitiesEnabled": true,
|
|
"conversationsEnabled": true
|
|
},
|
|
"scopes": ["contacts.write", "opportunities.write"]
|
|
}'
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
|
|
def create_user(token, company_id, first_name, last_name, email, password,
|
|
location_ids, role="user", type_="account", phone=None,
|
|
permissions=None, scopes=None, language="es"):
|
|
"""
|
|
POST /users/ — Create a user in GHL.
|
|
|
|
Args:
|
|
token: API token (pit-xxx or OAuth)
|
|
company_id: Agency ID
|
|
location_ids: List of location IDs to assign user to
|
|
permissions: Dict of 39 boolean flags (see §6 of research doc)
|
|
scopes: List of scope strings (see §7 of research doc)
|
|
|
|
Returns:
|
|
dict with user data (id, name, email, phone) or error
|
|
"""
|
|
payload = {
|
|
"companyId": company_id,
|
|
"firstName": first_name,
|
|
"lastName": last_name,
|
|
"email": email,
|
|
"password": password,
|
|
"type": type_,
|
|
"role": role,
|
|
"locationIds": location_ids,
|
|
"platformLanguage": language
|
|
}
|
|
|
|
if phone:
|
|
payload["phone"] = phone
|
|
if permissions:
|
|
payload["permissions"] = permissions
|
|
if scopes:
|
|
payload["scopes"] = scopes
|
|
|
|
body = json.dumps(payload).encode()
|
|
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("POST", "/users/", body, {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
|
|
if res.status == 201:
|
|
return data.get("user", data) # Response wraps in 'user' sometimes
|
|
else:
|
|
return {"error": res.status, "message": data.get("message", str(data))}
|
|
|
|
|
|
# ── Usage ──
|
|
user = create_user(
|
|
token="pit-xxxxxxxx",
|
|
company_id="ve9EPM428h8vShlRW1KT",
|
|
first_name="Martin",
|
|
last_name="Macedo",
|
|
email="abejorrito79@gmail.com",
|
|
password="********",
|
|
phone="+527775610738",
|
|
location_ids=["U0S0QntXgSOz9Fx18Db4"],
|
|
role="user",
|
|
permissions={
|
|
"contactsEnabled": True,
|
|
"opportunitiesEnabled": True,
|
|
"conversationsEnabled": True,
|
|
"assignedDataOnly": True
|
|
},
|
|
scopes=["contacts.write", "opportunities.write", "conversations.write"]
|
|
)
|
|
print(user) # {"id": "0IHuJvc2ofPAAA8GzTRi", "name": "Martin Macedo", ...}
|
|
```
|
|
|
|
### Request Body Schema
|
|
|
|
| Campo | Tipo | Requerido | Descripción |
|
|
|-------|------|-----------|-------------|
|
|
| `companyId` | string | ✅ | ID de la agencia |
|
|
| `firstName` | string | ✅ | Nombre |
|
|
| `lastName` | string | ✅ | Apellido |
|
|
| `email` | string | ✅ | Email (debe ser único) |
|
|
| `password` | string | ✅ | Contraseña |
|
|
| `type` | string | ✅ | `account` o `agency` |
|
|
| `role` | string | ✅ | `admin`, `user`, `agency_admin` |
|
|
| `locationIds` | string[] | ✅ | Array de Location IDs |
|
|
| `phone` | string | | Teléfono en E.164 |
|
|
| `permissions` | object | | 39 flags booleanos |
|
|
| `scopes` | string[] | | 189 scopes OAuth |
|
|
| `scopesAssignedToOnly` | string[] | | Scopes asignados |
|
|
| `profilePhoto` | string | | URL de foto |
|
|
| `twilioPhone` | object | | `{"locId": "+123"}` — semántica replace |
|
|
| `platformLanguage` | string | | `es`, `en_US`, `fr_CA`, etc. |
|
|
|
|
### Response `201 Created`
|
|
|
|
```json
|
|
{
|
|
"id": "0IHuJvc2ofPAAA8GzTRi",
|
|
"name": "John Deo",
|
|
"firstName": "John",
|
|
"lastName": "Deo",
|
|
"email": "john@deo.com",
|
|
"phone": "+1 808-868-8888",
|
|
"extension": "",
|
|
"permissions": {...},
|
|
"scopes": "...",
|
|
"roles": {...},
|
|
"deleted": false,
|
|
"lcPhone": {"locationId": "+1234556677"},
|
|
"platformLanguage": "en_US"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. GET /users/{userId} — Get User
|
|
|
|
Obtiene todos los datos de un usuario por su ID.
|
|
|
|
### cURL
|
|
|
|
```bash
|
|
curl -X GET https://services.leadconnectorhq.com/users/{userId} \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0"
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
|
|
def get_user(token, user_id):
|
|
"""GET /users/{userId} — Get user details by ID."""
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("GET", f"/users/{user_id}", "", {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
|
|
if res.status == 200:
|
|
return data.get("user", data)
|
|
return {"error": res.status, "message": data.get("message", str(data))}
|
|
|
|
|
|
# ── Usage ──
|
|
user = get_user(token="pit-xxxxxxxx", user_id="0IHuJvc2ofPAAA8GzTRi")
|
|
print(user["firstName"], user["email"], user["permissions"])
|
|
```
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"id": "0IHuJvc2ofPAAA8GzTRi",
|
|
"name": "John Deo",
|
|
"firstName": "John",
|
|
"lastName": "Deo",
|
|
"email": "john@deo.com",
|
|
"phone": "+1 808-868-8888",
|
|
"extension": "",
|
|
"permissions": {...},
|
|
"scopes": "...",
|
|
"roles": {...},
|
|
"deleted": false,
|
|
"lcPhone": {"locationId": "+1234556677"},
|
|
"platformLanguage": "en_US"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. PUT /users/{userId} — Update User
|
|
|
|
Actualiza datos del usuario. **Todos los campos son opcionales** — solo envía lo que necesites cambiar.
|
|
|
|
### cURL — Cambiar permisos y scopes
|
|
|
|
```bash
|
|
curl -X PUT https://services.leadconnectorhq.com/users/{userId} \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0" \
|
|
-d '{
|
|
"permissions": {"contactsEnabled": true, "opportunitiesEnabled": true},
|
|
"scopes": ["contacts.write", "opportunities.write"]
|
|
}'
|
|
```
|
|
|
|
### cURL — Mover usuario a otra location
|
|
|
|
```bash
|
|
curl -X PUT https://services.leadconnectorhq.com/users/{userId} \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0" \
|
|
-d '{"locationIds": ["LOC_ID_NUEVA"]}'
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
|
|
def update_user(token, user_id, **kwargs):
|
|
"""
|
|
PUT /users/{userId} — Update user fields.
|
|
|
|
All kwargs are optional. Only send what you want to change.
|
|
|
|
Examples:
|
|
update_user(token, uid, permissions={"contactsEnabled": True})
|
|
update_user(token, uid, location_ids=["LOC_A", "LOC_B"])
|
|
update_user(token, uid, password="new_password")
|
|
update_user(token, uid, scopes=["contacts.write"])
|
|
update_user(token, uid, role="admin", type="account")
|
|
"""
|
|
# Filter out None values
|
|
payload = {k: v for k, v in kwargs.items() if v is not None}
|
|
|
|
if not payload:
|
|
return {"error": "No fields to update"}
|
|
|
|
body = json.dumps(payload).encode()
|
|
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("PUT", f"/users/{user_id}", body, {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
|
|
if res.status == 200:
|
|
return data.get("user", data)
|
|
return {"error": res.status, "message": data.get("message", str(data))}
|
|
|
|
|
|
# ── Usage examples ──
|
|
|
|
# Example 1: Update permissions and scopes
|
|
update_user(
|
|
token="pit-xxxxxxxx",
|
|
user_id="0IHuJvc2ofPAAA8GzTRi",
|
|
permissions={
|
|
"contactsEnabled": True,
|
|
"opportunitiesEnabled": True,
|
|
"conversationsEnabled": True,
|
|
"assignedDataOnly": True
|
|
},
|
|
scopes=["contacts.write", "opportunities.write", "conversations.write"]
|
|
)
|
|
|
|
# Example 2: Move user to a different location
|
|
update_user(
|
|
token="pit-xxxxxxxx",
|
|
user_id="0IHuJvc2ofPAAA8GzTRi",
|
|
location_ids=["U0S0QntXgSOz9Fx18Db4"] # ZACATEPEC
|
|
)
|
|
|
|
# Example 3: Change role and type
|
|
update_user(
|
|
token="pit-xxxxxxxx",
|
|
user_id="0IHuJvc2ofPAAA8GzTRi",
|
|
role="admin",
|
|
type="account"
|
|
)
|
|
|
|
# Example 4: Reset password
|
|
update_user(
|
|
token="pit-xxxxxxxx",
|
|
user_id="0IHuJvc2ofPAAA8GzTRi",
|
|
password="new_secure_password"
|
|
)
|
|
```
|
|
|
|
### Campos actualizables
|
|
|
|
| Campo | Tipo | Deprecado | Descripción |
|
|
|-------|------|-----------|-------------|
|
|
| `firstName` | string | | Nombre |
|
|
| `lastName` | string | | Apellido |
|
|
| `email` | string | ⚠️ SÍ | Ya no soportado por seguridad |
|
|
| `password` | string | | Nueva contraseña |
|
|
| `phone` | string | | Teléfono |
|
|
| `type` | string | | `account` / `agency` |
|
|
| `role` | string | | `admin` / `user` / `agency_admin` |
|
|
| `companyId` | string | | ID de agencia (requerido para agency-level) |
|
|
| `locationIds` | string[] | | Reasignar sucursales |
|
|
| `permissions` | object | | 39 flags booleanos |
|
|
| `scopes` | string[] | | 189 scopes OAuth |
|
|
| `scopesAssignedToOnly` | string[] | | Scopes asignados |
|
|
| `profilePhoto` | string | | URL de foto |
|
|
| `twilioPhone` | object | | Números inbound — semántica replace |
|
|
| `platformLanguage` | string | | `es`, `en_US`, etc. |
|
|
|
|
---
|
|
|
|
## 4. DELETE /users/{userId} — Delete User
|
|
|
|
Soft delete asíncrono. El usuario se encola para eliminación y toma efecto en minutos.
|
|
|
|
### cURL
|
|
|
|
```bash
|
|
curl -X DELETE https://services.leadconnectorhq.com/users/{userId} \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0"
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
|
|
def delete_user(token, user_id):
|
|
"""DELETE /users/{userId} — Soft delete (async, takes minutes)."""
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("DELETE", f"/users/{user_id}", "", {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
return data # {"succeded": true, "message": "Queued deleting user..."}
|
|
|
|
|
|
# ── Usage ──
|
|
result = delete_user(token="pit-xxxxxxxx", user_id="0IHuJvc2ofPAAA8GzTRi")
|
|
print(result["message"]) # "Queued deleting user with e-mail john@deo.com..."
|
|
```
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"succeded": true,
|
|
"message": "Queued deleting user with e-mail john@deo.com and name John Deo. Will take effect in a few minutes."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. GET /users/search — Search Users
|
|
|
|
Búsqueda avanzada con filtros por companyId, locationId, nombre, email, rol, tipo, IDs específicos, y ordenamiento.
|
|
|
|
### Parameters
|
|
|
|
| Name | Type | Required | Description |
|
|
|------|------|----------|-------------|
|
|
| `Version` | string | ✅ | API Version header |
|
|
| `companyId` | string | ✅ | ID de la agencia |
|
|
| `query` | string | | Búsqueda por nombre, email o teléfono |
|
|
| `locationId` | string | | Filtrar por location |
|
|
| `type` | string | | `agency`, `account` |
|
|
| `role` | string | | `admin`, `user`, `agency_admin` |
|
|
| `ids` | string | | IDs separados por coma |
|
|
| `skip` | string | | Paginación offset (default: 0) |
|
|
| `limit` | string | | Page size (default: 25) |
|
|
| `sort` | string | | Campo de orden: `dateAdded` |
|
|
| `sortDirection` | string | | `asc` o `desc` |
|
|
|
|
### cURL
|
|
|
|
```bash
|
|
curl -X GET "https://services.leadconnectorhq.com/users/search?companyId=5DP41231LkQsiKESj6rh&locationId=C2QujeCh8ZnC7al2InWR&query=John&limit=10&sort=dateAdded&sortDirection=desc" \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0"
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
import urllib.parse
|
|
|
|
def search_users(token, company_id, query=None, location_id=None,
|
|
user_type=None, role=None, ids=None, skip=0, limit=25,
|
|
sort="dateAdded", sort_direction="desc"):
|
|
"""GET /users/search — Advanced user search."""
|
|
params = {
|
|
"companyId": company_id,
|
|
"skip": str(skip),
|
|
"limit": str(limit),
|
|
"sort": sort,
|
|
"sortDirection": sort_direction
|
|
}
|
|
if query:
|
|
params["query"] = query
|
|
if location_id:
|
|
params["locationId"] = location_id
|
|
if user_type:
|
|
params["type"] = user_type
|
|
if role:
|
|
params["role"] = role
|
|
if ids:
|
|
params["ids"] = ids
|
|
|
|
qs = urllib.parse.urlencode(params)
|
|
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("GET", f"/users/search?{qs}", "", {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
|
|
if res.status == 200:
|
|
return data # {"users": [...], "count": N}
|
|
return {"error": res.status, "message": data.get("message", str(data))}
|
|
|
|
|
|
# ── Usage examples ──
|
|
|
|
# Find all users in a location
|
|
users = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
|
|
location_id="U0S0QntXgSOz9Fx18Db4")
|
|
print(f"Found {users['count']} users in ZACATEPEC")
|
|
|
|
# Search by name
|
|
users = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
|
|
query="Martin Macedo")
|
|
for u in users.get("users", []):
|
|
print(f" {u['id']}: {u['name']} ({u['email']})")
|
|
|
|
# Filter by role
|
|
admins = search_users(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
|
|
role="admin", limit=50)
|
|
```
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"users": [
|
|
{
|
|
"id": "0IHuJvc2ofPAAA8GzTRi",
|
|
"name": "John Deo",
|
|
"firstName": "John",
|
|
"lastName": "Deo",
|
|
"email": "john@deo.com",
|
|
"phone": "+1 808-868-8888",
|
|
"extension": "",
|
|
"permissions": {...},
|
|
"scopes": "...",
|
|
"roles": {...},
|
|
"deleted": false,
|
|
"lcPhone": {"locationId": "+1234556677"},
|
|
"platformLanguage": "en_US"
|
|
}
|
|
],
|
|
"count": 1
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. POST /users/search/filter-by-email — Filter by Email
|
|
|
|
Filtra usuarios por array de emails. Ideal para verificar duplicados antes de crear.
|
|
|
|
### cURL
|
|
|
|
```bash
|
|
curl -X POST https://services.leadconnectorhq.com/users/search/filter-by-email \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Version: 2021-07-28" \
|
|
-H "User-Agent: Mozilla/5.0" \
|
|
-d '{
|
|
"companyId": "ve9EPM428h8vShlRW1KT",
|
|
"emails": ["john@deo.com", "jane@deo.com"],
|
|
"includeDeleted": false
|
|
}'
|
|
```
|
|
|
|
### Python
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
|
|
def filter_users_by_email(token, company_id, emails, include_deleted=False):
|
|
"""
|
|
POST /users/search/filter-by-email — Check if emails already exist.
|
|
|
|
Args:
|
|
emails: List of email strings to check
|
|
include_deleted: Include soft-deleted users
|
|
|
|
Returns:
|
|
List of users matching the emails
|
|
"""
|
|
body = json.dumps({
|
|
"companyId": company_id,
|
|
"emails": emails,
|
|
"includeDeleted": include_deleted
|
|
}).encode()
|
|
|
|
conn = http.client.HTTPSConnection("services.leadconnectorhq.com", timeout=30)
|
|
conn.request("POST", "/users/search/filter-by-email", body, {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "Mozilla/5.0"
|
|
})
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
|
|
if res.status == 200:
|
|
return data # Array or {"users": [...]}
|
|
return {"error": res.status, "message": data.get("message", str(data))}
|
|
|
|
|
|
# ── Usage: Check for duplicate before creating ──
|
|
def email_exists(token, company_id, email):
|
|
"""Returns True if a user with this email already exists."""
|
|
result = filter_users_by_email(token, company_id, [email])
|
|
users = result.get("users", result if isinstance(result, list) else [])
|
|
return len(users) > 0
|
|
|
|
|
|
existing = email_exists(token="pit-xxxxxxxx", company_id="ve9EPM428h8vShlRW1KT",
|
|
email="abejorrito79@gmail.com")
|
|
if existing:
|
|
print("⚠️ Email already taken — cannot create user")
|
|
else:
|
|
print("✅ Email available — safe to create")
|
|
```
|
|
|
|
### Response 200
|
|
|
|
Devuelve array de usuarios que coinciden con los emails proporcionados.
|
|
|
|
---
|
|
|
|
## 7. GET /users/ — Get User by Location ⚠️ DEPRECADO
|
|
|
|
**No usar.** Reemplazado por `GET /users/search` con query param `locationId`.
|
|
|
|
```bash
|
|
# ❌ Deprecado
|
|
GET /users/?locationId=LOC_ID
|
|
|
|
# ✅ Correcto
|
|
GET /users/search?locationId=LOC_ID&companyId=COMPANY_ID
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Response Codes
|
|
|
|
| Código | Significado | Schema |
|
|
|--------|-------------|--------|
|
|
| `200` | OK (GET, PUT, DELETE) | `UserSuccessfulResponseDto` / `DeleteUserSuccessfulResponseDto` |
|
|
| `201` | Created (POST) | `UserSuccessfulResponseDto` |
|
|
| `400` | Bad Request | `BadRequestDTO` |
|
|
| `401` | Unauthorized — token sin scope | `UnauthorizedDTO` |
|
|
| `422` | Unprocessable — email duplicado, locationIds inválido | `UnprocessableDTO` |
|
|
|
|
---
|
|
|
|
## 9. Scope Requirements
|
|
|
|
| Operación | Scope |
|
|
|-----------|-------|
|
|
| GET (search, get by id, filter by email) | `users.readonly` |
|
|
| POST (create) | `users.write` |
|
|
| PUT (update) | `users.write` |
|
|
| DELETE | `users.write` |
|
|
|
|
---
|
|
|
|
## 10. Notas importantes
|
|
|
|
1. **Email no se puede actualizar** — el campo `email` en PUT está deprecado por seguridad
|
|
2. **Delete es asíncrono** — la respuesta confirma encolado, no eliminación inmediata
|
|
3. **TwilioPhone tiene semántica replace** — si lo envías, reemplaza todo el mapa; no hace merge
|
|
4. **Scopes vacíos deshabilitan todo** — `"scopes": []` quita todos los permisos
|
|
5. **companyId es obligatorio** en search y filter-by-email — se obtiene del `GET /locations/{id}`
|
|
6. **locationIds acepta array** — un usuario puede pertenecer a múltiples sucursales simultáneamente
|
|
7. **Versión 2021-07-28** — todos los endpoints usan esta versión; algunos aceptan 2023-02-21
|
|
|
|
---
|
|
|
|
## 11. Python Helper — Clase completa
|
|
|
|
```python
|
|
import http.client
|
|
import json
|
|
import urllib.parse
|
|
|
|
class GHLUsersAPI:
|
|
"""Complete wrapper for GoHighLevel Users API."""
|
|
|
|
BASE = "services.leadconnectorhq.com"
|
|
|
|
def __init__(self, token):
|
|
self.token = token
|
|
self.headers = {
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"Version": "2021-07-28",
|
|
"User-Agent": "Mozilla/5.0"
|
|
}
|
|
|
|
def _request(self, method, path, body=None):
|
|
"""Generic HTTP request helper."""
|
|
conn = http.client.HTTPSConnection(self.BASE, timeout=30)
|
|
conn.request(method, path, body or "", self.headers)
|
|
res = conn.getresponse()
|
|
data = json.loads(res.read().decode())
|
|
conn.close()
|
|
return res.status, data
|
|
|
|
def create(self, company_id, first_name, last_name, email, password,
|
|
location_ids, role="user", type_="account", **kwargs):
|
|
"""POST /users/ — Create user."""
|
|
payload = {
|
|
"companyId": company_id, "firstName": first_name,
|
|
"lastName": last_name, "email": email, "password": password,
|
|
"type": type_, "role": role, "locationIds": location_ids,
|
|
"platformLanguage": kwargs.get("language", "es")
|
|
}
|
|
for k in ("phone", "permissions", "scopes", "profilePhoto", "twilioPhone"):
|
|
if k in kwargs and kwargs[k] is not None:
|
|
payload[k] = kwargs[k]
|
|
|
|
status, data = self._request("POST", "/users/",
|
|
json.dumps(payload).encode())
|
|
if status == 201:
|
|
return data.get("user", data)
|
|
raise Exception(f"Create failed: {status} {data.get('message', data)}")
|
|
|
|
def get(self, user_id):
|
|
"""GET /users/{userId} — Get user."""
|
|
status, data = self._request("GET", f"/users/{user_id}")
|
|
if status == 200:
|
|
return data.get("user", data)
|
|
raise Exception(f"Get failed: {status}")
|
|
|
|
def update(self, user_id, **kwargs):
|
|
"""PUT /users/{userId} — Update user fields."""
|
|
payload = {k: v for k, v in kwargs.items() if v is not None}
|
|
if not payload:
|
|
raise Exception("No fields to update")
|
|
status, data = self._request("PUT", f"/users/{user_id}",
|
|
json.dumps(payload).encode())
|
|
if status == 200:
|
|
return data.get("user", data)
|
|
raise Exception(f"Update failed: {status} {data.get('message', data)}")
|
|
|
|
def delete(self, user_id):
|
|
"""DELETE /users/{userId} — Soft delete."""
|
|
status, data = self._request("DELETE", f"/users/{user_id}")
|
|
if status == 200:
|
|
return data
|
|
raise Exception(f"Delete failed: {status}")
|
|
|
|
def search(self, company_id, **kwargs):
|
|
"""GET /users/search — Search users."""
|
|
params = {"companyId": company_id, "skip": "0", "limit": "25"}
|
|
for k in ("query", "locationId", "type", "role", "ids", "sort",
|
|
"sortDirection"):
|
|
if k in kwargs and kwargs[k] is not None:
|
|
params[k] = str(kwargs[k])
|
|
|
|
qs = urllib.parse.urlencode(params)
|
|
status, data = self._request("GET", f"/users/search?{qs}")
|
|
if status == 200:
|
|
return data
|
|
raise Exception(f"Search failed: {status}")
|
|
|
|
def filter_by_email(self, company_id, emails, include_deleted=False):
|
|
"""POST /users/search/filter-by-email — Filter by email list."""
|
|
payload = {"companyId": company_id, "emails": emails,
|
|
"includeDeleted": include_deleted}
|
|
status, data = self._request("POST", "/users/search/filter-by-email",
|
|
json.dumps(payload).encode())
|
|
if status == 200:
|
|
return data
|
|
raise Exception(f"Filter failed: {status}")
|
|
|
|
|
|
# ── Complete usage example ──
|
|
api = GHLUsersAPI(token="pit-xxxxxxxx")
|
|
|
|
# 1. Check if email exists
|
|
try:
|
|
existing = api.filter_by_email("ve9EPM428h8vShlRW1KT", ["test@example.com"])
|
|
print(f"Email check: {len(existing.get('users', []))} found")
|
|
except:
|
|
print("Email available")
|
|
|
|
# 2. Create user
|
|
user = api.create(
|
|
company_id="ve9EPM428h8vShlRW1KT",
|
|
first_name="Martin", last_name="Macedo",
|
|
email="abejorrito79@gmail.com", password="********",
|
|
phone="+527775610738",
|
|
location_ids=["U0S0QntXgSOz9Fx18Db4"],
|
|
role="user",
|
|
permissions={"contactsEnabled": True, "opportunitiesEnabled": True},
|
|
scopes=["contacts.write", "opportunities.write"]
|
|
)
|
|
print(f"Created: {user['id']}")
|
|
|
|
# 3. Update permissions
|
|
api.update(user["id"],
|
|
permissions={"contactsEnabled": True, "opportunitiesEnabled": True,
|
|
"conversationsEnabled": True, "assignedDataOnly": True},
|
|
scopes=["contacts.write", "opportunities.write", "conversations.write"])
|
|
|
|
# 4. Search users in location
|
|
result = api.search("ve9EPM428h8vShlRW1KT",
|
|
locationId="U0S0QntXgSOz9Fx18Db4")
|
|
print(f"Users in ZACATEPEC: {result['count']}")
|
|
|
|
# 5. Delete user (if needed)
|
|
# api.delete(user["id"])
|
|
```
|