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

# media.viewed@1 · OpenVibe.Services

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

# MediaViewedPayload `media.viewed@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.viewed.v1.json`](/docs/contracts/media.viewed.json)

media.viewed v1 (OpenVibe.Media server/views/service.js, POST /api/v1/views). A view counted for an item: one per person (or anonymous session) per item per 30 minutes; a preview counts from 1 second and a watch or live view when playback started. ref names the item in its owner's id space (service, kind, id). how is preview, watch or live. site is the site the view happened on when it differs from the owner, creator the creator's canonical subject or channel username when known, both for Report and never required. seconds is the cumulative watch time the event carried; of\_seconds the item's length when known; concurrent is the number of live watchers Media saw at that moment for a live view, null otherwise. 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 |  | - `one of "preview", "watch", "live"` |
| `occurred_at` | string | yes | When Media counted the view. | - `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 \| null |  | The cumulative watch time this view carried. | - `minimum 0`<br>- `maximum 86400` |
| `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: live

~~~~
{
  "ref": {
    "service": "live",
    "kind": "stream",
    "id": "311",
    "site": "network",
    "creator": "maticus"
  },
  "how": "live",
  "occurred_at": "2026-11-12T10:04:05.123Z",
  "seconds": 61,
  "of_seconds": null,
  "concurrent": 12
}
~~~~

Rejected: no-time

~~~~
{
  "ref": {
    "service": "media",
    "kind": "vod",
    "id": "42"
  },
  "how": "watch"
}
~~~~

## Validate

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