Skip to main content
GET /api/v1/interaction-summaries reads rows from the materialized interaction-summary store — the 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 read helpers directly.

What it does

Each materialized row aggregates raw interaction events for one (customerId, offerId | creativeId | channelId, periodType, periodKey) tuple. The endpoint 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:
Response (abbreviated):

How it works

Authentication and roles

The endpoint requires the admin, editor, or viewer role and a tenant header. All reads are scoped by tenantId. Cross-tenant reads are not possible.

Filter composition

The endpoint composes a filter from query parameters. tenantId and customerId are always required. Optional periodType, offerId, and channelId filters are AND-ed onto the base clause when supplied. Results are ordered 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. Cursor pagination is not supported — for large result sets, narrow the query with periodType or offerId filters.

Reference

Query Parameters

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

Response

Returned at route.ts:54-73.
array
Matching summary rows, ordered by (periodType ASC, periodKey DESC).
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.
string
daily, weekly, monthly, or alltime.
string
Period bucket identifier. For daily, YYYY-MM-DD. For weekly, YYYY-Www. For monthly, YYYY-MM. For alltime, the literal "alltime".
string
string | null
Present when the summary is per-offer. Null on aggregate-by-channel rows.
string | null
string | null
number
Count of impression-class outcomes in this period.
number
Count of outcomes whose OutcomeType.classification is "positive".
number
Count of outcomes whose classification is "negative".
number
Count of outcomes whose classification is "neutral".
number
Count of conversions specifically (typically a subset of positive).
number
Sum of conversionValue across all positive outcomes in this period.
string | null
ISO timestamp of the most recent interaction in this period.
string | null
OutcomeType.key of the most recent interaction.
string | null
"inbound" or "outbound" — the direction of the most recent interaction.

Status codes

Required headers

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.