Skip to content

API Quickstart

The Kotally machine API lives at https://app.<your-domain>/api/v2. Use it to manage studio members and credits from your booking app, website, or automation — with or without GoHighLevel.

This guide covers the API-only launch path. Full per-endpoint docs are in the API reference.

  1. Sign up and verify your email (Getting Started).
  2. Open the setup modal on the overview, or go to Admin → Locations → Add location.
  3. Choose Use the API (no GHL). Kotally creates a standalone location.
  4. Open Admin → API Clients → create a key for that location.
  5. Copy the raw token once (ktly_<uuid>_<uuid>) and store it in a secret manager.

A free trial can activate the location without a card; paid billing is still required before the trial ends.

Scopes

ScopeAccess
manageMembers, entitlements, and product configs — POST /api/v2/contacts, PATCH /api/v2/contacts/:id, entitlement ops, PATCH /api/v2/product-configs/:id
grantPOST /api/v2/grants
checkPOST /api/v2/entitlements/check-eligibility
deductPOST /api/v2/entitlements/deduct
restorePOST /api/v2/entitlements/restore
summaryRead members, summaries, and attention — GET /api/v2/contacts, all GET /api/v2/contacts/{id}/… reads, product config list, location attention
refundsList and decide refund clawbacks

Grant only the scopes each automation needs.

Authorization: Bearer ktly_<your-token>
Content-Type: application/json

Tokens are per-location. The location_id in each request must match the client’s location.

POST /api/v2/contacts requires the manage scope. Use your CRM id as external_contact_id (or omit it to auto-generate one).

Terminal window
curl -X POST "https://app.<your-domain>/api/v2/contacts" \
-H "Authorization: Bearer ktly_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"location_id": "kota:…",
"request_id": "member-crm_jane_1",
"email": "[email protected]",
"name": "Jane Doe",
"external_contact_id": "crm_jane_1"
}'

Grants can still create a member on first payment if you prefer upsert-via-grant. Prefer an explicit create when you sync members from your CRM before selling packages.

Contact ids: every write that takes a contact also accepts ghl_contact_id as an alias for external_contact_id. Prefer external_contact_id for non-GHL systems.

Typical CRM sync before selling packages:

  1. GET /api/v2/contacts?location_id=… — list Kotally members (summary scope)
  2. POST /api/v2/contacts — create rows for CRM ids missing locally (manage scope)
  3. PATCH /api/v2/contacts/{external_contact_id} — update email/name when the CRM changes (manage scope)
  4. POST /api/v2/grants — sell packs to synced members (grant scope)

See Contacts for list, create, and patch details.

Create a product config in Admin → Product Configs (or via the Product configs API), then:

Terminal window
curl -X POST "https://app.<your-domain>/api/v2/grants" \
-H "Authorization: Bearer ktly_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"location_id": "kota:…",
"request_id": "pay_abc_grant",
"external_payment_id": "pay_abc",
"external_contact_id": "crm_jane_1",
"product_config_id": "pc_…",
"email": "[email protected]",
"name": "Jane Doe",
"amount_cents": 9900,
"currency": "USD"
}'

Requires grant. Always send a unique request_id so retries are safe.

Use check / deduct / restore scopes with the same contact ref and product_config_id (or calendar/service rules if you map bookings). The Booking kit maps these three calls to a typical reservation flow with idempotency keys and reason codes.

See also:

With the summary scope:

Terminal window
curl "https://app.<your-domain>/api/v2/contacts/crm_jane_1/summary?location_id=kota:…" \
-H "Authorization: Bearer ktly_<your-token>"

Path {id} is the stored contact key (external_contact_id / ghl_contact_id). Full list: Contact API.