List contacts
Returns one page of contacts, oldest change first.
This endpoint is also the export. Set limit to 500 and follow
next_cursor until it is null.
To read only what changed, send updated_since. The order makes this
safe: a contact that changes during the export moves forward in the
order, not backward, so the export cannot miss it.
curl --request GET \
--url https://kaisupport.com/api/v1/contacts \
--header 'Authorization: Bearer <token>'import requests
url = "https://kaisupport.com/api/v1/contacts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://kaisupport.com/api/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://kaisupport.com/api/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://kaisupport.com/api/v1/contacts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://kaisupport.com/api/v1/contacts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://kaisupport.com/api/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "4f1d6f2a-6a1e-4a0b-9c53-2a9a1f0c77d1",
"external_id": "DK-1042",
"name": "Ayşe Yılmaz",
"email": "ayse@example.com",
"phone": "+905321112233",
"note": "The parent buys. The student is in year 12.",
"attributes": {
"package": "Tam Destek",
"exam_year": 2027
},
"owner": {
"member_id": "2c9a0d51-6f4b-4b0d-8f2a-1d3c5e7a9b11",
"name": "Baran Demir",
"email": "baran@example.com"
},
"created_at": "2026-03-02T09:14:00.000Z",
"updated_at": "2026-08-05T18:20:11.000Z"
}
],
"next_cursor": "MjAyNi0wOC0wNVQxODoyMDoxMS4wMDBafDRmMWQ2ZjJh",
"has_more": true
}{
"error": {
"type": "invalid_request",
"message": "Send at least one of \"external_id\", \"email\", \"phone\" or \"identities\" so the contact can be matched."
}
}{
"error": {
"type": "unauthorized",
"message": "That API key is not valid."
}
}{
"error": {
"type": "forbidden",
"message": "This key does not have the \"contacts:write\" permission."
}
}Authorizations
Send your API key as Authorization: Bearer kai_live_....
Make, edit and revoke keys in Kai, under Settings → API. Kai stores only a hash of the key, so the full key is shown one time. You can change the name and the permissions of a key later without a new key.
Query Parameters
Contacts per page, from 1 to 500. The default is 50.
1 <= x <= 500The next_cursor value of the last response.
Matches a part of the name, email, phone or external id.
A member id to list the contacts of one account owner. Send none
for the contacts that have no owner.
An ISO 8601 timestamp. Returns contacts changed at or after it.
curl --request GET \
--url https://kaisupport.com/api/v1/contacts \
--header 'Authorization: Bearer <token>'import requests
url = "https://kaisupport.com/api/v1/contacts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://kaisupport.com/api/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://kaisupport.com/api/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://kaisupport.com/api/v1/contacts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://kaisupport.com/api/v1/contacts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://kaisupport.com/api/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "4f1d6f2a-6a1e-4a0b-9c53-2a9a1f0c77d1",
"external_id": "DK-1042",
"name": "Ayşe Yılmaz",
"email": "ayse@example.com",
"phone": "+905321112233",
"note": "The parent buys. The student is in year 12.",
"attributes": {
"package": "Tam Destek",
"exam_year": 2027
},
"owner": {
"member_id": "2c9a0d51-6f4b-4b0d-8f2a-1d3c5e7a9b11",
"name": "Baran Demir",
"email": "baran@example.com"
},
"created_at": "2026-03-02T09:14:00.000Z",
"updated_at": "2026-08-05T18:20:11.000Z"
}
],
"next_cursor": "MjAyNi0wOC0wNVQxODoyMDoxMS4wMDBafDRmMWQ2ZjJh",
"has_more": true
}{
"error": {
"type": "invalid_request",
"message": "Send at least one of \"external_id\", \"email\", \"phone\" or \"identities\" so the contact can be matched."
}
}{
"error": {
"type": "unauthorized",
"message": "That API key is not valid."
}
}{
"error": {
"type": "forbidden",
"message": "This key does not have the \"contacts:write\" permission."
}
}