---
title: "media.engaged@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/media.engaged"
updated: null
---

# media.engaged@1 · OpenVibe.Services

HTML: [media.engaged@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/media.engaged)

# MediaEngagedPayload `media.engaged@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: media
- Visibility: internal
- Status: active
- Compatibility: backward
- Decision: [ADR-004](/docs/adr/ADR-004)
- Schema: [`https://openvibe.network/contracts/events/payloads/media.engaged.v1.json`](/docs/contracts/media.engaged.json)

media.engaged v1 (OpenVibe.Media server/views/service.js, POST /api/v1/views). A viewer became engaged with an item for the first time: they watched 30 seconds or a third of its length (whichever came first, of\_seconds known) on a watch, or 60 seconds of a live stream. One event per person (or anonymous session) per item, ever, however many heartbeats follow. ref names the item in its owner's id space; site and creator are the same optional Report dimensions as media.viewed. seconds is the cumulative watch time that reached the threshold, threshold the number that was needed. No IP address, user agent or raw anonymous session id is in the payload or stored with the view. Events are outbox rows written with the count in the same transaction and relayed to OpenVibe.Events; subject { type: <kind>, id: <id> }, visibility internal, actor service:media.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `ref` | object | yes |  | - `no other fields` |
| `ref.service` | string | yes |  | - `pattern ^[a-z][a-z0-9-]{1,39}$` |
| `ref.kind` | string | yes |  | - `pattern ^[a-z][a-z0-9_]{1,39}$` |
| `ref.id` | string | yes |  | - `minLength 1`<br>- `maxLength 200` |
| `ref.site` | string \| null |  | The site the view happened on, or null when it is the owning service. | - `maxLength 40` |
| `ref.creator` | string \| null |  | The creator's canonical subject or channel username, or null when unknown. | - `maxLength 200` |
| `how` | enum | yes | A preview never engages. | - `one of "watch", "live"` |
| `occurred_at` | string | yes | When Media marked the viewer engaged. | - `pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$`<br>- `format date-time` |
| `seconds` | number | yes | The cumulative watch time that reached the threshold. | - `minimum 1`<br>- `maximum 86400` |
| `threshold` | number | yes | The seconds that were needed: 60 for a live stream, else min(30, of\_seconds / 3) when of\_seconds was known, otherwise 30. | - `minimum 1`<br>- `maximum 60` |
| `of_seconds` | number \| null |  | The item's length when the client knew it. | - `maximum 86400` |
| `concurrent` | integer \| null |  | Live watchers Media saw at that moment (how = live), null otherwise. | - `minimum 1` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: watch

~~~~
{
  "ref": {
    "service": "media",
    "kind": "clip",
    "id": "7"
  },
  "how": "watch",
  "occurred_at": "2026-11-12T10:04:05.123Z",
  "seconds": 30,
  "threshold": 30,
  "of_seconds": 120
}
~~~~

Rejected: preview

~~~~
{
  "ref": {
    "service": "media",
    "kind": "clip",
    "id": "7"
  },
  "how": "preview",
  "occurred_at": "2026-11-12T10:04:05.123Z",
  "seconds": 30,
  "threshold": 30
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('media.engaged@1', value);   // { valid, errors: [{ path, message }] }
~~~~
