Restore credits
POST
/api/v2/entitlements/restore Restores credits to a contact’s entitlement, typically when an appointment is cancelled. Requires the restore scope, is idempotent via request_id, and is subject to the workspace cancellation-window setting.
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 restore into |
calendar_id | string | yes* | Alternative to product_config_id |
amount | integer | no | Credits to restore; defaults to 1 |
external_ref | string | no | Booking or event reference |
appointment_time | string | no | ISO 8601 timestamp of the cancelled appointment (used for the window check) |
* 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/restore \ -H "Authorization: Bearer ktly_<your-token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "loc_1", "request_id": "booking-123-restore", "ghl_contact_id": "ghl_contact_123", "product_config_id": "pc_package_1", "amount": 1, "external_ref": "booking_123", "appointment_time": "2026-05-01T10:00:00.000Z" }'const res = await fetch( "https://app.<your-domain>/api/v2/entitlements/restore", { method: "POST", headers: { Authorization: `Bearer ${process.env.KOTALLY_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ location_id: "loc_1", request_id: "booking-123-restore", ghl_contact_id: "ghl_contact_123", product_config_id: "pc_package_1", amount: 1, external_ref: "booking_123", appointment_time: "2026-05-01T10:00:00.000Z", }), },);const data = await res.json();// data.ok, data.balance_afterResponse (200)
Section titled “Response (200)”{ "ok": true, "reason_code": "restored", "correlation_id": "a1b2c3d4-...", "balance_after": 10, "entitlement_id": "kotally-entitlement-uuid"}- Pass
appointment_timeso Kotally can enforce the cancellation window; a restore outside the window is rejected withCANCELLATION_WINDOW_EXPIRED.
Failure reason codes
Section titled “Failure reason codes”reason_code | Meaning |
|---|---|
NO_ENTITLEMENT | Contact has no matching entitlement |
CANCELLATION_WINDOW_EXPIRED | The appointment is outside the allowed cancellation window |
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.