Search recipes
Copy-paste Kustomer search queries for common tasks, plus patterns for exporting beyond the 100-page limit, incremental sync and counting.
Ready-to-use bodies for POST /v1/customers/search. See Search for the full syntax and every attribute.
Conversations
{
"queryContext": "conversation",
"and": [
{ "conversation_status": { "equals": "open" } },
{ "conversation_priority": { "gte": 4 } }
]
}{
"queryContext": "conversation",
"and": [
{ "conversation_status": { "in": ["open", "snoozed"] } },
{ "conversation_assigned_users": { "not_set": "" } }
]
}{
"queryContext": "conversation",
"and": [{ "conversation_created_at": { "gte": "2026-10-19" } }],
"timeZone": "America/Los_Angeles"
}{
"queryContext": "conversation",
"and": [{ "conversation_custom_orderNumberStr": { "equals": "4417" } }]
}Customers
{
"queryContext": "customer",
"and": [{ "customer_email": { "contains": "@acme.example" } }]
}{
"queryContext": "customer",
"and": [{ "conversation_status": { "equals": "open" } }],
"sort": [{ "customer_last_message_at": "desc" }]
}{
"queryContext": "customer",
"and": [
{ "customer_custom_loyaltyTierStr": { "equals": "gold" } },
{ "customer_last_seen_at": { "lt": "2026-09-21" } }
]
}Messages and custom objects
{
"queryContext": "message",
"and": [
{ "message_body": { "contains": "refund" } },
{ "message_direction": { "equals": "in" } },
{ "message_created_at": { "gte": "2026-10-01" } }
]
}{
"queryContext": "kobject_ecomm-order",
"and": [{ "kobject_custom_ecomm-order_orderStatusStr": { "equals": "shipped" } }]
}Check attribute names against the standard attributes tables. A misspelled attribute returns an error or no results.
Export more than the page limit
Search stops at 10,000 results per query: a request where page × pageSize is over 10,000 returns 400, so with pageSize=500 the last page you can read is 20. To export everything, split the query into time windows on updated_at and page through each window. Make each window small enough to stay under the limit.
async function* exportConversations(from: Date, to: Date, windowHours = 24) {
for (let start = from; start < to; start = new Date(start.getTime() + windowHours * 3_600_000)) {
const end = new Date(Math.min(start.getTime() + windowHours * 3_600_000, to.getTime()))
const body = {
queryContext: 'conversation',
and: [
{ conversation_updated_at: { gte: start.toISOString() } },
{ conversation_updated_at: { lt: end.toISOString() } },
],
}
for (let page = 1; ; page++) {
const res = await kfetch(`/v1/customers/search?page=${page}&pageSize=500`, { method: 'POST', body: JSON.stringify(body) })
const { data, meta } = await res.json()
yield* data
if (page >= (meta.totalPages ?? 0)) break
if (page * 500 >= 10_000) throw new Error(`Window ${start.toISOString()} has more than 10,000 results; use a smaller window`)
}
}
}UTC timestamps (with Z) are used exactly, so adjacent windows with gte and lt never overlap or leave gaps.
Incremental sync
After a full export, only fetch what changed since your last run:
const since = await store.get('lastRun') // ISO timestamp
const runStartedAt = new Date().toISOString()
for await (const c of exportConversations(new Date(since), new Date(runStartedAt), 6)) await upsertLocal(c)
await store.set('lastRun', runStartedAt)Store the time the run started, not finished, so records updated during the run are picked up next time. Search updates asynchronously, so overlap windows by a few minutes if you can tolerate duplicates.
Count without fetching
Use pageSize=1 and read meta.total:
curl -X POST "https://acme.api.kustomerapp.com/v1/customers/search?pageSize=1" \
-H "Authorization: Bearer $KUSTOMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "queryContext": "conversation", "and": [{ "conversation_status": { "equals": "open" } }] }' \
| jq '.meta.total'IDs only
For large syncs where you'll fetch records separately, add ?idsOnly=true to return only matching IDs.
Records older than two years
Search only covers records updated in the last two years. Send the same body to archive search for older data.
Remember the limits
- Machine users can make 100 search requests per minute.
- Sorting only works on customer attributes.
- Results reflect the search index, which can lag writes by a moment.