
The decision flow whose outcomes /respond records.
POST /api/v1/respond
Records the outcome of a recommendation delivered via the Recommend API. Outcomes feed into behavioral metrics, adaptive model training, experiment analysis, and attribution.Data plane. This endpoint is part of the public data plane — the runtime
decisioning loop (
/recommend, /respond, /respond/bulk, /capture) —
and is callable with any krn_ API key, including default keys minted
without scopes. Management endpoints, by contrast, require control-plane
access (see API Keys).Minimal Request (Recommended)
The smallest way to record an outcome — 5 fields. The system resolves the offer, creative, and channel from the recommendation record.idempotencyKey is required on every call (a request without it is rejected with 400).
Full Request (all optional fields)
Field Reference
Fields marked Yes* require at least one resolution path. You must provide either
creativeId OR recommendationId + rank to identify the offer. The field outcome is required (the legacy alias interactionType is also accepted).Prior to this fix,
channelId was only resolved when offerId was missing, causing per-channel frequency caps to be silently broken.Attribution — how recommendationId + rank resolves the offer
When recommendationId and rank are supplied (without a direct creativeId), the route looks up the original recommendation through a three-tier ladder. Tiers run in order; the first match wins.
If none of the three tiers resolve a row, the route returns
400 No recommendation found for customer=… rank=….
Limitation: multi-placement /recommend responses return a recommendationId in the response body but write no interaction_history rows (pre-existing behaviour). Precise tier-1 attribution therefore applies to single-flow recommendations and auto-impressions only. Multi-placement callers should supply creativeId directly.
Idempotency
Every call to the Respond API requires an idempotency key to prevent duplicate outcome recording. You can provide it in two ways:- Request body:
"idempotencyKey": "unique-key-here" - HTTP header:
Idempotency-Key: unique-key-here
"status": "already_recorded" and a 200 status code (not 201).
recorded_without_adaptation (200). A positive outcome for a (customer, offer, creative) combination that has no matching recommendation and no recent impression (a 30-day lookback) is still written, but adaptive-model training is skipped so a phantom positive can’t inflate attribution past delivery. In that case the response is { "ok": true, "status": "recorded_without_adaptation", "interactionId": "…", "warning": "…" } with a 200 status. Impression/neutral/negative outcomes are unaffected.GET /api/v1/recommend now writes the same recommendation and impression rows as POST and returns interactionId/recommendationId, so outcomes for GET-served recommendations attribute normally — GET-driven traffic no longer lands in recorded_without_adaptation when you close the loop with recommendationId + rank.Model adaptations — attributed to the model that decided
Per-offer/category/channel/direction/global model adaptations (the propensity-learning rows the scorer reads back — see Adaptive Learning) are written for exactly one model: the model that produced the decision. The target model is resolved from the delivery row’s recordedmodelId; if no delivery row for this (customer, offer) carries a model attribution, no adaptation rows are written at all, rather than fanning the outcome out to every active model. Models are isolated — one model’s outcome stream can no longer shift another model’s scores through shared adaptation cells.
Separately, per-outcome incremental model-state updates still apply to every active incremental-type model (bayesian, thompson_bandit, epsilon_greedy, online_learner), using the request’s attributes as the feature bag. For bandits configured with armScope: "offer_channel", the outcome’s channelId selects the arm — the update lands on <offerId>:<channelId> so channel-specific stats accumulate separately (see How Scoring Works §6c).
Response: New Record (201)
The response includes enriched names for easy display without additional lookups.Response: Duplicate (200)
When the sameidempotencyKey is sent again:
Outcome Types
Outcomes must be registered in the platform before they can be recorded. Each outcome type has a classification that drives downstream behavior:
Register outcome types in Studio > Outcome Types or via
POST /api/v1/outcome-types.
Examples
Record an Impression
Record a Click
Record a Conversion with Value
Error Responses
Example error (unknown outcome type):