Overview
The Kotally machine API lets external automations grant, check, deduct, and restore credits and read contact entitlement data over plain HTTP — with or without GoHighLevel. The API Quickstart walks through your first calls; this reference documents every endpoint.
Conventions
Section titled “Conventions”- Base URL:
https://app.<your-domain>/api/v2 - Auth: send
Authorization: Bearer ktly_<token>on every request. Tokens are created under Admin → API Clients, are scoped to a single location, and carry a set of scopes (see below). The token format isktly_<uuid>_<uuid>and is shown once at creation. - Format: requests and responses are JSON.
- Outcome model: business outcomes return HTTP 200 with
ok: true|falseand areason_code— an ineligible check or an insufficient balance is a200withok: false, not an HTTP error. Reserve HTTP4xx/5xxfor transport-level problems (auth, validation, rate limits).
Common headers
Section titled “Common headers”Every endpoint takes the same headers:
| Header | Required | Value |
|---|---|---|
Authorization | yes | Bearer ktly_<token> |
Content-Type | yes (for POST) | application/json |
Scopes
Section titled “Scopes”Each API client is granted a subset of scopes. Grant only what an automation needs.
| Scope | Grants access to |
|---|---|
grant | POST /api/v2/grants |
check | POST /api/v2/entitlements/check-eligibility |
deduct | POST /api/v2/entitlements/deduct |
restore | POST /api/v2/entitlements/restore |
summary | All GET /api/v2/contacts/… read endpoints |
Idempotency
Section titled “Idempotency”The state-changing endpoints — grants, deduct, and restore — require a request_id field.
- Choose a stable, unique string per logical operation (e.g. your payment id for a grant, or your booking id suffixed with
-deduct). - On a timeout or network error, resend the exact same payload with the same
request_id— Kotally returns the original result without repeating the operation. - If the same
request_idarrives while the first request is still processing, Kotally returnsREQUEST_IN_PROGRESSwith HTTP409; wait a moment and retry.
Do not reuse a request_id across different operations or contacts.
Pagination
Section titled “Pagination”List endpoints (the contact ledger, payments, and appointments reads) accept an optional ?limit= query parameter. The default is 20 and the maximum is 100.
Errors
Section titled “Errors”Every response includes ok and reason_code; mutating endpoints also include a unique correlation_id — include it in support requests.
{ "ok": false, "reason_code": "INSUFFICIENT_CREDITS", "correlation_id": "a1b2c3d4-...", "message": "Optional human-readable detail"}HTTP status codes
Section titled “HTTP status codes”| Status | When |
|---|---|
200 | Business-logic outcome (including ineligible/insufficient — check ok) |
400 | Validation error (missing required field, wrong type) |
401 | Missing or invalid bearer token, insufficient scope, or wrong location |
409 | REQUEST_IN_PROGRESS — a duplicate in-flight request |
429 | RATE_LIMITED — slow down and retry |
Common reason codes
Section titled “Common reason codes”reason_code | Meaning |
|---|---|
UNAUTHORIZED | Missing/invalid token, insufficient scope, or wrong location |
RATE_LIMITED | Too many requests from this API client |
VALIDATION_ERROR | A required field is missing or has an invalid value |
NO_ENTITLEMENT | The contact has no entitlement matching the product config or calendar |
INSUFFICIENT_CREDITS | The contact’s entitlement does not have enough credits |
CANCELLATION_WINDOW_EXPIRED | A restore was rejected — the appointment is past the cancellation window |
BILLING_SUSPENDED | Workspace billing is suspended; credit mutations are paused |
duplicate_payment_event | A grant was skipped — the external_payment_id was already processed |
REQUEST_IN_PROGRESS | The same request_id is being processed by a concurrent request |
NOT_FOUND | The requested contact does not exist for this location |