Deduct credits
POST
/api/v2/entitlements/deduct Deducts credits from a contact’s entitlement — call it when a booking is confirmed or a visit happens. Requires the deduct scope and is idempotent via request_id.
Request body
Section titled “Request body”| Field | Type | Required | Notes |
|---|---|---|---|
location_id | string | yes | Must match the token’s location |
request_id | string | yes | Idempotency key |
external_contact_id | string | yes† | Provider-neutral contact id. Send this or ghl_contact_id — interchangeable; must match the id used on the grant |
ghl_contact_id | string | yes† | The GoHighLevel contact id. Interchangeable with external_contact_id |
product_config_id | string | yes* | The product config to deduct from |
calendar_id | string | yes* | Alternative to product_config_id |
amount | integer | no | Credits to deduct; defaults to 1 |
external_ref | string | no | Booking or event reference |
appointment_time | string | no | ISO 8601 timestamp of the appointment |
* Either product_config_id or calendar_id is required.
† Exactly one of external_contact_id or ghl_contact_id is required.
curl -X POST https://app.<your-domain>/api/v2/entitlements/deduct \ -H "Authorization: Bearer ktly_<your-token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "loc_1", "request_id": "booking-123-deduct", "ghl_contact_id": "ghl_contact_123", "product_config_id": "pc_package_1", "amount": 1, "external_ref": "booking_123" }'const res = await fetch( "https://app.<your-domain>/api/v2/entitlements/deduct", { method: "POST", headers: { Authorization: `Bearer ${process.env.KOTALLY_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ location_id: "loc_1", request_id: "booking-123-deduct", ghl_contact_id: "ghl_contact_123", product_config_id: "pc_package_1", amount: 1, external_ref: "booking_123", }), },);const data = await res.json();// data.ok, data.balance_afterResponse (200)
Section titled “Response (200)”{ "ok": true, "reason_code": "deducted", "correlation_id": "a1b2c3d4-...", "balance_after": 9, "entitlement_id": "kotally-entitlement-uuid"}balance_after— the contact’s remaining available credits after the deduction.
Failure reason codes
Section titled “Failure reason codes”reason_code | Meaning |
|---|---|
NO_ENTITLEMENT | Contact has no matching entitlement |
INSUFFICIENT_CREDITS | Not enough credits to complete the deduction |
BILLING_SUSPENDED | Workspace billing is suspended |
REQUEST_IN_PROGRESS | Same request_id is still being processed — retry after a short delay |
See the Overview for HTTP status codes and the full reason-code list.