Skip to main content

Events API rules

Scope​

This page defines runtime rules for:

  • POST /events
  • POST /events/batch

It focuses on behavior rules and processing semantics. Endpoint fields are covered by API reference docs.

Event identity and request idempotency​

Every item must provide an opaque event_key. Vluna binds that key to one event fact within the resolved Billing User:

  • the first (realm_id, billing_user_id, event_key) write creates the stored event
  • every later submission of that key for the same user returns the stored event, even when event fields, payload, or labels differ
  • different users may reuse the same event key
  • two events may have identical content when they use different event keys

The key defines the producer's desired uniqueness boundary. For example, use signup for one signup event per user, login:2026-09-08 for one login event per user per day, or invite-accepted:inv_456 for one acceptance of an invitation. Vluna does not require event type or event key registration.

Idempotency-Key is separate: it identifies the HTTP write request. A batch has one request-level Idempotency-Key, while every item has its own durable event_key. Reuse the request key for an exact transport retry. Use a new request key when intentionally making another submission that converges on an existing business event key.

request_hash is a diagnostic digest of the canonicalized facts accepted by the first write:

  • Realm and resolved Billing Account/User
  • semantic_kind
  • event_type
  • normalized occurred_at
  • subject_ref
  • nested payload
  • normalized labels

Neither event_key nor the transport Idempotency-Key is part of that digest. The digest is not used to decide whether an event key is a duplicate.

User, account, and realm scoping​

  • Billing user is resolved from X-Principal-Id + X-User-Id or from bearer token claims, not caller-provided body identity.
  • Billing account remains the parent payor scope for the resolved user.
  • Writes are user-scoped and realm-scoped.
  • Missing runtime user context rejects the request.

Validation rules​

  • Runtime context must include a resolved billing_user_id.
  • event_key is required, must not have surrounding whitespace, and has a max length of 255.
  • semantic_kind must be activity or outcome.
  • occurred_at must be a valid timestamp.
  • event_type is developer-managed, case-sensitive, required, must have no surrounding whitespace, and has a max length of 128.
  • subject_ref is optional; max length is 256.
  • payload must be a JSON object; omitted payload defaults to {}.

occurred_at semantics​

occurred_at is the business event time, not request arrival time.

How it is used:

  • Policy version resolution uses occurred_at to choose the effective active version.
  • Contract term resolution uses occurred_at to select effective term values.
  • Event-to-rating conversion evaluates rules against the event's occurred_at.

Impact on downstream ratings and reports:

  • Late-arriving events are rated under the policy/contract context that was effective at their occurred_at.
  • Time-window reports and traceability views should treat event-time and rating-time as different dimensions.
  • Correct occurred_at is required for accurate backfill, reconciliation, and period analysis.

Recommended usage:

  • Send UTC timestamps from your source system's event clock.
  • Preserve original event time on retries and replays.
  • Do not overwrite occurred_at with API call time.

Payload design for E2R rules​

payload is a freeform object, but E2R rules rely on stable field paths and stable value types.

Design rules:

  • Keep field paths stable over time (for example usage.input_tokens).
  • Keep type stable per field (do not alternate between number/string/object).
  • Store billable quantities as numeric values in minor units.
  • Use explicit schema versioning (for example schema_version) when payload shape evolves.
  • Keep high-cardinality diagnostics out of billable rule paths.

How E2R reads payload:

  • Single-event rules read payload by dot-path (for example payload_int: usage.input_tokens).
  • Predicate comparisons and numeric expressions coerce numeric-like values.
  • Aggregate numeric ops (sum/avg/min/max) only work reliably on numeric sources.

Recommended split:

  • payload: business facts and quantities used by pricing/rating logic.
  • labels: low-cardinality dimensions for filtering and reporting dimensions.

Example payload:

{
"schema_version": "v1",
"usage": {
"input_tokens": 1200,
"output_tokens": 340
},
"model": {
"name": "gpt-5",
"tier": "pro"
},
"request": {
"region": "us-central1"
}
}

/events response semantics​

  • First accepted write returns 201.
  • A later submission of the same key for the same user returns 200, regardless of submitted content.
  • Response returns event_id, event_key, request_hash, and the stored fact.

/events/batch response semantics​

Batch always returns 207 with per-item status.

Per-item status values:

  • accepted: inserted as new event
  • duplicate: deduped against existing event
  • invalid: payload-level validation failed
  • failed: processing failed

Batch summary includes:

  • accepted_count
  • failed_count

Label persistence rules​

  • Labels are stored only when an event is newly accepted.
  • Duplicate replays do not rewrite stored labels.
  • Replay responses return the labels stored with the original fact.
  • Label keys are normalized to lowercase and typed into one value slot (text/uuid/bool/number).

Campaign matching reads nested payload directly. It does not require Campaign fields to be duplicated into labels.

Hand-off to events -> ratings pipeline​

  • POST /events:
    • accepted event is enqueued
    • event processing is also attempted inline
  • POST /events/batch:
    • accepted events are enqueued
    • processing is deferred to worker/sweep flow

Verify checklist​

  • Same user and event_key returns duplicate behavior (200 or duplicate item status), even if submitted content changed.
  • Different users may reuse the same event_key.
  • A genuinely different occurrence for the same user uses a different key.
  • Batch result preserves input order by index.
  • Accepted events appear in processing queue for downstream rating.