← Documentation

Partner API

Getting started

Ten minutes, three steps: mint a key, prove it works, create a client. Everything on this page runs today.

1. Mint a key

  1. In LaunchSite OS, go to Settings → Private Integrations.
  2. Choose the Partner API scope. This matters — an AI Brain key will not work here, and the two grant different things.
  3. Name it after the system that will hold it, not after yourself. GoHighLevel tells you what to revoke in six months; my key does not.
  4. Copy the key. It starts with 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.

2. Prove the key works

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.

3. Create a client

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."
  }'

That Idempotency-Key is not optional in practice

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.

SituationWhat you get
Nobody has started itProceeds normally
Someone finished itThe stored response, plus Idempotent-Replay: true
Someone is running it right now409 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”.

Who sends the welcome email

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.

Reading data back

These are the reads, and each one pairs with something you might want to react to:

EndpointGives you
GET /v1/clientsThe clients this key’s coach owns
GET /v1/clients/{id}One client, including their latest check-in date
GET /v1/clients/{id}/complianceCompliance over a window, 7 days by default
GET /v1/clients/{id}/check-insWhen check-ins happened and whether they were reviewed
GET /v1/clients/{id}/daily-logsWhich 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.

Things that will bite you

Fields are rejected, not truncated

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.

You cannot name a different coach

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.

Rate limits are per key, not per IP

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.

Next

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.