openvibers/openvibe.contracts Commits
Contracts 0.79.0: service-token capabilities for every internal call that still used X-Internal-Key (plan T2, register C-50–C-58): network.avatar.write, network.registry.read, network.coins.read, live.avatar.write, live.url_registry.refresh, live.analytics.read, media.avatar.ingest and tools.analytics.read, each with request/result contracts modelled on the real handlers, catalog entries and fixtures; avatar URLs must be on https://openvibe.media/. The owning services' manifests list them. npm test: 14295 checks passed; compat vs v0.78.0 without breaking changes.
Changes
@@@ -4,6 +4,17 @@ 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.79.0 — 2026-09-29++++**Service-token capabilities for every internal call that still used X-Internal-Key** (plan T2, register C-50–C-58).++- New internal capabilities, each with request/result contracts and fixtures: `network.avatar.write` (a site reports an++ avatar change to Network), `network.registry.read` (the resolved URL registry), `network.coins.read` (OpenCoins totals,++ never balances of named people), `live.avatar.write` (Network pushes an avatar to Live), `live.url_registry.refresh`,++ `live.analytics.read` (aggregate totals for Network's navigation), `media.avatar.ingest` (Network asks Media to import++ an avatar), `tools.analytics.read` (the Tools gateway's and tool sites' aggregate analytics).++- Avatar URLs in the avatar requests must be on `https://openvibe.media/`.++- The owning services' manifests list them.++## 0.78.0 — 2026-09-29**OpenVibe.Realtime is gone from the estate** (plan T0).@@@ -2893,6 +2893,15 @@"compatibility": "backward","status": "active"},++ {++ "id": "tools.analytics-read-result",++ "version": "1.0.0",++ "owner": "tools",++ "schema": "tools/analytics-read-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "tools.job-read-result","version": "1.0.0",@@@ -4339,6 +4348,24 @@"compatibility": "backward","status": "active"},++ {++ "id": "media.avatar-ingest-request",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/avatar-ingest-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "media.avatar-ingest-result",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/avatar-ingest-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "media.file-read-result","version": "1.0.0",@@@ -4485,6 +4512,42 @@"compatibility": "backward","status": "active"},++ {++ "id": "live.analytics-read-result",++ "version": "1.0.0",++ "owner": "live",++ "schema": "live/analytics-read-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "live.avatar-write-request",++ "version": "1.0.0",++ "owner": "live",++ "schema": "live/avatar-write-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "live.avatar-write-result",++ "version": "1.0.0",++ "owner": "live",++ "schema": "live/avatar-write-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "live.url-registry-refresh-result",++ "version": "1.0.0",++ "owner": "live",++ "schema": "live/url-registry-refresh-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "live.follower-page","version": "1.0.0",@@@ -5004,6 +5067,42 @@"status": "active","adr": "ADR-034"},++ {++ "id": "network.avatar-write-request",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/avatar-write-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.avatar-write-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/avatar-write-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.registry-read-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/registry-read-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.coins-read-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/coins-read-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "platform.resource-offer","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,39 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",++ "title": "LiveAnalyticsReadResult",++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "summary"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "summary": {++ "type": "object",++ "description": "Aggregate totals; open."++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,36 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",++ "title": "LiveAvatarWriteRequest",++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed \u2014 the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/\u2026 address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",++ "type": "object",++ "required": [++ "openvibenetwork_id",++ "avatar_url"++ ],++ "properties": {++ "openvibenetwork_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Fallback when no linked account matches."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500,++ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",++ "title": "LiveAvatarWriteResult",++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",++ "type": "object",++ "required": [++ "ok",++ "id",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "id": {++ "type": "integer",++ "minimum": 1++ },++ "changed": {++ "type": "boolean"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,38 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",++ "title": "LiveUrlRegistryRefreshResult",++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "message"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "message": {++ "type": "string"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",++ "title": "MediaAvatarIngestRequest",++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",++ "type": "object",++ "required": [++ "url",++ "user_id"++ ],++ "properties": {++ "url": {++ "type": "string",++ "description": "The https address of the picture to import."++ },++ "user_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The account that owns the stored paste."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Used only for the paste title."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,39 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",++ "title": "MediaAvatarIngestResult",++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",++ "type": "object",++ "required": [++ "ok",++ "slug",++ "url",++ "width",++ "height",++ "bytes"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "slug": {++ "type": "string"++ },++ "url": {++ "type": "string"++ },++ "width": {++ "type": "integer",++ "minimum": 1++ },++ "height": {++ "type": "integer",++ "minimum": 1++ },++ "bytes": {++ "type": "integer",++ "minimum": 1++ }++ }++}nnew file mode 100644@@@ -0,0 +1,32 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",++ "title": "NetworkAvatarWriteRequest",++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",++ "type": "object",++ "required": [++ "user_id",++ "avatar_url"++ ],++ "properties": {++ "user_id": {++ "type": "integer",++ "minimum": 1,++ "description": "The Network (SSO) user id."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/",++ "maxLength": 500++ },++ "origin": {++ "type": "string",++ "maxLength": 40,++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",++ "title": "NetworkAvatarWriteResult",++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",++ "type": "object",++ "required": [++ "ok",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "changed": {++ "type": "boolean"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,78 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",++ "title": "NetworkCoinsReadResult",++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",++ "type": "object",++ "required": [++ "earned",++ "spent",++ "circulating",++ "holders",++ "transactions",++ "recent"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "type": "integer",++ "minimum": 0++ },++ "spent": {++ "type": "integer",++ "minimum": 0++ },++ "circulating": {++ "type": "integer",++ "minimum": 0++ },++ "holders": {++ "type": "integer",++ "minimum": 0++ },++ "transactions": {++ "type": "integer",++ "minimum": 0++ },++ "recent": {++ "type": "object",++ "required": [++ "earned",++ "spent",++ "holders"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "$ref": "#/$defs/window"++ },++ "spent": {++ "$ref": "#/$defs/window"++ },++ "holders": {++ "$ref": "#/$defs/window"++ }++ }++ }++ },++ "$defs": {++ "window": {++ "type": "object",++ "required": [++ "w",++ "pw"++ ],++ "additionalProperties": false,++ "properties": {++ "w": {++ "type": "integer",++ "minimum": 0++ },++ "pw": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",++ "title": "NetworkRegistryReadResult",++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",++ "type": "object",++ "required": [++ "ok",++ "registry"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "registry": {++ "type": "object",++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",++ "title": "ToolsAnalyticsReadResult",++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",++ "type": "object",++ "required": [++ "ok",++ "analytics"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "analytics": {++ "type": "object",++ "description": "Aggregate analytics; open."++ }++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "error": "analytics store unavailable" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "summary": { "pageviews": 125000, "sessions": 40211, "bots": 8300 } }nnew file mode 100644@@@ -0,0 +1 @@++{ "openvibenetwork_id": 4821, "username": "aurora" }nnew file mode 100644@@@ -0,0 +1 @@++{ "openvibenetwork_id": 4821, "username": "aurora", "avatar_url": "https://openvibe.media/p/hero-shot/screenshot" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "changed": false }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "id": 77, "changed": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "error": "registry unreachable" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "message": "URL registry refreshed" }nnew file mode 100644@@@ -0,0 +1 @@++{ "user_id": 4821, "username": "aurora" }nnew file mode 100644@@@ -0,0 +1 @@++{ "url": "https://cdn.example.com/me.png", "user_id": 4821, "username": "aurora" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "error": "That link is not a picture" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "slug": "avatar-9f3k2", "url": "https://openvibe.media/p/avatar-9f3k2/screenshot", "width": 512, "height": 512, "bytes": 40231 }nnew file mode 100644@@@ -0,0 +1 @@++{ "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }nnew file mode 100644@@@ -0,0 +1 @@++{ "user_id": 4821, "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "changed": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "changed": true }nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "earned": 128400,++ "spent": 91200,++ "circulating": 37200,++ "holders": 1842,++ "transactions": 44190++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "earned": 128400,++ "spent": 91200,++ "circulating": 37200,++ "holders": 1842,++ "transactions": 44190,++ "recent": {++ "earned": { "w": 6400, "pw": 5900 },++ "spent": { "w": 4100, "pw": 4300 },++ "holders": { "w": 210, "pw": 198 }++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true }nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "ok": true,++ "registry": {++ "NETWORK_PUBLIC_URL": "https://openvibe.network",++ "LIVE_PUBLIC_URL": "https://openvibe.live",++ "MEDIA_PUBLIC_URL": "https://openvibe.media"++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true }nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "ok": true,++ "analytics": {++ "summary": { "total_pageviews": 40231, "avg_response_ms": 84 },++ "realtime": { "online": 12 },++ "apps": [++ { "name": "img", "total_pageviews": 20000 },++ { "name": "docs", "total_pageviews": 20231 }++ ]++ }++}@@@ -30143,6 +30143,31 @@]}},++ "tools.analytics-read-result": {++ "version": "1.0.0",++ "owner": "tools",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",++ "title": "ToolsAnalyticsReadResult",++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",++ "type": "object",++ "required": [++ "ok",++ "analytics"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "analytics": {++ "type": "object",++ "description": "Aggregate analytics; open."++ }++ }++ }++ },"tools.job-read-result": {"version": "1.0.0","owner": "tools",@@@ -46861,6 +46886,86 @@}}},++ "media.avatar-ingest-request": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",++ "title": "MediaAvatarIngestRequest",++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",++ "type": "object",++ "required": [++ "url",++ "user_id"++ ],++ "properties": {++ "url": {++ "type": "string",++ "description": "The https address of the picture to import."++ },++ "user_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The account that owns the stored paste."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Used only for the paste title."++ }++ }++ }++ },++ "media.avatar-ingest-result": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",++ "title": "MediaAvatarIngestResult",++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",++ "type": "object",++ "required": [++ "ok",++ "slug",++ "url",++ "width",++ "height",++ "bytes"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "slug": {++ "type": "string"++ },++ "url": {++ "type": "string"++ },++ "width": {++ "type": "integer",++ "minimum": 1++ },++ "height": {++ "type": "integer",++ "minimum": 1++ },++ "bytes": {++ "type": "integer",++ "minimum": 1++ }++ }++ }++ },"media.file-read-result": {"version": "1.0.0","owner": "media",@@@ -48662,6 +48767,164 @@]}},++ "live.analytics-read-result": {++ "version": "1.0.0",++ "owner": "live",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",++ "title": "LiveAnalyticsReadResult",++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "summary"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "summary": {++ "type": "object",++ "description": "Aggregate totals; open."++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]++ }++ },++ "live.avatar-write-request": {++ "version": "1.0.0",++ "owner": "live",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",++ "title": "LiveAvatarWriteRequest",++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",++ "type": "object",++ "required": [++ "openvibenetwork_id",++ "avatar_url"++ ],++ "properties": {++ "openvibenetwork_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Fallback when no linked account matches."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500,++ "description": "An https://openvibe.media/… picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/"++ }++ }++ }++ },++ "live.avatar-write-result": {++ "version": "1.0.0",++ "owner": "live",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",++ "title": "LiveAvatarWriteResult",++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",++ "type": "object",++ "required": [++ "ok",++ "id",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "id": {++ "type": "integer",++ "minimum": 1++ },++ "changed": {++ "type": "boolean"++ }++ }++ }++ },++ "live.url-registry-refresh-result": {++ "version": "1.0.0",++ "owner": "live",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",++ "title": "LiveUrlRegistryRefreshResult",++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "message"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "message": {++ "type": "string"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]++ }++ },"live.follower-page": {"version": "1.0.0","owner": "live",@@@ -55230,6 +55493,176 @@}}},++ "network.avatar-write-request": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",++ "title": "NetworkAvatarWriteRequest",++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",++ "type": "object",++ "required": [++ "user_id",++ "avatar_url"++ ],++ "properties": {++ "user_id": {++ "type": "integer",++ "minimum": 1,++ "description": "The Network (SSO) user id."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "An https://openvibe.media/… picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/",++ "maxLength": 500++ },++ "origin": {++ "type": "string",++ "maxLength": 40,++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."++ }++ }++ }++ },++ "network.avatar-write-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",++ "title": "NetworkAvatarWriteResult",++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",++ "type": "object",++ "required": [++ "ok",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "changed": {++ "type": "boolean"++ }++ }++ }++ },++ "network.registry-read-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",++ "title": "NetworkRegistryReadResult",++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",++ "type": "object",++ "required": [++ "ok",++ "registry"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "registry": {++ "type": "object",++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."++ }++ }++ }++ },++ "network.coins-read-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",++ "title": "NetworkCoinsReadResult",++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",++ "type": "object",++ "required": [++ "earned",++ "spent",++ "circulating",++ "holders",++ "transactions",++ "recent"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "type": "integer",++ "minimum": 0++ },++ "spent": {++ "type": "integer",++ "minimum": 0++ },++ "circulating": {++ "type": "integer",++ "minimum": 0++ },++ "holders": {++ "type": "integer",++ "minimum": 0++ },++ "transactions": {++ "type": "integer",++ "minimum": 0++ },++ "recent": {++ "type": "object",++ "required": [++ "earned",++ "spent",++ "holders"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "$ref": "#/$defs/window"++ },++ "spent": {++ "$ref": "#/$defs/window"++ },++ "holders": {++ "$ref": "#/$defs/window"++ }++ }++ }++ },++ "$defs": {++ "window": {++ "type": "object",++ "required": [++ "w",++ "pw"++ ],++ "additionalProperties": false,++ "properties": {++ "w": {++ "type": "integer",++ "minimum": 0++ },++ "pw": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },"platform.resource-offer": {"version": "1.0.0","owner": "network",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -82,22 +82,22 @@"service": "live","name": "OpenVibe.Live","origin": "https://openvibe.live",-- "operations": 27,-- "capabilities": 6++ "operations": 30,++ "capabilities": 9},{"service": "media","name": "OpenVibe.Media","origin": "https://openvibe.media",-- "operations": 6,-- "capabilities": 4++ "operations": 7,++ "capabilities": 5},{"service": "network","name": "OpenVibe.Network","origin": "https://openvibe.network",-- "operations": 26,-- "capabilities": 19++ "operations": 29,++ "capabilities": 22},{"service": "news",@@@ -145,8 +145,8 @@"service": "tools","name": "OpenVibe.Tools","origin": "https://openvibe.tools",-- "operations": 12,-- "capabilities": 6++ "operations": 14,++ "capabilities": 7},{"service": "trade",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -1172,6 +1172,52 @@}}},++ "/internal/analytics-summary": {++ "get": {++ "operationId": "get_internal_analytics_summary",++ "summary": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",++ "description": "**live.analytics.read** (internal): Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",++ "tags": [++ "live.analytics"++ ],++ "x-openvibe-capabilities": [++ "live.analytics.read"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "live.analytics.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/live.analytics-read-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/followers": {"get": {"operationId": "get_internal_followers",@@@ -1512,6 +1558,108 @@}}}++ },++ "/internal/url-registry/refresh": {++ "post": {++ "operationId": "post_internal_url_registry_refresh",++ "summary": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",++ "description": "**live.url_registry.refresh** (internal): Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",++ "tags": [++ "live.url_registry"++ ],++ "x-openvibe-capabilities": [++ "live.url_registry.refresh"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "live.url_registry.refresh"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/live.url-registry-refresh-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/internal/user-avatar": {++ "post": {++ "operationId": "post_internal_user_avatar",++ "summary": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat).",++ "description": "**live.avatar.write** (internal): Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",++ "tags": [++ "live.avatar"++ ],++ "x-openvibe-capabilities": [++ "live.avatar.write"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "live.avatar.write"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/live.avatar-write-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/live.avatar-write-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }}},"components": {@@@ -1977,6 +2125,100 @@}}},++ "live.analytics-read-result.v1": {++ "title": "LiveAnalyticsReadResult",++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "summary"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "summary": {++ "type": "object",++ "description": "Aggregate totals; open."++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]++ },++ "live.avatar-write-request.v1": {++ "title": "LiveAvatarWriteRequest",++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",++ "type": "object",++ "required": [++ "openvibenetwork_id",++ "avatar_url"++ ],++ "properties": {++ "openvibenetwork_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Fallback when no linked account matches."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500,++ "description": "An https://openvibe.media/… picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/"++ }++ }++ },++ "live.avatar-write-result.v1": {++ "title": "LiveAvatarWriteResult",++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",++ "type": "object",++ "required": [++ "ok",++ "id",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "id": {++ "type": "integer",++ "minimum": 1++ },++ "changed": {++ "type": "boolean"++ }++ }++ },"live.channel-read-result.v1": {"title": "LiveChannelReadResult","description": "live.channel-read-result@1: answers of live.channel.read on OpenVibe.Live (public; the channel is addressed by username). GET /api/streams/channel/:username → the channel page: { channel, language, stream, streams, managed_streams (public projection), vods, clips, aiClips, … with totals and paging }; GET …/:username/live → { channel: { username, display_name, user_id }, streams }; GET …/:username/popular → { vod, clip, vods, clips, ranges }; GET …/:username/clips-taken → { clips, total, facets, limit, offset, hasMore, sort, of, includeSelf }; GET …/:username/bio-en → { from, to: en, text } (from and text null when there is nothing to translate); GET /api/auth/user/:username → { user } (the public profile). Unknown channels are 404 { error }.",@@@ -3209,6 +3451,42 @@}}}++ },++ "live.url-registry-refresh-result.v1": {++ "title": "LiveUrlRegistryRefreshResult",++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",++ "anyOf": [++ {++ "type": "object",++ "required": [++ "ok",++ "message"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "message": {++ "type": "string"++ }++ }++ },++ {++ "type": "object",++ "required": [++ "ok",++ "error"++ ],++ "properties": {++ "ok": {++ "const": false++ },++ "error": {++ "type": "string"++ }++ }++ }++ ]}},"securitySchemes": {@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -385,6 +385,62 @@}}}++ },++ "/internal/avatar-ingest": {++ "post": {++ "operationId": "post_internal_avatar_ingest",++ "summary": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",++ "description": "**media.avatar.ingest** (internal): Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",++ "tags": [++ "media.avatar"++ ],++ "x-openvibe-capabilities": [++ "media.avatar.ingest"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "media.avatar.ingest"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.avatar-ingest-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.avatar-ingest-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }}},"components": {@@@ -455,6 +511,72 @@}}},++ "media.avatar-ingest-request.v1": {++ "title": "MediaAvatarIngestRequest",++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",++ "type": "object",++ "required": [++ "url",++ "user_id"++ ],++ "properties": {++ "url": {++ "type": "string",++ "description": "The https address of the picture to import."++ },++ "user_id": {++ "type": [++ "integer",++ "string"++ ],++ "description": "The account that owns the stored paste."++ },++ "username": {++ "type": [++ "string",++ "null"++ ],++ "description": "Used only for the paste title."++ }++ }++ },++ "media.avatar-ingest-result.v1": {++ "title": "MediaAvatarIngestResult",++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",++ "type": "object",++ "required": [++ "ok",++ "slug",++ "url",++ "width",++ "height",++ "bytes"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "slug": {++ "type": "string"++ },++ "url": {++ "type": "string"++ },++ "width": {++ "type": "integer",++ "minimum": 1++ },++ "height": {++ "type": "integer",++ "minimum": 1++ },++ "bytes": {++ "type": "integer",++ "minimum": 1++ }++ }++ },"media.file-read-result.v1": {"title": "MediaFileReadResult","description": "media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -594,6 +594,52 @@}}},++ "/internal/coins/stats": {++ "get": {++ "operationId": "get_internal_coins_stats",++ "summary": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",++ "description": "**network.coins.read** (internal): Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",++ "tags": [++ "network.coins"++ ],++ "x-openvibe-capabilities": [++ "network.coins.read"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "network.coins.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.coins-read-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/coins/transfer": {"post": {"operationId": "post_internal_coins_transfer",@@@ -1638,6 +1684,108 @@}}}++ },++ "/internal/url-registry/resolved": {++ "get": {++ "operationId": "get_internal_url_registry_resolved",++ "summary": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",++ "description": "**network.registry.read** (internal): Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",++ "tags": [++ "network.registry"++ ],++ "x-openvibe-capabilities": [++ "network.registry.read"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "network.registry.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.registry-read-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/internal/user-avatar": {++ "post": {++ "operationId": "post_internal_user_avatar",++ "summary": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites.",++ "description": "**network.avatar.write** (internal): A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",++ "tags": [++ "network.avatar"++ ],++ "x-openvibe-capabilities": [++ "network.avatar.write"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "network.avatar.write"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.avatar-write-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.avatar-write-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }}},"components": {@@@ -2137,6 +2285,54 @@}}},++ "network.avatar-write-request.v1": {++ "title": "NetworkAvatarWriteRequest",++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",++ "type": "object",++ "required": [++ "user_id",++ "avatar_url"++ ],++ "properties": {++ "user_id": {++ "type": "integer",++ "minimum": 1,++ "description": "The Network (SSO) user id."++ },++ "avatar_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "An https://openvibe.media/… picture; null clears the avatar.",++ "pattern": "^https://openvibe\\.media/",++ "maxLength": 500++ },++ "origin": {++ "type": "string",++ "maxLength": 40,++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."++ }++ }++ },++ "network.avatar-write-result.v1": {++ "title": "NetworkAvatarWriteResult",++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",++ "type": "object",++ "required": [++ "ok",++ "changed"++ ],++ "additionalProperties": false,++ "properties": {++ "ok": {++ "const": true++ },++ "changed": {++ "type": "boolean"++ }++ }++ },"network.blocks-result.v1": {"title": "NetworkBlocksResult","description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",@@@ -2233,6 +2429,82 @@}}},++ "network.coins-read-result.v1": {++ "title": "NetworkCoinsReadResult",++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",++ "type": "object",++ "required": [++ "earned",++ "spent",++ "circulating",++ "holders",++ "transactions",++ "recent"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "type": "integer",++ "minimum": 0++ },++ "spent": {++ "type": "integer",++ "minimum": 0++ },++ "circulating": {++ "type": "integer",++ "minimum": 0++ },++ "holders": {++ "type": "integer",++ "minimum": 0++ },++ "transactions": {++ "type": "integer",++ "minimum": 0++ },++ "recent": {++ "type": "object",++ "required": [++ "earned",++ "spent",++ "holders"++ ],++ "additionalProperties": false,++ "properties": {++ "earned": {++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"++ },++ "spent": {++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"++ },++ "holders": {++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"++ }++ }++ }++ },++ "$defs": {++ "window": {++ "type": "object",++ "required": [++ "w",++ "pw"++ ],++ "additionalProperties": false,++ "properties": {++ "w": {++ "type": "integer",++ "minimum": 0++ },++ "pw": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ },"network.coins-transfer-request.v1": {"title": "NetworkCoinsTransferRequest","description": "network.coins-transfer-request@1: the body of network.coins.transfer on OpenVibe.Network. POST /internal/coins/transfer: { from_user_id, to_user_id, app_id?, amount, reason?, ref?, idempotency_key } moves OpenCoins between two Network users atomically (the two ids are Network user ids and must differ). Refusals are { error } bodies: 400 invalid_from_user_id, invalid_to_user_id, invalid_transfer (the same person), invalid_amount or missing_idempotency_key; 404 user_not_found; 409 { error: insufficient_funds, balance } when the sender cannot cover it. Unknown fields are ignored.",@@@ -3340,6 +3612,24 @@}}},++ "network.registry-read-result.v1": {++ "title": "NetworkRegistryReadResult",++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",++ "type": "object",++ "required": [++ "ok",++ "registry"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "registry": {++ "type": "object",++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."++ }++ }++ },"network.staff-list-result.v1": {"title": "NetworkStaffListResult","description": "network.staff-list-result@1: the answer of network.staff.read on OpenVibe.Network. GET /api/v1/staff/moderators[?service=chat|live|community|pastes|calls] (a service token holding network.staff.read, or a signed-in person holding staff.moderation.logs) → { service, staff }: the network's global moderators, admins and the owner who are not banned, with the staff capabilities their role holds in the contracts staff map; ?service keeps those holding the capability that moderates that service (another service name keeps those holding staff.content.moderate; an invalid value is ignored and answered as null). Answered with Cache-Control: private, no-store; a person without staff.moderation.logs gets 403 { error: forbidden }.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.79.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": "OpenRe.Stream API",-- "version": "0.78.0",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.Reviews API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -16,6 +16,98 @@}],"paths": {++ "/api/internal/analytics": {++ "get": {++ "operationId": "get_api_internal_analytics",++ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",++ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",++ "tags": [++ "tools.analytics"++ ],++ "x-openvibe-capabilities": [++ "tools.analytics.read"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "tools.analytics.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/tools.analytics-read-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/internal/analytics/bots": {++ "get": {++ "operationId": "get_api_internal_analytics_bots",++ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",++ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",++ "tags": [++ "tools.analytics"++ ],++ "x-openvibe-capabilities": [++ "tools.analytics.read"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "tools.analytics.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/tools.analytics-read-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/api/v1/jobs": {"post": {"operationId": "post_api_v1_jobs",@@@ -814,6 +906,24 @@}}},++ "tools.analytics-read-result.v1": {++ "title": "ToolsAnalyticsReadResult",++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",++ "type": "object",++ "required": [++ "ok",++ "analytics"++ ],++ "properties": {++ "ok": {++ "const": true++ },++ "analytics": {++ "type": "object",++ "description": "Aggregate analytics; open."++ }++ }++ },"tools.job-create-request.v1": {"title": "ToolsJobCreateRequest","description": "tools.job-create-request@1: the request of tools.job.create on the Tools satellites that run jobs. POST /api/v1/jobs: tools.job-request@1 (JSON or multipart). POST /api/v1/jobs/:id/retry and PUT|DELETE /api/v1/jobs/:id/references/:ref take no body.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.78.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"@@@ -30500,6 +30500,18 @@ export interface ToolsJobRequest {*/export type ToolsJobCreateRequest = ToolsJobRequest | NoBody;++/** [email protected] (owner: tools) */++/**++ * tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).++ */++export interface ToolsAnalyticsReadResult {++ ok: true;++ /**++ * Aggregate analytics; open.++ */++ analytics: {};++}++/** [email protected] (owner: tools) *//*** tools.job-read-result@1: answers of tools.job.read on the Tools satellites. GET /api/v1/jobs/:id → tools.job@1, and so is the data of every event on GET /api/v1/jobs/:id/events (Server-Sent Events, Last-Event-ID resume); GET /api/v1/jobs/:id/files/:n → the result file's bytes (Content-Type of the file; 409 tools.job.not_ready before the job succeeded).@@@ -43291,6 +43303,38 @@ export interface MediaObjectVisibilityChangedPayload {changed_at: string;}++/** [email protected] (owner: media) */++/**++ * media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).++ */++export interface MediaAvatarIngestRequest {++ /**++ * The https address of the picture to import.++ */++ url: string;++ /**++ * The account that owns the stored paste.++ */++ user_id: number | string;++ /**++ * Used only for the paste title.++ */++ username?: string | null;++}++++/** [email protected] (owner: media) */++/**++ * media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).++ */++export interface MediaAvatarIngestResult {++ ok: true;++ slug: string;++ url: string;++ width: number;++ height: number;++ bytes: number;++}++/** [email protected] (owner: media) *//*** media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).@@@ -43886,6 +43930,66 @@ export type LiveDiscoveryResult =partial: boolean;};++/** [email protected] (owner: live) */++/**++ * live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.++ */++export type LiveAnalyticsReadResult =++ | {++ ok: true;++ /**++ * Aggregate totals; open.++ */++ summary: {};++ }++ | {++ ok: false;++ error: string;++ };++++/** [email protected] (owner: live) */++/**++ * live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.++ */++export interface LiveAvatarWriteRequest {++ /**++ * The Network (SSO) user id; matched against linked_accounts. A Live id never goes here.++ */++ openvibenetwork_id: number | string;++ /**++ * Fallback when no linked account matches.++ */++ username?: string | null;++ /**++ * An https://openvibe.media/… picture; null clears the avatar.++ */++ avatar_url: string | null;++}++++/** [email protected] (owner: live) */++/**++ * live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).++ */++export interface LiveAvatarWriteResult {++ ok: true;++ id: number;++ changed: boolean;++}++++/** [email protected] (owner: live) */++/**++ * live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.++ */++export type LiveUrlRegistryRefreshResult =++ | {++ ok: true;++ message: string;++ }++ | {++ ok: false;++ error: string;++ };++/** [email protected] (owner: live) *//*** live.follower-page@1: the answer of GET /internal/followers?stream_id=&limit=&after= on OpenVibe.Live (live.follower.read, service token, loopback): the followers of the channel that streams stream_id, for Network's go-live notifications. A follower is named by their Network subject when Live knows it, else their Network user id. next is the cursor for the following page (null on the last). 400 without stream_id, 404 for an unknown stream.@@@ -46288,6 +46392,63 @@ export interface NetworkNodeListResult {generated_at: string;}++/** [email protected] (owner: network) */++/**++ * network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.++ */++export interface NetworkAvatarWriteRequest {++ /**++ * The Network (SSO) user id.++ */++ user_id: number;++ /**++ * An https://openvibe.media/… picture; null clears the avatar.++ */++ avatar_url: string | null;++ /**++ * Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back.++ */++ origin?: string;++}++++/** [email protected] (owner: network) */++/**++ * network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).++ */++export interface NetworkAvatarWriteResult {++ ok: true;++ changed: boolean;++}++++/** [email protected] (owner: network) */++/**++ * network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.++ */++export interface NetworkRegistryReadResult {++ ok: true;++ /**++ * Setting name → resolved value; secrets and sensitive keys are omitted.++ */++ registry: {};++}++++/** [email protected] (owner: network) */++/**++ * network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.++ */++export interface NetworkCoinsReadResult {++ earned: number;++ spent: number;++ circulating: number;++ holders: number;++ transactions: number;++ recent: {++ earned: Window;++ spent: Window;++ holders: Window;++ };++}++/** [email protected] (owner: network) *//*** What one node or external provider can run right now (roadmap WS-Z9, decision 43): capabilities, multidimensional capacity, measured latencies, health and pricing. First-party only: it carries capacity the public node registry (network.node@1) deliberately leaves out.nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "live.analytics.read",++ "version": "1.0",++ "owner": "live",++ "status": "active",++ "visibility": "internal",++ "description": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",++ "permissions": [++ "analytics:summary"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /internal/analytics-summary"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "live.analytics-read-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "live.avatar.write",++ "version": "1.0",++ "owner": "live",++ "status": "active",++ "visibility": "internal",++ "description": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",++ "permissions": [++ "users:avatar"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "POST /internal/user-avatar"++ ],++ "inputSchema": "live.avatar-write-request@1",++ "outputSchema": "live.avatar-write-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "live.url_registry.refresh",++ "version": "1.0",++ "owner": "live",++ "status": "active",++ "visibility": "internal",++ "description": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",++ "permissions": [++ "registry:refresh"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "POST /internal/url-registry/refresh"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "live.url-registry-refresh-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "media.avatar.ingest",++ "version": "1.0",++ "owner": "media",++ "status": "active",++ "visibility": "internal",++ "description": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",++ "permissions": [++ "avatars:ingest"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "POST /internal/avatar-ingest"++ ],++ "inputSchema": "media.avatar-ingest-request@1",++ "outputSchema": "media.avatar-ingest-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.avatar.write",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "internal",++ "description": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",++ "permissions": [++ "profile:avatar"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "POST /internal/user-avatar"++ ],++ "inputSchema": "network.avatar-write-request@1",++ "outputSchema": "network.avatar-write-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.coins.read",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "internal",++ "description": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",++ "permissions": [++ "coins:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /internal/coins/stats"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "network.coins-read-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.registry.read",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "internal",++ "description": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",++ "permissions": [++ "registry:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /internal/url-registry/resolved"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "network.registry-read-result@1"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "tools.analytics.read",++ "version": "1.0",++ "owner": "tools",++ "status": "active",++ "visibility": "internal",++ "description": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",++ "permissions": [++ "analytics:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /api/internal/analytics",++ "GET /api/internal/analytics/bots"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "tools.analytics-read-result@1"++}@@@ -28,16 +28,19 @@"position": 0},"capabilities": [++ "live.analytics.read",++ "live.avatar.write",++ "live.channel.read","live.chat_context.read","live.chat_effects.write","live.chat_mirror.write",++ "live.discovery.read","live.follower.read","live.lineage.resolve",-- "live.tips_delivery.write",-- "live.channel.read",++ "live.owner.resolve","live.stream.read",-- "live.discovery.read",-- "live.owner.resolve"++ "live.tips_delivery.write",++ "live.url_registry.refresh"],"eventsProduced": ["live.index_document.deleted",@@@ -27,15 +27,16 @@"position": 4},"capabilities": [-- "media.object.upload",-- "media.object.read",-- "media.object.list",-- "media.object.delete",-- "media.upload.create",++ "media.avatar.ingest","media.derivative.create","media.derivative.read","media.lifecycle.read",-- "media.lifecycle.transition"++ "media.lifecycle.transition",++ "media.object.delete",++ "media.object.list",++ "media.object.read",++ "media.object.upload",++ "media.upload.create"],"eventsProduced": ["media.clip.failed",@@@ -37,20 +37,23 @@"network.account.deletion.confirm","network.account.export.contribute","network.analytics.creator.read",++ "network.avatar.write","network.blocks.read","network.coins.credit","network.coins.debit",++ "network.coins.read","network.coins.transfer","network.follows.read","network.follows.write","network.integration.github.read","network.modules.read","network.modules.write",++ "network.node.report","network.notifications.push","network.operator.alert",-- "network.node.report","network.project.manage","network.project.read",++ "network.registry.read","network.staff.read","network.status.incident"],@@@ -27,12 +27,13 @@"position": 1},"capabilities": [++ "tools.analytics.read",++ "tools.job.cancel","tools.job.create","tools.job.read",-- "tools.job.cancel",++ "tools.net.probe","tools.tool.read",-- "tools.tool.run",-- "tools.net.probe"++ "tools.tool.run"],"eventsProduced": ["tools.job.created",@@@ -88,14 +89,14 @@],"window": "automatic only while ovhost waits for readiness after the restart (60 s in host.example.json); manual afterwards, as long as no blocker applies","blockers": {-- "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"++ "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"}},"contracts": {"range": ">=0.7.0 <1.0.0"},"leases": {-- "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued → running)"++ "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued \u2192 running)"}},"notes": "Public at openvibe.tools: 169 tools in 9 families on the gateway (port 4001) and its satellites, catalogue at GET /api/catalog.json (re-served by Network). Asynchronous jobs (Wave 11) run on the img, audio and docs satellites only (POST /api/v1/jobs, GET/DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT|DELETE /api/v1/jobs/:id/references/:ref, SSE events, result files); the gateway has no /api/v1/jobs yet. Job results are stored as private Media objects (TOOLS_JOB_RESULTS=media: Tools holds media.object.upload and media.object.read, namespace tools) and job lifecycle events tools.job.created|started|succeeded|failed are published to OpenVibe.Events through an outbox in each satellite's jobs.db (EVENTS_URL set; events.event.publish). The tool registry (ADR-027, tools.tool.read) is served: GET /api/v1/tools[/:id[/schema]] on the gateway, for all 169 tools, and on the img, audio, docs, text, yt, maps and food satellites for their own. Planned (ADR-027): the uniform run API POST /api/v1/tools/:id/run (tools.tool.run; network probes need tools.net.probe), a jobs facade on the gateway, tiered quotas and a pseudonymous abuse log; the legacy /api/process, /api/net/* and /api/dev/* routes keep working with Deprecation and Sunset (2026-12-31) headers. GET /api/ready is truthful on the gateway (it checks every satellite) and on each satellite."@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.78.0",++ "version": "0.79.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,17 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,17 @@ 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.79.0 — 2026-09-29
+
+**Service-token capabilities for every internal call that still used X-Internal-Key** (plan T2, register C-50–C-58).
+- New internal capabilities, each with request/result contracts and fixtures: `network.avatar.write` (a site reports an
+ avatar change to Network), `network.registry.read` (the resolved URL registry), `network.coins.read` (OpenCoins totals,
+ never balances of named people), `live.avatar.write` (Network pushes an avatar to Live), `live.url_registry.refresh`,
+ `live.analytics.read` (aggregate totals for Network's navigation), `media.avatar.ingest` (Network asks Media to import
+ an avatar), `tools.analytics.read` (the Tools gateway's and tool sites' aggregate analytics).
+- Avatar URLs in the avatar requests must be on `https://openvibe.media/`.
+- The owning services' manifests list them.
+
## 0.78.0 — 2026-09-29
## 0.78.0 — 2026-09-29
**OpenVibe.Realtime is gone from the estate** (plan T0).
**OpenVibe.Realtime is gone from the estate** (plan T0).
@@ -2893,6 +2893,15 @@
@@ -2893,6 +2893,15 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "tools.analytics-read-result",
+ "version": "1.0.0",
+ "owner": "tools",
+ "schema": "tools/analytics-read-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "tools.job-read-result",
"id": "tools.job-read-result",
"version": "1.0.0",
"version": "1.0.0",
@@ -4339,6 +4348,24 @@
@@ -4339,6 +4348,24 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "media.avatar-ingest-request",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/avatar-ingest-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "media.avatar-ingest-result",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/avatar-ingest-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "media.file-read-result",
"id": "media.file-read-result",
"version": "1.0.0",
"version": "1.0.0",
@@ -4485,6 +4512,42 @@
@@ -4485,6 +4512,42 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "live.analytics-read-result",
+ "version": "1.0.0",
+ "owner": "live",
+ "schema": "live/analytics-read-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "live.avatar-write-request",
+ "version": "1.0.0",
+ "owner": "live",
+ "schema": "live/avatar-write-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "live.avatar-write-result",
+ "version": "1.0.0",
+ "owner": "live",
+ "schema": "live/avatar-write-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "live.url-registry-refresh-result",
+ "version": "1.0.0",
+ "owner": "live",
+ "schema": "live/url-registry-refresh-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "live.follower-page",
"id": "live.follower-page",
"version": "1.0.0",
"version": "1.0.0",
@@ -5004,6 +5067,42 @@
@@ -5004,6 +5067,42 @@
"status": "active",
"status": "active",
"adr": "ADR-034"
"adr": "ADR-034"
},
},
+ {
+ "id": "network.avatar-write-request",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/avatar-write-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.avatar-write-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/avatar-write-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.registry-read-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/registry-read-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.coins-read-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/coins-read-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "platform.resource-offer",
"id": "platform.resource-offer",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",
+ "title": "LiveAnalyticsReadResult",
+ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "summary"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "summary": {
+ "type": "object",
+ "description": "Aggregate totals; open."
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",
+ "title": "LiveAvatarWriteRequest",
+ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed \u2014 the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/\u2026 address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
+ "type": "object",
+ "required": [
+ "openvibenetwork_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "openvibenetwork_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Fallback when no linked account matches."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500,
+ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",
+ "title": "LiveAvatarWriteResult",
+ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
+ "type": "object",
+ "required": [
+ "ok",
+ "id",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",
+ "title": "LiveUrlRegistryRefreshResult",
+ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "message"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",
+ "title": "MediaAvatarIngestRequest",
+ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
+ "type": "object",
+ "required": [
+ "url",
+ "user_id"
+ ],
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The https address of the picture to import."
+ },
+ "user_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The account that owns the stored paste."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Used only for the paste title."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",
+ "title": "MediaAvatarIngestResult",
+ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
+ "type": "object",
+ "required": [
+ "ok",
+ "slug",
+ "url",
+ "width",
+ "height",
+ "bytes"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "slug": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "bytes": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",
+ "title": "NetworkAvatarWriteRequest",
+ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
+ "type": "object",
+ "required": [
+ "user_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "user_id": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The Network (SSO) user id."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/",
+ "maxLength": 500
+ },
+ "origin": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",
+ "title": "NetworkAvatarWriteResult",
+ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
+ "type": "object",
+ "required": [
+ "ok",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",
+ "title": "NetworkCoinsReadResult",
+ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "circulating",
+ "holders",
+ "transactions",
+ "recent"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "spent": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "circulating": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "holders": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "transactions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "recent": {
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "holders"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "$ref": "#/$defs/window"
+ },
+ "spent": {
+ "$ref": "#/$defs/window"
+ },
+ "holders": {
+ "$ref": "#/$defs/window"
+ }
+ }
+ }
+ },
+ "$defs": {
+ "window": {
+ "type": "object",
+ "required": [
+ "w",
+ "pw"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "w": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "pw": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",
+ "title": "NetworkRegistryReadResult",
+ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
+ "type": "object",
+ "required": [
+ "ok",
+ "registry"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "registry": {
+ "type": "object",
+ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",
+ "title": "ToolsAnalyticsReadResult",
+ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
+ "type": "object",
+ "required": [
+ "ok",
+ "analytics"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "analytics": {
+ "type": "object",
+ "description": "Aggregate analytics; open."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "error": "analytics store unavailable" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "summary": { "pageviews": 125000, "sessions": 40211, "bots": 8300 } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "openvibenetwork_id": 4821, "username": "aurora" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "openvibenetwork_id": 4821, "username": "aurora", "avatar_url": "https://openvibe.media/p/hero-shot/screenshot" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "changed": false }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "id": 77, "changed": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "error": "registry unreachable" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "message": "URL registry refreshed" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "user_id": 4821, "username": "aurora" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "url": "https://cdn.example.com/me.png", "user_id": 4821, "username": "aurora" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "error": "That link is not a picture" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "slug": "avatar-9f3k2", "url": "https://openvibe.media/p/avatar-9f3k2/screenshot", "width": 512, "height": 512, "bytes": 40231 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "user_id": 4821, "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "changed": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "changed": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "earned": 128400,
+ "spent": 91200,
+ "circulating": 37200,
+ "holders": 1842,
+ "transactions": 44190
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "earned": 128400,
+ "spent": 91200,
+ "circulating": 37200,
+ "holders": 1842,
+ "transactions": 44190,
+ "recent": {
+ "earned": { "w": 6400, "pw": 5900 },
+ "spent": { "w": 4100, "pw": 4300 },
+ "holders": { "w": 210, "pw": 198 }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "ok": true,
+ "registry": {
+ "NETWORK_PUBLIC_URL": "https://openvibe.network",
+ "LIVE_PUBLIC_URL": "https://openvibe.live",
+ "MEDIA_PUBLIC_URL": "https://openvibe.media"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "ok": true,
+ "analytics": {
+ "summary": { "total_pageviews": 40231, "avg_response_ms": 84 },
+ "realtime": { "online": 12 },
+ "apps": [
+ { "name": "img", "total_pageviews": 20000 },
+ { "name": "docs", "total_pageviews": 20231 }
+ ]
+ }
+}
@@ -30143,6 +30143,31 @@
@@ -30143,6 +30143,31 @@
]
]
}
}
},
},
+ "tools.analytics-read-result": {
+ "version": "1.0.0",
+ "owner": "tools",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",
+ "title": "ToolsAnalyticsReadResult",
+ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
+ "type": "object",
+ "required": [
+ "ok",
+ "analytics"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "analytics": {
+ "type": "object",
+ "description": "Aggregate analytics; open."
+ }
+ }
+ }
+ },
"tools.job-read-result": {
"tools.job-read-result": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "tools",
"owner": "tools",
@@ -46861,6 +46886,86 @@
@@ -46861,6 +46886,86 @@
}
}
}
}
},
},
+ "media.avatar-ingest-request": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",
+ "title": "MediaAvatarIngestRequest",
+ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
+ "type": "object",
+ "required": [
+ "url",
+ "user_id"
+ ],
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The https address of the picture to import."
+ },
+ "user_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The account that owns the stored paste."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Used only for the paste title."
+ }
+ }
+ }
+ },
+ "media.avatar-ingest-result": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",
+ "title": "MediaAvatarIngestResult",
+ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
+ "type": "object",
+ "required": [
+ "ok",
+ "slug",
+ "url",
+ "width",
+ "height",
+ "bytes"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "slug": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "bytes": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+ }
+ },
"media.file-read-result": {
"media.file-read-result": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "media",
"owner": "media",
@@ -48662,6 +48767,164 @@
@@ -48662,6 +48767,164 @@
]
]
}
}
},
},
+ "live.analytics-read-result": {
+ "version": "1.0.0",
+ "owner": "live",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",
+ "title": "LiveAnalyticsReadResult",
+ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "summary"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "summary": {
+ "type": "object",
+ "description": "Aggregate totals; open."
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "live.avatar-write-request": {
+ "version": "1.0.0",
+ "owner": "live",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",
+ "title": "LiveAvatarWriteRequest",
+ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
+ "type": "object",
+ "required": [
+ "openvibenetwork_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "openvibenetwork_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Fallback when no linked account matches."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500,
+ "description": "An https://openvibe.media/… picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/"
+ }
+ }
+ }
+ },
+ "live.avatar-write-result": {
+ "version": "1.0.0",
+ "owner": "live",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",
+ "title": "LiveAvatarWriteResult",
+ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
+ "type": "object",
+ "required": [
+ "ok",
+ "id",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "live.url-registry-refresh-result": {
+ "version": "1.0.0",
+ "owner": "live",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",
+ "title": "LiveUrlRegistryRefreshResult",
+ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "message"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ }
+ },
"live.follower-page": {
"live.follower-page": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "live",
"owner": "live",
@@ -55230,6 +55493,176 @@
@@ -55230,6 +55493,176 @@
}
}
}
}
},
},
+ "network.avatar-write-request": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",
+ "title": "NetworkAvatarWriteRequest",
+ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
+ "type": "object",
+ "required": [
+ "user_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "user_id": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The Network (SSO) user id."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "An https://openvibe.media/… picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/",
+ "maxLength": 500
+ },
+ "origin": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
+ }
+ }
+ }
+ },
+ "network.avatar-write-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",
+ "title": "NetworkAvatarWriteResult",
+ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
+ "type": "object",
+ "required": [
+ "ok",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "network.registry-read-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",
+ "title": "NetworkRegistryReadResult",
+ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
+ "type": "object",
+ "required": [
+ "ok",
+ "registry"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "registry": {
+ "type": "object",
+ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
+ }
+ }
+ }
+ },
+ "network.coins-read-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",
+ "title": "NetworkCoinsReadResult",
+ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "circulating",
+ "holders",
+ "transactions",
+ "recent"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "spent": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "circulating": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "holders": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "transactions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "recent": {
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "holders"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "$ref": "#/$defs/window"
+ },
+ "spent": {
+ "$ref": "#/$defs/window"
+ },
+ "holders": {
+ "$ref": "#/$defs/window"
+ }
+ }
+ }
+ },
+ "$defs": {
+ "window": {
+ "type": "object",
+ "required": [
+ "w",
+ "pw"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "w": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "pw": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
"platform.resource-offer": {
"platform.resource-offer": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "network",
"owner": "network",
@@ -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.78.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -82,22 +82,22 @@
@@ -82,22 +82,22 @@
"service": "live",
"service": "live",
"name": "OpenVibe.Live",
"name": "OpenVibe.Live",
"origin": "https://openvibe.live",
"origin": "https://openvibe.live",
- "operations": 27,
- "capabilities": 6
+ "operations": 30,
+ "capabilities": 9
},
},
{
{
"service": "media",
"service": "media",
"name": "OpenVibe.Media",
"name": "OpenVibe.Media",
"origin": "https://openvibe.media",
"origin": "https://openvibe.media",
- "operations": 6,
- "capabilities": 4
+ "operations": 7,
+ "capabilities": 5
},
},
{
{
"service": "network",
"service": "network",
"name": "OpenVibe.Network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
"origin": "https://openvibe.network",
- "operations": 26,
- "capabilities": 19
+ "operations": 29,
+ "capabilities": 22
},
},
{
{
"service": "news",
"service": "news",
@@ -145,8 +145,8 @@
@@ -145,8 +145,8 @@
"service": "tools",
"service": "tools",
"name": "OpenVibe.Tools",
"name": "OpenVibe.Tools",
"origin": "https://openvibe.tools",
"origin": "https://openvibe.tools",
- "operations": 12,
- "capabilities": 6
+ "operations": 14,
+ "capabilities": 7
},
},
{
{
"service": "trade",
"service": "trade",
@@ -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.78.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -1172,6 +1172,52 @@
@@ -1172,6 +1172,52 @@
}
}
}
}
},
},
+ "/internal/analytics-summary": {
+ "get": {
+ "operationId": "get_internal_analytics_summary",
+ "summary": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
+ "description": "**live.analytics.read** (internal): Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
+ "tags": [
+ "live.analytics"
+ ],
+ "x-openvibe-capabilities": [
+ "live.analytics.read"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "live.analytics.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/live.analytics-read-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/followers": {
"/internal/followers": {
"get": {
"get": {
"operationId": "get_internal_followers",
"operationId": "get_internal_followers",
@@ -1512,6 +1558,108 @@
@@ -1512,6 +1558,108 @@
}
}
}
}
}
}
+ },
+ "/internal/url-registry/refresh": {
+ "post": {
+ "operationId": "post_internal_url_registry_refresh",
+ "summary": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
+ "description": "**live.url_registry.refresh** (internal): Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
+ "tags": [
+ "live.url_registry"
+ ],
+ "x-openvibe-capabilities": [
+ "live.url_registry.refresh"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "live.url_registry.refresh"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/live.url-registry-refresh-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/internal/user-avatar": {
+ "post": {
+ "operationId": "post_internal_user_avatar",
+ "summary": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat).",
+ "description": "**live.avatar.write** (internal): Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",
+ "tags": [
+ "live.avatar"
+ ],
+ "x-openvibe-capabilities": [
+ "live.avatar.write"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "live.avatar.write"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/live.avatar-write-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/live.avatar-write-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
},
},
"components": {
"components": {
@@ -1977,6 +2125,100 @@
@@ -1977,6 +2125,100 @@
}
}
}
}
},
},
+ "live.analytics-read-result.v1": {
+ "title": "LiveAnalyticsReadResult",
+ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "summary"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "summary": {
+ "type": "object",
+ "description": "Aggregate totals; open."
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ },
+ "live.avatar-write-request.v1": {
+ "title": "LiveAvatarWriteRequest",
+ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
+ "type": "object",
+ "required": [
+ "openvibenetwork_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "openvibenetwork_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Fallback when no linked account matches."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500,
+ "description": "An https://openvibe.media/… picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/"
+ }
+ }
+ },
+ "live.avatar-write-result.v1": {
+ "title": "LiveAvatarWriteResult",
+ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
+ "type": "object",
+ "required": [
+ "ok",
+ "id",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+ },
"live.channel-read-result.v1": {
"live.channel-read-result.v1": {
"title": "LiveChannelReadResult",
"title": "LiveChannelReadResult",
"description": "live.channel-read-result@1: answers of live.channel.read on OpenVibe.Live (public; the channel is addressed by username). GET /api/streams/channel/:username → the channel page: { channel, language, stream, streams, managed_streams (public projection), vods, clips, aiClips, … with totals and paging }; GET …/:username/live → { channel: { username, display_name, user_id }, streams }; GET …/:username/popular → { vod, clip, vods, clips, ranges }; GET …/:username/clips-taken → { clips, total, facets, limit, offset, hasMore, sort, of, includeSelf }; GET …/:username/bio-en → { from, to: en, text } (from and text null when there is nothing to translate); GET /api/auth/user/:username → { user } (the public profile). Unknown channels are 404 { error }.",
"description": "live.channel-read-result@1: answers of live.channel.read on OpenVibe.Live (public; the channel is addressed by username). GET /api/streams/channel/:username → the channel page: { channel, language, stream, streams, managed_streams (public projection), vods, clips, aiClips, … with totals and paging }; GET …/:username/live → { channel: { username, display_name, user_id }, streams }; GET …/:username/popular → { vod, clip, vods, clips, ranges }; GET …/:username/clips-taken → { clips, total, facets, limit, offset, hasMore, sort, of, includeSelf }; GET …/:username/bio-en → { from, to: en, text } (from and text null when there is nothing to translate); GET /api/auth/user/:username → { user } (the public profile). Unknown channels are 404 { error }.",
@@ -3209,6 +3451,42 @@
@@ -3209,6 +3451,42 @@
}
}
}
}
}
}
+ },
+ "live.url-registry-refresh-result.v1": {
+ "title": "LiveUrlRegistryRefreshResult",
+ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "message"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "ok",
+ "error"
+ ],
+ "properties": {
+ "ok": {
+ "const": false
+ },
+ "error": {
+ "type": "string"
+ }
+ }
+ }
+ ]
}
}
},
},
"securitySchemes": {
"securitySchemes": {
@@ -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.78.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -385,6 +385,62 @@
@@ -385,6 +385,62 @@
}
}
}
}
}
}
+ },
+ "/internal/avatar-ingest": {
+ "post": {
+ "operationId": "post_internal_avatar_ingest",
+ "summary": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
+ "description": "**media.avatar.ingest** (internal): Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
+ "tags": [
+ "media.avatar"
+ ],
+ "x-openvibe-capabilities": [
+ "media.avatar.ingest"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "media.avatar.ingest"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.avatar-ingest-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.avatar-ingest-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
},
},
"components": {
"components": {
@@ -455,6 +511,72 @@
@@ -455,6 +511,72 @@
}
}
}
}
},
},
+ "media.avatar-ingest-request.v1": {
+ "title": "MediaAvatarIngestRequest",
+ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
+ "type": "object",
+ "required": [
+ "url",
+ "user_id"
+ ],
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The https address of the picture to import."
+ },
+ "user_id": {
+ "type": [
+ "integer",
+ "string"
+ ],
+ "description": "The account that owns the stored paste."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Used only for the paste title."
+ }
+ }
+ },
+ "media.avatar-ingest-result.v1": {
+ "title": "MediaAvatarIngestResult",
+ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
+ "type": "object",
+ "required": [
+ "ok",
+ "slug",
+ "url",
+ "width",
+ "height",
+ "bytes"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "slug": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "bytes": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+ },
"media.file-read-result.v1": {
"media.file-read-result.v1": {
"title": "MediaFileReadResult",
"title": "MediaFileReadResult",
"description": "media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).",
"description": "media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).",
@@ -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.78.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -594,6 +594,52 @@
@@ -594,6 +594,52 @@
}
}
}
}
},
},
+ "/internal/coins/stats": {
+ "get": {
+ "operationId": "get_internal_coins_stats",
+ "summary": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
+ "description": "**network.coins.read** (internal): Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
+ "tags": [
+ "network.coins"
+ ],
+ "x-openvibe-capabilities": [
+ "network.coins.read"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.coins.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.coins-read-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/coins/transfer": {
"/internal/coins/transfer": {
"post": {
"post": {
"operationId": "post_internal_coins_transfer",
"operationId": "post_internal_coins_transfer",
@@ -1638,6 +1684,108 @@
@@ -1638,6 +1684,108 @@
}
}
}
}
}
}
+ },
+ "/internal/url-registry/resolved": {
+ "get": {
+ "operationId": "get_internal_url_registry_resolved",
+ "summary": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
+ "description": "**network.registry.read** (internal): Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
+ "tags": [
+ "network.registry"
+ ],
+ "x-openvibe-capabilities": [
+ "network.registry.read"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.registry.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.registry-read-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/internal/user-avatar": {
+ "post": {
+ "operationId": "post_internal_user_avatar",
+ "summary": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites.",
+ "description": "**network.avatar.write** (internal): A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",
+ "tags": [
+ "network.avatar"
+ ],
+ "x-openvibe-capabilities": [
+ "network.avatar.write"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.avatar.write"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.avatar-write-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.avatar-write-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
},
},
"components": {
"components": {
@@ -2137,6 +2285,54 @@
@@ -2137,6 +2285,54 @@
}
}
}
}
},
},
+ "network.avatar-write-request.v1": {
+ "title": "NetworkAvatarWriteRequest",
+ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
+ "type": "object",
+ "required": [
+ "user_id",
+ "avatar_url"
+ ],
+ "properties": {
+ "user_id": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The Network (SSO) user id."
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "An https://openvibe.media/… picture; null clears the avatar.",
+ "pattern": "^https://openvibe\\.media/",
+ "maxLength": 500
+ },
+ "origin": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
+ }
+ }
+ },
+ "network.avatar-write-result.v1": {
+ "title": "NetworkAvatarWriteResult",
+ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
+ "type": "object",
+ "required": [
+ "ok",
+ "changed"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "changed": {
+ "type": "boolean"
+ }
+ }
+ },
"network.blocks-result.v1": {
"network.blocks-result.v1": {
"title": "NetworkBlocksResult",
"title": "NetworkBlocksResult",
"description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",
"description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",
@@ -2233,6 +2429,82 @@
@@ -2233,6 +2429,82 @@
}
}
}
}
},
},
+ "network.coins-read-result.v1": {
+ "title": "NetworkCoinsReadResult",
+ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "circulating",
+ "holders",
+ "transactions",
+ "recent"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "spent": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "circulating": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "holders": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "transactions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "recent": {
+ "type": "object",
+ "required": [
+ "earned",
+ "spent",
+ "holders"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "earned": {
+ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
+ },
+ "spent": {
+ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
+ },
+ "holders": {
+ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
+ }
+ }
+ }
+ },
+ "$defs": {
+ "window": {
+ "type": "object",
+ "required": [
+ "w",
+ "pw"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "w": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "pw": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ },
"network.coins-transfer-request.v1": {
"network.coins-transfer-request.v1": {
"title": "NetworkCoinsTransferRequest",
"title": "NetworkCoinsTransferRequest",
"description": "network.coins-transfer-request@1: the body of network.coins.transfer on OpenVibe.Network. POST /internal/coins/transfer: { from_user_id, to_user_id, app_id?, amount, reason?, ref?, idempotency_key } moves OpenCoins between two Network users atomically (the two ids are Network user ids and must differ). Refusals are { error } bodies: 400 invalid_from_user_id, invalid_to_user_id, invalid_transfer (the same person), invalid_amount or missing_idempotency_key; 404 user_not_found; 409 { error: insufficient_funds, balance } when the sender cannot cover it. Unknown fields are ignored.",
"description": "network.coins-transfer-request@1: the body of network.coins.transfer on OpenVibe.Network. POST /internal/coins/transfer: { from_user_id, to_user_id, app_id?, amount, reason?, ref?, idempotency_key } moves OpenCoins between two Network users atomically (the two ids are Network user ids and must differ). Refusals are { error } bodies: 400 invalid_from_user_id, invalid_to_user_id, invalid_transfer (the same person), invalid_amount or missing_idempotency_key; 404 user_not_found; 409 { error: insufficient_funds, balance } when the sender cannot cover it. Unknown fields are ignored.",
@@ -3340,6 +3612,24 @@
@@ -3340,6 +3612,24 @@
}
}
}
}
},
},
+ "network.registry-read-result.v1": {
+ "title": "NetworkRegistryReadResult",
+ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
+ "type": "object",
+ "required": [
+ "ok",
+ "registry"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "registry": {
+ "type": "object",
+ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
+ }
+ }
+ },
"network.staff-list-result.v1": {
"network.staff-list-result.v1": {
"title": "NetworkStaffListResult",
"title": "NetworkStaffListResult",
"description": "network.staff-list-result@1: the answer of network.staff.read on OpenVibe.Network. GET /api/v1/staff/moderators[?service=chat|live|community|pastes|calls] (a service token holding network.staff.read, or a signed-in person holding staff.moderation.logs) → { service, staff }: the network's global moderators, admins and the owner who are not banned, with the staff capabilities their role holds in the contracts staff map; ?service keeps those holding the capability that moderates that service (another service name keeps those holding staff.content.moderate; an invalid value is ignored and answered as null). Answered with Cache-Control: private, no-store; a person without staff.moderation.logs gets 403 { error: forbidden }.",
"description": "network.staff-list-result@1: the answer of network.staff.read on OpenVibe.Network. GET /api/v1/staff/moderators[?service=chat|live|community|pastes|calls] (a service token holding network.staff.read, or a signed-in person holding staff.moderation.logs) → { service, staff }: the network's global moderators, admins and the owner who are not banned, with the staff capabilities their role holds in the contracts staff map; ?service keeps those holding the capability that moderates that service (another service name keeps those holding staff.content.moderate; an invalid value is ignored and answered as null). Answered with Cache-Control: private, no-store; a person without staff.moderation.logs gets 403 { error: forbidden }.",
@@ -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.78.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.79.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": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.78.0",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.78.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -16,6 +16,98 @@
@@ -16,6 +16,98 @@
}
}
],
],
"paths": {
"paths": {
+ "/api/internal/analytics": {
+ "get": {
+ "operationId": "get_api_internal_analytics",
+ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
+ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
+ "tags": [
+ "tools.analytics"
+ ],
+ "x-openvibe-capabilities": [
+ "tools.analytics.read"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "tools.analytics.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/tools.analytics-read-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/internal/analytics/bots": {
+ "get": {
+ "operationId": "get_api_internal_analytics_bots",
+ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
+ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
+ "tags": [
+ "tools.analytics"
+ ],
+ "x-openvibe-capabilities": [
+ "tools.analytics.read"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "tools.analytics.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/tools.analytics-read-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/v1/jobs": {
"/api/v1/jobs": {
"post": {
"post": {
"operationId": "post_api_v1_jobs",
"operationId": "post_api_v1_jobs",
@@ -814,6 +906,24 @@
@@ -814,6 +906,24 @@
}
}
}
}
},
},
+ "tools.analytics-read-result.v1": {
+ "title": "ToolsAnalyticsReadResult",
+ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
+ "type": "object",
+ "required": [
+ "ok",
+ "analytics"
+ ],
+ "properties": {
+ "ok": {
+ "const": true
+ },
+ "analytics": {
+ "type": "object",
+ "description": "Aggregate analytics; open."
+ }
+ }
+ },
"tools.job-create-request.v1": {
"tools.job-create-request.v1": {
"title": "ToolsJobCreateRequest",
"title": "ToolsJobCreateRequest",
"description": "tools.job-create-request@1: the request of tools.job.create on the Tools satellites that run jobs. POST /api/v1/jobs: tools.job-request@1 (JSON or multipart). POST /api/v1/jobs/:id/retry and PUT|DELETE /api/v1/jobs/:id/references/:ref take no body.",
"description": "tools.job-create-request@1: the request of tools.job.create on the Tools satellites that run jobs. POST /api/v1/jobs: tools.job-request@1 (JSON or multipart). POST /api/v1/jobs/:id/retry and PUT|DELETE /api/v1/jobs/:id/references/:ref take no body.",
@@ -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.78.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.79.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.78.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.78.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.79.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.79.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"
@@ -30500,6 +30500,18 @@ export interface ToolsJobRequest {
@@ -30500,6 +30500,18 @@ export interface ToolsJobRequest {
*/
*/
export type ToolsJobCreateRequest = ToolsJobRequest | NoBody;
export type ToolsJobCreateRequest = ToolsJobRequest | NoBody;
+/** [email protected] (owner: tools) */
+/**
+ * tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).
+ */
+export interface ToolsAnalyticsReadResult {
+ ok: true;
+ /**
+ * Aggregate analytics; open.
+ */
+ analytics: {};
+}
+
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* tools.job-read-result@1: answers of tools.job.read on the Tools satellites. GET /api/v1/jobs/:id → tools.job@1, and so is the data of every event on GET /api/v1/jobs/:id/events (Server-Sent Events, Last-Event-ID resume); GET /api/v1/jobs/:id/files/:n → the result file's bytes (Content-Type of the file; 409 tools.job.not_ready before the job succeeded).
* tools.job-read-result@1: answers of tools.job.read on the Tools satellites. GET /api/v1/jobs/:id → tools.job@1, and so is the data of every event on GET /api/v1/jobs/:id/events (Server-Sent Events, Last-Event-ID resume); GET /api/v1/jobs/:id/files/:n → the result file's bytes (Content-Type of the file; 409 tools.job.not_ready before the job succeeded).
@@ -43291,6 +43303,38 @@ export interface MediaObjectVisibilityChangedPayload {
@@ -43291,6 +43303,38 @@ export interface MediaObjectVisibilityChangedPayload {
changed_at: string;
changed_at: string;
}
}
+/** [email protected] (owner: media) */
+/**
+ * media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).
+ */
+export interface MediaAvatarIngestRequest {
+ /**
+ * The https address of the picture to import.
+ */
+ url: string;
+ /**
+ * The account that owns the stored paste.
+ */
+ user_id: number | string;
+ /**
+ * Used only for the paste title.
+ */
+ username?: string | null;
+}
+
+/** [email protected] (owner: media) */
+/**
+ * media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).
+ */
+export interface MediaAvatarIngestResult {
+ ok: true;
+ slug: string;
+ url: string;
+ width: number;
+ height: number;
+ bytes: number;
+}
+
/** [email protected] (owner: media) */
/** [email protected] (owner: media) */
/**
/**
* media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).
* media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).
@@ -43886,6 +43930,66 @@ export type LiveDiscoveryResult =
@@ -43886,6 +43930,66 @@ export type LiveDiscoveryResult =
partial: boolean;
partial: boolean;
};
};
+/** [email protected] (owner: live) */
+/**
+ * live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.
+ */
+export type LiveAnalyticsReadResult =
+ | {
+ ok: true;
+ /**
+ * Aggregate totals; open.
+ */
+ summary: {};
+ }
+ | {
+ ok: false;
+ error: string;
+ };
+
+/** [email protected] (owner: live) */
+/**
+ * live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.
+ */
+export interface LiveAvatarWriteRequest {
+ /**
+ * The Network (SSO) user id; matched against linked_accounts. A Live id never goes here.
+ */
+ openvibenetwork_id: number | string;
+ /**
+ * Fallback when no linked account matches.
+ */
+ username?: string | null;
+ /**
+ * An https://openvibe.media/… picture; null clears the avatar.
+ */
+ avatar_url: string | null;
+}
+
+/** [email protected] (owner: live) */
+/**
+ * live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).
+ */
+export interface LiveAvatarWriteResult {
+ ok: true;
+ id: number;
+ changed: boolean;
+}
+
+/** [email protected] (owner: live) */
+/**
+ * live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.
+ */
+export type LiveUrlRegistryRefreshResult =
+ | {
+ ok: true;
+ message: string;
+ }
+ | {
+ ok: false;
+ error: string;
+ };
+
/** [email protected] (owner: live) */
/** [email protected] (owner: live) */
/**
/**
* live.follower-page@1: the answer of GET /internal/followers?stream_id=&limit=&after= on OpenVibe.Live (live.follower.read, service token, loopback): the followers of the channel that streams stream_id, for Network's go-live notifications. A follower is named by their Network subject when Live knows it, else their Network user id. next is the cursor for the following page (null on the last). 400 without stream_id, 404 for an unknown stream.
* live.follower-page@1: the answer of GET /internal/followers?stream_id=&limit=&after= on OpenVibe.Live (live.follower.read, service token, loopback): the followers of the channel that streams stream_id, for Network's go-live notifications. A follower is named by their Network subject when Live knows it, else their Network user id. next is the cursor for the following page (null on the last). 400 without stream_id, 404 for an unknown stream.
@@ -46288,6 +46392,63 @@ export interface NetworkNodeListResult {
@@ -46288,6 +46392,63 @@ export interface NetworkNodeListResult {
generated_at: string;
generated_at: string;
}
}
+/** [email protected] (owner: network) */
+/**
+ * network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.
+ */
+export interface NetworkAvatarWriteRequest {
+ /**
+ * The Network (SSO) user id.
+ */
+ user_id: number;
+ /**
+ * An https://openvibe.media/… picture; null clears the avatar.
+ */
+ avatar_url: string | null;
+ /**
+ * Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back.
+ */
+ origin?: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).
+ */
+export interface NetworkAvatarWriteResult {
+ ok: true;
+ changed: boolean;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.
+ */
+export interface NetworkRegistryReadResult {
+ ok: true;
+ /**
+ * Setting name → resolved value; secrets and sensitive keys are omitted.
+ */
+ registry: {};
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.
+ */
+export interface NetworkCoinsReadResult {
+ earned: number;
+ spent: number;
+ circulating: number;
+ holders: number;
+ transactions: number;
+ recent: {
+ earned: Window;
+ spent: Window;
+ holders: Window;
+ };
+}
+
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* What one node or external provider can run right now (roadmap WS-Z9, decision 43): capabilities, multidimensional capacity, measured latencies, health and pricing. First-party only: it carries capacity the public node registry (network.node@1) deliberately leaves out.
* What one node or external provider can run right now (roadmap WS-Z9, decision 43): capabilities, multidimensional capacity, measured latencies, health and pricing. First-party only: it carries capacity the public node registry (network.node@1) deliberately leaves out.
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "live.analytics.read",
+ "version": "1.0",
+ "owner": "live",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
+ "permissions": [
+ "analytics:summary"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /internal/analytics-summary"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "live.analytics-read-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "live.avatar.write",
+ "version": "1.0",
+ "owner": "live",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",
+ "permissions": [
+ "users:avatar"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/user-avatar"
+ ],
+ "inputSchema": "live.avatar-write-request@1",
+ "outputSchema": "live.avatar-write-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "live.url_registry.refresh",
+ "version": "1.0",
+ "owner": "live",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
+ "permissions": [
+ "registry:refresh"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/url-registry/refresh"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "live.url-registry-refresh-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "media.avatar.ingest",
+ "version": "1.0",
+ "owner": "media",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
+ "permissions": [
+ "avatars:ingest"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/avatar-ingest"
+ ],
+ "inputSchema": "media.avatar-ingest-request@1",
+ "outputSchema": "media.avatar-ingest-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.avatar.write",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "internal",
+ "description": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",
+ "permissions": [
+ "profile:avatar"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/user-avatar"
+ ],
+ "inputSchema": "network.avatar-write-request@1",
+ "outputSchema": "network.avatar-write-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.coins.read",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
+ "permissions": [
+ "coins:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /internal/coins/stats"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "network.coins-read-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.registry.read",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
+ "permissions": [
+ "registry:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /internal/url-registry/resolved"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "network.registry-read-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "tools.analytics.read",
+ "version": "1.0",
+ "owner": "tools",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
+ "permissions": [
+ "analytics:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /api/internal/analytics",
+ "GET /api/internal/analytics/bots"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "tools.analytics-read-result@1"
+}
@@ -28,16 +28,19 @@
@@ -28,16 +28,19 @@
"position": 0
"position": 0
},
},
"capabilities": [
"capabilities": [
+ "live.analytics.read",
+ "live.avatar.write",
+ "live.channel.read",
"live.chat_context.read",
"live.chat_context.read",
"live.chat_effects.write",
"live.chat_effects.write",
"live.chat_mirror.write",
"live.chat_mirror.write",
+ "live.discovery.read",
"live.follower.read",
"live.follower.read",
"live.lineage.resolve",
"live.lineage.resolve",
- "live.tips_delivery.write",
- "live.channel.read",
+ "live.owner.resolve",
"live.stream.read",
"live.stream.read",
- "live.discovery.read",
- "live.owner.resolve"
+ "live.tips_delivery.write",
+ "live.url_registry.refresh"
],
],
"eventsProduced": [
"eventsProduced": [
"live.index_document.deleted",
"live.index_document.deleted",
@@ -27,15 +27,16 @@
@@ -27,15 +27,16 @@
"position": 4
"position": 4
},
},
"capabilities": [
"capabilities": [
- "media.object.upload",
- "media.object.read",
- "media.object.list",
- "media.object.delete",
- "media.upload.create",
+ "media.avatar.ingest",
"media.derivative.create",
"media.derivative.create",
"media.derivative.read",
"media.derivative.read",
"media.lifecycle.read",
"media.lifecycle.read",
- "media.lifecycle.transition"
+ "media.lifecycle.transition",
+ "media.object.delete",
+ "media.object.list",
+ "media.object.read",
+ "media.object.upload",
+ "media.upload.create"
],
],
"eventsProduced": [
"eventsProduced": [
"media.clip.failed",
"media.clip.failed",
@@ -37,20 +37,23 @@
@@ -37,20 +37,23 @@
"network.account.deletion.confirm",
"network.account.deletion.confirm",
"network.account.export.contribute",
"network.account.export.contribute",
"network.analytics.creator.read",
"network.analytics.creator.read",
+ "network.avatar.write",
"network.blocks.read",
"network.blocks.read",
"network.coins.credit",
"network.coins.credit",
"network.coins.debit",
"network.coins.debit",
+ "network.coins.read",
"network.coins.transfer",
"network.coins.transfer",
"network.follows.read",
"network.follows.read",
"network.follows.write",
"network.follows.write",
"network.integration.github.read",
"network.integration.github.read",
"network.modules.read",
"network.modules.read",
"network.modules.write",
"network.modules.write",
+ "network.node.report",
"network.notifications.push",
"network.notifications.push",
"network.operator.alert",
"network.operator.alert",
- "network.node.report",
"network.project.manage",
"network.project.manage",
"network.project.read",
"network.project.read",
+ "network.registry.read",
"network.staff.read",
"network.staff.read",
"network.status.incident"
"network.status.incident"
],
],
@@ -27,12 +27,13 @@
@@ -27,12 +27,13 @@
"position": 1
"position": 1
},
},
"capabilities": [
"capabilities": [
+ "tools.analytics.read",
+ "tools.job.cancel",
"tools.job.create",
"tools.job.create",
"tools.job.read",
"tools.job.read",
- "tools.job.cancel",
+ "tools.net.probe",
"tools.tool.read",
"tools.tool.read",
- "tools.tool.run",
- "tools.net.probe"
+ "tools.tool.run"
],
],
"eventsProduced": [
"eventsProduced": [
"tools.job.created",
"tools.job.created",
@@ -88,14 +89,14 @@
@@ -88,14 +89,14 @@
],
],
"window": "automatic only while ovhost waits for readiness after the restart (60 s in host.example.json); manual afterwards, as long as no blocker applies",
"window": "automatic only while ovhost waits for readiness after the restart (60 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
"blockers": {
- "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
+ "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
}
},
},
"contracts": {
"contracts": {
"range": ">=0.7.0 <1.0.0"
"range": ">=0.7.0 <1.0.0"
},
},
"leases": {
"leases": {
- "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued → running)"
+ "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued \u2192 running)"
}
}
},
},
"notes": "Public at openvibe.tools: 169 tools in 9 families on the gateway (port 4001) and its satellites, catalogue at GET /api/catalog.json (re-served by Network). Asynchronous jobs (Wave 11) run on the img, audio and docs satellites only (POST /api/v1/jobs, GET/DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT|DELETE /api/v1/jobs/:id/references/:ref, SSE events, result files); the gateway has no /api/v1/jobs yet. Job results are stored as private Media objects (TOOLS_JOB_RESULTS=media: Tools holds media.object.upload and media.object.read, namespace tools) and job lifecycle events tools.job.created|started|succeeded|failed are published to OpenVibe.Events through an outbox in each satellite's jobs.db (EVENTS_URL set; events.event.publish). The tool registry (ADR-027, tools.tool.read) is served: GET /api/v1/tools[/:id[/schema]] on the gateway, for all 169 tools, and on the img, audio, docs, text, yt, maps and food satellites for their own. Planned (ADR-027): the uniform run API POST /api/v1/tools/:id/run (tools.tool.run; network probes need tools.net.probe), a jobs facade on the gateway, tiered quotas and a pseudonymous abuse log; the legacy /api/process, /api/net/* and /api/dev/* routes keep working with Deprecation and Sunset (2026-12-31) headers. GET /api/ready is truthful on the gateway (it checks every satellite) and on each satellite."
"notes": "Public at openvibe.tools: 169 tools in 9 families on the gateway (port 4001) and its satellites, catalogue at GET /api/catalog.json (re-served by Network). Asynchronous jobs (Wave 11) run on the img, audio and docs satellites only (POST /api/v1/jobs, GET/DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT|DELETE /api/v1/jobs/:id/references/:ref, SSE events, result files); the gateway has no /api/v1/jobs yet. Job results are stored as private Media objects (TOOLS_JOB_RESULTS=media: Tools holds media.object.upload and media.object.read, namespace tools) and job lifecycle events tools.job.created|started|succeeded|failed are published to OpenVibe.Events through an outbox in each satellite's jobs.db (EVENTS_URL set; events.event.publish). The tool registry (ADR-027, tools.tool.read) is served: GET /api/v1/tools[/:id[/schema]] on the gateway, for all 169 tools, and on the img, audio, docs, text, yt, maps and food satellites for their own. Planned (ADR-027): the uniform run API POST /api/v1/tools/:id/run (tools.tool.run; network probes need tools.net.probe), a jobs facade on the gateway, tiered quotas and a pseudonymous abuse log; the legacy /api/process, /api/net/* and /api/dev/* routes keep working with Deprecation and Sunset (2026-12-31) headers. GET /api/ready is truthful on the gateway (it checks every satellite) and on each satellite."
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.78.0",
+ "version": "0.79.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",