Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt

Use this file to discover all available pages before exploring further.

GET /api/v1/interaction-summaries reads rows from the InteractionSummary table — the materialized aggregate that powers contact-policy enforcement, behavioral-metric values, and frequency-cap evaluation. The endpoint is intended for verification, debugging, and integration tests; production read paths use the cached helpers in lib/interaction-summary directly.

What it does

Each row in InteractionSummary aggregates raw InteractionHistory rows for one (customerId, offerId | creativeId | channelId, periodType, periodKey) tuple. The route at src/app/api/v1/interaction-summaries/route.ts:22-77 accepts query filters and returns the matching rows with the count fields (impressions, positive, negative, neutral, converts, totalValue) and last-contact metadata. customerId is required — there is no all-customers list mode. For the related materialized-aggregate writer and decay/retention rules, see Interaction Summary.

Quick start

Read all summaries for a customer across every period type:
curl 'https://playground.kaireonai.com/api/v1/interaction-summaries?customerId=cust_42' \
  -H "X-API-Key: krn_your_api_key" \
  -H "X-Tenant-Id: 5a9904b9-..."
Response (abbreviated):
{
  "data": [
    {
      "periodType": "daily",
      "periodKey": "2026-04-30",
      "customerId": "cust_42",
      "offerId": "off_premium_card",
      "creativeId": "crv_email_a",
      "channelId": "ch_email",
      "impressions": 3,
      "positive": 1,
      "negative": 0,
      "neutral": 0,
      "converts": 0,
      "totalValue": 0,
      "lastContactAt": "2026-04-30T14:22:01.123Z",
      "lastOutcomeKey": "click",
      "direction": "outbound"
    }
  ],
  "total": 1
}

How it works

Authentication and roles

requireRole(request, "admin", "editor", "viewer") runs first (route.ts:26). All subsequent reads scope by tenantId from requireTenant. Cross-tenant reads are not possible.

Filter composition

The handler composes a where clause from query parameters at route.ts:35-44. tenantId and customerId are always present. Optional periodType, offerId, and channelId filters are AND-ed onto the base clause when supplied. The query orders results by (periodType ASC, periodKey DESC) so the most recent period in each type appears first.

Pagination

A single integer limit clamps results to 200 maximum (route.ts:46). Cursor pagination is not supported — for large result sets, narrow the query with periodType or offerId filters.

Reference

Query Parameters

customerId
string
required
Customer identifier. Missing this parameter returns 400 customerId is required (route.ts:31-33).
periodType
string
One of daily, weekly, monthly, alltime. When omitted, all period types are returned.
offerId
string
Filter to summaries for one offer.
channelId
string
Filter to summaries for one channel.
limit
number
default:"50"
Maximum rows returned. Clamped to [1, 200] (route.ts:46).

Response

Returned at route.ts:54-73.
data
array
Matching summary rows, ordered by (periodType ASC, periodKey DESC).
total
number
Length of data after the limit was applied. Not the total matching count — there is no separate count query.

data[] per-item shape

Built at route.ts:55-71.
periodType
string
daily, weekly, monthly, or alltime.
periodKey
string
Period bucket identifier. For daily, YYYY-MM-DD. For weekly, YYYY-Www. For monthly, YYYY-MM. For alltime, the literal "alltime".
customerId
string
offerId
string | null
Present when the summary is per-offer. Null on aggregate-by-channel rows.
creativeId
string | null
channelId
string | null
impressions
number
Count of impression-class outcomes in this period.
positive
number
Count of outcomes whose OutcomeType.classification is "positive".
negative
number
Count of outcomes whose classification is "negative".
neutral
number
Count of outcomes whose classification is "neutral".
converts
number
Count of conversions specifically (typically a subset of positive).
totalValue
number
Sum of conversionValue across all positive outcomes in this period.
lastContactAt
string | null
ISO timestamp of the most recent interaction in this period.
lastOutcomeKey
string | null
OutcomeType.key of the most recent interaction.
direction
string | null
"inbound" or "outbound" — the direction of the most recent interaction.

Status codes

CodeWhenSource
200Returns matching rows (possibly empty)route.ts:54
400Missing customerId query parameterroute.ts:31-33
401Caller is not authenticatedrequireRole
403Caller is not viewer, editor, or adminroute.ts:26
500Unexpected errorroute.ts:75

Required headers

HeaderRequiredRead atPurpose
X-API-KeyYes (one of the two)tenant.ts:97API key (krn_…)
X-Tenant-IdYes (one of the two)tenant.ts:113Direct tenant id

Honest limits

  • The total field counts rows in the current response, not total matching rows. Callers needing a true count must run a separate COUNT(*) query against the underlying table.
  • No cursor pagination. With limit capped at 200, a customer with > 200 daily rows must narrow the query (e.g., add periodType=monthly or offerId=...) to read the rest.
  • The 400 and 500 envelopes use the legacy { title, detail } shape (route.ts:32) rather than the standard apiError envelope from lib/api-error.ts. Callers parsing errors should accept both shapes.