Partner API
GoHighLevel tells you what to revoke in six months; my key does not.lsk_live_.You will not see it again. Only a hash is stored, so we cannot show it to you later — if you lose it, revoke it and mint another. Revoking never deletes the record, because that record is the audit trail.
Before wiring anything up, check the key on an endpoint that changes nothing. Send it either as a bearer token or as X-Api-Key — both are accepted, and some platforms only let you set one of them.
curl https://launchsite-os.com/api/v1/ping \
-H "Authorization: Bearer lsk_live_your_key_here"A working key comes back with who it belongs to and what it can do:
{
"ok": true,
"coach": { "id": "…", "email": "you@yourpractice.com" },
"scope": "partner"
}If you get 401, the key is wrong, revoked, or was pasted with a trailing space. If you get 403, the key is real but has the wrong scope — mint a new one with Partner API selected.
Only email is strictly required, but a client with no name is not much use to anybody, so send full_name too.
curl -X POST https://launchsite-os.com/api/v1/clients \
-H "Authorization: Bearer lsk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: crm_contact_88213" \
-d '{
"full_name": "Jane Smith",
"email": "jane@example.com",
"phone": "+15551234567",
"goals": "Lower fasting glucose and sleep through the night."
}'Creating a client makes a real account and emails a real person. Automation platforms retry — on timeouts, on 5xx, and on a success whose response they failed to read — so without an idempotency key a retry means a second account and a second credentials email to someone who already got one.
Use a stable id from your own system. A CRM contact id is ideal, because it survives someone correcting a typo in the email address. If you omit the header we derive one from the email, which is better than nothing but breaks the moment the address is edited.
| Situation | What you get |
|---|---|
| Nobody has started it | Proceeds normally |
| Someone finished it | The stored response, plus Idempotent-Replay: true |
| Someone is running it right now | 409 with Retry-After: 5 |
Idempotent-Replay rides on whatever status the original produced. A replayed 400carries it too, so read it as “you have seen this answer before” — not as “it succeeded”.
By default we do, exactly as the in-app flow does, and intake_url comes back null because it has already gone out.
Send "send_welcome": false to take delivery yourself. You then get temp_password and intake_url back so your own branded message can carry both, and the intake is recorded as issued rather than sent— so the coach’s Intakes list does not claim “Awaiting response” about somebody we never contacted.
temp_password also comes back when send_welcome was true but our send failed, so an account never exists with nobody holding a way into it.
These are the reads, and each one pairs with something you might want to react to:
| Endpoint | Gives you |
|---|---|
GET /v1/clients | The clients this key’s coach owns |
GET /v1/clients/{id} | One client, including their latest check-in date |
GET /v1/clients/{id}/compliance | Compliance over a window, 7 days by default |
GET /v1/clients/{id}/check-ins | When check-ins happened and whether they were reviewed |
GET /v1/clients/{id}/daily-logs | Which dates have a daily log |
Check-in answers are deliberately not available. You get the date, not what the client wrote. Adding a field later is additive and safe; removing one after an integration depends on it is not — so the line is drawn on the careful side.
A value longer than its documented limit returns 400naming the field and the limit. It does not quietly store a shortened version, because silently corrupting a coach’s data is worse than an error your platform can show you.
The coach is resolved from the key and nowhere else. No endpoint accepts a coach_id, owner_id or team_id in a payload. A key proves which coach it is and has no authority to claim to be another.
Reads filter on ownership too, so a client handed to a teammate is not your key’s to read.
300 requests per minute, and 20 per minute for POST /v1/clients/{id}/login-link. Over either and you get 429 with a Retry-Afterheader — honour it. The bucket is the key precisely because automation platforms call from shared egress pools, where an IP limit would punish you for somebody else’s traffic.
Wire it to a real platform with the GoHighLevel guide, or open the API reference to see every field and error shape and to try requests against your own key.