Event API
Stream playback, engagement, and revenue signals to NU. Validated revenue events feed settlements. Requires events:write.
Endpoints
| Method | Path | Success | Purpose |
|---|---|---|---|
| POST | /events | 200 | Send one event |
| POST | /events/batch | 207 | Send up to 500 events and return a result per item |
Event types
Only the following event_type values are accepted. Unknown types return validation_failed.
| event_type | Meaning | Required fields | Settlement |
|---|---|---|---|
IMPRESSION | Title or poster impression | — | No |
TITLE_VIEW | Title detail opened | title_id | No |
EPISODE_STARTED | Playback started | title_id, episode_id | No |
PLAYBACK_PROGRESS | Heartbeat or quartile progress | episode_id, completion_rate | No |
EPISODE_COMPLETED | Playback completed | episode_id, completion_rate | No |
EPISODE_UNLOCKED | Paid unlock granted | episode_id | Metrics only |
PAYMENT_COMPLETED | Revenue | title_id, revenue_amount, currency | Yes |
REFUND | Refund or reversal | title_id, revenue_amount, currency | Yes (negative) |
SUBSCRIPTION_RENEWAL | Recurring revenue | title_id, revenue_amount, currency | Yes |
AD_IMPRESSION | Advertising or engagement signal | — | No |
OTHER | Non-standard | — | No |
Settlement eligibility is derived by the ingestion pipeline from the type, never from a partner-supplied flag.
Required fields and validation
Every event requires event_id and event_type. occurred_at is optional and defaults to server receipt time. Type-specific fields are listed above.
event_idis unique per organization and provides idempotency. A duplicate is a no-op returning409 duplicate_event_id; it is not counted twice.completion_ratemust be within[0, 1].- Revenue types require
title_id,revenue_amount, and ISO 4217currency; otherwise they returnvalidation_failed. Revenue must be attributed to a title so it can be reconciled. occurred_atcannot be in the future. Future-skewed events are rejected asfailedwithoccurred_at_in_future. Events older than 24 hours are accepted but markedpayload.late = true.viewer_id_hashmust be a hashed identifier and contain no raw PII; otherwise the event returnsvalidation_failed.- When sent,
title_id,episode_id, andplayback_session_idmust be visible in the key’s environment and belong to your organization. Reference failures returnvalidation_failedwith details such asunknown_title_for_environment,unknown_episode_for_environment,playback_session_mismatch,license_not_active, orepisode_not_licensed.
Field limits
String fields have length limits; values over the limit return validation_failed:
| Field | Type | Constraint |
|---|---|---|
event_id | string | 1–200 characters (required) |
event_type | string | 1–64 characters (required) |
title_id | string | At most 64 characters |
episode_id | string | At most 64 characters |
playback_session_id | string | At most 64 characters |
viewer_id_hash | string | At most 128 characters; hash only |
country | string | At most 8 characters |
currency | string | At most 8 characters |
device | string | At most 64 characters |
completion_rate | number | 0–1 |
payload | object | Free-form JSON, at most 4 KB serialized |
payload is an optional free-form JSON object for partner metadata. Its serialized size must be at most 4,096 bytes (4 KB), or it returns validation_failed with The value did not pass validation..
Send one event
curl -X POST "https://signal-partners.newunivers.ai/v1/events" \
-H "Authorization: Bearer nsp_live_xxx" \
-H "X-NU-Partner-Id: org_acme" \
-H "Accept-Language: en" \
-H "Content-Type: application/json" \
-d '{
"event_id": "evt_acme_0001",
"event_type": "PAYMENT_COMPLETED",
"title_id": "ttl_abc",
"episode_id": "ep_001",
"viewer_id_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"country": "KR",
"device": "web",
"revenue_amount": 3.99,
"currency": "USD",
"occurred_at": "2026-06-24T00:00:00Z"
}'{ "data": { "event_id": "evt_acme_0001", "status": "validated", "received_at": "2026-06-24T00:00:05.123Z" } }Successful single-event ingestion returns HTTP 200, status: "validated", and an ISO-8601 received_at timestamp.
Duplicate
Sending the same event_id again returns:
{
"error": {
"code": "duplicate_event_id",
"message": "The event ID was already accepted.",
"request_id": "req_..."
}
}Validation error
{
"error": {
"code": "validation_failed",
"message": "Request validation failed.",
"request_id": "req_...",
"details": [
{
"field": "currency",
"issue": "required",
"message": "A required value is missing."
}
]
}
}Batch events
Send up to 500 events per request. Each item is validated independently; the response is HTTP 207 with per-item results, so the request can succeed even when some items fail.
curl -X POST "https://signal-partners.newunivers.ai/v1/events/batch" \
-H "Authorization: Bearer nsp_live_xxx" \
-H "X-NU-Partner-Id: org_acme" \
-H "Accept-Language: en" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "event_id": "evt_1", "event_type": "EPISODE_STARTED", "title_id": "ttl_abc", "episode_id": "ep_001", "occurred_at": "2026-06-24T00:00:00Z" },
{ "event_id": "evt_1", "event_type": "EPISODE_STARTED", "title_id": "ttl_abc", "episode_id": "ep_001", "occurred_at": "2026-06-24T00:00:01Z" },
{ "event_id": "evt_3", "event_type": "PAYMENT_COMPLETED", "occurred_at": "2026-06-24T00:00:02Z" }
]
}'{
"data": {
"received": 3,
"accepted": 1,
"duplicated": 1,
"failed": 1,
"results": [
{ "event_id": "evt_1", "status": "accepted" },
{ "event_id": "evt_1", "status": "duplicated" },
{ "event_id": "evt_3", "status": "failed" }
]
}
}received is the submitted count (1–500). Each item’s status is exactly one of accepted | duplicated | failed. Items do not include an error_code field.
Idempotency
Server-side deduplication by event_id makes retries safe. Reuse the same event_id for retries and generate it for the real event, not each HTTP attempt. Check ingestion status in the response and your request logs. Accepted events are stored as VALIDATED; repeated IDs become duplicated no-ops. Revenue events feed settlements.