Send your first events
Before the batch upload, prove the streaming path works: send two
events for one user through the
ingestion endpoint, then read them back from the entity's timeline.
Two things happen on first contact that matter for the rest of the quickstart:
- the entity type
useris auto-registered for your project the first time an event about auserflows through the stream — step 4's label definition requires a registered entity type. Batch syncs perform the same registration for every type their mapping produces before committing, but this step proves the streaming path independently; - idempotency is on from the start: the
event_idyou supply makes any retry of the same fact a no-op.
- curl
- Python SDK (planned)
#!/usr/bin/env bash
# Quickstart step 1 — send your first events through the ingestion API.
# Works against any Tessera deployment: only the environment changes.
set -euo pipefail
: "${TESSERA_API_BASE:?e.g. https://api.staging.tessera.zone/v1}"
: "${TESSERA_INGEST_BASE:?e.g. https://ingest.staging.tessera.zone/v1}"
: "${TESSERA_API_KEY:?your tsk_test_… key}"
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Two events for one user. `event_id` makes retries idempotent: sending the
# same batch twice can never double-count (the Idempotency-Key header
# additionally dedupes the whole HTTP request).
curl -sS -X POST "$TESSERA_INGEST_BASE/events" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-first-events" \
-d '{
"events": [
{
"event_id": "hello-0001",
"entity": {"type": "user", "id": "u_hello_1"},
"event_type": "order.placed",
"timestamp": "'"$NOW"'",
"attributes": {"order_value": 42.5, "item_count": 2, "category": "apparel"}
},
{
"event_id": "hello-0002",
"entity": {"type": "user", "id": "u_hello_1"},
"event_type": "support.ticket_opened",
"timestamp": "'"$NOW"'",
"attributes": {"ticket_topic": "delivery"}
}
]
}' | python3 -m json.tool
# Acceptance means durably enqueued; within a couple of seconds the events
# appear on the entity's timeline.
for _ in $(seq 1 30); do
TIMELINE="$(curl -sS "$TESSERA_API_BASE/entities/user/u_hello_1/events" \
-H "Authorization: Bearer $TESSERA_API_KEY")"
COUNT="$(printf '%s' "$TIMELINE" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["data"]))')"
if [ "$COUNT" -ge 2 ]; then break; fi
sleep 1
done
echo "$TIMELINE" | python3 -m json.tool
echo "timeline events for u_hello_1: $COUNT"
Download quickstart/01-first-events.sh — this exact file runs in CI on every change (hosts shown are for this environment).
"""Quickstart step 1 (planned Python SDK) — send your first events."""
import os
import time
from datetime import UTC, datetime
from tessera import Tessera
client = Tessera( # the API key comes from TESSERA_API_KEY
base_url=os.environ["TESSERA_API_BASE"],
ingest_url=os.environ["TESSERA_INGEST_BASE"],
)
now = datetime.now(UTC)
# track() validates locally and buffers; nothing hits the network yet.
client.events.track(
{"type": "user", "id": "u_hello_1"},
"order.placed",
{"order_value": 42.5, "item_count": 2, "category": "apparel"},
timestamp=now,
event_id="hello-0001",
)
client.events.track(
{"type": "user", "id": "u_hello_1"},
"support.ticket_opened",
{"ticket_topic": "delivery"},
timestamp=now,
event_id="hello-0002",
)
# flush() drains the buffer and reports what the platform accepted.
result = client.events.flush()
print(f"accepted={result.accepted} rejected={result.rejected}")
# Within a couple of seconds the events appear on the entity's timeline.
for _ in range(30):
timeline = client.api.list_entity_events(entity_type="user", entity_id="u_hello_1")
if len(timeline.data) >= 2:
break
time.sleep(1)
for item in timeline.data:
print("timeline:", item.event.event_type)
print(f"timeline events for u_hello_1: {len(timeline.data)}")
client.close()
Download quickstart-sdk/01-first-events.py — this exact file runs in CI on every change (hosts shown are for this environment).
The POST /events response tells you the batch was durably enqueued
(accepted), not that every event passed every check — contract-level
violations found downstream land in your project's
dead-letter view, replayable after
you fix the cause. Within a couple of seconds the events are on the
timeline: that read (GET /entities/user/u_hello_1/events) comes from the
per-entity ledger that scoring will later replay.
Next: upload the historical data.