openvibers/openvibe.contracts Commits

Contracts 0.152.0: video.playlist.read/manage are active (openvibe.video's playlist API) (#108)

OP OpenVibers committed 12 Oct 2026 · 4275459c0e · 66 files, +2201 −72

openvibe.video's playlist API now serves people's tokens and apps (MediaHub#33). This release marks its two capabilities active.

- **`video.playlist.read`** (GET routes) and **`video.playlist.manage`** (create, edit, reorder, add, remove, delete) are now `active`. Their descriptions say who may call them and how app ownership works, and `implementedBy` lists the routes.
- **New schemas, owned by media-hub:**
  - `video.playlist@1`
  - `video.playlist-read-request@1` and `video.playlist-read-result@1`
  - `video.playlist-manage-request@1` and `video.playlist-manage-result@1`

  They follow MediaHub's wire shapes and error codes, and have valid and invalid fixtures. The capabilities name them.
- **media-hub manifest notes:** the playlist API is listed. `video.vod.read` stays planned, because the watch pages are public HTML.
- `generated/openapi/` was regenerated: the version bump, plus a new `media-hub.json` now that media-hub implements active capabilities.

`compat.js v0.151.0` reports no breaking changes. `test/run.js` passes 19,374 checks.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex f0cf456e..273b2a89 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.152.0 — 2026-10-12
++
++- **`video.playlist.read` and `video.playlist.manage` are active.** openvibe.video's playlist API (MediaHub#33) takes a
++ person's Network token, an app/agent/service token with one of these, or no token for a public or unlisted playlist
++ by id and a person's public list (`GET /api/v1/video/users/:username/playlists`). An app owns playlists under its own
++ requester and project and never touches a person's.
++- New schemas (owner media-hub): `video.playlist@1`, `video.playlist-read-request@1`, `video.playlist-read-result@1`,
++ `video.playlist-manage-request@1` and `video.playlist-manage-result@1`, with fixtures; the two capabilities name them.
++ Additive only (compat vs 0.151.0: no breaking changes).
++
## 0.151.0 — 2026-10-12
- The media-hub site is named **Video** (tagline "Past streams, clips and your drive"): its public origin is openvibe.video,
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 2dde8a4d..83e36270 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -7013,6 +7013,51 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-004"
++ },
++ {
++ "id": "video.playlist",
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "schema": "video/playlist.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "video.playlist-read-request",
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "schema": "video/playlist-read-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "video.playlist-read-result",
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "schema": "video/playlist-read-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "video.playlist-manage-request",
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "schema": "video/playlist-manage-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "video.playlist-manage-result",
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "schema": "video/playlist-manage-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
}
]
}
ddiff --git a/contracts/video/playlist-manage-request.v1.json b/contracts/video/playlist-manage-request.v1.json
nnew file mode 100644
iindex 00000000..007cfe45
---- /dev/null
++++ b/contracts/video/playlist-manage-request.v1.json
@@@ -0,0 +1,99 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-manage-request.v1.json",
++ "title": "VideoPlaylistManageRequest",
++ "description": "video.playlist-manage-request@1: bodies of video.playlist.manage on openvibe.video. POST /api/v1/video/playlists { title, description?, visibility? } creates one (default private; 422 playlist.invalid_title over 120 characters, playlist.invalid_description over 2,000, playlist.invalid_visibility, playlist.limit at 100 per owner). PATCH /api/v1/video/playlists/:id { title?, description?, visibility? }. POST /api/v1/video/playlists/:id/items { kind, id } adds one (409 playlist.item_duplicate; 422 playlist.invalid_kind, playlist.invalid_item, playlist.items_limit at 500). PATCH /api/v1/video/playlists/:id/items { items: [{ kind, id }, ...] } reorders: the full list, each item once (422 playlist.invalid_items). DELETE /api/v1/video/playlists/:id and /items/:kind/:itemId take no body. Another owner's playlist answers 404 playlist.not_found.",
++ "anyOf": [
++ {
++ "type": "object",
++ "minProperties": 1,
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "description": {
++ "type": "string"
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/video/playlist-manage-result.v1.json b/contracts/video/playlist-manage-result.v1.json
nnew file mode 100644
iindex 00000000..9829caa8
---- /dev/null
++++ b/contracts/video/playlist-manage-result.v1.json
@@@ -0,0 +1,100 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-manage-result.v1.json",
++ "title": "VideoPlaylistManageResult",
++ "description": "video.playlist-manage-result@1: { playlist } after a create (201, Location) or an edit, { item } after an add (201), { items } after a reorder; a delete answers 204 with no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlist"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "playlist.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "item"
++ ],
++ "properties": {
++ "item": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/video/playlist-read-request.v1.json b/contracts/video/playlist-read-request.v1.json
nnew file mode 100644
iindex 00000000..87c08259
---- /dev/null
++++ b/contracts/video/playlist-read-request.v1.json
@@@ -0,0 +1,18 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-read-request.v1.json",
++ "title": "VideoPlaylistReadRequest",
++ "description": "video.playlist-read-request@1: video.playlist.read takes no body. GET /api/v1/video/playlists (the caller's own), GET /api/v1/video/playlists/:id (the owner sees a private one; anyone a public or unlisted one; otherwise 404 playlist.not_found, never 403) and the open GET /api/v1/video/users/:username/playlists, whose query is { limit?, cursor? } (limit 1-50, default 20; cursor is the previous page's next_cursor; 404 user.not_found).",
++ "type": "object",
++ "properties": {
++ "limit": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 50
++ },
++ "cursor": {
++ "type": "string",
++ "maxLength": 400
++ }
++ }
++}
ddiff --git a/contracts/video/playlist-read-result.v1.json b/contracts/video/playlist-read-result.v1.json
nnew file mode 100644
iindex 00000000..e0e58564
---- /dev/null
++++ b/contracts/video/playlist-read-result.v1.json
@@@ -0,0 +1,74 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-read-result.v1.json",
++ "title": "VideoPlaylistReadResult",
++ "description": "video.playlist-read-result@1: a list ({ playlists, next_cursor? }) or one playlist with its ordered items ({ playlist, items }).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlists"
++ ],
++ "properties": {
++ "playlists": {
++ "type": "array",
++ "items": {
++ "$ref": "playlist.v1.json"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "playlist",
++ "items"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "playlist.v1.json"
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/video/playlist.v1.json b/contracts/video/playlist.v1.json
nnew file mode 100644
iindex 00000000..78e288e5
---- /dev/null
++++ b/contracts/video/playlist.v1.json
@@@ -0,0 +1,73 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist.v1.json",
++ "title": "VideoPlaylist",
++ "description": "video.playlist@1: one playlist on openvibe.video as the API returns it. `owner` is present on reads of one playlist: a person (kind user, with their username when known) or an app, agent or service (never named).",
++ "type": "object",
++ "required": [
++ "id",
++ "title",
++ "description",
++ "visibility",
++ "item_count",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "A playlist id (vpl_ + ULID)."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 2000
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ },
++ "item_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "updated_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "owner": {
++ "type": "object",
++ "required": [
++ "kind"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "user",
++ "app",
++ "agent",
++ "service"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/video.playlist-manage-request/invalid/bad-kind.json b/fixtures/video.playlist-manage-request/invalid/bad-kind.json
nnew file mode 100644
iindex 00000000..9072eee6
---- /dev/null
++++ b/fixtures/video.playlist-manage-request/invalid/bad-kind.json
@@@ -0,0 +1,4 @@
++{
++ "kind": "stream",
++ "id": 1
++}
ddiff --git a/fixtures/video.playlist-manage-request/valid/add.json b/fixtures/video.playlist-manage-request/valid/add.json
nnew file mode 100644
iindex 00000000..d270ec98
---- /dev/null
++++ b/fixtures/video.playlist-manage-request/valid/add.json
@@@ -0,0 +1,4 @@
++{
++ "kind": "clip",
++ "id": 441
++}
ddiff --git a/fixtures/video.playlist-manage-request/valid/create.json b/fixtures/video.playlist-manage-request/valid/create.json
nnew file mode 100644
iindex 00000000..431659c5
---- /dev/null
++++ b/fixtures/video.playlist-manage-request/valid/create.json
@@@ -0,0 +1,4 @@
++{
++ "title": "Best of the week",
++ "visibility": "public"
++}
ddiff --git a/fixtures/video.playlist-manage-request/valid/reorder.json b/fixtures/video.playlist-manage-request/valid/reorder.json
nnew file mode 100644
iindex 00000000..d0344160
---- /dev/null
++++ b/fixtures/video.playlist-manage-request/valid/reorder.json
@@@ -0,0 +1,12 @@
++{
++ "items": [
++ {
++ "kind": "clip",
++ "id": 441
++ },
++ {
++ "kind": "vod",
++ "id": 5717
++ }
++ ]
++}
ddiff --git a/fixtures/video.playlist-manage-result/invalid/bad-item.json b/fixtures/video.playlist-manage-result/invalid/bad-item.json
nnew file mode 100644
iindex 00000000..cecaa503
---- /dev/null
++++ b/fixtures/video.playlist-manage-result/invalid/bad-item.json
@@@ -0,0 +1,8 @@
++{
++ "item": {
++ "kind": "vod",
++ "id": 0,
++ "position": 0,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ }
++}
ddiff --git a/fixtures/video.playlist-manage-result/valid/added.json b/fixtures/video.playlist-manage-result/valid/added.json
nnew file mode 100644
iindex 00000000..8fa6c326
---- /dev/null
++++ b/fixtures/video.playlist-manage-result/valid/added.json
@@@ -0,0 +1,8 @@
++{
++ "item": {
++ "kind": "vod",
++ "id": 5717,
++ "position": 0,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ }
++}
ddiff --git a/fixtures/video.playlist-manage-result/valid/created.json b/fixtures/video.playlist-manage-result/valid/created.json
nnew file mode 100644
iindex 00000000..bc417e1d
---- /dev/null
++++ b/fixtures/video.playlist-manage-result/valid/created.json
@@@ -0,0 +1,11 @@
++{
++ "playlist": {
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++ }
++}
ddiff --git a/fixtures/video.playlist-manage-result/valid/reordered.json b/fixtures/video.playlist-manage-result/valid/reordered.json
nnew file mode 100644
iindex 00000000..01181bbd
---- /dev/null
++++ b/fixtures/video.playlist-manage-result/valid/reordered.json
@@@ -0,0 +1,10 @@
++{
++ "items": [
++ {
++ "kind": "vod",
++ "id": 5717,
++ "position": 0,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ }
++ ]
++}
ddiff --git a/fixtures/video.playlist-read-request/invalid/limit-too-big.json b/fixtures/video.playlist-read-request/invalid/limit-too-big.json
nnew file mode 100644
iindex 00000000..f156f526
---- /dev/null
++++ b/fixtures/video.playlist-read-request/invalid/limit-too-big.json
@@@ -0,0 +1,3 @@
++{
++ "limit": 51
++}
ddiff --git a/fixtures/video.playlist-read-request/valid/none.json b/fixtures/video.playlist-read-request/valid/none.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/video.playlist-read-request/valid/none.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/video.playlist-read-request/valid/page.json b/fixtures/video.playlist-read-request/valid/page.json
nnew file mode 100644
iindex 00000000..d98ecf56
---- /dev/null
++++ b/fixtures/video.playlist-read-request/valid/page.json
@@@ -0,0 +1,4 @@
++{
++ "limit": 20,
++ "cursor": "eyJ1cGRhdGVkX2F0IjoiMjAyNiJ9"
++}
ddiff --git a/fixtures/video.playlist-read-result/invalid/bad-item.json b/fixtures/video.playlist-read-result/invalid/bad-item.json
nnew file mode 100644
iindex 00000000..6067f296
---- /dev/null
++++ b/fixtures/video.playlist-read-result/invalid/bad-item.json
@@@ -0,0 +1,19 @@
++{
++ "playlist": {
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++ },
++ "items": [
++ {
++ "kind": "stream",
++ "id": 5717,
++ "position": 0,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ }
++ ]
++}
ddiff --git a/fixtures/video.playlist-read-result/valid/list.json b/fixtures/video.playlist-read-result/valid/list.json
nnew file mode 100644
iindex 00000000..6a0a648e
---- /dev/null
++++ b/fixtures/video.playlist-read-result/valid/list.json
@@@ -0,0 +1,14 @@
++{
++ "playlists": [
++ {
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++ }
++ ],
++ "next_cursor": null
++}
ddiff --git a/fixtures/video.playlist-read-result/valid/one.json b/fixtures/video.playlist-read-result/valid/one.json
nnew file mode 100644
iindex 00000000..c04274ba
---- /dev/null
++++ b/fixtures/video.playlist-read-result/valid/one.json
@@@ -0,0 +1,29 @@
++{
++ "playlist": {
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z",
++ "owner": {
++ "kind": "user",
++ "username": "kim"
++ }
++ },
++ "items": [
++ {
++ "kind": "vod",
++ "id": 5717,
++ "position": 0,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ },
++ {
++ "kind": "clip",
++ "id": 441,
++ "position": 1,
++ "added_at": "2026-10-12T19:01:00.000Z"
++ }
++ ]
++}
ddiff --git a/fixtures/video.playlist/invalid/bad-id.json b/fixtures/video.playlist/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..57ad07cc
---- /dev/null
++++ b/fixtures/video.playlist/invalid/bad-id.json
@@@ -0,0 +1,9 @@
++{
++ "id": "pl_1",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++}
ddiff --git a/fixtures/video.playlist/invalid/bad-visibility.json b/fixtures/video.playlist/invalid/bad-visibility.json
nnew file mode 100644
iindex 00000000..d930eecd
---- /dev/null
++++ b/fixtures/video.playlist/invalid/bad-visibility.json
@@@ -0,0 +1,9 @@
++{
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "friends",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++}
ddiff --git a/fixtures/video.playlist/valid/app-owner.json b/fixtures/video.playlist/valid/app-owner.json
nnew file mode 100644
iindex 00000000..c9df2229
---- /dev/null
++++ b/fixtures/video.playlist/valid/app-owner.json
@@@ -0,0 +1,12 @@
++{
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z",
++ "owner": {
++ "kind": "app"
++ }
++}
ddiff --git a/fixtures/video.playlist/valid/basic.json b/fixtures/video.playlist/valid/basic.json
nnew file mode 100644
iindex 00000000..9e8020c7
---- /dev/null
++++ b/fixtures/video.playlist/valid/basic.json
@@@ -0,0 +1,9 @@
++{
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z"
++}
ddiff --git a/fixtures/video.playlist/valid/with-owner.json b/fixtures/video.playlist/valid/with-owner.json
nnew file mode 100644
iindex 00000000..704c8712
---- /dev/null
++++ b/fixtures/video.playlist/valid/with-owner.json
@@@ -0,0 +1,13 @@
++{
++ "id": "vpl_01K7A1B2C3D4E5F6G7H8J9K0MN",
++ "title": "Best of the week",
++ "description": "",
++ "visibility": "public",
++ "item_count": 2,
++ "created_at": "2026-10-12T19:00:00.000Z",
++ "updated_at": "2026-10-12T19:05:00.000Z",
++ "owner": {
++ "kind": "user",
++ "username": "kim"
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex a30287a9..fc067fb6 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -78643,6 +78643,395 @@
}
}
}
++ },
++ "video.playlist": {
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist.v1.json",
++ "title": "VideoPlaylist",
++ "description": "video.playlist@1: one playlist on openvibe.video as the API returns it. `owner` is present on reads of one playlist: a person (kind user, with their username when known) or an app, agent or service (never named).",
++ "type": "object",
++ "required": [
++ "id",
++ "title",
++ "description",
++ "visibility",
++ "item_count",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "A playlist id (vpl_ + ULID)."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 2000
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ },
++ "item_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "updated_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "owner": {
++ "type": "object",
++ "required": [
++ "kind"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "user",
++ "app",
++ "agent",
++ "service"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ },
++ "video.playlist-read-request": {
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-read-request.v1.json",
++ "title": "VideoPlaylistReadRequest",
++ "description": "video.playlist-read-request@1: video.playlist.read takes no body. GET /api/v1/video/playlists (the caller's own), GET /api/v1/video/playlists/:id (the owner sees a private one; anyone a public or unlisted one; otherwise 404 playlist.not_found, never 403) and the open GET /api/v1/video/users/:username/playlists, whose query is { limit?, cursor? } (limit 1-50, default 20; cursor is the previous page's next_cursor; 404 user.not_found).",
++ "type": "object",
++ "properties": {
++ "limit": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 50
++ },
++ "cursor": {
++ "type": "string",
++ "maxLength": 400
++ }
++ }
++ }
++ },
++ "video.playlist-read-result": {
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-read-result.v1.json",
++ "title": "VideoPlaylistReadResult",
++ "description": "video.playlist-read-result@1: a list ({ playlists, next_cursor? }) or one playlist with its ordered items ({ playlist, items }).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlists"
++ ],
++ "properties": {
++ "playlists": {
++ "type": "array",
++ "items": {
++ "$ref": "playlist.v1.json"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "playlist",
++ "items"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "playlist.v1.json"
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ }
++ },
++ "video.playlist-manage-request": {
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-manage-request.v1.json",
++ "title": "VideoPlaylistManageRequest",
++ "description": "video.playlist-manage-request@1: bodies of video.playlist.manage on openvibe.video. POST /api/v1/video/playlists { title, description?, visibility? } creates one (default private; 422 playlist.invalid_title over 120 characters, playlist.invalid_description over 2,000, playlist.invalid_visibility, playlist.limit at 100 per owner). PATCH /api/v1/video/playlists/:id { title?, description?, visibility? }. POST /api/v1/video/playlists/:id/items { kind, id } adds one (409 playlist.item_duplicate; 422 playlist.invalid_kind, playlist.invalid_item, playlist.items_limit at 500). PATCH /api/v1/video/playlists/:id/items { items: [{ kind, id }, ...] } reorders: the full list, each item once (422 playlist.invalid_items). DELETE /api/v1/video/playlists/:id and /items/:kind/:itemId take no body. Another owner's playlist answers 404 playlist.not_found.",
++ "anyOf": [
++ {
++ "type": "object",
++ "minProperties": 1,
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "description": {
++ "type": "string"
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ }
++ },
++ "video.playlist-manage-result": {
++ "version": "1.0.0",
++ "owner": "media-hub",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/video/playlist-manage-result.v1.json",
++ "title": "VideoPlaylistManageResult",
++ "description": "video.playlist-manage-result@1: { playlist } after a create (201, Location) or an edit, { item } after an add (201), { items } after a reorder; a delete answers 204 with no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlist"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "playlist.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "item"
++ ],
++ "properties": {
++ "item": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 78f6edbd..d624b7d6 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 2828dc54..809b2c31 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 1214d580..fea98817 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 8052ace1..af286608 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 483791e4..fb21d328 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 0f05e3ea..725e6c59 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 2d460926..8350f8d4 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 0107dbde..104c7f02 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex de543c02..313b8027 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex da2e9432..505addac 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex 83e747f1..100a17b1 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 500e218b..b4086508 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex ddc85cfb..9bd491ef 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex b090c0e5..977ebf4c 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -113,6 +113,13 @@
"operations": 9,
"capabilities": 6
},
++ {
++ "service": "media-hub",
++ "name": "OpenVibe.MediaHub",
++ "origin": "https://openvibe.video",
++ "operations": 8,
++ "capabilities": 2
++ },
{
"service": "network",
"name": "OpenVibe.Network",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 1d2370f4..a0cede48 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex adf25da8..9b064685 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
nnew file mode 100644
iindex 00000000..0231a30d
---- /dev/null
++++ b/generated/openapi/media-hub.json
@@@ -0,0 +1,980 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.MediaHub API",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.video"
++ }
++ ],
++ "paths": {
++ "/api/v1/video/playlists": {
++ "get": {
++ "operationId": "get_api_v1_video_playlists",
++ "summary": "Read playlists on openvibe.video: the ones the caller owns (a person, or an app, agent or service under its own requester) and any public or unlisted playlist by id, with its ordered items (vod or cli",
++ "description": "**video.playlist.read** (public): Read playlists on openvibe.video: the ones the caller owns (a person, or an app, agent or service under its own requester) and any public or unlisted playlist by id, with its ordered items (vod or clip ids from OpenVibe.Live). A private playlist answers 404 to anyone but its owner. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download, the media hub's) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.read"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/video.playlist-read-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "limit",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 50
++ }
++ },
++ {
++ "name": "cursor",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 400
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_video_playlists",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/video/playlists/{id}": {
++ "delete": {
++ "operationId": "delete_api_v1_video_playlists_id",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "get": {
++ "operationId": "get_api_v1_video_playlists_id",
++ "summary": "Read playlists on openvibe.video: the ones the caller owns (a person, or an app, agent or service under its own requester) and any public or unlisted playlist by id, with its ordered items (vod or cli",
++ "description": "**video.playlist.read** (public): Read playlists on openvibe.video: the ones the caller owns (a person, or an app, agent or service under its own requester) and any public or unlisted playlist by id, with its ordered items (vod or clip ids from OpenVibe.Live). A private playlist answers 404 to anyone but its owner. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download, the media hub's) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.read"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/video.playlist-read-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "limit",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 50
++ }
++ },
++ {
++ "name": "cursor",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 400
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "patch": {
++ "operationId": "patch_api_v1_video_playlists_id",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/video/playlists/{id}/items": {
++ "patch": {
++ "operationId": "patch_api_v1_video_playlists_id_items",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_video_playlists_id_items",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/video/playlists/{id}/items/{kind}/{itemId}": {
++ "delete": {
++ "operationId": "delete_api_v1_video_playlists_id_items_kind_itemId",
++ "summary": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns.",
++ "description": "**video.playlist.manage** (public): Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
++ "tags": [
++ "video.playlist"
++ ],
++ "x-openvibe-capabilities": [
++ "video.playlist.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "video-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "video.playlist.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/video.playlist-manage-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "kind",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "itemId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/video.playlist-manage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "video.playlist-manage-request.v1": {
++ "title": "VideoPlaylistManageRequest",
++ "description": "video.playlist-manage-request@1: bodies of video.playlist.manage on openvibe.video. POST /api/v1/video/playlists { title, description?, visibility? } creates one (default private; 422 playlist.invalid_title over 120 characters, playlist.invalid_description over 2,000, playlist.invalid_visibility, playlist.limit at 100 per owner). PATCH /api/v1/video/playlists/:id { title?, description?, visibility? }. POST /api/v1/video/playlists/:id/items { kind, id } adds one (409 playlist.item_duplicate; 422 playlist.invalid_kind, playlist.invalid_item, playlist.items_limit at 500). PATCH /api/v1/video/playlists/:id/items { items: [{ kind, id }, ...] } reorders: the full list, each item once (422 playlist.invalid_items). DELETE /api/v1/video/playlists/:id and /items/:kind/:itemId take no body. Another owner's playlist answers 404 playlist.not_found.",
++ "anyOf": [
++ {
++ "type": "object",
++ "minProperties": 1,
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "description": {
++ "type": "string"
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "anyOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ {
++ "type": "string",
++ "pattern": "^[1-9][0-9]{0,15}$"
++ }
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ },
++ "video.playlist-manage-result.v1": {
++ "title": "VideoPlaylistManageResult",
++ "description": "video.playlist-manage-result@1: { playlist } after a create (201, Location) or an edit, { item } after an add (201), { items } after a reorder; a delete answers 204 with no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlist"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "#/components/schemas/video.playlist.v1"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "item"
++ ],
++ "properties": {
++ "item": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ },
++ "video.playlist-read-request.v1": {
++ "title": "VideoPlaylistReadRequest",
++ "description": "video.playlist-read-request@1: video.playlist.read takes no body. GET /api/v1/video/playlists (the caller's own), GET /api/v1/video/playlists/:id (the owner sees a private one; anyone a public or unlisted one; otherwise 404 playlist.not_found, never 403) and the open GET /api/v1/video/users/:username/playlists, whose query is { limit?, cursor? } (limit 1-50, default 20; cursor is the previous page's next_cursor; 404 user.not_found).",
++ "type": "object",
++ "properties": {
++ "limit": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 50
++ },
++ "cursor": {
++ "type": "string",
++ "maxLength": 400
++ }
++ }
++ },
++ "video.playlist-read-result.v1": {
++ "title": "VideoPlaylistReadResult",
++ "description": "video.playlist-read-result@1: a list ({ playlists, next_cursor? }) or one playlist with its ordered items ({ playlist, items }).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "playlists"
++ ],
++ "properties": {
++ "playlists": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/video.playlist.v1"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "playlist",
++ "items"
++ ],
++ "properties": {
++ "playlist": {
++ "$ref": "#/components/schemas/video.playlist.v1"
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id",
++ "position",
++ "added_at"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "vod",
++ "clip"
++ ],
++ "description": "An OpenVibe.Live recording (vod) or clip."
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The item's OpenVibe.Live id."
++ },
++ "position": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "added_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ },
++ "video.playlist.v1": {
++ "title": "VideoPlaylist",
++ "description": "video.playlist@1: one playlist on openvibe.video as the API returns it. `owner` is present on reads of one playlist: a person (kind user, with their username when known) or an app, agent or service (never named).",
++ "type": "object",
++ "required": [
++ "id",
++ "title",
++ "description",
++ "visibility",
++ "item_count",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "A playlist id (vpl_ + ULID)."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 2000
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "private"
++ ]
++ },
++ "item_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "updated_at": {
++ "type": "string",
++ "description": "ISO 8601 time."
++ },
++ "owner": {
++ "type": "object",
++ "required": [
++ "kind"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "user",
++ "app",
++ "agent",
++ "service"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex f2a0e643..00f4acf8 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex eb63d657..318bcf73 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex f56d3af9..aa255c89 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex aee5d1fa..13c9e8b7 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.151.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex a7a7f2d2..a3fac657 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex ba4a4beb..d1070203 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex cc69b7ad..47cc4254 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex c6249788..f52c7d37 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex cea4be4b..894d9d54 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 3c4b1623..0dd4158c 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 539afe65..6fb57980 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex ce5d008a..41224639 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 446737ee..c1fef412 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 75981620..8d35fd9b 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex e48549c0..62baa7c2 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex b91411a4..d1568f30 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 62fd2f0e..df055ea6 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.151.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.151.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.152.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.152.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 05e68a6b..8a7e027e 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -173584,3 +173584,140 @@ export interface SpaceDomainVerifiedPayload {
*/
hostname: string;
}
++
++/** [email protected] (owner: media-hub) */
++/**
++ * video.playlist@1: one playlist on openvibe.video as the API returns it. `owner` is present on reads of one playlist: a person (kind user, with their username when known) or an app, agent or service (never named).
++ */
++export interface VideoPlaylist {
++ /**
++ * A playlist id (vpl_ + ULID).
++ */
++ id: string;
++ title: string;
++ description: string;
++ visibility: "public" | "unlisted" | "private";
++ item_count: number;
++ /**
++ * ISO 8601 time.
++ */
++ created_at: string;
++ /**
++ * ISO 8601 time.
++ */
++ updated_at: string;
++ owner?: {
++ kind: "user" | "app" | "agent" | "service";
++ username?: string | null;
++ };
++}
++
++/** [email protected] (owner: media-hub) */
++/**
++ * video.playlist-read-request@1: video.playlist.read takes no body. GET /api/v1/video/playlists (the caller's own), GET /api/v1/video/playlists/:id (the owner sees a private one; anyone a public or unlisted one; otherwise 404 playlist.not_found, never 403) and the open GET /api/v1/video/users/:username/playlists, whose query is { limit?, cursor? } (limit 1-50, default 20; cursor is the previous page's next_cursor; 404 user.not_found).
++ */
++export interface VideoPlaylistReadRequest {
++ limit?: number;
++ cursor?: string;
++}
++
++/** [email protected] (owner: media-hub) */
++/**
++ * video.playlist-read-result@1: a list ({ playlists, next_cursor? }) or one playlist with its ordered items ({ playlist, items }).
++ */
++export type VideoPlaylistReadResult =
++ | {
++ playlists: VideoPlaylist[];
++ next_cursor?: string | null;
++ }
++ | {
++ playlist: VideoPlaylist;
++ items: {
++ /**
++ * An OpenVibe.Live recording (vod) or clip.
++ */
++ kind: "vod" | "clip";
++ /**
++ * The item's OpenVibe.Live id.
++ */
++ id: number;
++ position: number;
++ /**
++ * ISO 8601 time.
++ */
++ added_at: string;
++ }[];
++ };
++
++/** [email protected] (owner: media-hub) */
++/**
++ * video.playlist-manage-request@1: bodies of video.playlist.manage on openvibe.video. POST /api/v1/video/playlists { title, description?, visibility? } creates one (default private; 422 playlist.invalid_title over 120 characters, playlist.invalid_description over 2,000, playlist.invalid_visibility, playlist.limit at 100 per owner). PATCH /api/v1/video/playlists/:id { title?, description?, visibility? }. POST /api/v1/video/playlists/:id/items { kind, id } adds one (409 playlist.item_duplicate; 422 playlist.invalid_kind, playlist.invalid_item, playlist.items_limit at 500). PATCH /api/v1/video/playlists/:id/items { items: [{ kind, id }, ...] } reorders: the full list, each item once (422 playlist.invalid_items). DELETE /api/v1/video/playlists/:id and /items/:kind/:itemId take no body. Another owner's playlist answers 404 playlist.not_found.
++ */
++export type VideoPlaylistManageRequest =
++ | {
++ title?: string;
++ description?: string;
++ visibility?: "public" | "unlisted" | "private";
++ }
++ | {
++ /**
++ * An OpenVibe.Live recording (vod) or clip.
++ */
++ kind: "vod" | "clip";
++ id: number | string;
++ }
++ | {
++ /**
++ * @maxItems 500
++ */
++ items: {
++ /**
++ * An OpenVibe.Live recording (vod) or clip.
++ */
++ kind: "vod" | "clip";
++ id: number | string;
++ }[];
++ };
++
++/** [email protected] (owner: media-hub) */
++/**
++ * video.playlist-manage-result@1: { playlist } after a create (201, Location) or an edit, { item } after an add (201), { items } after a reorder; a delete answers 204 with no body.
++ */
++export type VideoPlaylistManageResult =
++ | {
++ playlist: VideoPlaylist;
++ }
++ | {
++ item: {
++ /**
++ * An OpenVibe.Live recording (vod) or clip.
++ */
++ kind: "vod" | "clip";
++ /**
++ * The item's OpenVibe.Live id.
++ */
++ id: number;
++ position: number;
++ /**
++ * ISO 8601 time.
++ */
++ added_at: string;
++ };
++ }
++ | {
++ items: {
++ /**
++ * An OpenVibe.Live recording (vod) or clip.
++ */
++ kind: "vod" | "clip";
++ /**
++ * The item's OpenVibe.Live id.
++ */
++ id: number;
++ position: number;
++ /**
++ * ISO 8601 time.
++ */
++ added_at: string;
++ }[];
++ };
ddiff --git a/manifests/capabilities/video.playlist.manage.json b/manifests/capabilities/video.playlist.manage.json
iindex 4145b78c..08f872af 100644
---- a/manifests/capabilities/video.playlist.manage.json
++++ b/manifests/capabilities/video.playlist.manage.json
@@@ -2,10 +2,10 @@
"id": "video.playlist.manage",
"version": "1.0",
"owner": "media-hub",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
"sensitive": true,
-- "description": "PLANNED: create, rename, reorder and delete a playlist on openvibe.video as the acting person (plan T10 D6). Account-only.",
++ "description": "Create, rename, describe, change the visibility of, reorder, add items to, remove items from and delete playlists on openvibe.video that the caller owns. An app, agent or service owns a playlist under its own requester (app:app_..., agent:agt_..., service:<slug>) and the playlist records its project; it can never touch a person's playlist. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download) needs this one.",
"permissions": [
"video:write"
],
@@@ -14,5 +14,14 @@
],
"quotaClass": "video-write",
"events": [],
-- "implementedBy": []
++ "implementedBy": [
++ "POST /api/v1/video/playlists",
++ "PATCH /api/v1/video/playlists/:id",
++ "DELETE /api/v1/video/playlists/:id",
++ "POST /api/v1/video/playlists/:id/items",
++ "PATCH /api/v1/video/playlists/:id/items",
++ "DELETE /api/v1/video/playlists/:id/items/:kind/:itemId"
++ ],
++ "inputSchema": "video.playlist-manage-request@1",
++ "outputSchema": "video.playlist-manage-result@1"
}
ddiff --git a/manifests/capabilities/video.playlist.read.json b/manifests/capabilities/video.playlist.read.json
iindex 1e3748c6..32aa4944 100644
---- a/manifests/capabilities/video.playlist.read.json
++++ b/manifests/capabilities/video.playlist.read.json
@@@ -2,9 +2,9 @@
"id": "video.playlist.read",
"version": "1.0",
"owner": "media-hub",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
-- "description": "PLANNED: read a playlist and its ordered items on openvibe.video (a new table; playlists do not exist anywhere today, plan T10 D6).",
++ "description": "Read playlists on openvibe.video: the ones the caller owns (a person, or an app, agent or service under its own requester) and any public or unlisted playlist by id, with its ordered items (vod or clip ids from OpenVibe.Live). A private playlist answers 404 to anyone but its owner. A person acting for themself needs no capability; an app, agent or service token (audience openvibe.download, the media hub's) needs this one.",
"permissions": [
"video:read"
],
@@@ -13,5 +13,10 @@
],
"quotaClass": "video-read",
"events": [],
-- "implementedBy": []
++ "implementedBy": [
++ "GET /api/v1/video/playlists",
++ "GET /api/v1/video/playlists/:id"
++ ],
++ "inputSchema": "video.playlist-read-request@1",
++ "outputSchema": "video.playlist-read-result@1"
}
ddiff --git a/manifests/services/media-hub.json b/manifests/services/media-hub.json
iindex f9f9a5d7..c48959ed 100644
---- a/manifests/services/media-hub.json
++++ b/manifests/services/media-hub.json
@@@ -87,7 +87,7 @@
"none": "no claims: one process serves the three domains"
}
},
-- "notes": "Port 4990, unit openvibe-media-hub.service, one process for openvibe.download, openvibe.pics and openvibe.video (plan T10 D4, by Host header). v1: openvibe.download is a private drive — account-only uploads streamed into OpenVibe.Media (app media-hub; a plain form up to 256 MB, resumable multipart beyond), private files and folders with per-person limits, share links that expire, need a signed-in downloader and may name accounts (matched by subject), reports that suspend a share for staff, and sandboxed attachment downloads. openvibe.pics and openvibe.video answer with coming pages: their uploads wait for scanning and abuse intake (the video.*/pics.*/download.* capabilities stay planned until an app may call them). Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client media-hub, JWKS, identity.subject.resolve) and OpenVibe.Media; answers account export and deletion (ADR-033). Since 2026-10-12 the same process serves openvibe.video, OpenVibe.Video v1: public, read-only watch pages for OpenVibe.Live's stream recordings and clips (read from Live's public API, files on OpenVibe.Media), with WebVTT captions from the AI transcript and chapters from the AI moments. video.* stays planned until an app may call it; openvibe.pics stays parked.",
++ "notes": "Port 4990, unit openvibe-media-hub.service, one process for openvibe.download, openvibe.pics and openvibe.video (plan T10 D4, by Host header). v1: openvibe.download is a private drive — account-only uploads streamed into OpenVibe.Media (app media-hub; a plain form up to 256 MB, resumable multipart beyond), private files and folders with per-person limits, share links that expire, need a signed-in downloader and may name accounts (matched by subject), reports that suspend a share for staff, and sandboxed attachment downloads. openvibe.pics and openvibe.video answer with coming pages: their uploads wait for scanning and abuse intake (the video.*/pics.*/download.* capabilities stay planned until an app may call them). Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client media-hub, JWKS, identity.subject.resolve) and OpenVibe.Media; answers account export and deletion (ADR-033). Since 2026-10-12 the same process serves openvibe.video, OpenVibe.Video v1: public, read-only watch pages for OpenVibe.Live's stream recordings and clips (read from Live's public API, files on OpenVibe.Media), with WebVTT captions from the AI transcript and chapters from the AI moments. Its playlists (MediaHub#31) have an API for people and apps since 0.152.0 (video.playlist.read, video.playlist.manage; MediaHub#33); video.vod.read stays planned (the watch pages are public HTML). openvibe.pics stays parked.",
"health": "/api/health",
"ready": "/api/ready",
"internalOrigin": "http://127.0.0.1:4990",
ddiff --git a/package.json b/package.json
iindex 41b11b9d..033a1f82 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.151.0",
++ "version": "0.152.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",