openvibers/openvibe.contracts Commits
Contracts 0.172.0: OpenVibe.Play — titles, achievements and leaderboards for every game (play.title.manage, play.result.write, play.player.read) (#128)
Changes
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `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.172.0 — 2026-10-14++++- **`play`, a new service** (OpenVibe.Games ADR-0007 decision 10): OpenVibe.Play at play.openvibe.network (port 5040),++ the platform's game services. A title is registered by its own principal, which alone writes to it; it holds++ achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, and guest++ adoption. Boards and titles are public reads. People's rows are in the account export and erasure (consumes++ `network.account.export_requested` and `network.account.deleted`). Site block: Play, position 35 (the games icon).++- Capabilities (active, public): **`play.title.manage`**, **`play.result.write`**, **`play.player.read`**.++- Additive.++## 0.171.0 — 2026-10-14- **`input`, a computer kind** (plan T14/T17, "keyboard and mouse input"): `bot.command@1` takes@@@ -7155,6 +7155,56 @@"visibility": "first-party","compatibility": "backward","status": "active"++ },++ {++ "id": "play.title-definition",++ "version": "1.0.0",++ "owner": "play",++ "schema": "play/title-definition.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "Games ADR-0007"++ },++ {++ "id": "play.title-result",++ "version": "1.0.0",++ "owner": "play",++ "schema": "play/title-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "Games ADR-0007"++ },++ {++ "id": "play.result-request",++ "version": "1.0.0",++ "owner": "play",++ "schema": "play/result-request.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "Games ADR-0007"++ },++ {++ "id": "play.result-response",++ "version": "1.0.0",++ "owner": "play",++ "schema": "play/result-response.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "Games ADR-0007"++ },++ {++ "id": "play.player-achievements",++ "version": "1.0.0",++ "owner": "play",++ "schema": "play/player-achievements.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "Games ADR-0007"}]}nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",++ "title": "PlayPlayerAchievements",++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",++ "type": "object",++ "required": [++ "title",++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "unlocked_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "unlocked_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,106 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",++ "title": "PlayResultRequest",++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "board",++ "ref",++ "entries"++ ],++ "additionalProperties": false,++ "properties": {++ "board": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "ref": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "description": "the round or run; a repeat changes nothing"++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ },++ "entries": {++ "type": "array",++ "minItems": 1,++ "maxItems": 100,++ "items": {++ "type": "object",++ "required": [++ "player",++ "name",++ "score"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 40++ },++ "score": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "minItems": 1,++ "maxItems": 200,++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "person"++ ],++ "additionalProperties": false,++ "properties": {++ "person": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,60 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",++ "title": "PlayResultResponse",++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "recorded",++ "repeated"++ ],++ "additionalProperties": false,++ "properties": {++ "recorded": {++ "type": "integer",++ "minimum": 0++ },++ "repeated": {++ "type": "integer",++ "minimum": 0++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocked"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocked": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocks",++ "scores"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocks": {++ "type": "integer",++ "minimum": 0++ },++ "scores": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,93 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",++ "title": "PlayTitleDefinition",++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",++ "type": "object",++ "required": [++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "url": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^https://",++ "maxLength": 300,++ "description": "where the game is played"++ },++ "summary": {++ "type": "string",++ "maxLength": 300++ },++ "achievements": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "hidden": {++ "type": "boolean",++ "description": "a secret achievement: its name and text show only to whoever unlocked it"++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ],++ "description": "best: each player's best score; sum: the total of their scores"++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,111 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",++ "title": "PlayTitleResult",++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",++ "type": "object",++ "required": [++ "title",++ "created"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "object",++ "required": [++ "id",++ "name",++ "achievements",++ "boards"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "name": {++ "type": "string"++ },++ "url": {++ "type": [++ "string",++ "null"++ ]++ },++ "summary": {++ "type": "string"++ },++ "players": {++ "type": "integer",++ "minimum": 0++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "hidden": {++ "type": "boolean"++ },++ "unlocked_by": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ]++ }++ }++ }++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "created": {++ "type": "boolean"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "title": "salvage-rush",++ "player": "nobody",++ "achievements": []++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "title": "salvage-rush",++ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",++ "achievements": [++ {++ "id": "first-haul",++ "name": "First Haul",++ "description": "Finish a round with scrap.",++ "unlocked_at": "2026-10-14T12:00:00.000Z"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "board": "best",++ "ref": "round-1",++ "entries": [++ {++ "player": "bob",++ "name": "Bob",++ "score": 1++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "board": "best",++ "ref": "round-1",++ "entries": [++ {++ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",++ "name": "Ada",++ "score": 450++ },++ {++ "player": "guest:a1b2c3d4e5f6",++ "name": "Guest 7",++ "score": 300++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "recorded": -1,++ "repeated": 0++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "unlocked": [++ "first-haul"++ ]++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "name": "X",++ "boards": [++ {++ "id": "best",++ "name": "Best",++ "kind": "median"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "name": "Salvage Rush",++ "url": "https://openvibe.games/play?game=salvage-rush",++ "achievements": [++ {++ "id": "first-haul",++ "name": "First Haul",++ "description": "Finish a round with scrap."++ }++ ],++ "boards": [++ {++ "id": "best",++ "name": "Best round",++ "kind": "best"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "title": {++ "id": "salvage-rush"++ },++ "created": "yes"++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "title": {++ "id": "salvage-rush",++ "name": "Salvage Rush",++ "url": null,++ "summary": "",++ "players": 2,++ "achievements": [++ {++ "id": "first-haul",++ "name": "First Haul",++ "description": "",++ "unlocked_by": 2++ }++ ],++ "boards": [++ {++ "id": "best",++ "name": "Best round",++ "kind": "best"++ }++ ],++ "created_at": "2026-10-14T12:00:00.000Z",++ "updated_at": "2026-10-14T12:00:00.000Z"++ },++ "created": true++}@@@ -80306,6 +80306,453 @@}}}++ },++ "play.title-definition": {++ "version": "1.0.0",++ "owner": "play",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",++ "title": "PlayTitleDefinition",++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",++ "type": "object",++ "required": [++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "url": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^https://",++ "maxLength": 300,++ "description": "where the game is played"++ },++ "summary": {++ "type": "string",++ "maxLength": 300++ },++ "achievements": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "hidden": {++ "type": "boolean",++ "description": "a secret achievement: its name and text show only to whoever unlocked it"++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ],++ "description": "best: each player's best score; sum: the total of their scores"++ }++ }++ }++ }++ }++ }++ },++ "play.title-result": {++ "version": "1.0.0",++ "owner": "play",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",++ "title": "PlayTitleResult",++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",++ "type": "object",++ "required": [++ "title",++ "created"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "object",++ "required": [++ "id",++ "name",++ "achievements",++ "boards"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "name": {++ "type": "string"++ },++ "url": {++ "type": [++ "string",++ "null"++ ]++ },++ "summary": {++ "type": "string"++ },++ "players": {++ "type": "integer",++ "minimum": 0++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "hidden": {++ "type": "boolean"++ },++ "unlocked_by": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ]++ }++ }++ }++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "created": {++ "type": "boolean"++ }++ }++ }++ },++ "play.result-request": {++ "version": "1.0.0",++ "owner": "play",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",++ "title": "PlayResultRequest",++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "board",++ "ref",++ "entries"++ ],++ "additionalProperties": false,++ "properties": {++ "board": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "ref": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "description": "the round or run; a repeat changes nothing"++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ },++ "entries": {++ "type": "array",++ "minItems": 1,++ "maxItems": 100,++ "items": {++ "type": "object",++ "required": [++ "player",++ "name",++ "score"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 40++ },++ "score": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "minItems": 1,++ "maxItems": 200,++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "person"++ ],++ "additionalProperties": false,++ "properties": {++ "person": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ ]++ }++ },++ "play.result-response": {++ "version": "1.0.0",++ "owner": "play",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",++ "title": "PlayResultResponse",++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "recorded",++ "repeated"++ ],++ "additionalProperties": false,++ "properties": {++ "recorded": {++ "type": "integer",++ "minimum": 0++ },++ "repeated": {++ "type": "integer",++ "minimum": 0++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocked"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocked": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocks",++ "scores"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocks": {++ "type": "integer",++ "minimum": 0++ },++ "scores": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++ }++ },++ "play.player-achievements": {++ "version": "1.0.0",++ "owner": "play",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",++ "title": "PlayPlayerAchievements",++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",++ "type": "object",++ "required": [++ "title",++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "unlocked_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "unlocked_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ }++ }++ }}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Food API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -148,6 +148,13 @@"operations": 24,"capabilities": 8},++ {++ "service": "play",++ "name": "OpenVibe.Play",++ "origin": "https://play.openvibe.network",++ "operations": 5,++ "capabilities": 3++ },{"service": "reviews","name": "OpenVibe.Reviews",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.MediaHub API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRestream API",-- "version": "0.171.0",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"nnew file mode 100644@@@ -0,0 +1,846 @@++{++ "openapi": "3.1.0",++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",++ "info": {++ "title": "OpenVibe.Play API",++ "version": "0.172.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.172.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://play.openvibe.network"++ }++ ],++ "paths": {++ "/api/v1/titles/{title}": {++ "put": {++ "operationId": "put_api_v1_titles_title",++ "summary": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, na",++ "description": "**play.title.manage** (public): Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",++ "tags": [++ "play.title"++ ],++ "x-openvibe-capabilities": [++ "play.title.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "play-define",++ "security": [++ {++ "openvibeToken": [++ "play.title.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.title-definition.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "title",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.title-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/titles/{title}/players/{guest}/adopt": {++ "post": {++ "operationId": "post_api_v1_titles_title_players_guest_adopt",++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",++ "tags": [++ "play.result"++ ],++ "x-openvibe-capabilities": [++ "play.result.write"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "play-write",++ "security": [++ {++ "openvibeToken": [++ "play.result.write"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "title",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "guest",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-response.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/titles/{title}/players/{player}/achievements": {++ "get": {++ "operationId": "get_api_v1_titles_title_players_player_achievements",++ "summary": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have.",++ "description": "**play.player.read** (public): Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",++ "tags": [++ "play.player"++ ],++ "x-openvibe-capabilities": [++ "play.player.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "play.player.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "title",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "player",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.player-achievements.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/titles/{title}/scores": {++ "post": {++ "operationId": "post_api_v1_titles_title_scores",++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",++ "tags": [++ "play.result"++ ],++ "x-openvibe-capabilities": [++ "play.result.write"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "play-write",++ "security": [++ {++ "openvibeToken": [++ "play.result.write"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "title",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-response.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/titles/{title}/unlocks": {++ "post": {++ "operationId": "post_api_v1_titles_title_unlocks",++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",++ "tags": [++ "play.result"++ ],++ "x-openvibe-capabilities": [++ "play.result.write"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "play-write",++ "security": [++ {++ "openvibeToken": [++ "play.result.write"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "title",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/play.result-response.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}."++ }++ }++ },++ "play.player-achievements.v1": {++ "title": "PlayPlayerAchievements",++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",++ "type": "object",++ "required": [++ "title",++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "unlocked_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "unlocked_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ }++ }++ },++ "play.result-request.v1": {++ "title": "PlayResultRequest",++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "board",++ "ref",++ "entries"++ ],++ "additionalProperties": false,++ "properties": {++ "board": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "ref": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "description": "the round or run; a repeat changes nothing"++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ },++ "entries": {++ "type": "array",++ "minItems": 1,++ "maxItems": 100,++ "items": {++ "type": "object",++ "required": [++ "player",++ "name",++ "score"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 40++ },++ "score": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "player",++ "achievements"++ ],++ "additionalProperties": false,++ "properties": {++ "player": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"++ },++ "achievements": {++ "type": "array",++ "minItems": 1,++ "maxItems": 200,++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ },++ "at": {++ "type": "integer",++ "minimum": 1577836800000,++ "description": "epoch milliseconds, never in the future (an import of past results); default now"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "person"++ ],++ "additionalProperties": false,++ "properties": {++ "person": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ ]++ },++ "play.result-response.v1": {++ "title": "PlayResultResponse",++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "recorded",++ "repeated"++ ],++ "additionalProperties": false,++ "properties": {++ "recorded": {++ "type": "integer",++ "minimum": 0++ },++ "repeated": {++ "type": "integer",++ "minimum": 0++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocked"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocked": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ }++ }++ }++ },++ {++ "type": "object",++ "required": [++ "unlocks",++ "scores"++ ],++ "additionalProperties": false,++ "properties": {++ "unlocks": {++ "type": "integer",++ "minimum": 0++ },++ "scores": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++ },++ "play.title-definition.v1": {++ "title": "PlayTitleDefinition",++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",++ "type": "object",++ "required": [++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "url": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^https://",++ "maxLength": 300,++ "description": "where the game is played"++ },++ "summary": {++ "type": "string",++ "maxLength": 300++ },++ "achievements": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "hidden": {++ "type": "boolean",++ "description": "a secret achievement: its name and text show only to whoever unlocked it"++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ],++ "description": "best: each player's best score; sum: the total of their scores"++ }++ }++ }++ }++ }++ },++ "play.title-result.v1": {++ "title": "PlayTitleResult",++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",++ "type": "object",++ "required": [++ "title",++ "created"++ ],++ "additionalProperties": false,++ "properties": {++ "title": {++ "type": "object",++ "required": [++ "id",++ "name",++ "achievements",++ "boards"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"++ },++ "name": {++ "type": "string"++ },++ "url": {++ "type": [++ "string",++ "null"++ ]++ },++ "summary": {++ "type": "string"++ },++ "players": {++ "type": "integer",++ "minimum": 0++ },++ "achievements": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "description": {++ "type": "string"++ },++ "hidden": {++ "type": "boolean"++ },++ "unlocked_by": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ },++ "boards": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "kind"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"++ },++ "name": {++ "type": "string"++ },++ "kind": {++ "enum": [++ "best",++ "sum"++ ]++ }++ }++ }++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "created": {++ "type": "boolean"++ }++ }++ }++ },++ "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."++ }++ }++ }++}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Run API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Services API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Watch API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Work API",-- "version": "0.171.0",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.172.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"@@@ -183915,3 +183915,160 @@ export interface CodesTask {error?: string;};}++++/** [email protected] (owner: play) */++/**++ * A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.++ */++export interface PlayTitleDefinition {++ name: string;++ /**++ * where the game is played++ */++ url?: string | null;++ summary?: string;++ /**++ * @maxItems 200++ */++ achievements?: {++ id: string;++ name: string;++ description?: string;++ /**++ * a secret achievement: its name and text show only to whoever unlocked it++ */++ hidden?: boolean;++ }[];++ /**++ * @maxItems 200++ */++ boards?: {++ id: string;++ name: string;++ /**++ * best: each player's best score; sum: the total of their scores++ */++ kind: "best" | "sum";++ }[];++}++++/** [email protected] (owner: play) */++/**++ * The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).++ */++export interface PlayTitleResult {++ title: {++ id: string;++ name: string;++ url?: string | null;++ summary?: string;++ players?: number;++ achievements: {++ id: string;++ name: string;++ description?: string;++ hidden?: boolean;++ unlocked_by?: number;++ }[];++ boards: {++ id: string;++ name: string;++ kind: "best" | "sum";++ }[];++ created_at?: string;++ updated_at?: string;++ };++ created: boolean;++}++++/** [email protected] (owner: play) */++/**++ * What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).++ */++export type PlayResultRequest =++ | {++ board: string;++ /**++ * the round or run; a repeat changes nothing++ */++ ref: string;++ /**++ * epoch milliseconds, never in the future (an import of past results); default now++ */++ at?: number;++ /**++ * @minItems 1++ * @maxItems 100++ */++ entries: [++ {++ /**++ * a person (usr_) or a guest the title names (guest:<opaque>)++ */++ player: string;++ name: string;++ score: number;++ },++ ...{++ /**++ * a person (usr_) or a guest the title names (guest:<opaque>)++ */++ player: string;++ name: string;++ score: number;++ }[]++ ];++ }++ | {++ /**++ * a person (usr_) or a guest the title names (guest:<opaque>)++ */++ player: string;++ /**++ * @minItems 1++ * @maxItems 200++ */++ achievements: [string, ...string[]];++ /**++ * epoch milliseconds, never in the future (an import of past results); default now++ */++ at?: number;++ }++ | {++ person: string;++ };++++/** [email protected] (owner: play) */++/**++ * The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.++ */++export type PlayResultResponse =++ | {++ recorded: number;++ repeated: number;++ }++ | {++ unlocked: string[];++ }++ | {++ unlocks: number;++ scores: number;++ };++++/** [email protected] (owner: play) */++/**++ * One player's unlocked achievements in a title the caller manages (play.player.read), newest first.++ */++export interface PlayPlayerAchievements {++ title: string;++ /**++ * a person (usr_) or a guest the title names (guest:<opaque>)++ */++ player: string;++ achievements: {++ id: string;++ name: string;++ description?: string;++ unlocked_at: string;++ }[];++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "play.player.read",++ "version": "1.0",++ "owner": "play",++ "status": "active",++ "visibility": "public",++ "description": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",++ "inputSchema": "common.no-body@1",++ "outputSchema": "play.player-achievements@1",++ "permissions": [++ "play:player:read"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /api/v1/titles/:title/players/:player/achievements"++ ]++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "play.result.write",++ "version": "1.0",++ "owner": "play",++ "status": "active",++ "visibility": "public",++ "description": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",++ "inputSchema": "play.result-request@1",++ "outputSchema": "play.result-response@1",++ "permissions": [++ "play:result:write"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "play-write",++ "events": [],++ "implementedBy": [++ "POST /api/v1/titles/:title/scores",++ "POST /api/v1/titles/:title/unlocks",++ "POST /api/v1/titles/:title/players/:guest/adopt"++ ]++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "play.title.manage",++ "version": "1.0",++ "owner": "play",++ "status": "active",++ "visibility": "public",++ "description": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",++ "inputSchema": "play.title-definition@1",++ "outputSchema": "play.title-result@1",++ "permissions": [++ "play:title:manage"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "play-define",++ "events": [],++ "implementedBy": [++ "PUT /api/v1/titles/:title"++ ]++}nnew file mode 100644@@@ -0,0 +1,91 @@++{++ "version": "0.1.0",++ "contractRanges": {++ "openvibe-contracts": ">=0.122.0 <1.0.0"++ },++ "id": "play",++ "name": "OpenVibe.Play",++ "status": "alpha",++ "repository": "OpenVibers/OpenVibe.Play",++ "domains": [++ "play.openvibe.network"++ ],++ "publicOrigin": "https://play.openvibe.network",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "capabilities": [++ "play.title.manage",++ "play.result.write",++ "play.player.read"++ ],++ "eventsProduced": [],++ "eventsConsumed": [++ "network.account.export_requested",++ "network.account.deleted"++ ],++ "namespacesOwned": [++ "play.*"++ ],++ "lifecycle": {++ "liveness": {++ "endpoint": "/api/health",++ "means": "the process is up and answers HTTP; the database is what ready checks"++ },++ "shutdown": {++ "signal": "SIGTERM",++ "deadlineSeconds": 10,++ "drains": [++ "HTTP server closed",++ "Events subscription retries stopped",++ "Network key refresher stopped",++ "store closed"++ ]++ },++ "startupRecovery": {++ "resumes": [++ {++ "kind": "sessions",++ "what": "sign-in sessions are cookies verified against Network's keys, fetched again at boot"++ },++ {++ "kind": "state",++ "what": "every title, unlock and score is a committed row; posts are idempotent, so a game retries after a restart without counting twice"++ },++ {++ "kind": "consumer",++ "what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop (at least once; account_data_events applies each once)"++ }++ ]++ },++ "rollback": {++ "conditions": [++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",++ "manual: ovhost rollback <service> --to <sha>"++ ],++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",++ "blockers": {++ "none": "migration 0001 only creates Play's own tables"++ }++ },++ "contracts": {++ "range": ">=0.122.0 <1.0.0"++ },++ "leases": {++ "none": "no claims: one process serves the site and the API"++ }++ },++ "notes": "Port 5040, unit openvibe-play.service, play.openvibe.network (the openvibe.network wildcard certificate). The platform's game services (OpenVibe.Games ADR-0007 decision 10): titles registered by their own principal, achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, guest adoption, in its own PostgreSQL database (ov_play). OpenVibe.Games is its first title owner (Scraplandia, Salvage Rush). People's rows are in the account export and erased with the account (ADR-033).",++ "health": "/api/health",++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:5040",++ "site": {++ "name": "Play",++ "icon": "games",++ "tagline": "Achievements and leaderboards for every OpenVibe game.",++ "what": "See this week's leaders and the all-time boards of every OpenVibe game, and every achievement you have unlocked with your account.",++ "legalProfile": "ugc",++ "position": 35++ }++}@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.171.0",++ "version": "0.172.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",
@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -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,
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`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.172.0 — 2026-10-14
+
+- **`play`, a new service** (OpenVibe.Games ADR-0007 decision 10): OpenVibe.Play at play.openvibe.network (port 5040),
+ the platform's game services. A title is registered by its own principal, which alone writes to it; it holds
+ achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, and guest
+ adoption. Boards and titles are public reads. People's rows are in the account export and erasure (consumes
+ `network.account.export_requested` and `network.account.deleted`). Site block: Play, position 35 (the games icon).
+- Capabilities (active, public): **`play.title.manage`**, **`play.result.write`**, **`play.player.read`**.
+- Additive.
+
## 0.171.0 — 2026-10-14
## 0.171.0 — 2026-10-14
- **`input`, a computer kind** (plan T14/T17, "keyboard and mouse input"): `bot.command@1` takes
- **`input`, a computer kind** (plan T14/T17, "keyboard and mouse input"): `bot.command@1` takes
@@ -7155,6 +7155,56 @@
@@ -7155,6 +7155,56 @@
"visibility": "first-party",
"visibility": "first-party",
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
+ },
+ {
+ "id": "play.title-definition",
+ "version": "1.0.0",
+ "owner": "play",
+ "schema": "play/title-definition.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "Games ADR-0007"
+ },
+ {
+ "id": "play.title-result",
+ "version": "1.0.0",
+ "owner": "play",
+ "schema": "play/title-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "Games ADR-0007"
+ },
+ {
+ "id": "play.result-request",
+ "version": "1.0.0",
+ "owner": "play",
+ "schema": "play/result-request.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "Games ADR-0007"
+ },
+ {
+ "id": "play.result-response",
+ "version": "1.0.0",
+ "owner": "play",
+ "schema": "play/result-response.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "Games ADR-0007"
+ },
+ {
+ "id": "play.player-achievements",
+ "version": "1.0.0",
+ "owner": "play",
+ "schema": "play/player-achievements.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "Games ADR-0007"
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",
+ "title": "PlayPlayerAchievements",
+ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
+ "type": "object",
+ "required": [
+ "title",
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "unlocked_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "unlocked_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",
+ "title": "PlayResultRequest",
+ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "board",
+ "ref",
+ "entries"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "board": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "ref": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "description": "the round or run; a repeat changes nothing"
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ },
+ "entries": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "required": [
+ "player",
+ "name",
+ "score"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 40
+ },
+ "score": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 200,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "person"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "person": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",
+ "title": "PlayResultResponse",
+ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "recorded",
+ "repeated"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "recorded": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "repeated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocked"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocked": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocks",
+ "scores"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocks": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "scores": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",
+ "title": "PlayTitleDefinition",
+ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^https://",
+ "maxLength": 300,
+ "description": "where the game is played"
+ },
+ "summary": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "achievements": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "hidden": {
+ "type": "boolean",
+ "description": "a secret achievement: its name and text show only to whoever unlocked it"
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ],
+ "description": "best: each player's best score; sum: the total of their scores"
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",
+ "title": "PlayTitleResult",
+ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
+ "type": "object",
+ "required": [
+ "title",
+ "created"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "achievements",
+ "boards"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "summary": {
+ "type": "string"
+ },
+ "players": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "unlocked_by": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ]
+ }
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "created": {
+ "type": "boolean"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "title": "salvage-rush",
+ "player": "nobody",
+ "achievements": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "title": "salvage-rush",
+ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
+ "achievements": [
+ {
+ "id": "first-haul",
+ "name": "First Haul",
+ "description": "Finish a round with scrap.",
+ "unlocked_at": "2026-10-14T12:00:00.000Z"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "board": "best",
+ "ref": "round-1",
+ "entries": [
+ {
+ "player": "bob",
+ "name": "Bob",
+ "score": 1
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "board": "best",
+ "ref": "round-1",
+ "entries": [
+ {
+ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
+ "name": "Ada",
+ "score": 450
+ },
+ {
+ "player": "guest:a1b2c3d4e5f6",
+ "name": "Guest 7",
+ "score": 300
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "recorded": -1,
+ "repeated": 0
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "unlocked": [
+ "first-haul"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "name": "X",
+ "boards": [
+ {
+ "id": "best",
+ "name": "Best",
+ "kind": "median"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "name": "Salvage Rush",
+ "url": "https://openvibe.games/play?game=salvage-rush",
+ "achievements": [
+ {
+ "id": "first-haul",
+ "name": "First Haul",
+ "description": "Finish a round with scrap."
+ }
+ ],
+ "boards": [
+ {
+ "id": "best",
+ "name": "Best round",
+ "kind": "best"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "title": {
+ "id": "salvage-rush"
+ },
+ "created": "yes"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "title": {
+ "id": "salvage-rush",
+ "name": "Salvage Rush",
+ "url": null,
+ "summary": "",
+ "players": 2,
+ "achievements": [
+ {
+ "id": "first-haul",
+ "name": "First Haul",
+ "description": "",
+ "unlocked_by": 2
+ }
+ ],
+ "boards": [
+ {
+ "id": "best",
+ "name": "Best round",
+ "kind": "best"
+ }
+ ],
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "updated_at": "2026-10-14T12:00:00.000Z"
+ },
+ "created": true
+}
@@ -80306,6 +80306,453 @@
@@ -80306,6 +80306,453 @@
}
}
}
}
}
}
+ },
+ "play.title-definition": {
+ "version": "1.0.0",
+ "owner": "play",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",
+ "title": "PlayTitleDefinition",
+ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^https://",
+ "maxLength": 300,
+ "description": "where the game is played"
+ },
+ "summary": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "achievements": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "hidden": {
+ "type": "boolean",
+ "description": "a secret achievement: its name and text show only to whoever unlocked it"
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ],
+ "description": "best: each player's best score; sum: the total of their scores"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "play.title-result": {
+ "version": "1.0.0",
+ "owner": "play",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",
+ "title": "PlayTitleResult",
+ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
+ "type": "object",
+ "required": [
+ "title",
+ "created"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "achievements",
+ "boards"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "summary": {
+ "type": "string"
+ },
+ "players": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "unlocked_by": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ]
+ }
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "created": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "play.result-request": {
+ "version": "1.0.0",
+ "owner": "play",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",
+ "title": "PlayResultRequest",
+ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "board",
+ "ref",
+ "entries"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "board": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "ref": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "description": "the round or run; a repeat changes nothing"
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ },
+ "entries": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "required": [
+ "player",
+ "name",
+ "score"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 40
+ },
+ "score": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 200,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "person"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "person": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "play.result-response": {
+ "version": "1.0.0",
+ "owner": "play",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",
+ "title": "PlayResultResponse",
+ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "recorded",
+ "repeated"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "recorded": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "repeated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocked"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocked": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocks",
+ "scores"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocks": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "scores": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ "play.player-achievements": {
+ "version": "1.0.0",
+ "owner": "play",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",
+ "title": "PlayPlayerAchievements",
+ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
+ "type": "object",
+ "required": [
+ "title",
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "unlocked_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "unlocked_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
}
}
}
}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Actor API",
"title": "OpenVibe.Actor API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Food API",
"title": "OpenVibe.Food API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -148,6 +148,13 @@
@@ -148,6 +148,13 @@
"operations": 24,
"operations": 24,
"capabilities": 8
"capabilities": 8
},
},
+ {
+ "service": "play",
+ "name": "OpenVibe.Play",
+ "origin": "https://play.openvibe.network",
+ "operations": 5,
+ "capabilities": 3
+ },
{
{
"service": "reviews",
"service": "reviews",
"name": "OpenVibe.Reviews",
"name": "OpenVibe.Reviews",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Inventory API",
"title": "OpenVibe.Inventory API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.MediaHub API",
"title": "OpenVibe.MediaHub API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRestream API",
"title": "OpenRestream API",
- "version": "0.171.0",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
new file mode 100644
new file mode 100644
@@ -0,0 +1,846 @@
@@ -0,0 +1,846 @@
+{
+ "openapi": "3.1.0",
+ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
+ "info": {
+ "title": "OpenVibe.Play API",
+ "version": "0.172.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.172.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://play.openvibe.network"
+ }
+ ],
+ "paths": {
+ "/api/v1/titles/{title}": {
+ "put": {
+ "operationId": "put_api_v1_titles_title",
+ "summary": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, na",
+ "description": "**play.title.manage** (public): Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",
+ "tags": [
+ "play.title"
+ ],
+ "x-openvibe-capabilities": [
+ "play.title.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "play-define",
+ "security": [
+ {
+ "openvibeToken": [
+ "play.title.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.title-definition.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "title",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.title-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/titles/{title}/players/{guest}/adopt": {
+ "post": {
+ "operationId": "post_api_v1_titles_title_players_guest_adopt",
+ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
+ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
+ "tags": [
+ "play.result"
+ ],
+ "x-openvibe-capabilities": [
+ "play.result.write"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "play-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "play.result.write"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "title",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "guest",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-response.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/titles/{title}/players/{player}/achievements": {
+ "get": {
+ "operationId": "get_api_v1_titles_title_players_player_achievements",
+ "summary": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have.",
+ "description": "**play.player.read** (public): Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",
+ "tags": [
+ "play.player"
+ ],
+ "x-openvibe-capabilities": [
+ "play.player.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "play.player.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "title",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "player",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.player-achievements.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/titles/{title}/scores": {
+ "post": {
+ "operationId": "post_api_v1_titles_title_scores",
+ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
+ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
+ "tags": [
+ "play.result"
+ ],
+ "x-openvibe-capabilities": [
+ "play.result.write"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "play-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "play.result.write"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "title",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-response.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/titles/{title}/unlocks": {
+ "post": {
+ "operationId": "post_api_v1_titles_title_unlocks",
+ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
+ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
+ "tags": [
+ "play.result"
+ ],
+ "x-openvibe-capabilities": [
+ "play.result.write"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "play-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "play.result.write"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "title",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/play.result-response.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}."
+ }
+ }
+ },
+ "play.player-achievements.v1": {
+ "title": "PlayPlayerAchievements",
+ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
+ "type": "object",
+ "required": [
+ "title",
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "unlocked_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "unlocked_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ }
+ }
+ },
+ "play.result-request.v1": {
+ "title": "PlayResultRequest",
+ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "board",
+ "ref",
+ "entries"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "board": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "ref": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "description": "the round or run; a repeat changes nothing"
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ },
+ "entries": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "required": [
+ "player",
+ "name",
+ "score"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 40
+ },
+ "score": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "player",
+ "achievements"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "player": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
+ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
+ },
+ "achievements": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 200,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 1577836800000,
+ "description": "epoch milliseconds, never in the future (an import of past results); default now"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "person"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "person": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ ]
+ },
+ "play.result-response.v1": {
+ "title": "PlayResultResponse",
+ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "recorded",
+ "repeated"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "recorded": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "repeated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocked"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocked": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "unlocks",
+ "scores"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "unlocks": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "scores": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+ },
+ "play.title-definition.v1": {
+ "title": "PlayTitleDefinition",
+ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^https://",
+ "maxLength": 300,
+ "description": "where the game is played"
+ },
+ "summary": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "achievements": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "hidden": {
+ "type": "boolean",
+ "description": "a secret achievement: its name and text show only to whoever unlocked it"
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ],
+ "description": "best: each player's best score; sum: the total of their scores"
+ }
+ }
+ }
+ }
+ }
+ },
+ "play.title-result.v1": {
+ "title": "PlayTitleResult",
+ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
+ "type": "object",
+ "required": [
+ "title",
+ "created"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "achievements",
+ "boards"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "summary": {
+ "type": "string"
+ },
+ "players": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "achievements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "unlocked_by": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "boards": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "kind"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "best",
+ "sum"
+ ]
+ }
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "created": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "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."
+ }
+ }
+ }
+}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Run API",
"title": "OpenVibe.Run API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Services API",
"title": "OpenVibe.Services API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Watch API",
"title": "OpenVibe.Watch API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Work API",
"title": "OpenVibe.Work API",
- "version": "0.171.0",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.171.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.172.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.172.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -183915,3 +183915,160 @@ export interface CodesTask {
@@ -183915,3 +183915,160 @@ export interface CodesTask {
error?: string;
error?: string;
};
};
}
}
+
+/** [email protected] (owner: play) */
+/**
+ * A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.
+ */
+export interface PlayTitleDefinition {
+ name: string;
+ /**
+ * where the game is played
+ */
+ url?: string | null;
+ summary?: string;
+ /**
+ * @maxItems 200
+ */
+ achievements?: {
+ id: string;
+ name: string;
+ description?: string;
+ /**
+ * a secret achievement: its name and text show only to whoever unlocked it
+ */
+ hidden?: boolean;
+ }[];
+ /**
+ * @maxItems 200
+ */
+ boards?: {
+ id: string;
+ name: string;
+ /**
+ * best: each player's best score; sum: the total of their scores
+ */
+ kind: "best" | "sum";
+ }[];
+}
+
+/** [email protected] (owner: play) */
+/**
+ * The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).
+ */
+export interface PlayTitleResult {
+ title: {
+ id: string;
+ name: string;
+ url?: string | null;
+ summary?: string;
+ players?: number;
+ achievements: {
+ id: string;
+ name: string;
+ description?: string;
+ hidden?: boolean;
+ unlocked_by?: number;
+ }[];
+ boards: {
+ id: string;
+ name: string;
+ kind: "best" | "sum";
+ }[];
+ created_at?: string;
+ updated_at?: string;
+ };
+ created: boolean;
+}
+
+/** [email protected] (owner: play) */
+/**
+ * What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).
+ */
+export type PlayResultRequest =
+ | {
+ board: string;
+ /**
+ * the round or run; a repeat changes nothing
+ */
+ ref: string;
+ /**
+ * epoch milliseconds, never in the future (an import of past results); default now
+ */
+ at?: number;
+ /**
+ * @minItems 1
+ * @maxItems 100
+ */
+ entries: [
+ {
+ /**
+ * a person (usr_) or a guest the title names (guest:<opaque>)
+ */
+ player: string;
+ name: string;
+ score: number;
+ },
+ ...{
+ /**
+ * a person (usr_) or a guest the title names (guest:<opaque>)
+ */
+ player: string;
+ name: string;
+ score: number;
+ }[]
+ ];
+ }
+ | {
+ /**
+ * a person (usr_) or a guest the title names (guest:<opaque>)
+ */
+ player: string;
+ /**
+ * @minItems 1
+ * @maxItems 200
+ */
+ achievements: [string, ...string[]];
+ /**
+ * epoch milliseconds, never in the future (an import of past results); default now
+ */
+ at?: number;
+ }
+ | {
+ person: string;
+ };
+
+/** [email protected] (owner: play) */
+/**
+ * The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.
+ */
+export type PlayResultResponse =
+ | {
+ recorded: number;
+ repeated: number;
+ }
+ | {
+ unlocked: string[];
+ }
+ | {
+ unlocks: number;
+ scores: number;
+ };
+
+/** [email protected] (owner: play) */
+/**
+ * One player's unlocked achievements in a title the caller manages (play.player.read), newest first.
+ */
+export interface PlayPlayerAchievements {
+ title: string;
+ /**
+ * a person (usr_) or a guest the title names (guest:<opaque>)
+ */
+ player: string;
+ achievements: {
+ id: string;
+ name: string;
+ description?: string;
+ unlocked_at: string;
+ }[];
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "play.player.read",
+ "version": "1.0",
+ "owner": "play",
+ "status": "active",
+ "visibility": "public",
+ "description": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "play.player-achievements@1",
+ "permissions": [
+ "play:player:read"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/titles/:title/players/:player/achievements"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "play.result.write",
+ "version": "1.0",
+ "owner": "play",
+ "status": "active",
+ "visibility": "public",
+ "description": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
+ "inputSchema": "play.result-request@1",
+ "outputSchema": "play.result-response@1",
+ "permissions": [
+ "play:result:write"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "play-write",
+ "events": [],
+ "implementedBy": [
+ "POST /api/v1/titles/:title/scores",
+ "POST /api/v1/titles/:title/unlocks",
+ "POST /api/v1/titles/:title/players/:guest/adopt"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "play.title.manage",
+ "version": "1.0",
+ "owner": "play",
+ "status": "active",
+ "visibility": "public",
+ "description": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",
+ "inputSchema": "play.title-definition@1",
+ "outputSchema": "play.title-result@1",
+ "permissions": [
+ "play:title:manage"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "play-define",
+ "events": [],
+ "implementedBy": [
+ "PUT /api/v1/titles/:title"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
+{
+ "version": "0.1.0",
+ "contractRanges": {
+ "openvibe-contracts": ">=0.122.0 <1.0.0"
+ },
+ "id": "play",
+ "name": "OpenVibe.Play",
+ "status": "alpha",
+ "repository": "OpenVibers/OpenVibe.Play",
+ "domains": [
+ "play.openvibe.network"
+ ],
+ "publicOrigin": "https://play.openvibe.network",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "capabilities": [
+ "play.title.manage",
+ "play.result.write",
+ "play.player.read"
+ ],
+ "eventsProduced": [],
+ "eventsConsumed": [
+ "network.account.export_requested",
+ "network.account.deleted"
+ ],
+ "namespacesOwned": [
+ "play.*"
+ ],
+ "lifecycle": {
+ "liveness": {
+ "endpoint": "/api/health",
+ "means": "the process is up and answers HTTP; the database is what ready checks"
+ },
+ "shutdown": {
+ "signal": "SIGTERM",
+ "deadlineSeconds": 10,
+ "drains": [
+ "HTTP server closed",
+ "Events subscription retries stopped",
+ "Network key refresher stopped",
+ "store closed"
+ ]
+ },
+ "startupRecovery": {
+ "resumes": [
+ {
+ "kind": "sessions",
+ "what": "sign-in sessions are cookies verified against Network's keys, fetched again at boot"
+ },
+ {
+ "kind": "state",
+ "what": "every title, unlock and score is a committed row; posts are idempotent, so a game retries after a restart without counting twice"
+ },
+ {
+ "kind": "consumer",
+ "what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop (at least once; account_data_events applies each once)"
+ }
+ ]
+ },
+ "rollback": {
+ "conditions": [
+ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
+ "manual: ovhost rollback <service> --to <sha>"
+ ],
+ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
+ "blockers": {
+ "none": "migration 0001 only creates Play's own tables"
+ }
+ },
+ "contracts": {
+ "range": ">=0.122.0 <1.0.0"
+ },
+ "leases": {
+ "none": "no claims: one process serves the site and the API"
+ }
+ },
+ "notes": "Port 5040, unit openvibe-play.service, play.openvibe.network (the openvibe.network wildcard certificate). The platform's game services (OpenVibe.Games ADR-0007 decision 10): titles registered by their own principal, achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, guest adoption, in its own PostgreSQL database (ov_play). OpenVibe.Games is its first title owner (Scraplandia, Salvage Rush). People's rows are in the account export and erased with the account (ADR-033).",
+ "health": "/api/health",
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:5040",
+ "site": {
+ "name": "Play",
+ "icon": "games",
+ "tagline": "Achievements and leaderboards for every OpenVibe game.",
+ "what": "See this week's leaders and the all-time boards of every OpenVibe game, and every achievement you have unlocked with your account.",
+ "legalProfile": "ugc",
+ "position": 35
+ }
+}
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.171.0",
+ "version": "0.172.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",