openvibers/openvibe.contracts Commits

One AI meter across the network: the event and Report's routes (L40/L41) (#267)

OP OpenVibers committed 13 days ago · 46e3b1447d · 68 files, +1882 −85

* One AI meter across the network: ai.usage.recorded@1 (L40)

Every AI call on the network leaves one usage record: who and where it happened, what ran, who paid, the tokens with the cache read and the cache write counted apart, the list cost, the charged cost, the fee and what the cache saved. No prompt and no answer text, ever: the payload has no field for either and the schema closes the object.

* The meter belongs to Report: the AI usage APIs and the report.ai_usage.read capability (L41)

The public totals, the grouped meter and the shape of a person's own rows are now specified here, so Report's routes are written against a contract and not against a guess. The route for a person answers 501 until Report has sign-in.

* Keep node_modules out of the tree

* 0.258.0: the AI meter, on top of 0.257.0 (L40/L41)

The keys schemas took 0.257.0, so the meter is 0.258.0: the event ai.usage.recorded@1, Report's three usage routes and the report.ai_usage.read capability. The generated files carry the version.

* The per-call meter is ai.call.recorded@1, not ai.usage.recorded

Every <service>.usage.recorded is ADR-014's common project usage rollup, owned by its service and consumed by Network. The meter's record is a different thing: one AI call, with who and where it came from. It is ai.call.recorded@1 now, in the schema, the catalog, the manifests, the fixtures and the CHANGELOG.

* 0.260.0: One AI meter across the network: the event and Report's routes (L40/L41)

Changes

ddiff --git a/.gitignore b/.gitignore
iindex c2658d7d..e768632e 100644
---- a/.gitignore
++++ b/.gitignore
@@@ -1 +1,2 @@
node_modules/
++node_modules
ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 40555261..1fe9ebea 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,28 @@
# Changelog
++## 0.260.0 — 2026-10-31
++
++- **One AI meter across the network** (plan L40): every AI call on the network leaves one usage record, whoever made it.
++ - **`ai.call.recorded@1`** (`contracts/events/payloads/ai.call.recorded.v1.json`): one call, and who and where it
++ happened (`subject`, `attribution`, `surface`), what ran (`system`, `provider`, `model`, `effort`), who paid
++ (`paid_by`: `own_key`, `openvibe_key`, `plan`, `own_machine`, `free_tier`, `network_allowance`, `org_key`, with
++ `paid_by_detail`), the tokens (`input` uncached, `output`, `reasoning`, `cache_read`, `cache_write` with
++ `cache_write_ttl` `5m` or `1h`, `images`, `audio_seconds`, `video_seconds`), the money in USD (`list_cost_usd`,
++ `charged_usd`, `fee_usd`, `cache_saved_usd`, with `price_source` and `price_date`), how it went (`latency_ms`,
++ `ttft_ms`, `attempts`, `outcome`) and the ids (`run_id` or `turn_id`, `at`).
++ - **No prompt and no answer text, ever.** The payload has no field for either, and the schema closes the object.
++ - **The meter belongs to OpenVibe.Report** (plan L40 and L41): OpenVibe.AI writes the records, Report keeps the ledger
++ and rolls it up.
++ - **`report.ai-usage-open-result@1`**: the public AI totals (tokens, the cache share, the own-key share, the spend by
++ week). No person, no site and no key in it.
++ - **`report.ai-usage-result@1`**: the same meter in groups (day, site, model, system, paid_by) for a service token with
++ the new **`report.ai_usage.read`** capability. A group that fewer than ten people share reads 'fewer than 10'
++ instead of counting them.
++ - **`report.ai-usage-me-result@1`**: the AI usage of one person (GET /api/v1/me/ai/usage), for the person and nobody
++ else. The route answers 501 until Report has sign-in; the shape is fixed here first.
++ - Phase 1 is OpenVibe.AI's own runs; Codes, Actor and the rest feed the same event later. Report rolls the
++ records up, and a record taken twice is stored once (idempotent by `system` and `run_id` or `turn_id`).
++
## 0.259.0 — 2026-10-31
- **`network.notification-push-request@1` addresses a person by subject as well as by user id (F1, familiar faces).**
@@@ -46,6 +69,7 @@
session on AI's own site behind a same-origin check, reaches them. A service token is refused, and so is any token
carrying `on_behalf_of`, whatever capabilities it holds (the AI #204/#205 rule).
++
## 0.256.0 — 2026-10-31
- **`ai.looks` is retired** (`network.consent@1`). The owner removed the rule that AI text never describes people
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 5d0b64e2..35732af4 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -1380,6 +1380,16 @@
"status": "active",
"adr": "ADR-015"
},
++ {
++ "id": "ai.call.recorded",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "events/payloads/ai.call.recorded.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-033"
++ },
{
"id": "ai.run.queued",
"version": "1.0.0",
@@@ -8310,6 +8320,33 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "report.ai-usage-open-result",
++ "version": "1.0.0",
++ "owner": "report",
++ "schema": "report/ai-usage-open-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "report.ai-usage-result",
++ "version": "1.0.0",
++ "owner": "report",
++ "schema": "report/ai-usage-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "report.ai-usage-me-result",
++ "version": "1.0.0",
++ "owner": "report",
++ "schema": "report/ai-usage-me-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "bench.benchmark",
"version": "1.0.0",
ddiff --git a/contracts/events/payloads/ai.call.recorded.v1.json b/contracts/events/payloads/ai.call.recorded.v1.json
nnew file mode 100644
iindex 00000000..79def7a4
---- /dev/null
++++ b/contracts/events/payloads/ai.call.recorded.v1.json
@@@ -0,0 +1,100 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.call.recorded.v1.json",
++ "title": "AiCallRecordedPayload",
++ "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.",
++ "type": "object",
++ "required": [
++ "system",
++ "surface",
++ "paid_by",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "cache_write_ttl",
++ "images",
++ "audio_seconds",
++ "video_seconds",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd",
++ "price_source",
++ "price_date",
++ "latency_ms",
++ "ttft_ms",
++ "attempts",
++ "outcome",
++ "at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": ["string", "null"],
++ "maxLength": 128,
++ "description": "The person the call was made for, or null when the call was anonymous."
++ },
++ "attribution": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known."
++ },
++ "surface": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract."
++ },
++ "system": {
++ "type": "string",
++ "maxLength": 100,
++ "description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
++ },
++ "provider": { "type": ["string", "null"], "maxLength": 128 },
++ "model": { "type": ["string", "null"], "maxLength": 200 },
++ "effort": { "type": ["string", "null"], "maxLength": 64, "description": "The reasoning effort the call asked for, when it asked for one." },
++ "paid_by": {
++ "type": "string",
++ "enum": ["own_key", "openvibe_key", "plan", "own_machine", "free_tier", "network_allowance", "org_key"]
++ },
++ "paid_by_detail": {
++ "type": ["string", "null"],
++ "maxLength": 200,
++ "description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
++ },
++ "input": { "type": "integer", "minimum": 0, "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields." },
++ "output": { "type": "integer", "minimum": 0 },
++ "reasoning": { "type": "integer", "minimum": 0 },
++ "cache_read": { "type": "integer", "minimum": 0 },
++ "cache_write": { "type": "integer", "minimum": 0 },
++ "cache_write_ttl": {
++ "type": ["string", "null"],
++ "enum": ["5m", "1h", null],
++ "description": "How long the written cache entry lives, when the provider says."
++ },
++ "images": { "type": "integer", "minimum": 0 },
++ "audio_seconds": { "type": "number", "minimum": 0 },
++ "video_seconds": { "type": "number", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0, "description": "What the tokens cost at the human-typed list prices." },
++ "charged_usd": { "type": "number", "minimum": 0, "description": "What the payer was actually charged. A person's own key is never charged here: 0." },
++ "fee_usd": { "type": "number", "minimum": 0, "description": "The network's fee on top of the cost, when the OpenVibe key paid." },
++ "cache_saved_usd": { "type": "number", "minimum": 0, "description": "What the cached tokens saved against the plain input price." },
++ "price_source": {
++ "type": "string",
++ "maxLength": 64,
++ "description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price)."
++ },
++ "price_date": { "type": ["string", "null"], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "description": "The day the price was checked, when it was." },
++ "latency_ms": { "type": ["integer", "null"], "minimum": 0 },
++ "ttft_ms": { "type": ["integer", "null"], "minimum": 0, "description": "Time to the first token, when the call streamed and it was measured." },
++ "attempts": { "type": "integer", "minimum": 0 },
++ "outcome": {
++ "type": "string",
++ "enum": ["succeeded", "failed", "cancelled", "cached"]
++ },
++ "run_id": { "type": ["string", "null"], "maxLength": 100 },
++ "turn_id": { "type": ["string", "null"], "maxLength": 100 },
++ "at": { "type": "string", "format": "date-time" }
++ }
++}
ddiff --git a/contracts/report/ai-usage-me-result.v1.json b/contracts/report/ai-usage-me-result.v1.json
nnew file mode 100644
iindex 00000000..9a01b704
---- /dev/null
++++ b/contracts/report/ai-usage-me-result.v1.json
@@@ -0,0 +1,35 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-me-result.v1.json",
++ "title": "ReportAiUsageMeResult",
++ "description": "report.ai-usage-me-result@1: the AI usage of one person (plan L40/L41). GET /api/v1/me/ai/usage answers for the person themselves and nobody else: their rows, grouped by day, site, model, system or paid_by. The route answers 501 until Report has sign-in: the shape is fixed here first, so the route does not have to change when it opens.",
++ "type": "object",
++ "required": ["group", "usage", "totals"],
++ "additionalProperties": false,
++ "properties": {
++ "group": { "type": "string", "enum": ["day", "site", "model", "system", "paid_by"] },
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": ["group", "key", "calls", "input", "output", "reasoning", "cache_read", "cache_write", "list_cost_usd", "charged_usd", "fee_usd", "cache_saved_usd"],
++ "additionalProperties": false,
++ "properties": {
++ "group": { "type": "string", "enum": ["day", "site", "model", "system", "paid_by"] },
++ "key": { "type": "string", "maxLength": 200 },
++ "calls": { "type": "integer", "minimum": 0 },
++ "input": { "type": "integer", "minimum": 0 },
++ "output": { "type": "integer", "minimum": 0 },
++ "reasoning": { "type": "integer", "minimum": 0 },
++ "cache_read": { "type": "integer", "minimum": 0 },
++ "cache_write": { "type": "integer", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0 },
++ "charged_usd": { "type": "number", "minimum": 0 },
++ "fee_usd": { "type": "number", "minimum": 0 },
++ "cache_saved_usd": { "type": "number", "minimum": 0 }
++ }
++ }
++ },
++ "totals": { "type": "object" }
++ }
++}
ddiff --git a/contracts/report/ai-usage-open-result.v1.json b/contracts/report/ai-usage-open-result.v1.json
nnew file mode 100644
iindex 00000000..97ca8c51
---- /dev/null
++++ b/contracts/report/ai-usage-open-result.v1.json
@@@ -0,0 +1,45 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-open-result.v1.json",
++ "title": "ReportAiUsageOpenResult",
++ "description": "report.ai-usage-open-result@1: the open AI meter (plan L40/L41). What the whole network spends on AI, in public: the tokens, the share of them that came from a provider cache, the share that the own keys of people paid, and the spend by week. No person, no site and no key is in it: the rows are summed before they are answered.",
++ "type": "object",
++ "required": ["calls", "tokens", "cache_share", "cache_saved_usd", "own_key_share", "own_key_calls", "list_cost_usd", "charged_usd", "fee_usd", "spend_by_week"],
++ "additionalProperties": false,
++ "properties": {
++ "calls": { "type": "integer", "minimum": 0, "description": "The AI calls behind these numbers." },
++ "tokens": {
++ "type": "object",
++ "required": ["input", "output", "cache_read", "cache_write"],
++ "additionalProperties": false,
++ "properties": {
++ "input": { "type": "integer", "minimum": 0, "description": "The uncached input tokens." },
++ "output": { "type": "integer", "minimum": 0 },
++ "cache_read": { "type": "integer", "minimum": 0 },
++ "cache_write": { "type": "integer", "minimum": 0 }
++ }
++ },
++ "cache_share": { "type": "number", "minimum": 0, "maximum": 1, "description": "The share of the prompt tokens that came from a provider cache." },
++ "cache_saved_usd": { "type": "number", "minimum": 0 },
++ "own_key_share": { "type": "number", "minimum": 0, "maximum": 1, "description": "The share of the tokens that the own keys of people paid." },
++ "own_key_calls": { "type": "integer", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0 },
++ "charged_usd": { "type": "number", "minimum": 0 },
++ "fee_usd": { "type": "number", "minimum": 0 },
++ "spend_by_week": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": ["week", "calls", "list_cost_usd", "charged_usd", "cache_saved_usd"],
++ "additionalProperties": false,
++ "properties": {
++ "week": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "description": "The Monday of the week." },
++ "calls": { "type": "integer", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0 },
++ "charged_usd": { "type": "number", "minimum": 0 },
++ "cache_saved_usd": { "type": "number", "minimum": 0 }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/report/ai-usage-result.v1.json b/contracts/report/ai-usage-result.v1.json
nnew file mode 100644
iindex 00000000..8a78bd59
---- /dev/null
++++ b/contracts/report/ai-usage-result.v1.json
@@@ -0,0 +1,63 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-result.v1.json",
++ "title": "ReportAiUsageResult",
++ "description": "report.ai-usage-result@1: the AI meter in groups (plan L40/L41). GET /api/v1/ai/usage needs a service token with report.ai_usage.read; the rows are grouped by day, site, model, system or paid_by. A group that fewer than ten people share reads fewer than 10 instead of counting them.",
++ "type": "object",
++ "required": ["group", "usage", "totals"],
++ "additionalProperties": false,
++ "properties": {
++ "group": { "type": "string", "enum": ["day", "site", "model", "system", "paid_by"] },
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": ["group", "key", "calls", "people", "input", "output", "reasoning", "cache_read", "cache_write", "list_cost_usd", "charged_usd", "fee_usd", "cache_saved_usd"],
++ "additionalProperties": false,
++ "properties": {
++ "group": { "type": "string", "enum": ["day", "site", "model", "system", "paid_by"] },
++ "key": { "type": "string", "maxLength": 200, "description": "The name of the group: the day, the site, the model, the system or the payer." },
++ "calls": { "type": "integer", "minimum": 0 },
++ "people": {
++ "oneOf": [
++ { "type": "integer", "minimum": 10, "description": "How many people the group holds, when there are ten or more." },
++ { "const": "fewer than 10", "description": "Fewer than ten people: the count is not given." }
++ ]
++ },
++ "input": { "type": "integer", "minimum": 0 },
++ "output": { "type": "integer", "minimum": 0 },
++ "reasoning": { "type": "integer", "minimum": 0 },
++ "cache_read": { "type": "integer", "minimum": 0 },
++ "cache_write": { "type": "integer", "minimum": 0 },
++ "images": { "type": "integer", "minimum": 0 },
++ "audio_seconds": { "type": "number", "minimum": 0 },
++ "video_seconds": { "type": "number", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0 },
++ "charged_usd": { "type": "number", "minimum": 0 },
++ "fee_usd": { "type": "number", "minimum": 0 },
++ "cache_saved_usd": { "type": "number", "minimum": 0 }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": ["calls", "input", "output", "reasoning", "cache_read", "cache_write", "list_cost_usd", "charged_usd", "fee_usd", "cache_saved_usd"],
++ "additionalProperties": false,
++ "properties": {
++ "calls": { "type": "integer", "minimum": 0 },
++ "input": { "type": "integer", "minimum": 0 },
++ "output": { "type": "integer", "minimum": 0 },
++ "reasoning": { "type": "integer", "minimum": 0 },
++ "cache_read": { "type": "integer", "minimum": 0 },
++ "cache_write": { "type": "integer", "minimum": 0 },
++ "images": { "type": "integer", "minimum": 0 },
++ "audio_seconds": { "type": "number", "minimum": 0 },
++ "video_seconds": { "type": "number", "minimum": 0 },
++ "list_cost_usd": { "type": "number", "minimum": 0 },
++ "charged_usd": { "type": "number", "minimum": 0 },
++ "fee_usd": { "type": "number", "minimum": 0 },
++ "cache_saved_usd": { "type": "number", "minimum": 0 }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/ai.call.recorded/invalid/carries-output-text.json b/fixtures/ai.call.recorded/invalid/carries-output-text.json
nnew file mode 100644
iindex 00000000..1f4cfea6
---- /dev/null
++++ b/fixtures/ai.call.recorded/invalid/carries-output-text.json
@@@ -0,0 +1,34 @@
++{
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "attribution": "site:design",
++ "surface": "design.generate",
++ "system": "ai",
++ "provider": "anthropic",
++ "model": "claude-sonnet-4-5",
++ "effort": "high",
++ "paid_by": "own_key",
++ "paid_by_detail": "anthropic ...a1b2",
++ "input": 1200,
++ "output": 300,
++ "reasoning": 0,
++ "cache_read": 8000,
++ "cache_write": 1500,
++ "cache_write_ttl": "5m",
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "list_cost_usd": 0.012345,
++ "charged_usd": 0,
++ "fee_usd": 0,
++ "cache_saved_usd": 0.00321,
++ "price_source": "models",
++ "price_date": "2026-10-31",
++ "latency_ms": 4200,
++ "ttft_ms": 640,
++ "attempts": 1,
++ "outcome": "succeeded",
++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "turn_id": null,
++ "at": "2026-10-31T18:00:00.000Z",
++ "output_text": "the answer the model gave"
++}
ddiff --git a/fixtures/ai.call.recorded/invalid/unknown-cache-write-ttl.json b/fixtures/ai.call.recorded/invalid/unknown-cache-write-ttl.json
nnew file mode 100644
iindex 00000000..dde19514
---- /dev/null
++++ b/fixtures/ai.call.recorded/invalid/unknown-cache-write-ttl.json
@@@ -0,0 +1,33 @@
++{
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "attribution": "site:design",
++ "surface": "design.generate",
++ "system": "ai",
++ "provider": "anthropic",
++ "model": "claude-sonnet-4-5",
++ "effort": "high",
++ "paid_by": "own_key",
++ "paid_by_detail": "anthropic ...a1b2",
++ "input": 1200,
++ "output": 300,
++ "reasoning": 0,
++ "cache_read": 8000,
++ "cache_write": 1500,
++ "cache_write_ttl": "10m",
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "list_cost_usd": 0.012345,
++ "charged_usd": 0,
++ "fee_usd": 0,
++ "cache_saved_usd": 0.00321,
++ "price_source": "models",
++ "price_date": "2026-10-31",
++ "latency_ms": 4200,
++ "ttft_ms": 640,
++ "attempts": 1,
++ "outcome": "succeeded",
++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "turn_id": null,
++ "at": "2026-10-31T18:00:00.000Z"
++}
ddiff --git a/fixtures/ai.call.recorded/invalid/unknown-paid-by.json b/fixtures/ai.call.recorded/invalid/unknown-paid-by.json
nnew file mode 100644
iindex 00000000..8ad0f968
---- /dev/null
++++ b/fixtures/ai.call.recorded/invalid/unknown-paid-by.json
@@@ -0,0 +1,33 @@
++{
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "attribution": "site:design",
++ "surface": "design.generate",
++ "system": "ai",
++ "provider": "anthropic",
++ "model": "claude-sonnet-4-5",
++ "effort": "high",
++ "paid_by": "someone_else",
++ "paid_by_detail": "anthropic ...a1b2",
++ "input": 1200,
++ "output": 300,
++ "reasoning": 0,
++ "cache_read": 8000,
++ "cache_write": 1500,
++ "cache_write_ttl": "5m",
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "list_cost_usd": 0.012345,
++ "charged_usd": 0,
++ "fee_usd": 0,
++ "cache_saved_usd": 0.00321,
++ "price_source": "models",
++ "price_date": "2026-10-31",
++ "latency_ms": 4200,
++ "ttft_ms": 640,
++ "attempts": 1,
++ "outcome": "succeeded",
++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "turn_id": null,
++ "at": "2026-10-31T18:00:00.000Z"
++}
ddiff --git a/fixtures/ai.call.recorded/valid/openvibe-key.json b/fixtures/ai.call.recorded/valid/openvibe-key.json
nnew file mode 100644
iindex 00000000..f3c2b576
---- /dev/null
++++ b/fixtures/ai.call.recorded/valid/openvibe-key.json
@@@ -0,0 +1,33 @@
++{
++ "subject": null,
++ "attribution": "",
++ "surface": "ai.complete",
++ "system": "harness:claude-code",
++ "provider": "openai",
++ "model": "gpt-5",
++ "effort": null,
++ "paid_by": "openvibe_key",
++ "paid_by_detail": "openvibe:gpt-5",
++ "input": 200,
++ "output": 80,
++ "reasoning": 40,
++ "cache_read": 800,
++ "cache_write": 0,
++ "cache_write_ttl": null,
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "list_cost_usd": 0.0021,
++ "charged_usd": 0.0021,
++ "fee_usd": 0.000105,
++ "cache_saved_usd": 0,
++ "price_source": "no cache price",
++ "price_date": null,
++ "latency_ms": 1800,
++ "ttft_ms": null,
++ "attempts": 2,
++ "outcome": "succeeded",
++ "run_id": null,
++ "turn_id": "tn_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "at": "2026-10-31T18:05:00.000Z"
++}
ddiff --git a/fixtures/ai.call.recorded/valid/own-key.json b/fixtures/ai.call.recorded/valid/own-key.json
nnew file mode 100644
iindex 00000000..3f828e35
---- /dev/null
++++ b/fixtures/ai.call.recorded/valid/own-key.json
@@@ -0,0 +1,33 @@
++{
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "attribution": "site:design",
++ "surface": "design.generate",
++ "system": "ai",
++ "provider": "anthropic",
++ "model": "claude-sonnet-4-5",
++ "effort": "high",
++ "paid_by": "own_key",
++ "paid_by_detail": "anthropic ...a1b2",
++ "input": 1200,
++ "output": 300,
++ "reasoning": 0,
++ "cache_read": 8000,
++ "cache_write": 1500,
++ "cache_write_ttl": "5m",
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "list_cost_usd": 0.012345,
++ "charged_usd": 0,
++ "fee_usd": 0,
++ "cache_saved_usd": 0.00321,
++ "price_source": "models",
++ "price_date": "2026-10-31",
++ "latency_ms": 4200,
++ "ttft_ms": 640,
++ "attempts": 1,
++ "outcome": "succeeded",
++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "turn_id": null,
++ "at": "2026-10-31T18:00:00.000Z"
++}
ddiff --git a/fixtures/report.ai-usage-me-result/invalid/carries-a-person.json b/fixtures/report.ai-usage-me-result/invalid/carries-a-person.json
nnew file mode 100644
iindex 00000000..a36db2e0
---- /dev/null
++++ b/fixtures/report.ai-usage-me-result/invalid/carries-a-person.json
@@@ -0,0 +1,6 @@
++{
++ "group": "day",
++ "usage": [],
++ "totals": { "calls": 0 },
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/report.ai-usage-me-result/valid/mine.json b/fixtures/report.ai-usage-me-result/valid/mine.json
nnew file mode 100644
iindex 00000000..fd92f20c
---- /dev/null
++++ b/fixtures/report.ai-usage-me-result/valid/mine.json
@@@ -0,0 +1,11 @@
++{
++ "group": "day",
++ "usage": [
++ {
++ "group": "day", "key": "2026-10-31", "calls": 2,
++ "input": 200, "output": 80, "reasoning": 0, "cache_read": 800, "cache_write": 0,
++ "list_cost_usd": 0.0021, "charged_usd": 0, "fee_usd": 0, "cache_saved_usd": 0
++ }
++ ],
++ "totals": { "calls": 2, "input": 200, "output": 80 }
++}
ddiff --git a/fixtures/report.ai-usage-open-result/invalid/names-a-person.json b/fixtures/report.ai-usage-open-result/invalid/names-a-person.json
nnew file mode 100644
iindex 00000000..189b47cc
---- /dev/null
++++ b/fixtures/report.ai-usage-open-result/invalid/names-a-person.json
@@@ -0,0 +1,13 @@
++{
++ "calls": 120,
++ "tokens": { "input": 1000, "output": 200, "cache_read": 800, "cache_write": 150 },
++ "cache_share": 0.41,
++ "cache_saved_usd": 0.12,
++ "own_key_share": 0.25,
++ "own_key_calls": 30,
++ "list_cost_usd": 1.23,
++ "charged_usd": 0.9,
++ "fee_usd": 0.02,
++ "spend_by_week": [],
++ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/report.ai-usage-open-result/valid/open.json b/fixtures/report.ai-usage-open-result/valid/open.json
nnew file mode 100644
iindex 00000000..14329526
---- /dev/null
++++ b/fixtures/report.ai-usage-open-result/valid/open.json
@@@ -0,0 +1,14 @@
++{
++ "calls": 120,
++ "tokens": { "input": 1000, "output": 200, "cache_read": 800, "cache_write": 150 },
++ "cache_share": 0.41,
++ "cache_saved_usd": 0.12,
++ "own_key_share": 0.25,
++ "own_key_calls": 30,
++ "list_cost_usd": 1.23,
++ "charged_usd": 0.9,
++ "fee_usd": 0.02,
++ "spend_by_week": [
++ { "week": "2026-10-27", "calls": 30, "list_cost_usd": 0.4, "charged_usd": 0.3, "cache_saved_usd": 0.05 }
++ ]
++}
ddiff --git a/fixtures/report.ai-usage-result/invalid/counts-few-people.json b/fixtures/report.ai-usage-result/invalid/counts-few-people.json
nnew file mode 100644
iindex 00000000..0e8df853
---- /dev/null
++++ b/fixtures/report.ai-usage-result/invalid/counts-few-people.json
@@@ -0,0 +1,14 @@
++{
++ "group": "site",
++ "usage": [
++ {
++ "group": "site", "key": "site:design", "calls": 3, "people": 3,
++ "input": 10, "output": 5, "reasoning": 0, "cache_read": 0, "cache_write": 0,
++ "list_cost_usd": 0.01, "charged_usd": 0.01, "fee_usd": 0, "cache_saved_usd": 0
++ }
++ ],
++ "totals": {
++ "calls": 3, "input": 10, "output": 5, "reasoning": 0, "cache_read": 0, "cache_write": 0,
++ "list_cost_usd": 0.01, "charged_usd": 0.01, "fee_usd": 0, "cache_saved_usd": 0
++ }
++}
ddiff --git a/fixtures/report.ai-usage-result/valid/by-day.json b/fixtures/report.ai-usage-result/valid/by-day.json
nnew file mode 100644
iindex 00000000..a63d585f
---- /dev/null
++++ b/fixtures/report.ai-usage-result/valid/by-day.json
@@@ -0,0 +1,14 @@
++{
++ "group": "day",
++ "usage": [
++ {
++ "group": "day", "key": "2026-10-31", "calls": 12, "people": 14,
++ "input": 100, "output": 50, "reasoning": 0, "cache_read": 800, "cache_write": 0,
++ "list_cost_usd": 0.1, "charged_usd": 0.1, "fee_usd": 0, "cache_saved_usd": 0.02
++ }
++ ],
++ "totals": {
++ "calls": 12, "input": 100, "output": 50, "reasoning": 0, "cache_read": 800, "cache_write": 0,
++ "list_cost_usd": 0.1, "charged_usd": 0.1, "fee_usd": 0, "cache_saved_usd": 0.02
++ }
++}
ddiff --git a/fixtures/report.ai-usage-result/valid/fewer-than-ten.json b/fixtures/report.ai-usage-result/valid/fewer-than-ten.json
nnew file mode 100644
iindex 00000000..17e8dbc8
---- /dev/null
++++ b/fixtures/report.ai-usage-result/valid/fewer-than-ten.json
@@@ -0,0 +1,14 @@
++{
++ "group": "site",
++ "usage": [
++ {
++ "group": "site", "key": "site:design", "calls": 3, "people": "fewer than 10",
++ "input": 10, "output": 5, "reasoning": 0, "cache_read": 0, "cache_write": 0,
++ "list_cost_usd": 0.01, "charged_usd": 0.01, "fee_usd": 0, "cache_saved_usd": 0
++ }
++ ],
++ "totals": {
++ "calls": 3, "input": 10, "output": 5, "reasoning": 0, "cache_read": 0, "cache_write": 0,
++ "list_cost_usd": 0.01, "charged_usd": 0.01, "fee_usd": 0, "cache_saved_usd": 0
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 73309472..0f62f1a7 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -15000,6 +15000,235 @@
}
}
},
++ "ai.call.recorded": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.call.recorded.v1.json",
++ "title": "AiCallRecordedPayload",
++ "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.",
++ "type": "object",
++ "required": [
++ "system",
++ "surface",
++ "paid_by",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "cache_write_ttl",
++ "images",
++ "audio_seconds",
++ "video_seconds",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd",
++ "price_source",
++ "price_date",
++ "latency_ms",
++ "ttft_ms",
++ "attempts",
++ "outcome",
++ "at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128,
++ "description": "The person the call was made for, or null when the call was anonymous."
++ },
++ "attribution": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known."
++ },
++ "surface": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract."
++ },
++ "system": {
++ "type": "string",
++ "maxLength": 100,
++ "description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ },
++ "effort": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64,
++ "description": "The reasoning effort the call asked for, when it asked for one."
++ },
++ "paid_by": {
++ "type": "string",
++ "enum": [
++ "own_key",
++ "openvibe_key",
++ "plan",
++ "own_machine",
++ "free_tier",
++ "network_allowance",
++ "org_key"
++ ]
++ },
++ "paid_by_detail": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_ttl": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "5m",
++ "1h",
++ null
++ ],
++ "description": "How long the written cache entry lives, when the provider says."
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the tokens cost at the human-typed list prices."
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the payer was actually charged. A person's own key is never charged here: 0."
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The network's fee on top of the cost, when the OpenVibe key paid."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the cached tokens saved against the plain input price."
++ },
++ "price_source": {
++ "type": "string",
++ "maxLength": 64,
++ "description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price)."
++ },
++ "price_date": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The day the price was checked, when it was."
++ },
++ "latency_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "ttft_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Time to the first token, when the call streamed and it was measured."
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "outcome": {
++ "type": "string",
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ]
++ },
++ "run_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "turn_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
"ai.run.queued": {
"version": "1.0.0",
"owner": "ai",
@@@ -92933,6 +93162,450 @@
]
}
},
++ "report.ai-usage-open-result": {
++ "version": "1.0.0",
++ "owner": "report",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-open-result.v1.json",
++ "title": "ReportAiUsageOpenResult",
++ "description": "report.ai-usage-open-result@1: the open AI meter (plan L40/L41). What the whole network spends on AI, in public: the tokens, the share of them that came from a provider cache, the share that the own keys of people paid, and the spend by week. No person, no site and no key is in it: the rows are summed before they are answered.",
++ "type": "object",
++ "required": [
++ "calls",
++ "tokens",
++ "cache_share",
++ "cache_saved_usd",
++ "own_key_share",
++ "own_key_calls",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "spend_by_week"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "calls": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The AI calls behind these numbers."
++ },
++ "tokens": {
++ "type": "object",
++ "required": [
++ "input",
++ "output",
++ "cache_read",
++ "cache_write"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "cache_share": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1,
++ "description": "The share of the prompt tokens that came from a provider cache."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "own_key_share": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1,
++ "description": "The share of the tokens that the own keys of people paid."
++ },
++ "own_key_calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "spend_by_week": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "week",
++ "calls",
++ "list_cost_usd",
++ "charged_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "week": {
++ "type": "string",
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The Monday of the week."
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "report.ai-usage-result": {
++ "version": "1.0.0",
++ "owner": "report",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-result.v1.json",
++ "title": "ReportAiUsageResult",
++ "description": "report.ai-usage-result@1: the AI meter in groups (plan L40/L41). GET /api/v1/ai/usage needs a service token with report.ai_usage.read; the rows are grouped by day, site, model, system or paid_by. A group that fewer than ten people share reads fewer than 10 instead of counting them.",
++ "type": "object",
++ "required": [
++ "group",
++ "usage",
++ "totals"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "group",
++ "key",
++ "calls",
++ "people",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The name of the group: the day, the site, the model, the system or the payer."
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "people": {
++ "oneOf": [
++ {
++ "type": "integer",
++ "minimum": 10,
++ "description": "How many people the group holds, when there are ten or more."
++ },
++ {
++ "const": "fewer than 10",
++ "description": "Fewer than ten people: the count is not given."
++ }
++ ]
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": [
++ "calls",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
++ "report.ai-usage-me-result": {
++ "version": "1.0.0",
++ "owner": "report",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-usage-me-result.v1.json",
++ "title": "ReportAiUsageMeResult",
++ "description": "report.ai-usage-me-result@1: the AI usage of one person (plan L40/L41). GET /api/v1/me/ai/usage answers for the person themselves and nobody else: their rows, grouped by day, site, model, system or paid_by. The route answers 501 until Report has sign-in: the shape is fixed here first, so the route does not have to change when it opens.",
++ "type": "object",
++ "required": [
++ "group",
++ "usage",
++ "totals"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "group",
++ "key",
++ "calls",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object"
++ }
++ }
++ }
++ },
"bench.benchmark": {
"version": "1.0.0",
"owner": "bench",
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 5add8c56..d8af117a 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 1139a395..147f9b48 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.259.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/bench.json b/generated/openapi/bench.json
iindex 6efa4094..b4ccd5eb 100644
---- a/generated/openapi/bench.json
++++ b/generated/openapi/bench.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bench API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 1bddd43d..13fda2a6 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.259.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 efc6cdec..26c90b17 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.259.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 960e9d5c..bf19fd06 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 b1dd0f73..ac05aaef 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.259.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 19cf8105..d8da028c 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.259.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 0537f1b5..898d5550 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.259.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 20f903f6..55dd4cde 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.259.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 0b81422d..45a49f1f 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.259.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/domains.json b/generated/openapi/domains.json
iindex 264e2b3e..7f17d1c0 100644
---- a/generated/openapi/domains.json
++++ b/generated/openapi/domains.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Domains API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 dd554407..4a912820 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.259.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex e560db2a..3982bf4c 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 755433ca..2630b149 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.259.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 cdd71a78..1e74b626 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.259.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 dfe0a1b6..4f484488 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -176,6 +176,13 @@
"operations": 3,
"capabilities": 1
},
++ {
++ "service": "report",
++ "name": "OpenVibe.Report",
++ "origin": "https://openvibe.report",
++ "operations": 1,
++ "capabilities": 1
++ },
{
"service": "reviews",
"name": "OpenVibe.Reviews",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex d660ef49..68750847 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 61f0c156..cc5f8471 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.259.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
iindex 267009e9..3aa10f7f 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 7585a3d4..03190463 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.259.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 68043703..b9d9280d 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.259.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 f593f2e7..0dcf325a 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.259.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 cfc9912e..5b6e7ca4 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.259.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/play.json b/generated/openapi/play.json
iindex 56aacf5f..f46e95d2 100644
---- a/generated/openapi/play.json
++++ b/generated/openapi/play.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Play API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/quest.json b/generated/openapi/quest.json
iindex 5accff87..344b54f6 100644
---- a/generated/openapi/quest.json
++++ b/generated/openapi/quest.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Quest API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/report.json b/generated/openapi/report.json
nnew file mode 100644
iindex 00000000..97190ce3
---- /dev/null
++++ b/generated/openapi/report.json
@@@ -0,0 +1,341 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Report API",
++ "version": "0.260.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.report"
++ }
++ ],
++ "paths": {
++ "/api/v1/ai/usage": {
++ "get": {
++ "operationId": "get_api_v1_ai_usage",
++ "summary": "Read the AI meter across the network: the usage records and their roll-ups, in groups (day, site, model, system, paid_by).",
++ "description": "**report.ai_usage.read** (internal): Read the AI meter across the network: the usage records and their roll-ups, in groups (day, site, model, system, paid_by). The public totals (GET /api/v1/ai/usage/open) need no token at all.",
++ "tags": [
++ "report.ai_usage"
++ ],
++ "x-openvibe-capabilities": [
++ "report.ai_usage.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "report-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.ai_usage.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/report.ai-usage-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "report.ai-usage-result.v1": {
++ "title": "ReportAiUsageResult",
++ "description": "report.ai-usage-result@1: the AI meter in groups (plan L40/L41). GET /api/v1/ai/usage needs a service token with report.ai_usage.read; the rows are grouped by day, site, model, system or paid_by. A group that fewer than ten people share reads fewer than 10 instead of counting them.",
++ "type": "object",
++ "required": [
++ "group",
++ "usage",
++ "totals"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "group",
++ "key",
++ "calls",
++ "people",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "group": {
++ "type": "string",
++ "enum": [
++ "day",
++ "site",
++ "model",
++ "system",
++ "paid_by"
++ ]
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The name of the group: the day, the site, the model, the system or the payer."
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "people": {
++ "oneOf": [
++ {
++ "type": "integer",
++ "minimum": 10,
++ "description": "How many people the group holds, when there are ten or more."
++ },
++ {
++ "const": "fewer than 10",
++ "description": "Fewer than ten people: the count is not given."
++ }
++ ]
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": [
++ "calls",
++ "input",
++ "output",
++ "reasoning",
++ "cache_read",
++ "cache_write",
++ "list_cost_usd",
++ "charged_usd",
++ "fee_usd",
++ "cache_saved_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 4dd847ae..b69ee7f6 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.259.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex 7fce6c3b..c2211bc4 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 eb1ef932..500abb47 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.259.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex 3b9e43c8..6c52a959 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 36aa888a..149be20f 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.259.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex de28509b..d2c6fda6 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 c55b228e..48ad70ca 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.259.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 f02dc430..888651c4 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.259.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 8bbe4de9..189e4a19 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.259.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 662d03b8..eb5aec31 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.259.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/vision.json b/generated/openapi/vision.json
iindex 76caa3f8..8f2a30b1 100644
---- a/generated/openapi/vision.json
++++ b/generated/openapi/vision.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Vision API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex c1034f50..d9fdd8da 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 848b6b5c..c4f2c35f 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.259.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 1a6b67e2..efc4f9ec 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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/zone.json b/generated/openapi/zone.json
iindex 9c1b4f2e..b94efacc 100644
---- a/generated/openapi/zone.json
++++ b/generated/openapi/zone.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Zone API",
-- "version": "0.259.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.259.0): each operation lists the capabilities it performs; a token for 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.260.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for 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 3dbee589..d3209f11 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -152300,6 +152300,89 @@ export interface AiRun {
finished_at: string | null;
}
++/** [email protected] (owner: ai) */
++/**
++ * ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.
++ */
++export interface AiCallRecordedPayload {
++ /**
++ * The person the call was made for, or null when the call was anonymous.
++ */
++ subject?: string | null;
++ /**
++ * The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known.
++ */
++ attribution?: string;
++ /**
++ * The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract.
++ */
++ surface: string;
++ /**
++ * What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'.
++ */
++ system: string;
++ provider?: string | null;
++ model?: string | null;
++ /**
++ * The reasoning effort the call asked for, when it asked for one.
++ */
++ effort?: string | null;
++ paid_by: "own_key" | "openvibe_key" | "plan" | "own_machine" | "free_tier" | "network_allowance" | "org_key";
++ /**
++ * Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret.
++ */
++ paid_by_detail?: string | null;
++ /**
++ * The uncached input tokens. Cache reads and cache writes are counted on their own fields.
++ */
++ input: number;
++ output: number;
++ reasoning: number;
++ cache_read: number;
++ cache_write: number;
++ /**
++ * How long the written cache entry lives, when the provider says.
++ */
++ cache_write_ttl: "5m" | "1h" | null;
++ images: number;
++ audio_seconds: number;
++ video_seconds: number;
++ /**
++ * What the tokens cost at the human-typed list prices.
++ */
++ list_cost_usd: number;
++ /**
++ * What the payer was actually charged. A person's own key is never charged here: 0.
++ */
++ charged_usd: number;
++ /**
++ * The network's fee on top of the cost, when the OpenVibe key paid.
++ */
++ fee_usd: number;
++ /**
++ * What the cached tokens saved against the plain input price.
++ */
++ cache_saved_usd: number;
++ /**
++ * Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price).
++ */
++ price_source: string;
++ /**
++ * The day the price was checked, when it was.
++ */
++ price_date: string | null;
++ latency_ms: number | null;
++ /**
++ * Time to the first token, when the call streamed and it was measured.
++ */
++ ttft_ms: number | null;
++ attempts: number;
++ outcome: "succeeded" | "failed" | "cancelled" | "cached";
++ run_id?: string | null;
++ turn_id?: string | null;
++ at: string;
++}
++
/** [email protected] (owner: ai) */
/**
* ai.run.queued v1 (OpenVibe.AI server/runs.js create). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run was stored queued (it then runs inline within ?wait or on the worker). Not published for a cache hit (ai.run.cached) or a create refused by quota or a full queue. A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority low, actor service:ai.
@@@ -276743,6 +276826,116 @@ export type ReportMetric = {
[k: string]: unknown | undefined;
};
++/** [email protected] (owner: report) */
++/**
++ * report.ai-usage-open-result@1: the open AI meter (plan L40/L41). What the whole network spends on AI, in public: the tokens, the share of them that came from a provider cache, the share that the own keys of people paid, and the spend by week. No person, no site and no key is in it: the rows are summed before they are answered.
++ */
++export interface ReportAiUsageOpenResult {
++ /**
++ * The AI calls behind these numbers.
++ */
++ calls: number;
++ tokens: {
++ /**
++ * The uncached input tokens.
++ */
++ input: number;
++ output: number;
++ cache_read: number;
++ cache_write: number;
++ };
++ /**
++ * The share of the prompt tokens that came from a provider cache.
++ */
++ cache_share: number;
++ cache_saved_usd: number;
++ /**
++ * The share of the tokens that the own keys of people paid.
++ */
++ own_key_share: number;
++ own_key_calls: number;
++ list_cost_usd: number;
++ charged_usd: number;
++ fee_usd: number;
++ spend_by_week: {
++ /**
++ * The Monday of the week.
++ */
++ week: string;
++ calls: number;
++ list_cost_usd: number;
++ charged_usd: number;
++ cache_saved_usd: number;
++ }[];
++}
++
++/** [email protected] (owner: report) */
++/**
++ * report.ai-usage-result@1: the AI meter in groups (plan L40/L41). GET /api/v1/ai/usage needs a service token with report.ai_usage.read; the rows are grouped by day, site, model, system or paid_by. A group that fewer than ten people share reads fewer than 10 instead of counting them.
++ */
++export interface ReportAiUsageResult {
++ group: "day" | "site" | "model" | "system" | "paid_by";
++ usage: {
++ group: "day" | "site" | "model" | "system" | "paid_by";
++ /**
++ * The name of the group: the day, the site, the model, the system or the payer.
++ */
++ key: string;
++ calls: number;
++ people: number | "fewer than 10";
++ input: number;
++ output: number;
++ reasoning: number;
++ cache_read: number;
++ cache_write: number;
++ images?: number;
++ audio_seconds?: number;
++ video_seconds?: number;
++ list_cost_usd: number;
++ charged_usd: number;
++ fee_usd: number;
++ cache_saved_usd: number;
++ }[];
++ totals: {
++ calls: number;
++ input: number;
++ output: number;
++ reasoning: number;
++ cache_read: number;
++ cache_write: number;
++ images?: number;
++ audio_seconds?: number;
++ video_seconds?: number;
++ list_cost_usd: number;
++ charged_usd: number;
++ fee_usd: number;
++ cache_saved_usd: number;
++ };
++}
++
++/** [email protected] (owner: report) */
++/**
++ * report.ai-usage-me-result@1: the AI usage of one person (plan L40/L41). GET /api/v1/me/ai/usage answers for the person themselves and nobody else: their rows, grouped by day, site, model, system or paid_by. The route answers 501 until Report has sign-in: the shape is fixed here first, so the route does not have to change when it opens.
++ */
++export interface ReportAiUsageMeResult {
++ group: "day" | "site" | "model" | "system" | "paid_by";
++ usage: {
++ group: "day" | "site" | "model" | "system" | "paid_by";
++ key: string;
++ calls: number;
++ input: number;
++ output: number;
++ reasoning: number;
++ cache_read: number;
++ cache_write: number;
++ list_cost_usd: number;
++ charged_usd: number;
++ fee_usd: number;
++ cache_saved_usd: number;
++ }[];
++ totals: {};
++}
++
/** [email protected] (owner: bench) */
/**
* bench.benchmark@1: a versioned, forkable benchmark on OpenVibe.Bench (spec 2026-10-30, stage B1). It holds the tasks, the judges, how they combine into one score, the limits a run may not pass, the default model and harness matrix, and the visibility of the set. A version is immutable once a run names it; a fork records its parent. Holdout tasks are hidden from everyone but the executor.
ddiff --git a/manifests/capabilities/report.ai_usage.read.json b/manifests/capabilities/report.ai_usage.read.json
nnew file mode 100644
iindex 00000000..df388941
---- /dev/null
++++ b/manifests/capabilities/report.ai_usage.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "report.ai_usage.read",
++ "version": "1.0",
++ "owner": "report",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read the AI meter across the network: the usage records and their roll-ups, in groups (day, site, model, system, paid_by). The public totals (GET /api/v1/ai/usage/open) need no token at all.",
++ "permissions": [
++ "report:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "report-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/ai/usage"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "report.ai-usage-result@1"
++}
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex beba4240..6c26b4d7 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -35,6 +35,7 @@
"ai.me.manage"
],
"eventsProduced": [
++ "ai.call.recorded",
"ai.run.queued",
"ai.run.succeeded",
"ai.run.failed",
ddiff --git a/manifests/services/report.json b/manifests/services/report.json
iindex f0ee7c1b..94eb60cb 100644
---- a/manifests/services/report.json
++++ b/manifests/services/report.json
@@@ -15,9 +15,13 @@
"state": "live",
"publicSite": "service"
},
-- "capabilities": [],
++ "capabilities": [
++ "report.ai_usage.read"
++ ],
"eventsProduced": [],
-- "eventsConsumed": [],
++ "eventsConsumed": [
++ "ai.call.recorded"
++ ],
"namespacesOwned": [
"report.*"
],
ddiff --git a/package-lock.json b/package-lock.json
iindex 5e136a1e..08f9b66c 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.259.0",
++ "version": "0.260.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.259.0",
++ "version": "0.260.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 10db029c..ebab33e4 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.259.0",
++ "version": "0.260.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",