Skip to main content

Tutorial: events then ratings (ingestion first)

Goal: ingest an outcome event as a fact record. Downstream rating is typically asynchronous.

You will:

  1. Record an event (POST /mgt/v1/events).
  2. Optionally record a batch (POST /mgt/v1/events/batch).

Prereqs

Step 1: record one event

curl

Use the SDK for the simplest first pass.

TypeScript (SDK)

// TODO

Python (SDK)

import asyncio
import os

from vlunaai_sdk import (
VlunaAIConfig,
RequestContext,
ServiceClientOptions,
ServiceKeyCredentials,
create_service_client,
)


def env(name: str) -> str:
v = os.environ.get(name)
if not v:
raise RuntimeError(f"Missing env: {name}")
return v


async def main() -> None:
client = create_service_client(
ServiceClientOptions(
config=VlunaAIConfig(
base_url=os.environ.get('VLUNA_SERVICE_BASE_URL', 'https://api.us-east-1.vluna.ai/mgt/v1'),
realm_id=env('VLUNA_REALM_ID'),
),
service_key=ServiceKeyCredentials(
key_id=env('VLUNA_SERVICE_KEY_ID'),
secret=env('VLUNA_SERVICE_KEY_SECRET'),
),
)
)
try:
principal_id = 'customer_123'
ctx = RequestContext(principal_id=principal_id, idempotency_key='ik_event_0001')
resp = await client.record_billing_event(
body={
'semantic_kind': 'outcome',
'event_type': 'outcome.job_succeeded',
'occurred_at': '2026-01-10T00:00:00Z',
'payload': {'run_id': 'run_456'},
'labels': {'project': 'alpha'},
},
context=ctx,
)
print(resp.model_dump())
finally:
await client.close()


asyncio.run(main())

Verify:

  • You receive 201 Created on first write, or 200 OK for an idempotent replay.

Step 2: record a batch

curl

Use the SDK for the simplest first pass.

TypeScript (SDK)

// TODO

Python (SDK)

# TODO

Verify:

  • You receive 207 Multi-Status with per-item results.

What this does not prove

Successful ingestion does not prove rating is complete. Rating is typically asynchronous and eventually consistent.

Next