openvibers/openvibe.contracts Commits

v0.73.0: a person's own provider key lives in OpenVibe.AI (roadmap WS-O task 2). ai.credential.manage (first-party): the service holding a person's consent stores their key (PUT /api/v1/credentials/{subject}, ai.credential-put@1: openai or anthropic, an https base_url, models per role, an optional budget_usd_per_day), reads it back without the key (ai.credential@1: a four-character hint and today's spend) or deletes it; only the storing service uses or changes it. ai.run-request@1 gains the optional credential { subject }: that provider with that key only, no fallback, never cached, the shared paid budget untouched, the credential's daily budget enforced; chat operations only.

OP OpenVibers committed 27 Sept 2026 · 872df2716e · 40 files, +1123 −65


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex ad314064..13c8e5cc 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,12 @@ the notes were in the tag and commit messages (`git tag -n1`).
## Unreleased
++## 0.73.0 — 2026-09-28
++
++**A person's own provider key lives in OpenVibe.AI** (roadmap WS-O task 2).
++- `ai.credential.manage` (first-party): the service that holds a person's consent (Live, for a streamer's AI viewers) stores their key at `PUT /api/v1/credentials/{subject}` (`ai.credential-put@1`: provider openai or anthropic, an https `base_url`, the key, models per role, and an optional `budget_usd_per_day`). It reads it back without the key (`ai.credential@1`, a four-character hint and today's spend) or deletes it. Only the storing service uses or changes a credential.
++- `ai.run-request@1` gains the optional `credential { subject }`. The run calls that provider with that key only: no fallback, never cached, the shared paid budget untouched, and the credential's own daily budget enforced. Chat operations only.
++
## 0.72.0 — 2026-09-27
**Mod principals in Network** (roadmap WS-M task 3, ADR-013).
ddiff --git a/contracts/ai/credential-put.v1.json b/contracts/ai/credential-put.v1.json
nnew file mode 100644
iindex 00000000..68ff91b0
---- /dev/null
++++ b/contracts/ai/credential-put.v1.json
@@@ -0,0 +1,70 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential-put.v1.json",
++ "title": "AiCredentialPut",
++ "description": "ai.credential-put@1 (OpenVibe.AI; roadmap WS-O task 2): a person's own provider key, stored by the service that holds their consent (Live, for a streamer's AI viewers), PUT /api/v1/credentials/{subject} with capability ai.credential.manage. OpenVibe.AI encrypts the key at rest and never returns it; runs that name the credential call that provider with it, never a shared key, and are limited by `budget_usd_per_day`. `base_url` is any https endpoint (OpenAI-compatible or Anthropic); OpenVibe.AI connects only to public addresses.",
++ "type": "object",
++ "required": [
++ "provider",
++ "api_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ],
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ "base_url": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$"
++ },
++ "api_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ }
++ }
++}
ddiff --git a/contracts/ai/credential.v1.json b/contracts/ai/credential.v1.json
nnew file mode 100644
iindex 00000000..1cda1d65
---- /dev/null
++++ b/contracts/ai/credential.v1.json
@@@ -0,0 +1,103 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential.v1.json",
++ "title": "AiCredential",
++ "description": "ai.credential@1 (OpenVibe.AI; roadmap WS-O task 2): a stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject}): never the key, only its last four characters.",
++ "type": "object",
++ "required": [
++ "subject",
++ "owner",
++ "provider",
++ "key_hint",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}$",
++ "description": "The service that stored it; only it may use or change it."
++ },
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ]
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ },
++ "key_hint": {
++ "type": "string",
++ "pattern": "^…[\\s\\S]{1,4}$"
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "spent_usd_today": {
++ "type": "number",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_used_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/ai/run-request.v1.json b/contracts/ai/run-request.v1.json
iindex d1062ebb..c80b59f4 100644
---- a/contracts/ai/run-request.v1.json
++++ b/contracts/ai/run-request.v1.json
@@@ -4,28 +4,69 @@
"title": "AiRunRequest",
"description": "Body of POST /api/v1/runs on OpenVibe.AI (capability ai.run.create; ADR-015). Runs the newest active version of a workflow (or the given active or deprecated version) on input that must match that workflow version's own input schema; per-workflow input and output schemas live in AI's registry (GET /api/v1/workflows/:key), not here. The token's ns claim limits which workflow namespaces may run (403 capability.namespace_denied). ?wait=ms waits for the result: 201 finished or served from cache, 202 still queued/running (poll GET /api/v1/runs/:id), 200 an idempotent replay. Errors are problem+json: 404 workflow.not_found, 409 workflow.inactive, 409 idempotency.conflict, 413 input.too_large, 422 input.invalid, 429 quota.exceeded or queue.full with Retry-After. The direct operations POST /api/v1/{chat,generate,summarize,classify,extract,enrich,embed} take the same fields (except workflow and version) with the input fields at the top level and run workflow ai.<op>. target (EntityRef): what the output is about, part of the cache scope and a run filter. attribution (EntityRef): what the spend is attributed to for quotas and usage. on_behalf_of (SubjectRef): the person or actor the caller acts for, part of the cache scope and per-actor quotas.",
"type": "object",
-- "required": ["workflow"],
++ "required": [
++ "workflow"
++ ],
"additionalProperties": false,
"properties": {
"workflow": {
-- "type": "string", "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)+$", "maxLength": 200,
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)+$",
++ "maxLength": 200,
"description": "Workflow key, <namespace>.<name> (live.chat_reply, news.summarize_story, network.site_copy…)."
},
-- "version": { "type": "integer", "minimum": 1, "description": "Pin a workflow version (active or deprecated). Omitted: the newest active version." },
-- "input": { "type": "object", "description": "Workflow input; validated against the workflow version's input_schema. Default {}." },
-- "target": { "$ref": "../common/entity-ref.v1.json" },
-- "attribution": { "$ref": "../common/entity-ref.v1.json" },
-- "on_behalf_of": { "$ref": "../identity/subject-ref.v1.json" },
++ "version": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Pin a workflow version (active or deprecated). Omitted: the newest active version."
++ },
++ "input": {
++ "type": "object",
++ "description": "Workflow input; validated against the workflow version's input_schema. Default {}."
++ },
++ "target": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "attribution": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "on_behalf_of": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
"idempotency_key": {
-- "type": "string", "pattern": "^[A-Za-z0-9._:-]{8,128}$",
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$",
"description": "Same (requester, key) returns the existing run; a different workflow or input under it is 409 idempotency.conflict. The Idempotency-Key header is used when this is absent."
},
"options": {
-- "type": "object", "additionalProperties": false,
++ "type": "object",
++ "additionalProperties": false,
"properties": {
-- "cache": { "type": "boolean", "default": true, "description": "false skips the scoped cache lookup and store." },
-- "debug": { "type": "boolean", "default": false, "description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on." }
++ "cache": {
++ "type": "boolean",
++ "default": true,
++ "description": "false skips the scoped cache lookup and store."
++ },
++ "debug": {
++ "type": "boolean",
++ "default": false,
++ "description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on."
++ }
}
++ },
++ "credential": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "subject"
++ ],
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "Run with the person's own provider key (ai.credential@1) that this service stored, instead of a shared route: only that provider is called (no fallback), the result is never cached or taken from the cache, the shared paid budget is not spent, and the credential's own daily budget applies (429 quota.exceeded). Chat operations only (text, JSON, vision)."
}
}
}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 14082392..c2b56ee0 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -1205,6 +1205,24 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "ai.credential-put",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/credential-put.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.credential",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/credential.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "ai.run-request",
"version": "1.0.0",
ddiff --git a/fixtures/ai.credential-put/invalid/http-base.json b/fixtures/ai.credential-put/invalid/http-base.json
nnew file mode 100644
iindex 00000000..0078d2f6
---- /dev/null
++++ b/fixtures/ai.credential-put/invalid/http-base.json
@@@ -0,0 +1 @@
++{"provider":"openai","base_url":"http://10.0.0.1/v1","api_key":"sk-EXAMPLEEXAMPLE"}
ddiff --git a/fixtures/ai.credential-put/valid/openrouter.json b/fixtures/ai.credential-put/valid/openrouter.json
nnew file mode 100644
iindex 00000000..ae83eb81
---- /dev/null
++++ b/fixtures/ai.credential-put/valid/openrouter.json
@@@ -0,0 +1 @@
++{"provider":"openai","base_url":"https://openrouter.ai/api/v1","api_key":"sk-or-v1-EXAMPLEEXAMPLEEXAMPLE","models":{"chat":"gpt-4o-mini"},"budget_usd_per_day":2}
ddiff --git a/fixtures/ai.credential/invalid/leaks-key.json b/fixtures/ai.credential/invalid/leaks-key.json
nnew file mode 100644
iindex 00000000..efa41415
---- /dev/null
++++ b/fixtures/ai.credential/invalid/leaks-key.json
@@@ -0,0 +1 @@
++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","owner":"live","provider":"openai","api_key":"sk-EXAMPLE","key_hint":"…MPLE","created_at":"2026-09-28T00:00:00.000Z","updated_at":"2026-09-28T00:00:00.000Z"}
ddiff --git a/fixtures/ai.credential/valid/stored.json b/fixtures/ai.credential/valid/stored.json
nnew file mode 100644
iindex 00000000..d0733f7c
---- /dev/null
++++ b/fixtures/ai.credential/valid/stored.json
@@@ -0,0 +1 @@
++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","owner":"live","provider":"openai","base_url":"https://openrouter.ai/api/v1","key_hint":"…MPLE","models":{"chat":"gpt-4o-mini"},"budget_usd_per_day":2,"spent_usd_today":0.12,"created_at":"2026-09-28T00:00:00.000Z","updated_at":"2026-09-28T00:00:00.000Z","last_used_at":null}
ddiff --git a/fixtures/ai.run-request/valid/with-credential.json b/fixtures/ai.run-request/valid/with-credential.json
nnew file mode 100644
iindex 00000000..5c2ebcaf
---- /dev/null
++++ b/fixtures/ai.run-request/valid/with-credential.json
@@@ -0,0 +1 @@
++{"workflow":"live.viewers.line","input":{"system":"s","user":"u"},"credential":{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"}}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 0247651f..25ea49c7 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -11818,6 +11818,189 @@
}
}
},
++ "ai.credential-put": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential-put.v1.json",
++ "title": "AiCredentialPut",
++ "description": "ai.credential-put@1 (OpenVibe.AI; roadmap WS-O task 2): a person's own provider key, stored by the service that holds their consent (Live, for a streamer's AI viewers), PUT /api/v1/credentials/{subject} with capability ai.credential.manage. OpenVibe.AI encrypts the key at rest and never returns it; runs that name the credential call that provider with it, never a shared key, and are limited by `budget_usd_per_day`. `base_url` is any https endpoint (OpenAI-compatible or Anthropic); OpenVibe.AI connects only to public addresses.",
++ "type": "object",
++ "required": [
++ "provider",
++ "api_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ],
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ "base_url": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$"
++ },
++ "api_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ }
++ }
++ }
++ },
++ "ai.credential": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential.v1.json",
++ "title": "AiCredential",
++ "description": "ai.credential@1 (OpenVibe.AI; roadmap WS-O task 2): a stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject}): never the key, only its last four characters.",
++ "type": "object",
++ "required": [
++ "subject",
++ "owner",
++ "provider",
++ "key_hint",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}$",
++ "description": "The service that stored it; only it may use or change it."
++ },
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ]
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ },
++ "key_hint": {
++ "type": "string",
++ "pattern": "^…[\\s\\S]{1,4}$"
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "spent_usd_today": {
++ "type": "number",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_used_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ },
"ai.run-request": {
"version": "1.0.0",
"owner": "ai",
@@@ -11877,6 +12060,20 @@
"description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on."
}
}
++ },
++ "credential": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "subject"
++ ],
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "Run with the person's own provider key (ai.credential@1) that this service stored, instead of a shared route: only that provider is called (no fallback), the result is never cached or taken from the cache, the shared paid budget is not spent, and the credential's own daily budget applies (429 quota.exceeded). Chat operations only (text, JSON, vision)."
}
}
}
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 4e8b0734..263eed24 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for 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"
@@@ -267,6 +267,354 @@
}
}
},
++ "/api/v1/credentials/{subject}": {
++ "delete": {
++ "operationId": "delete_api_v1_credentials_subject",
++ "summary": "Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential).",
++ "description": "**ai.credential.manage** (first-party): Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential). Only the service that stored a credential reads, uses or changes it.",
++ "tags": [
++ "ai.credential"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.credential.manage"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "ai-credentials",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.credential.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/ai.credential-put.v1"
++ },
++ "parameters": [
++ {
++ "name": "subject",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "provider",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ],
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ {
++ "name": "base_url",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$"
++ }
++ },
++ {
++ "name": "api_key",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400
++ }
++ },
++ {
++ "name": "models",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ {
++ "name": "budget_usd_per_day",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ },
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.credential.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "get": {
++ "operationId": "get_api_v1_credentials_subject",
++ "summary": "Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential).",
++ "description": "**ai.credential.manage** (first-party): Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential). Only the service that stored a credential reads, uses or changes it.",
++ "tags": [
++ "ai.credential"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.credential.manage"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "ai-credentials",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.credential.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/ai.credential-put.v1"
++ },
++ "parameters": [
++ {
++ "name": "subject",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "provider",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ],
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ {
++ "name": "base_url",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$"
++ }
++ },
++ {
++ "name": "api_key",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400
++ }
++ },
++ {
++ "name": "models",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ {
++ "name": "budget_usd_per_day",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ },
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.credential.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_api_v1_credentials_subject",
++ "summary": "Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential).",
++ "description": "**ai.credential.manage** (first-party): Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential). Only the service that stored a credential reads, uses or changes it.",
++ "tags": [
++ "ai.credential"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.credential.manage"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "ai-credentials",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.credential.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.credential-put.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "subject",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.credential.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/embed": {
"post": {
"operationId": "post_api_v1_embed",
@@@ -2154,6 +2502,175 @@
},
"components": {
"schemas": {
++ "ai.credential-put.v1": {
++ "title": "AiCredentialPut",
++ "description": "ai.credential-put@1 (OpenVibe.AI; roadmap WS-O task 2): a person's own provider key, stored by the service that holds their consent (Live, for a streamer's AI viewers), PUT /api/v1/credentials/{subject} with capability ai.credential.manage. OpenVibe.AI encrypts the key at rest and never returns it; runs that name the credential call that provider with it, never a shared key, and are limited by `budget_usd_per_day`. `base_url` is any https endpoint (OpenAI-compatible or Anthropic); OpenVibe.AI connects only to public addresses.",
++ "type": "object",
++ "required": [
++ "provider",
++ "api_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ],
++ "description": "openai = any OpenAI-compatible endpoint."
++ },
++ "base_url": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$"
++ },
++ "api_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this key; 0 or absent = no cap."
++ }
++ }
++ },
++ "ai.credential.v1": {
++ "title": "AiCredential",
++ "description": "ai.credential@1 (OpenVibe.AI; roadmap WS-O task 2): a stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject}): never the key, only its last four characters.",
++ "type": "object",
++ "required": [
++ "subject",
++ "owner",
++ "provider",
++ "key_hint",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}$",
++ "description": "The service that stored it; only it may use or change it."
++ },
++ "provider": {
++ "enum": [
++ "openai",
++ "anthropic"
++ ]
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ },
++ "key_hint": {
++ "type": "string",
++ "pattern": "^…[\\s\\S]{1,4}$"
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per Live role; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "spent_usd_today": {
++ "type": "number",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_used_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ },
"ai.definition-version-request.v1": {
"title": "AiDefinitionVersionRequest",
"description": "ai.definition-version-request@1: bodies of ai.workflow.manage on OpenVibe.AI. An edit is always a new version (the previous one stays readable); fields left out are taken from the newest existing version. POST /api/v1/templates/:key/versions: a prompt template (input_schema and output_schema must compile as JSON Schema; visibility public, first-party or internal). POST /api/v1/workflows/:key/versions (key <namespace>.<name>): steps (at least one; kind llm (naming an existing template), passthrough, transcribe or embed), schemas, default route, cache mode. POST /api/v1/routes/:key/versions: primary { provider, model } and fallbacks (every provider must exist), or alias_of another route. POST …/:key/versions/:version/status: { status } (templates and workflows draft|active|deprecated|archived; routes active|disabled). Refusals are 422 ai.invalid.",
@@@ -3469,6 +3986,20 @@
"description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on."
}
}
++ },
++ "credential": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "subject"
++ ],
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "Run with the person's own provider key (ai.credential@1) that this service stored, instead of a shared route: only that provider is called (no fallback), the result is never cached or taken from the cache, the shared paid budget is not spent, and the credential's own daily budget applies (429 quota.exceeded). Chat operations only (text, JSON, vision)."
}
}
},
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 5b927141..04b7cc5c 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex a54deb0f..bedc7812 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex f8c41397..0c0bbb0d 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex 7f7b92f3..e9deaf68 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex d2fd6476..a5c8e2d9 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex ff752913..9fd80efe 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex fa9618f8..f013162f 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 70af352c..fe030a13 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 2c532fbb..c5b8b1d6 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 42e36f9d..4babe8b1 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex ddfdd7bb..f0046182 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -5,8 +5,8 @@
"service": "ai",
"name": "OpenVibe.AI",
"origin": "https://ai.openvibe.network",
-- "operations": 37,
-- "capabilities": 5
++ "operations": 40,
++ "capabilities": 6
},
{
"service": "billing",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 5ebdd916..eb9f12ba 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 8efd63af..934672ea 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex 0b8e4b09..db761442 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex 0c648c47..e6bd125c 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex 5b0a2e37..f0a09bc0 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.72.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 52ce14e9..ffbb850f 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 5bd6e0c3..f069156c 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex fc9670a9..53b3eb2b 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 263c1d54..22003c3d 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex c606ef62..612c4351 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 2fcf0548..3acc3721 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 31d3de6b..e567db2a 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 6a92133d..e8f58da2 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.72.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.72.0): each operation lists the capabilities it performs; a token for 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.73.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.73.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex f16b7f92..8bc88ce2 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -15769,6 +15769,63 @@ export interface BlogThemeRequest {
theme: "vibe" | "paper" | "slate" | "sand" | "nord" | "high-contrast";
}
++/** [email protected] (owner: ai) */
++/**
++ * ai.credential-put@1 (OpenVibe.AI; roadmap WS-O task 2): a person's own provider key, stored by the service that holds their consent (Live, for a streamer's AI viewers), PUT /api/v1/credentials/{subject} with capability ai.credential.manage. OpenVibe.AI encrypts the key at rest and never returns it; runs that name the credential call that provider with it, never a shared key, and are limited by `budget_usd_per_day`. `base_url` is any https endpoint (OpenAI-compatible or Anthropic); OpenVibe.AI connects only to public addresses.
++ */
++export interface AiCredentialPut {
++ /**
++ * openai = any OpenAI-compatible endpoint.
++ */
++ provider: "openai" | "anthropic";
++ base_url?: string;
++ api_key: string;
++ /**
++ * Model per Live role; `chat` is the default for the others.
++ */
++ models?: {
++ chat?: string;
++ vision?: string;
++ director?: string;
++ summary?: string;
++ legacy?: string;
++ };
++ /**
++ * The person's own daily cap on estimated spend with this key; 0 or absent = no cap.
++ */
++ budget_usd_per_day?: number;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.credential@1 (OpenVibe.AI; roadmap WS-O task 2): a stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject}): never the key, only its last four characters.
++ */
++export interface AiCredential {
++ subject: string;
++ /**
++ * The service that stored it; only it may use or change it.
++ */
++ owner: string;
++ provider: "openai" | "anthropic";
++ base_url?: string | null;
++ key_hint: string;
++ /**
++ * Model per Live role; `chat` is the default for the others.
++ */
++ models?: {
++ chat?: string;
++ vision?: string;
++ director?: string;
++ summary?: string;
++ legacy?: string;
++ };
++ budget_usd_per_day?: number | null;
++ spent_usd_today?: number;
++ created_at: string;
++ updated_at: string;
++ last_used_at?: string | null;
++}
++
/** [email protected] (owner: ai) */
/**
* Body of POST /api/v1/runs on OpenVibe.AI (capability ai.run.create; ADR-015). Runs the newest active version of a workflow (or the given active or deprecated version) on input that must match that workflow version's own input schema; per-workflow input and output schemas live in AI's registry (GET /api/v1/workflows/:key), not here. The token's ns claim limits which workflow namespaces may run (403 capability.namespace_denied). ?wait=ms waits for the result: 201 finished or served from cache, 202 still queued/running (poll GET /api/v1/runs/:id), 200 an idempotent replay. Errors are problem+json: 404 workflow.not_found, 409 workflow.inactive, 409 idempotency.conflict, 413 input.too_large, 422 input.invalid, 429 quota.exceeded or queue.full with Retry-After. The direct operations POST /api/v1/{chat,generate,summarize,classify,extract,enrich,embed} take the same fields (except workflow and version) with the input fields at the top level and run workflow ai.<op>. target (EntityRef): what the output is about, part of the cache scope and a run filter. attribution (EntityRef): what the spend is attributed to for quotas and usage. on_behalf_of (SubjectRef): the person or actor the caller acts for, part of the cache scope and per-actor quotas.
@@@ -15803,6 +15860,12 @@ export interface AiRunRequest {
*/
debug?: boolean;
};
++ /**
++ * Run with the person's own provider key (ai.credential@1) that this service stored, instead of a shared route: only that provider is called (no fallback), the result is never cached or taken from the cache, the shared paid budget is not spent, and the credential's own daily budget applies (429 quota.exceeded). Chat operations only (text, JSON, vision).
++ */
++ credential?: {
++ subject: string;
++ };
}
/** [email protected] (owner: ai) */
ddiff --git a/manifests/capabilities/ai.credential.manage.json b/manifests/capabilities/ai.credential.manage.json
nnew file mode 100644
iindex 00000000..fb04125c
---- /dev/null
++++ b/manifests/capabilities/ai.credential.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "ai.credential.manage",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Store, read (never the key) or delete a person's own provider key, for runs made on their behalf with it (ai.run-request credential). Only the service that stored a credential reads, uses or changes it.",
++ "permissions": [
++ "ai:credentials"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "ai-credentials",
++ "events": [],
++ "implementedBy": [
++ "PUT /api/v1/credentials/:subject",
++ "GET /api/v1/credentials/:subject",
++ "DELETE /api/v1/credentials/:subject"
++ ],
++ "inputSchema": "ai.credential-put@1",
++ "outputSchema": "ai.credential@1"
++}
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex f18b10a3..b98fc7ca 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -20,11 +20,12 @@
"note": "called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page"
},
"capabilities": [
++ "ai.credential.manage",
++ "ai.provider.manage",
"ai.run.create",
"ai.run.read",
-- "ai.workflow.manage",
-- "ai.provider.manage",
-- "ai.usage.read"
++ "ai.usage.read",
++ "ai.workflow.manage"
],
"eventsProduced": [
"ai.run.queued",
ddiff --git a/package.json b/package.json
iindex ac295997..8ed1657b 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.72.0",
++ "version": "0.73.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",