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.

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. The smallest way to record an outcome — 4 fields. The system resolves the offer, creative, and channel from the recommendation record.
{
  "customerId": "CUST001",
  "recommendationId": "rec_7f3a2b1c-9d4e-5f6a-8b7c-0d1e2f3a4b5c",
  "rank": 1,
  "outcome": "click"
}

Full Request (all optional fields)

{
  "customerId": "CUST001",
  "recommendationId": "rec_7f3a2b1c-9d4e-5f6a-8b7c-0d1e2f3a4b5c",
  "rank": 1,
  "outcome": "click",
  "idempotencyKey": "click-cust001-offer001-1710590400",
  "creativeId": "creative_001",
  "offerId": "offer_001",
  "channelId": "channel_email",
  "direction": "outbound",
  "context": {
    "source": "email_campaign_q1",
    "utm_medium": "email"
  },
  "outcomeDetails": {
    "product_sku": "SKU-12345"
  },
  "timestamp": "2026-03-16T14:35:00.000Z",
  "conversionValue": 149.99
}

Field Reference

FieldTypeRequiredDescription
customerIdstringYesThe customer who interacted with the recommendation
recommendationIdstringYes*The recommendationId from the Recommend API response. Used with rank to resolve the offer automatically
rankintegerYes*Which offer from the recommendation (1-based). Combined with recommendationId to look up the offer, creative, and channel
outcomestringYesThe outcome type key (e.g., "click", "accept", "dismiss", "convert", "renewed", "unsubscribed"). Must match a registered Outcome Type
creativeIdstringNoExplicit Creative ID. Only needed if NOT using recommendationId + rank
idempotencyKeystringYesRequired. Unique key to prevent double-counting. Returns 400 if missing. Can also be sent as Idempotency-Key header
interactionIdstringNoLegacy field — use recommendationId instead
directionstringNoDirection of the interaction: "inbound" or "outbound". Default is "inbound" for most outcome types. For impression-category outcomes (e.g., impression, delivery), the default is "outbound" since those represent platform-initiated contacts
offerIdstringNoOffer ID. If omitted, resolved automatically from the creative’s parent offer
channelIdstringNoChannel ID. Always resolved from the creative when creativeId is provided, regardless of whether channelId is passed in the request body. This ensures interaction summaries and contact policy frequency caps use the correct channel.
placementIdstringNoPlacement ID. If omitted, resolved from the creative
rankintegerNoThe position at which the offer was displayed (1-indexed)
contextobjectNoArbitrary context metadata (campaign source, UTM params, etc.)
outcomeDetailsobjectNoStructured details about the outcome (e.g., product SKU, plan selected)
timestampstringNoISO 8601 timestamp of the interaction. Defaults to the current server time
conversionValuenumberNoMonetary value of the conversion (used for attribution and revenue reporting)
attributesobjectNoAdditional attributes (device, browser, etc.). Used for online model learning
channelstringNoChannel name string (stored in context)
placementstringNoPlacement name string (stored in context)
responseTimenumberNoTime in milliseconds the customer took to respond
deviceTypestringNoDevice type string (stored in context)
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.

Idempotency

Every call to the Respond API requires an idempotency key to prevent duplicate outcome recording. You can provide it in two ways:
  1. Request body: "idempotencyKey": "unique-key-here"
  2. HTTP header: Idempotency-Key: unique-key-here
If both are provided, the body value takes precedence. When a duplicate key is detected, the API returns the original record with "status": "already_recorded" and a 200 status code (not 201).

Response: New Record (201)

The response includes enriched names for easy display without additional lookups.
{
  "interactionId": "550e8400-e29b-41d4-a716-446655440000",
  "recommendationId": "c6184851-54f8-49df-88e3-0b11ed3b9fcb",
  "customerId": "CUST-00500",
  "outcome": "click",
  "classification": "positive",
  "rank": 4,
  "offerName": "Multi-Policy Bundle",
  "creativeName": "Multi-Policy Bundle — Email",
  "channelName": "Email Campaign",
  "categoryName": "Policy Renewal",
  "status": "recorded",
  "timestamp": "2026-03-16T14:35:00.000Z"
}

Response: Duplicate (200)

When the same idempotencyKey is sent again:
{
  "interactionId": "550e8400-e29b-41d4-a716-446655440000",
  "recommendationId": "c6184851-54f8-49df-88e3-0b11ed3b9fcb",
  "customerId": "CUST-00500",
  "outcome": "click",
  "status": "already_recorded",
  "timestamp": "2026-03-16T14:35:00.000Z"
}

Outcome Types

Outcomes must be registered in the platform before they can be recorded. Each outcome type has a classification that drives downstream behavior:
ClassificationEffectExamples
positiveIncrements positive counters, triggers attribution, updates adaptive models with rewardclick, accept, convert, purchase
negativeIncrements negative counters, updates models with penaltydismiss, not_interested, unsubscribe
neutralTracked but does not affect scoringimpression, view, requested_info
Register outcome types in Studio > Outcome Types or via POST /api/v1/outcome-types.

Examples

Record an Impression

curl -X POST https://playground.kaireonai.com/api/v1/respond \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -H "X-Api-Key: sk_live_abc123" \
  -d '{
    "customerId": "CUST001",
    "creativeId": "creative_001",
    "outcome": "impression",
    "idempotencyKey": "imp-cust001-creative001-20260316-1430",
    "interactionId": "550e8400-e29b-41d4-a716-446655440000",
    "rank": 1
  }'

Record a Click

curl -X POST https://playground.kaireonai.com/api/v1/respond \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -H "X-Api-Key: sk_live_abc123" \
  -d '{
    "customerId": "CUST001",
    "creativeId": "creative_001",
    "outcome": "click",
    "idempotencyKey": "click-cust001-creative001-20260316-1435",
    "interactionId": "550e8400-e29b-41d4-a716-446655440000",
    "context": {
      "source": "email_hero_banner"
    }
  }'

Record a Conversion with Value

curl -X POST https://playground.kaireonai.com/api/v1/respond \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -H "X-Api-Key: sk_live_abc123" \
  -d '{
    "customerId": "CUST001",
    "creativeId": "creative_001",
    "outcome": "convert",
    "idempotencyKey": "conv-cust001-offer001-20260316",
    "interactionId": "550e8400-e29b-41d4-a716-446655440000",
    "offerId": "offer_001",
    "conversionValue": 299.99,
    "outcomeDetails": {
      "plan": "platinum",
      "term_months": 12
    }
  }'

Error Responses

StatusCause
400Missing required fields (customerId, creativeId/treatmentId, outcome/interactionType, idempotencyKey), unknown outcome type, or invalid JSON
401Missing or invalid X-Tenant-Id / API key
404Creative not found or belongs to another tenant
415Content-Type header is not application/json
429Rate limit exceeded (1,000 requests per 60s per tenant)
500Internal server error
Example error (unknown outcome type):
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "Unknown outcome type: \"purchased\". Register it in Outcome Types first.",
    "status": 400,
    "recommendationId": "d4e5f678-90ab-cdef-1234-567890abcdef",
    "timestamp": "2026-03-16T14:35:00.000Z"
  }
}
See also: Outcome Types | Behavioral Metrics | API Tutorial