KLCTDevelopers

API reference

Base URL https://app.klctai.com/api. Send Authorization: Bearer <token> on every request. The scope on each endpoint is the OAuth scope an access token needs; an account API key passes them all. Machine-readable: openapi.json or Markdown.

Account

Identify the account a token belongs to and keep its brand in step with your product.

GET /platform/account identity Get the connected account

Returns which KLCT account the token belongs to and whether billing is active. Call this first after connecting to confirm you hold the right account.

Operation id getAccount

Example
curl "https://app.klctai.com/api/platform/account" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "account_id": 4821,
    "name": "Acme Dental",
    "billing_active": true,
    "setup_url": null
}
Response fields
FieldTypeDescription
account_id integer The KLCT account id.
name string Account name.
billing_active boolean False when the account has no active plan yet.
setup_url string Where a person can finish setup when billing is not active, otherwise null.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "identity" scope (error: insufficient_scope).
  • 429 Rate limit reached. Retry after the Retry-After header.
PUT /platform/branding branding:write Set the account brand

Updates the logo, colors, website and typeface KLCT uses in emails, pages and social images. Send only the fields you want to change. Never reads or deletes anything else.

Operation id updateBranding

Request body (JSON)
FieldTypeRequiredDescription
primary_color string Hex color, for example #1D4ED8.
secondary_color string Hex color.
accent_color string Hex color.
button_color string Hex color for buttons.
button_text_color string Hex color for button text.
logo_url string HTTPS URL of the logo.
email_logo_url string HTTPS URL of the logo used in email headers.
square_logo_url string HTTPS URL of a square logo.
favicon_url string HTTPS URL of the favicon.
website_url string HTTPS URL of the main website.
from_name string Sender name on emails, up to 120 characters.
brand_voice string How the brand should sound, up to 2000 characters. Used by the AI writers.
font_family string One of the typefaces KLCT offers.
socials object Social profile URLs keyed by network (HTTPS only).
Example
curl -X PUT "https://app.klctai.com/api/platform/branding" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"primary_color":"#1D4ED8","logo_url":"https://acme.example/logo.png"}'
Response 200
{
    "ok": true,
    "updated": [
        "primary_color",
        "logo_url"
    ]
}
Response fields
FieldTypeDescription
ok boolean True when saved.
updated array The fields that were changed.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "branding:write" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.

Contacts

The people your account talks to. Contacts are shared by calls, chat, tickets, campaigns and sequences.

GET /contacts contacts:read List contacts

Newest first. Page through with page and per_page.

Operation id listContacts

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
Example
curl "https://app.klctai.com/api/contacts" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "contacts": [
        {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": "+15551234567",
            "company": "Acme Dental",
            "website": null,
            "country": "US",
            "tags": [
                "vip"
            ],
            "metadata": {
                "crm_id": "c_77"
            },
            "lead_status": "new",
            "created_at": "2026-10-01T14:03:22Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
contacts array The page of contacts.
contacts[].contact_id integer Contact id.
contacts[].name string First name.
contacts[].last_name string Last name.
contacts[].email string Email address.
contacts[].phone string Phone number in E.164 form (+15551234567).
contacts[].company string Company name.
contacts[].website string Website.
contacts[].country string Country.
contacts[].tags array Free-form tags.
contacts[].metadata object Your own key/value data. Merged on update, never replaced.
contacts[].lead_status string Pipeline stage KLCT assigned.
contacts[].created_at string When the contact was created.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "contacts:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /contacts/{id} contacts:read Get a contact

Operation id getContact

Path parameters
NameTypeDescription
idintegerContact id.
Example
curl "https://app.klctai.com/api/contacts/123" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "contact": {
        "contact_id": 90412,
        "name": "Dana",
        "last_name": "Cole",
        "email": "[email protected]",
        "phone": "+15551234567",
        "company": "Acme Dental",
        "website": null,
        "country": "US",
        "tags": [
            "vip"
        ],
        "metadata": {
            "crm_id": "c_77"
        },
        "lead_status": "new",
        "created_at": "2026-10-01T14:03:22Z",
        "updated_at": "2026-10-02T09:00:00Z"
    }
}
Response fields
FieldTypeDescription
contact object
contact.contact_id integer Contact id.
contact.name string First name.
contact.last_name string Last name.
contact.email string Email address.
contact.phone string Phone number in E.164 form (+15551234567).
contact.company string Company name.
contact.website string Website.
contact.country string Country.
contact.tags array Free-form tags.
contact.metadata object Your own key/value data. Merged on update, never replaced.
contact.lead_status string Pipeline stage KLCT assigned.
contact.created_at string When the contact was created.
contact.updated_at string Last changed.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "contacts:read" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
PATCH /contacts/{id} contacts:write Update a contact

Send only the fields to change. metadata is merged key by key, so you can add one key without resending the rest. Contacts are never deleted through the API.

Operation id updateContact

Path parameters
NameTypeDescription
idintegerContact id.
Request body (JSON)
FieldTypeRequiredDescription
name string First name. Cannot be blank.
last_name string Last name.
email string Email address.
phone string Phone in any common format; stored as E.164. An unparseable value is rejected.
company string Company.
website string Website.
country string Country.
metadata object Merged into the existing metadata.
Example
curl -X PATCH "https://app.klctai.com/api/contacts/123" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Dana","phone":"+15551234567"}'
Response 200
{
    "contact": {
        "contact_id": 90412,
        "name": "Dana",
        "last_name": "Cole",
        "email": "[email protected]",
        "phone": "+15551234567",
        "company": "Acme Dental",
        "website": null,
        "country": "US",
        "tags": [
            "vip"
        ],
        "metadata": {
            "crm_id": "c_77"
        },
        "lead_status": "new",
        "created_at": "2026-10-01T14:03:22Z",
        "updated_at": "2026-10-02T09:00:00Z"
    }
}
Response fields
FieldTypeDescription
contact object
contact.contact_id integer Contact id.
contact.name string First name.
contact.last_name string Last name.
contact.email string Email address.
contact.phone string Phone number in E.164 form (+15551234567).
contact.company string Company name.
contact.website string Website.
contact.country string Country.
contact.tags array Free-form tags.
contact.metadata object Your own key/value data. Merged on update, never replaced.
contact.lead_status string Pipeline stage KLCT assigned.
contact.created_at string When the contact was created.
contact.updated_at string Last changed.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "contacts:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contacts/create contacts:write Create one or many contacts

Send one contact's fields at the top level, or a contacts array to create several in one transaction (any invalid row rejects the whole batch). Phone must be +1 plus ten digits. Creating the same email twice creates two contacts; use POST /platform/contacts/sync when you want create-or-update by email.

Operation id createContacts

Request body (JSON)
FieldTypeRequiredDescription
name string Yes First name.
last_name string Yes Last name.
email string Yes Email address.
phone string Yes +1 followed by ten digits.
company string Company.
website string Website.
country string Country.
metadata object Your own key/value data.
contacts array Alternative to the top-level fields: several contacts at once.
Example
curl -X POST "https://app.klctai.com/api/contacts/create" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Dana","last_name":"Cole","email":"[email protected]","phone":"+15551234567","contacts":[]}'
Response 200
{
    "contacts": {
        "contact_id": 90412,
        "name": "Dana"
    }
}
Response fields
FieldTypeDescription
contacts object One object for a single contact, an array of them for a batch.
contacts.contact_id integer New contact id.
contacts.name string First name.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "contacts:write" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /platform/contacts/sync contacts:write Create or update contacts by email

Up to 200 contacts per call. A contact whose email already exists is updated: blank fields are filled, existing values are kept, tags are unioned and metadata is merged. Rows that fail validation are reported in errors without stopping the rest. Limited to 30 calls per minute.

Operation id syncContacts

Request body (JSON)
FieldTypeRequiredDescription
contacts array Yes Between 1 and 200 contacts.
contacts[].email string Yes Email, the match key.
contacts[].name string First name.
contacts[].last_name string Last name.
contacts[].phone string Any common phone format; ignored if it cannot be parsed.
contacts[].company string Company.
contacts[].tags array Up to 10 tags, 50 characters each.
contacts[].metadata object Key/value data, merged.
Example
curl -X POST "https://app.klctai.com/api/platform/contacts/sync" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contacts":[{"email":"[email protected]","name":"Dana","tags":[]}]}'
Response 200
{
    "created": 1,
    "updated": 0,
    "errors": []
}
Response fields
FieldTypeDescription
created integer Contacts created.
updated integer Contacts updated.
errors array Rows that were skipped.
errors[].index integer Position in the request.
errors[].error string Why the row was skipped.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "contacts:write" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.

Contact lists

Groups of contacts used as audiences for campaigns and AI call batches. These routes need a valid token but no specific scope.

GET /contactslists/list List contact lists

Operation id listContactLists

Example
curl "https://app.klctai.com/api/contactslists/list" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "data": [
        {
            "contacts_list_id": 12,
            "name": "Spring leads",
            "target_industry": "Dental"
        }
    ]
}
Response fields
FieldTypeDescription
data array All lists in the account.
Errors
  • 401 Missing, invalid or expired token.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/create Create a contact list

Operation id createContactList

Request body (JSON)
FieldTypeRequiredDescription
name string Yes List name.
target_industry string Yes Industry the list targets.
notes string Yes Notes about the list.
metadata object Your own key/value data.
Example
curl -X POST "https://app.klctai.com/api/contactslists/create" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring leads","target_industry":"Dental","notes":"Imported from the open house"}'
Response 200
{
    "contact_list_id": 12
}
Response fields
FieldTypeDescription
contact_list_id integer New list id.
Errors
  • 401 Missing, invalid or expired token.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/{contactList}/update Rename or edit a contact list

Operation id updateContactList

Path parameters
NameTypeDescription
contactListintegerContact list id.
Request body (JSON)
FieldTypeRequiredDescription
name string Yes List name.
target_industry string Industry.
metadata object Merged into existing metadata.
Example
curl -X POST "https://app.klctai.com/api/contactslists/123/update" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring leads 2026"}'
Response 200
{
    "contact_list_id": 12
}
Response fields
FieldTypeDescription
contact_list_id integer List id.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/{contactList}/delete Delete a contact list

Deletes the list, not the contacts in it.

Operation id deleteContactList

Path parameters
NameTypeDescription
contactListintegerContact list id.
Example
curl -X POST "https://app.klctai.com/api/contactslists/123/delete" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "success": "Contact list deleted with success!"
}
Response fields
FieldTypeDescription
success string Confirmation sentence.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /contactslists/{contactList}/get_contacts List the contacts in a list

Operation id listContactListMembers

Path parameters
NameTypeDescription
contactListintegerContact list id.
Example
curl "https://app.klctai.com/api/contactslists/123/get_contacts" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "data": [
        {
            "contact_id": 90412,
            "name": "Dana"
        }
    ]
}
Response fields
FieldTypeDescription
data array Members of the list.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/{contactList}/create_sync_contacts Create contacts and put them in a list

Creates each contact (or updates the existing one with the same phone or email) and adds it to the list.

Operation id createAndSyncListContacts

Path parameters
NameTypeDescription
contactListintegerContact list id.
Request body (JSON)
FieldTypeRequiredDescription
contacts array Yes Contacts to create and add.
contacts[].name string Yes First name.
contacts[].email string Yes Email.
contacts[].last_name string Last name.
contacts[].phone string Phone.
contacts[].company string Company.
contacts[].website string Website.
contacts[].country string Country.
contacts[].metadata object Key/value data.
Example
curl -X POST "https://app.klctai.com/api/contactslists/123/create_sync_contacts" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contacts":[{"name":"Dana","email":"[email protected]","phone":"+15551234567"}]}'
Response 200
{
    "success": "The contacts where synced with success!"
}
Response fields
FieldTypeDescription
success string Confirmation sentence.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/{contactList}/sync_contacts Set the members of a list

Sets the list membership from contacts_ids.

Operation id syncListContacts

Path parameters
NameTypeDescription
contactListintegerContact list id.
Request body (JSON)
FieldTypeRequiredDescription
name string Yes List name (this endpoint requires it).
contacts_ids array Contact ids that should be in the list.
Example
curl -X POST "https://app.klctai.com/api/contactslists/123/sync_contacts" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring leads","contacts_ids":[90412]}'
Response 200
{
    "success": "The contacts where synced with success!"
}
Response fields
FieldTypeDescription
success string Confirmation sentence.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /contactslists/{contactList}/remove_contacts Remove contacts from a list

Removes membership only. The contacts themselves are kept.

Operation id removeListContacts

Path parameters
NameTypeDescription
contactListintegerContact list id.
Request body (JSON)
FieldTypeRequiredDescription
contacts_ids array Yes Contact ids to remove.
Example
curl -X POST "https://app.klctai.com/api/contactslists/123/remove_contacts" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contacts_ids":[90412]}'
Response 200
{
    "success": "Contacts removed"
}
Response fields
FieldTypeDescription
success string Confirmation sentence.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.

Calls

Calls the AI agents and your team made or received.

GET /calls calls:read List calls

Newest first, with outcome and duration.

Operation id listCalls

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
Example
curl "https://app.klctai.com/api/calls" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "calls": [
        {
            "phone_call_id": 55120,
            "contact_id": 90412,
            "from": "+15559876543",
            "to": "+15551234567",
            "status": "completed",
            "duration": 142,
            "interest_level": "high",
            "start_time": "2026-10-02T15:10:00Z",
            "created_at": "2026-10-02T15:09:41Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
calls array The page of calls.
calls[].phone_call_id integer Call id.
calls[].contact_id integer The contact on the call.
calls[].from string Caller number.
calls[].to string Dialed number.
calls[].status string Call status.
calls[].duration integer Seconds.
calls[].interest_level string How interested the AI graded the contact.
calls[].start_time string When the call started.
calls[].created_at string When the record was created.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "calls:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.

Tickets

Helpdesk tickets from email, chat, forms and the API. Replies go to the customer by email, or into the chat when the ticket came from chat. Tickets are never deleted through the API. Needs the Support module on the account.

GET /tickets tickets:read List tickets

Newest first. Filter by status, contact, search text, or what changed since a time (handy for incremental sync).

Operation id listTickets

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
status string One exact status, or `unresolved` for every status that still needs work.
q string Matches the subject or ticket number.
contact_id integer Only tickets for this contact.
updated_since string Only tickets changed at or after this time.
Example
curl "https://app.klctai.com/api/tickets?status=unresolved" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "tickets": [
        {
            "id": 1042,
            "ticket_number": "SUP-1042",
            "subject": "Cannot log in on mobile",
            "status": "open",
            "priority": 2,
            "source": "email",
            "contact": {
                "contact_id": 90412,
                "name": "Dana",
                "last_name": "Cole",
                "email": "[email protected]",
                "phone": "+15551234567"
            },
            "due_at": null,
            "first_response_at": null,
            "resolved_at": null,
            "created_at": "2026-10-14T09:12:00Z",
            "updated_at": "2026-10-14T09:12:00Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
tickets array The page of tickets.
tickets[].id integer Ticket id.
tickets[].ticket_number string Human reference such as SUP-1042.
tickets[].subject string Subject line.
tickets[].status string Current status.
tickets[].priority integer 0 (lowest) to 4 (highest).
tickets[].source string How it arrived: email, chat, form, api and others.
tickets[].contact object The requester, or null.
tickets[].contact.contact_id integer Contact id.
tickets[].contact.name string First name.
tickets[].contact.last_name string Last name.
tickets[].contact.email string Email.
tickets[].contact.phone string Phone.
tickets[].due_at string Due date, or null.
tickets[].first_response_at string When a person first replied, or null.
tickets[].resolved_at string When it was resolved, or null.
tickets[].created_at string Created.
tickets[].updated_at string Last changed.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "tickets:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /tickets/{id} tickets:read Get a ticket with its conversation

Includes the customer-visible message thread. Internal notes and staff details beyond a display name are left out.

Operation id getTicket

Path parameters
NameTypeDescription
idintegerTicket id.
Example
curl "https://app.klctai.com/api/tickets/123" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "ticket": {
        "id": 1042,
        "ticket_number": "SUP-1042",
        "subject": "Cannot log in on mobile",
        "status": "open",
        "priority": 2,
        "source": "email",
        "contact": {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": "+15551234567"
        },
        "due_at": null,
        "first_response_at": null,
        "resolved_at": null,
        "created_at": "2026-10-14T09:12:00Z",
        "updated_at": "2026-10-14T09:12:00Z",
        "description": "The app says my password is wrong but the website accepts it.",
        "reply_channel": "email",
        "conversation_id": null,
        "messages": [
            {
                "id": 3301,
                "direction": "in",
                "sender_type": "contact",
                "sender_name": "Dana Cole",
                "body": "The app says my password is wrong but the website accepts it.",
                "attachments": [],
                "created_at": "2026-10-14T09:12:00Z"
            }
        ]
    }
}
Response fields
FieldTypeDescription
ticket object
ticket.id integer Ticket id.
ticket.ticket_number string Human reference such as SUP-1042.
ticket.subject string Subject line.
ticket.status string Current status.
ticket.priority integer 0 (lowest) to 4 (highest).
ticket.source string How it arrived: email, chat, form, api and others.
ticket.contact object The requester, or null.
ticket.contact.contact_id integer Contact id.
ticket.contact.name string First name.
ticket.contact.last_name string Last name.
ticket.contact.email string Email.
ticket.contact.phone string Phone.
ticket.due_at string Due date, or null.
ticket.first_response_at string When a person first replied, or null.
ticket.resolved_at string When it was resolved, or null.
ticket.created_at string Created.
ticket.updated_at string Last changed.
ticket.description string The original request text.
ticket.reply_channel string
chat, email
How a reply reaches the customer.
ticket.conversation_id integer The chat conversation behind the ticket, or null.
ticket.messages array The customer-visible thread. Internal notes are never included.
ticket.messages[].id integer Message id.
ticket.messages[].direction string
in, out
in is from the customer, out is from your team.
ticket.messages[].sender_type string Who wrote it.
ticket.messages[].sender_name string Display name only.
ticket.messages[].body string Message text.
ticket.messages[].attachments array Attachment details. Files themselves are not downloadable through the API.
ticket.messages[].attachments[].name string File name.
ticket.messages[].attachments[].mime string Content type.
ticket.messages[].attachments[].size integer Bytes.
ticket.messages[].created_at string Sent.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "tickets:read" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /tickets tickets:write Create a ticket

Creates an open ticket with source api. This records the ticket and fires the ticket.created webhook; it does not send an acknowledgement email to the requester.

Operation id createTicket

Request body (JSON)
FieldTypeRequiredDescription
subject string Yes Subject line, up to 255 characters.
description string What happened, up to 20000 characters.
priority integer 0 to 4. Defaults to 0.
contact_id integer Requester. Must be a contact in this account.
due_at string When it should be handled by.
Example
curl -X POST "https://app.klctai.com/api/tickets" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subject":"Cannot log in on mobile","description":"The app says my password is wrong.","priority":2,"contact_id":90412}'
Response 201
{
    "ticket": {
        "id": 1042,
        "ticket_number": "SUP-1042",
        "subject": "Cannot log in on mobile",
        "status": "open",
        "priority": 2,
        "source": "email",
        "contact": {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": "+15551234567"
        },
        "due_at": null,
        "first_response_at": null,
        "resolved_at": null,
        "created_at": "2026-10-14T09:12:00Z",
        "updated_at": "2026-10-14T09:12:00Z",
        "description": "The app says my password is wrong but the website accepts it.",
        "reply_channel": "email",
        "conversation_id": null,
        "messages": [
            {
                "id": 3301,
                "direction": "in",
                "sender_type": "contact",
                "sender_name": "Dana Cole",
                "body": "The app says my password is wrong but the website accepts it.",
                "attachments": [],
                "created_at": "2026-10-14T09:12:00Z"
            }
        ]
    }
}
Response fields
FieldTypeDescription
ticket object
ticket.id integer Ticket id.
ticket.ticket_number string Human reference such as SUP-1042.
ticket.subject string Subject line.
ticket.status string Current status.
ticket.priority integer 0 (lowest) to 4 (highest).
ticket.source string How it arrived: email, chat, form, api and others.
ticket.contact object The requester, or null.
ticket.contact.contact_id integer Contact id.
ticket.contact.name string First name.
ticket.contact.last_name string Last name.
ticket.contact.email string Email.
ticket.contact.phone string Phone.
ticket.due_at string Due date, or null.
ticket.first_response_at string When a person first replied, or null.
ticket.resolved_at string When it was resolved, or null.
ticket.created_at string Created.
ticket.updated_at string Last changed.
ticket.description string The original request text.
ticket.reply_channel string
chat, email
How a reply reaches the customer.
ticket.conversation_id integer The chat conversation behind the ticket, or null.
ticket.messages array The customer-visible thread. Internal notes are never included.
ticket.messages[].id integer Message id.
ticket.messages[].direction string
in, out
in is from the customer, out is from your team.
ticket.messages[].sender_type string Who wrote it.
ticket.messages[].sender_name string Display name only.
ticket.messages[].body string Message text.
ticket.messages[].attachments array Attachment details. Files themselves are not downloadable through the API.
ticket.messages[].attachments[].name string File name.
ticket.messages[].attachments[].mime string Content type.
ticket.messages[].attachments[].size integer Bytes.
ticket.messages[].created_at string Sent.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "tickets:write" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
PATCH /tickets/{id} tickets:write Update a ticket

Send only the fields to change. Moving to resolved or closed stamps resolved_at; moving a resolved ticket back to an open status counts as a reopen. Each change is written to the ticket history.

Operation id updateTicket

Path parameters
NameTypeDescription
idintegerTicket id.
Request body (JSON)
FieldTypeRequiredDescription
subject string New subject.
description string New description.
priority integer 0 to 4.
status string A valid ticket status, for example open, pending, resolved, closed.
due_at string New due date.
Example
curl -X PATCH "https://app.klctai.com/api/tickets/123" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"priority":1,"status":"resolved"}'
Response 200
{
    "ticket": {
        "status": "resolved",
        "id": 1042,
        "ticket_number": "SUP-1042",
        "subject": "Cannot log in on mobile",
        "priority": 2,
        "source": "email",
        "contact": {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": "+15551234567"
        },
        "due_at": null,
        "first_response_at": null,
        "resolved_at": null,
        "created_at": "2026-10-14T09:12:00Z",
        "updated_at": "2026-10-14T09:12:00Z",
        "description": "The app says my password is wrong but the website accepts it.",
        "reply_channel": "email",
        "conversation_id": null,
        "messages": [
            {
                "id": 3301,
                "direction": "in",
                "sender_type": "contact",
                "sender_name": "Dana Cole",
                "body": "The app says my password is wrong but the website accepts it.",
                "attachments": [],
                "created_at": "2026-10-14T09:12:00Z"
            }
        ]
    }
}
Response fields
FieldTypeDescription
ticket object
ticket.id integer Ticket id.
ticket.ticket_number string Human reference such as SUP-1042.
ticket.subject string Subject line.
ticket.status string Current status.
ticket.priority integer 0 (lowest) to 4 (highest).
ticket.source string How it arrived: email, chat, form, api and others.
ticket.contact object The requester, or null.
ticket.contact.contact_id integer Contact id.
ticket.contact.name string First name.
ticket.contact.last_name string Last name.
ticket.contact.email string Email.
ticket.contact.phone string Phone.
ticket.due_at string Due date, or null.
ticket.first_response_at string When a person first replied, or null.
ticket.resolved_at string When it was resolved, or null.
ticket.created_at string Created.
ticket.updated_at string Last changed.
ticket.description string The original request text.
ticket.reply_channel string
chat, email
How a reply reaches the customer.
ticket.conversation_id integer The chat conversation behind the ticket, or null.
ticket.messages array The customer-visible thread. Internal notes are never included.
ticket.messages[].id integer Message id.
ticket.messages[].direction string
in, out
in is from the customer, out is from your team.
ticket.messages[].sender_type string Who wrote it.
ticket.messages[].sender_name string Display name only.
ticket.messages[].body string Message text.
ticket.messages[].attachments array Attachment details. Files themselves are not downloadable through the API.
ticket.messages[].attachments[].name string File name.
ticket.messages[].attachments[].mime string Content type.
ticket.messages[].attachments[].size integer Bytes.
ticket.messages[].created_at string Sent.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "tickets:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /tickets/{id}/notes tickets:write Add an internal note

A note for your team. It is recorded in the ticket history and is never shown to or sent to the customer.

Operation id addTicketNote

Path parameters
NameTypeDescription
idintegerTicket id.
Request body (JSON)
FieldTypeRequiredDescription
body string Yes Note text, up to 10000 characters.
Example
curl -X POST "https://app.klctai.com/api/tickets/123/notes" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"Reproduced on iOS 19. Passing to engineering."}'
Response 201
{
    "note": {
        "id": 5521,
        "body": "Reproduced on iOS 19. Passing to engineering.",
        "created_at": "2026-10-14T10:02:00Z"
    }
}
Response fields
FieldTypeDescription
note object
note.id integer Note id.
note.body string Note text.
note.created_at string Created.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "tickets:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /tickets/{id}/replies tickets:write Reply to the customer

Sends a real, customer-visible reply: by email, or into the chat if the ticket came from chat (which also takes the chat over from the AI). Records the first response time. Answers 422 if the requester has no email address or the send fails, and nothing is stored in that case.

Operation id replyToTicket

Path parameters
NameTypeDescription
idintegerTicket id.
Request body (JSON)
FieldTypeRequiredDescription
body string Yes Reply text, up to 16000 characters.
Example
curl -X POST "https://app.klctai.com/api/tickets/123/replies" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"Thanks Dana, we found the cause and a fix is rolling out today."}'
Response 201
{
    "message": {
        "id": 3302,
        "channel": "email",
        "direction": "out",
        "body": "Thanks Dana, we found the cause and a fix is rolling out today.",
        "created_at": "2026-10-14T10:05:00Z"
    }
}
Response fields
FieldTypeDescription
message object
message.id integer Message id.
message.channel string
chat, email
How it was sent.
message.direction string Always out.
message.body string Text sent.
message.created_at string Sent.
Errors
  • 401 Missing, invalid or expired token.
  • 403 Insufficient scope, or the Support module is switched off for the account.
  • 404 No such record in this account.
  • 422 Validation failed, or the reply could not be delivered (for example the requester has no email).
  • 429 Rate limit reached. Retry after the Retry-After header.

Chat conversations

Live chat and messaging threads (web chat, WhatsApp, Instagram, email). Needs the Support module on the account.

GET /conversations conversations:read List chat conversations

Most recently active first.

Operation id listConversations

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
status string One exact status, or `unresolved` for everything not resolved or closed.
contact_id integer Only conversations with this contact.
updated_since string Only conversations changed at or after this time.
Example
curl "https://app.klctai.com/api/conversations?status=unresolved" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "conversations": [
        {
            "id": 7781,
            "status": "open",
            "handling_mode": "ai",
            "channel": "web",
            "subject": null,
            "priority": "normal",
            "contact": {
                "contact_id": 90412,
                "name": "Dana",
                "last_name": "Cole",
                "email": "[email protected]",
                "phone": null
            },
            "ticket_id": null,
            "last_message_at": "2026-10-14T09:20:00Z",
            "resolved_at": null,
            "csat_rating": null,
            "created_at": "2026-10-14T09:18:00Z",
            "updated_at": "2026-10-14T09:20:00Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
conversations array The page of conversations.
conversations[].id integer Conversation id.
conversations[].status string open, pending, resolved, closed and others.
conversations[].handling_mode string
ai, human
Who is answering: the AI assistant or a person.
conversations[].channel string web, email, whatsapp, instagram and others.
conversations[].subject string Short title, if any.
conversations[].priority string Priority label.
conversations[].contact object The person chatting, or null if anonymous.
conversations[].contact.contact_id integer Contact id.
conversations[].contact.name string First name.
conversations[].contact.last_name string Last name.
conversations[].contact.email string Email.
conversations[].contact.phone string Phone.
conversations[].ticket_id integer Linked ticket, or null.
conversations[].last_message_at string Most recent customer-visible message.
conversations[].resolved_at string When it was resolved, or null.
conversations[].csat_rating integer The customer satisfaction rating, or null.
conversations[].created_at string Started.
conversations[].updated_at string Last changed.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "conversations:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /conversations/{id} conversations:read Get a conversation with its messages

Reading a conversation does not mark it read for your team.

Operation id getConversation

Path parameters
NameTypeDescription
idintegerConversation id.
Example
curl "https://app.klctai.com/api/conversations/123" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "conversation": {
        "id": 7781,
        "status": "open",
        "handling_mode": "ai",
        "channel": "web",
        "subject": null,
        "priority": "normal",
        "contact": {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": null
        },
        "ticket_id": null,
        "last_message_at": "2026-10-14T09:20:00Z",
        "resolved_at": null,
        "csat_rating": null,
        "created_at": "2026-10-14T09:18:00Z",
        "updated_at": "2026-10-14T09:20:00Z",
        "messages": [
            {
                "id": 1,
                "sender_type": "visitor",
                "sender_name": "Dana Cole",
                "body": "Do you open on Sundays?",
                "attachments": [],
                "created_at": "2026-10-14T09:18:00Z"
            },
            {
                "id": 2,
                "sender_type": "ai",
                "sender_name": "Assistant",
                "body": "Yes, 10am to 4pm.",
                "attachments": [],
                "created_at": "2026-10-14T09:18:05Z"
            }
        ]
    }
}
Response fields
FieldTypeDescription
conversation object
conversation.id integer Conversation id.
conversation.status string open, pending, resolved, closed and others.
conversation.handling_mode string
ai, human
Who is answering: the AI assistant or a person.
conversation.channel string web, email, whatsapp, instagram and others.
conversation.subject string Short title, if any.
conversation.priority string Priority label.
conversation.contact object The person chatting, or null if anonymous.
conversation.contact.contact_id integer Contact id.
conversation.contact.name string First name.
conversation.contact.last_name string Last name.
conversation.contact.email string Email.
conversation.contact.phone string Phone.
conversation.ticket_id integer Linked ticket, or null.
conversation.last_message_at string Most recent customer-visible message.
conversation.resolved_at string When it was resolved, or null.
conversation.csat_rating integer The customer satisfaction rating, or null.
conversation.created_at string Started.
conversation.updated_at string Last changed.
conversation.messages array Customer-visible messages, oldest first. Internal notes are never included.
conversation.messages[].id integer Message id.
conversation.messages[].sender_type string
visitor, operator, ai
Who wrote it.
conversation.messages[].sender_name string Display name only.
conversation.messages[].body string Message text.
conversation.messages[].attachments array Attachment details only.
conversation.messages[].attachments[].name string File name.
conversation.messages[].attachments[].mime string Content type.
conversation.messages[].attachments[].size integer Bytes.
conversation.messages[].created_at string Sent.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "conversations:read" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /conversations/{id}/messages conversations:write Send a reply in a conversation

Sends a real message to the customer on the conversation's channel, and takes the conversation over from the AI (handling_mode becomes human).

Operation id replyToConversation

Path parameters
NameTypeDescription
idintegerConversation id.
Request body (JSON)
FieldTypeRequiredDescription
body string Yes Message text, up to 16000 characters.
Example
curl -X POST "https://app.klctai.com/api/conversations/123/messages" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"Yes, we are open 10am to 4pm on Sundays."}'
Response 201
{
    "message": {
        "id": 3,
        "sender_type": "operator",
        "body": "Yes, we are open 10am to 4pm on Sundays.",
        "created_at": "2026-10-14T09:25:00Z"
    },
    "handling_mode": "human"
}
Response fields
FieldTypeDescription
message object
message.id integer Message id.
message.sender_type string Always operator.
message.body string Text sent.
message.created_at string Sent.
handling_mode string Now human.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "conversations:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /conversations/{id}/resolve conversations:write Resolve a conversation

Marks the conversation resolved. The customer may be asked for a rating, depending on the widget settings.

Operation id resolveConversation

Path parameters
NameTypeDescription
idintegerConversation id.
Example
curl -X POST "https://app.klctai.com/api/conversations/123/resolve" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "conversation": {
        "status": "resolved",
        "id": 7781,
        "handling_mode": "ai",
        "channel": "web",
        "subject": null,
        "priority": "normal",
        "contact": {
            "contact_id": 90412,
            "name": "Dana",
            "last_name": "Cole",
            "email": "[email protected]",
            "phone": null
        },
        "ticket_id": null,
        "last_message_at": "2026-10-14T09:20:00Z",
        "resolved_at": null,
        "csat_rating": null,
        "created_at": "2026-10-14T09:18:00Z",
        "updated_at": "2026-10-14T09:20:00Z"
    }
}
Response fields
FieldTypeDescription
conversation object
conversation.id integer Conversation id.
conversation.status string open, pending, resolved, closed and others.
conversation.handling_mode string
ai, human
Who is answering: the AI assistant or a person.
conversation.channel string web, email, whatsapp, instagram and others.
conversation.subject string Short title, if any.
conversation.priority string Priority label.
conversation.contact object The person chatting, or null if anonymous.
conversation.contact.contact_id integer Contact id.
conversation.contact.name string First name.
conversation.contact.last_name string Last name.
conversation.contact.email string Email.
conversation.contact.phone string Phone.
conversation.ticket_id integer Linked ticket, or null.
conversation.last_message_at string Most recent customer-visible message.
conversation.resolved_at string When it was resolved, or null.
conversation.csat_rating integer The customer satisfaction rating, or null.
conversation.created_at string Started.
conversation.updated_at string Last changed.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "conversations:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.

Changelog

The release notes on your customer portal. Use this to publish from a release pipeline. Publishing through the API never emails subscribers; entries are never deleted. Needs the Changelog module on the account.

GET /changelog changelog:read List changelog entries

Operation id listChangelog

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
status string
draft, published
Only drafts or only published entries.
Example
curl "https://app.klctai.com/api/changelog" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "entries": [
        {
            "id": 214,
            "title": "Faster ticket search",
            "slug": "faster-ticket-search",
            "version": "v2.4.0",
            "category": {
                "id": 3,
                "name": "Improvements"
            },
            "labels": [
                "New"
            ],
            "status": "published",
            "is_live": true,
            "published_at": "2026-10-14T12:00:00Z",
            "created_at": "2026-10-14T11:40:00Z",
            "updated_at": "2026-10-14T11:40:00Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
entries array The page of entries.
entries[].id integer Entry id.
entries[].title string Title.
entries[].slug string URL slug, unique in the portal.
entries[].version string Version label such as v2.4.0, or null.
entries[].category object Category, or null.
entries[].category.id integer Category id.
entries[].category.name string Category name.
entries[].labels array Free-form labels such as New or Fix.
entries[].status string
draft, published
draft is private, published is public once its date arrives.
entries[].is_live boolean True when published and its date has passed, so visitors can see it.
entries[].published_at string Publish time, or null for a draft.
entries[].created_at string Created.
entries[].updated_at string Last changed.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "changelog:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /changelog/{id} changelog:read Get a changelog entry

Operation id getChangelogEntry

Path parameters
NameTypeDescription
idintegerEntry id.
Example
curl "https://app.klctai.com/api/changelog/123" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "entry": {
        "id": 214,
        "title": "Faster ticket search",
        "slug": "faster-ticket-search",
        "version": "v2.4.0",
        "category": {
            "id": 3,
            "name": "Improvements"
        },
        "labels": [
            "New"
        ],
        "status": "published",
        "is_live": true,
        "published_at": "2026-10-14T12:00:00Z",
        "created_at": "2026-10-14T11:40:00Z",
        "updated_at": "2026-10-14T11:40:00Z",
        "content_html": "<p>Searching tickets is now twice as fast.</p>"
    }
}
Response fields
FieldTypeDescription
entry object
entry.id integer Entry id.
entry.title string Title.
entry.slug string URL slug, unique in the portal.
entry.version string Version label such as v2.4.0, or null.
entry.category object Category, or null.
entry.category.id integer Category id.
entry.category.name string Category name.
entry.labels array Free-form labels such as New or Fix.
entry.status string
draft, published
draft is private, published is public once its date arrives.
entry.is_live boolean True when published and its date has passed, so visitors can see it.
entry.published_at string Publish time, or null for a draft.
entry.created_at string Created.
entry.updated_at string Last changed.
entry.content_html string The body as sanitized HTML.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "changelog:read" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /changelog changelog:write Create a changelog entry

Saved as a draft unless you send status: published. The slug is generated from the title. Publishing fires the changelog.published webhook. Subscribers are not emailed.

Operation id createChangelogEntry

Request body (JSON)
FieldTypeRequiredDescription
title string Yes Title, 3 to 255 characters.
content string Yes Body up to 60000 characters. Plain text, Markdown or HTML; HTML is sanitized.
version string Version label, up to 50 characters.
category_id integer One of this portal's categories.
labels array Up to 10 labels, 40 characters each.
status string
draft, published
Defaults to draft on create.
published_at string When it goes live. Defaults to now when publishing.
Example
curl -X POST "https://app.klctai.com/api/changelog" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Faster ticket search","content":"Searching tickets is now **twice as fast**.","version":"v2.4.0","category_id":1,"labels":["New"],"status":"draft"}'
Response 201
{
    "entry": {
        "id": 214,
        "title": "Faster ticket search",
        "slug": "faster-ticket-search",
        "version": "v2.4.0",
        "category": {
            "id": 3,
            "name": "Improvements"
        },
        "labels": [
            "New"
        ],
        "status": "published",
        "is_live": true,
        "published_at": "2026-10-14T12:00:00Z",
        "created_at": "2026-10-14T11:40:00Z",
        "updated_at": "2026-10-14T11:40:00Z",
        "content_html": "<p>Searching tickets is now twice as fast.</p>"
    }
}
Response fields
FieldTypeDescription
entry object
entry.id integer Entry id.
entry.title string Title.
entry.slug string URL slug, unique in the portal.
entry.version string Version label such as v2.4.0, or null.
entry.category object Category, or null.
entry.category.id integer Category id.
entry.category.name string Category name.
entry.labels array Free-form labels such as New or Fix.
entry.status string
draft, published
draft is private, published is public once its date arrives.
entry.is_live boolean True when published and its date has passed, so visitors can see it.
entry.published_at string Publish time, or null for a draft.
entry.created_at string Created.
entry.updated_at string Last changed.
entry.content_html string The body as sanitized HTML.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "changelog:write" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
PATCH /changelog/{id} changelog:write Update a changelog entry

Send only the fields to change; an empty body is rejected. Published entries can be edited. Moving a draft to published fires changelog.published once.

Operation id updateChangelogEntry

Path parameters
NameTypeDescription
idintegerEntry id.
Request body (JSON)
FieldTypeRequiredDescription
title string Title, 3 to 255 characters.
content string Body up to 60000 characters. Plain text, Markdown or HTML; HTML is sanitized.
version string Version label, up to 50 characters.
category_id integer One of this portal's categories.
labels array Up to 10 labels, 40 characters each.
status string
draft, published
Defaults to draft on create.
published_at string When it goes live. Defaults to now when publishing.
Example
curl -X PATCH "https://app.klctai.com/api/changelog/123" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Faster ticket search","content":"Searching tickets is now **twice as fast**.","version":"v2.4.0","category_id":1,"labels":["New"],"status":"draft"}'
Response 200
{
    "entry": {
        "id": 214,
        "title": "Faster ticket search",
        "slug": "faster-ticket-search",
        "version": "v2.4.0",
        "category": {
            "id": 3,
            "name": "Improvements"
        },
        "labels": [
            "New"
        ],
        "status": "published",
        "is_live": true,
        "published_at": "2026-10-14T12:00:00Z",
        "created_at": "2026-10-14T11:40:00Z",
        "updated_at": "2026-10-14T11:40:00Z",
        "content_html": "<p>Searching tickets is now twice as fast.</p>"
    }
}
Response fields
FieldTypeDescription
entry object
entry.id integer Entry id.
entry.title string Title.
entry.slug string URL slug, unique in the portal.
entry.version string Version label such as v2.4.0, or null.
entry.category object Category, or null.
entry.category.id integer Category id.
entry.category.name string Category name.
entry.labels array Free-form labels such as New or Fix.
entry.status string
draft, published
draft is private, published is public once its date arrives.
entry.is_live boolean True when published and its date has passed, so visitors can see it.
entry.published_at string Publish time, or null for a draft.
entry.created_at string Created.
entry.updated_at string Last changed.
entry.content_html string The body as sanitized HTML.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "changelog:write" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.

Feedback and roadmap

The feature requests and roadmap from your customer portal. Read-only. Author names and emails are never returned. Needs the Feedback board and Roadmap modules on the account.

GET /feedback feedback:read List feedback posts

Most voted first.

Operation id listFeedback

Query parameters
NameTypeRequiredDescription
page integer Page number, starting at 1. (default 1)
per_page integer Rows per page, 1 to 100. (default 50)
status string Board status slug.
category_id integer Only posts in this category.
q string Matches the title or text.
Example
curl "https://app.klctai.com/api/feedback?status=planned" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "posts": [
        {
            "id": 88,
            "title": "Dark mode",
            "content": "Please add a dark theme to the dashboard.",
            "status": "planned",
            "category": {
                "id": 2,
                "name": "Feature request"
            },
            "vote_count": 41,
            "comments_count": 6,
            "on_roadmap": true,
            "created_at": "2026-09-02T08:30:00Z",
            "updated_at": "2026-10-01T10:00:00Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "total": 1
    }
}
Response fields
FieldTypeDescription
posts array The page of posts.
posts[].id integer Post id.
posts[].title string Title.
posts[].content string What the customer wrote.
posts[].status string Board status slug.
posts[].category object Category, or null.
posts[].category.id integer Category id.
posts[].category.name string Name.
posts[].vote_count integer Votes.
posts[].comments_count integer Comments.
posts[].on_roadmap boolean Whether the post sits on a roadmap column.
posts[].created_at string Posted.
posts[].updated_at string Last changed.
meta object Pagination state.
meta.current_page integer Page just returned, starting at 1.
meta.last_page integer Last page available for this query.
meta.total integer Total rows matching the query.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "feedback:read" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /roadmap feedback:read Get the roadmap

The roadmap columns in display order, each with its posts. Not paginated.

Operation id getRoadmap

Example
curl "https://app.klctai.com/api/roadmap" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "statuses": [
        {
            "slug": "planned",
            "name": "Planned",
            "color": "#6366f1",
            "posts": [
                {
                    "id": 88,
                    "title": "Dark mode",
                    "content": "Please add a dark theme to the dashboard.",
                    "status": "planned",
                    "category": {
                        "id": 2,
                        "name": "Feature request"
                    },
                    "vote_count": 41,
                    "comments_count": 6,
                    "on_roadmap": true,
                    "created_at": "2026-09-02T08:30:00Z",
                    "updated_at": "2026-10-01T10:00:00Z"
                }
            ]
        }
    ]
}
Response fields
FieldTypeDescription
statuses array Roadmap columns.
statuses[].slug string Column slug.
statuses[].name string Column name.
statuses[].color string Column color.
statuses[].posts array Posts in this column.
statuses[].posts[].id integer Post id.
statuses[].posts[].title string Title.
statuses[].posts[].content string What the customer wrote.
statuses[].posts[].status string Board status slug.
statuses[].posts[].category object Category, or null.
statuses[].posts[].category.id integer Category id.
statuses[].posts[].category.name string Name.
statuses[].posts[].vote_count integer Votes.
statuses[].posts[].comments_count integer Comments.
statuses[].posts[].on_roadmap boolean Whether the post sits on a roadmap column.
statuses[].posts[].created_at string Posted.
statuses[].posts[].updated_at string Last changed.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "feedback:read" scope (error: insufficient_scope).
  • 429 Rate limit reached. Retry after the Retry-After header.

Events and webhooks

Send events into KLCT to start sequences, and subscribe to events KLCT sends back. See the Webhooks guide for payloads and the signature check.

POST /integrations/sprwt/events sequences:trigger Forward a product event to start a sequence

Forwards something that happened in your product (order placed, cart abandoned, subscription renewed) so a matching KLCT sequence can enroll the contact. Contacts created this way are quarantined "shadow" contacts unless the account has opted in to exporting real contacts. If attribution carries ad parameters and trigger is order_create, user_create or user_subscriptions_create, a conversion is recorded for ad optimisation.

Operation id sendEvent

Request body (JSON)
FieldTypeRequiredDescription
trigger string Yes Event key a sequence trigger listens for.
external_ref string Your own id for the event, for traceability.
contact object Yes Who the event is about.
contact.name string Yes First name.
contact.last_name string Last name.
contact.email string Email. Either email or phone is required.
contact.phone string Phone.
metadata object Extra data stored on the contact.
attribution object utm_source, utm_medium, utm_campaign, utm_content, utm_term, fbclid, gclid, klct_ad.
kpi object Revenue for purchase events.
kpi.value_minor integer Order value in minor units (cents).
kpi.currency string ISO currency code.
Example
curl -X POST "https://app.klctai.com/api/integrations/sprwt/events" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"trigger":"order_create","external_ref":"order_8841","contact":{"name":"Dana","email":"[email protected]"},"kpi":{"value_minor":4900,"currency":"USD"}}'
Response 200
{
    "status": "ok",
    "enrolled": 1
}
Response fields
FieldTypeDescription
status string "ok".
enrolled integer How many sequences the contact was enrolled in.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "sequences:trigger" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
GET /oauth-platform/webhooks webhooks:manage List your webhook subscriptions

Only subscriptions created by the calling connection are visible. Requires an OAuth connection, not a manual API key.

Operation id listWebhooks

Example
curl "https://app.klctai.com/api/oauth-platform/webhooks" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "webhooks": [
        {
            "id": 7,
            "event_type": "contact.created",
            "target_url": "https://acme.example/hooks/klct",
            "is_active": true,
            "created_at": "2026-10-01T12:00:00Z"
        }
    ]
}
Response fields
FieldTypeDescription
webhooks array Subscriptions.
webhooks[].id integer Subscription id.
webhooks[].event_type string Event name.
webhooks[].target_url string Your HTTPS endpoint.
webhooks[].is_active boolean Whether deliveries are on.
webhooks[].created_at string Created.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "webhooks:manage" scope (error: insufficient_scope).
  • 429 Rate limit reached. Retry after the Retry-After header.
POST /oauth-platform/webhooks webhooks:manage Subscribe to an event

The signing_secret is returned once. Store it; you need it to verify the X-KLCT-Signature header. Requires an OAuth connection, not a manual API key.

Operation id createWebhook

Request body (JSON)
FieldTypeRequiredDescription
event_type string Yes One of the supported events (see the Webhooks guide).
target_url string Yes HTTPS URL KLCT will POST to.
Example
curl -X POST "https://app.klctai.com/api/oauth-platform/webhooks" \
  -H "Authorization: Bearer $KLCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event_type":"contact.created","target_url":"https://acme.example/hooks/klct"}'
Response 201
{
    "id": 7,
    "event_type": "contact.created",
    "target_url": "https://acme.example/hooks/klct",
    "signing_secret": "3f8c0b6d1e..."
}
Response fields
FieldTypeDescription
id integer Subscription id.
event_type string Event name.
target_url string Your URL.
signing_secret string HMAC secret. Shown once.
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "webhooks:manage" scope (error: insufficient_scope).
  • 422 Validation failed.
  • 429 Rate limit reached. Retry after the Retry-After header.
DELETE /oauth-platform/webhooks/{webhook} webhooks:manage Remove a webhook subscription

Operation id deleteWebhook

Path parameters
NameTypeDescription
webhookintegerSubscription id.
Example
curl -X DELETE "https://app.klctai.com/api/oauth-platform/webhooks/123" \
  -H "Authorization: Bearer $KLCT_API_KEY"
Response 200
{
    "status": "ok"
}
Response fields
FieldTypeDescription
status string "ok".
Errors
  • 401 Missing, invalid or expired token.
  • 403 The token does not carry the "webhooks:manage" scope (error: insufficient_scope).
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.

Meetings

Bookable meeting types, for embedding a booking link in another product.

GET /{account}/meetings first-party token List an account's bookable meetings

First-party only. Uses the shared machine-to-machine token (APP_API_TOKEN) instead of an account key, and is keyed by account id because slugs can be renamed. Account API keys and OAuth tokens are not accepted here.

Operation id listMeetings

Path parameters
NameTypeDescription
accountintegerKLCT account id.
Example
curl "https://app.klctai.com/api/123/meetings" \
  -H "Authorization: Bearer $KLCT_APP_API_TOKEN"
Response 200
{
    "ok": true,
    "account": {
        "id": 4821,
        "name": "Acme Dental"
    },
    "meetings": [
        {
            "id": 31,
            "name": "Intro call",
            "slug": "intro-call",
            "description": "15 minutes to talk through your goals.",
            "duration_minutes": 15,
            "location_type": "video",
            "url": "https://app.klctai.com/book/acme/intro-call"
        }
    ]
}
Response fields
FieldTypeDescription
ok boolean True.
account object
account.id integer Account id.
account.name string Account name.
meetings array
meetings[].id integer Meeting type id.
meetings[].name string Name.
meetings[].slug string URL slug.
meetings[].description string Description.
meetings[].duration_minutes integer Length.
meetings[].location_type string Where it happens (video, phone, in person).
meetings[].url string Public booking URL.
Errors
  • 401 Missing, invalid or expired token.
  • 404 No such record in this account.
  • 429 Rate limit reached. Retry after the Retry-After header.