ODK API Portal
English
  • Overview
  • MediaIO
  • Continue Watching
  • Player Event
  • 지원 종료
  • 최근 변경
Information
Player Events v2 (ODK)
Telemetry
    Send telemetry events (visit / heartbeat / view)post
Player Events v2 (Amasian legacy)
Schemas
powered by Zudoku
Player Event API V2
Player Event API V2

Telemetry

First-party telemetry (ABCP-114). JSON body, envelope + events[]. The envelope service is the one validated field: it must be one of odk, amasian, otherwise 400 with no row — an unknown service would land rows under the wrong service. An accepted request returns 202. Event types outside the allow-list (default visit, heartbeat, view, env TELEMETRY_EVENT_TYPES; the live list is on the route) and malformed events are kept and routed to the unknown quarantine table.


Send telemetry events (visit / heartbeat / view)

POST
https://pe.odkmedia.io
/telemetry/event

Fire-and-forget collector for first-party telemetry (ABCP-114). One request carries one identity envelope and 1..N events.

The one validated field is service. It must be present and one of: odk, amasian. A body that is not a JSON object, or whose service is missing or outside the allow-list, returns 400 with a detail message and produces no row. This is deliberate: an unknown service would land rows under the wrong service, which is worse than an unknown event type, so it fails at integration time instead of silently. The check is case-sensitive — ODK and odkr are rejected.

Every accepted request returns 202 with an empty body, including for bodies the server cannot use. Apart from service, the server never validates the payload shape; validation and quarantine happen in the warehouse (quarantine_telemetry).

What lands where: each event becomes one Kafka message on telemetry-{event_type} → raw_telemetry_{event_type}. An event_type outside the allow-list (visit, heartbeat, view; env TELEMETRY_EVENT_TYPES) goes to telemetry-unknown. Non-object items inside events, or a scalar events value, become _malformed rows. No row is produced for a rejected service, a server-side produce failure (Kafka unavailable, mid-batch error; still 202 and counted in the server's _stats log). Clients should not expect a retry signal.

Enrichment (server-side): raw client ip (from X-Forwarded-For, stored as-is like PEv2), geo fields (continent country region city metro time_zone), isp, autonomous_system_number, is_ias_blocked, is_office_ip. No hashed IP on this route.

Event types (v1, see the schemas): visit — apps/CTV, on app open (context.trigger=launch) and on foreground-resume after ≥ 30 min in background (resume). The 30-minute suppression is overridden in two cases, which always emit: the first foreground of a new UTC day — a session straddling midnight would otherwise drop the device out of DAU — and any deeplink entry, whose attribution is otherwise lost for good. Deeplink entries may also carry context.entry_source / deeplink_url / referrer / target_id — all optional, so send what the platform exposes. Clients strip credential-bearing parameters, including any nested url=, before sending deeplink_url. context.since_last_ms reports the elapsed time itself, so the threshold can be re-cut downstream. heartbeat — no context. Every ~15 min on every platform, but gated differently: apps and CTV beat while the app is foregrounded, whether or not anything is playing — suppress on screensaver or display-off where the platform exposes it — and web beats only while a video is playing, starting on play and stopping when nothing is playing. A web playback shorter than the interval therefore emits no heartbeat at all, which is fine: the view events either side of it are already close enough together to keep that session whole. The beat exists for the long watch. view — web, target_type=page, target_id=path, on every page load including SPA route changes; context.referrer / context.utm. Web sends no visit: web sessionization runs off view, and the playback-gated heartbeat is what keeps a long watch — which emits no view for its whole duration — from reading as an ended session.

Envelope identity and consent: osdid/islat use the same keys as PEv2. gpp (mobile, web) and us_privacy (CTV) are stored raw; the collector never gates on them. They are applied downstream on the restricted osdid paths (targeting, DMP, advertiser hand-off) only; analytics never read osdid.

event_id is client-generated and required: it is the idempotency key for retries and batch replays.

Send telemetry events (visit / heartbeat / view) › Request Body

TelemetryEnvelope
app_version
​string · required

App or web build version.

Example: 3.2.1
did
​string · required

Client-generated persistent device id: in an app, stable across app restarts and app updates; on web, a first-party UUID in localStorage with a cookie fallback. Send on every event, and send the same value on player events and telemetry — it is the join key between the two surfaces, so two separately minted UUIDs break the join silently. (Audience counting lives on the telemetry surface, not on player events.)

Example: 3f2c1e4a-9b7d-4c11-a2f0-5e8d6b1c9a04
​array · minItems: 1 · required

1..N events, identity fields above sent once per request. Typed schemas exist for the v1 types only (visit, heartbeat, view). Allow-listed on this deployment: visit, heartbeat, view — any allow-listed type outside the three is accepted with the base fields (event_id, event_type, schema_version, client_ts, context) and routed to its own topic; unlisted types are routed to the unknown quarantine table.

platform
​string · required

Platform, keep granular: web, android, androidtv, ios, tvos, roku, tizen, webos, firetv, vizio, hoteltv, box, chromecast. Web sends plain web.

Example: firetv
service
​string · required

Service: odk, amasian. Required. A value outside this allow-list, or an omitted field, returns 400 and no row is produced — an unknown service would land rows under the wrong service. Case-sensitive, so ODK and odkr are rejected; the wire value for ODK is odk.

Example: odk
​

IAB GPP consent string from the CMP (__gpp). Mobile and web.

Example: DBABLA~BVVqAAEABgA.QA
Default: null
​

OS limit-ad-tracking flag as reported by the OS: 0 or 1. Apps and CTV. Same key on PEv2 (query string) and telemetry (JSON); both are stored as the string "0"/"1". JSON here, so send the integer.

Example: 0
Default: null
​

OS advertising identifier, raw, one kind per platform: idfa (ios/tvos), gaid (android/androidtv), rida (roku), afai (firetv), tifa (tizen), lgudid (webos), vida (vizio). Not applicable on web. Send the OS value as-is, never suppress; the pipeline honours islat. Same key as PEv2.

Example: a1b2c3d4-0000-1111-2222-333344445555
Default: null
​

IAB US Privacy (USP) string, e.g. 1YNN. Backward compatibility for any client that cannot produce a GPP string yet — chiefly CTV, whose platforms have no GPP CMP. Web CMPs emit GPP, so web sends gpp only. Send both when both are available; the two fields are independent and neither is derived from the other.

Example: 1YNN
Default: null
​

Logged-in account id. Null or omitted for guests.

Example: 72407
Default: null

Send telemetry events (visit / heartbeat / view) › Responses

Accepted. Empty body.

No data returned
POST/telemetry/event
curl https://pe.odkmedia.io/telemetry/event \ --request POST \ --header 'Content-Type: application/json' \ --data '{ "app_version": "3.2.1", "did": "3f2c1e4a-9b7d-4c11-a2f0-5e8d6b1c9a04", "events": [ { "client_ts": "2026-09-04T17:18:00.421Z", "context": { "entry_source": "icon", "trigger": "launch" }, "event_id": "0d9e2f61-4b3a-4f7e-8c25-71a6de930b18", "event_type": "visit", "schema_version": "1.0.0" }, { "client_ts": "2026-09-04T17:33:00.104Z", "event_id": "6b1c9a04-1111-4c11-a2f0-5e8d3f2c1e4a", "event_type": "heartbeat", "schema_version": "1.0.0" }, { "client_ts": "2026-09-04T19:02:11.067Z", "context": { "deeplink_url": "odk://ondemandkorea.com/series/1234?utm_source=newsletter", "entry_source": "deeplink", "referrer": "android-app://com.google.android.gm", "since_last_ms": 5351000, "target_id": "/series/1234", "trigger": "resume" }, "event_id": "4c11a2f0-5e8d-6b1c-9a04-3f2c1e4a9b7d", "event_type": "visit", "schema_version": "1.0.0" } ], "islat": 0, "osdid": "a1b2c3d4-0000-1111-2222-333344445555", "platform": "firetv", "service": "odk", "us_privacy": "1YNN" }'
Example Request Body
{ "app_version": "3.2.1", "did": "3f2c1e4a-9b7d-4c11-a2f0-5e8d6b1c9a04", "events": [ { "client_ts": "2026-09-04T17:18:00.421Z", "context": { "entry_source": "icon", "trigger": "launch" }, "event_id": "0d9e2f61-4b3a-4f7e-8c25-71a6de930b18", "event_type": "visit", "schema_version": "1.0.0" }, { "client_ts": "2026-09-04T17:33:00.104Z", "event_id": "6b1c9a04-1111-4c11-a2f0-5e8d3f2c1e4a", "event_type": "heartbeat", "schema_version": "1.0.0" }, { "client_ts": "2026-09-04T19:02:11.067Z", "context": { "deeplink_url": "odk://ondemandkorea.com/series/1234?utm_source=newsletter", "entry_source": "deeplink", "referrer": "android-app://com.google.android.gm", "since_last_ms": 5351000, "target_id": "/series/1234", "trigger": "resume" }, "event_id": "4c11a2f0-5e8d-6b1c-9a04-3f2c1e4a9b7d", "event_type": "visit", "schema_version": "1.0.0" } ], "islat": 0, "osdid": "a1b2c3d4-0000-1111-2222-333344445555", "platform": "firetv", "service": "odk", "us_privacy": "1YNN" }
json
Example Responses
No example specified for this content type

Player Events v2 (ODK)Player Events v2 (Amasian legacy)