openvibers/openvibe.contracts Commits
Accept Vision alert event type in contract validation (#248)
Changes
@@@ -4,6 +4,31 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,the notes were in the tag and commit messages (`git tag -n1`).++## 0.240.0 — 2026-10-29++++- **OpenVibe.Vision's API** (plan §5.8b): the service manifest now lists the 20 capabilities its routes serve today —++ `vision.image.ask`, `vision.text.read`, `vision.model.compare`, `vision.ui.check`, `vision.alt.write`,++ `vision.media.find`, `vision.receipt.read`, `vision.product.match`, `vision.camera.watch`, `vision.video.moments`,++ `vision.face.detect`, `vision.face.blur`, `vision.face.consent.manage`, `vision.face.verify.self`,++ `vision.face.find.self`, `vision.face.library`, `vision.face.cast.match`, `vision.face.cast.read`,++ `vision.models.read` and `vision.benchmarks.read` — with their input and output schemas: `vision.image-request@1`,++ `vision.ask-request@1`, `vision.ocr-request@1`, `vision.compare-request@1`, `vision.find-request@1`,++ `vision.receipt-request@1`, `vision.product-request@1`, `vision.watch-request@1`, `vision.moments-request@1`,++ `vision.answer@1`, `vision.answer-result@1`, `vision.alt-result@1`, `vision.find-result@1`,++ `vision.receipt-result@1`, `vision.product-result@1`, `vision.watch-result@1`, `vision.moments-result@1`,++ `vision.face-detection@1`, `vision.face-blur@1`, `vision.face-enrol-request@1`, `vision.face-verify@1`,++ `vision.face-find-result@1`, `vision.face-cast-match-request@1`, `vision.face-cast-match@1`, `vision.face-tag@1`,++ `vision.face-tag-list@1`, `vision.face-library-request@1`, `vision.face-library-result@1`,++ `vision.face-library-group@1`, `vision.models@1` and `vision.benchmarks@1`.++- **Consent** `vision.consent@1`: whose face or voice (subject and modality), the purpose-scoped feature and scope,++ who signed which text version, when it expires, when and how it was revoked (subject, retention, account deletion),++ the retention limit and whether the template and derived data were erased. `vision.consent-request@1` and++ `vision.consent-revocation@1` are the grant and revoke bodies.++- **Events** `vision.result.ready@1` (a result's identifiers, engine, provenance, cost, latency, retention and++ consent id; never an image or answer text) and `vision.alert@1` (an owner-granted camera alert: source reference,++ classes and confidence). `events.event-envelope@1` now accepts a two-segment event type (`vision.alert` is the++ first), a backward-compatible widening; every capability id still needs three segments.++## 0.239.0 — 2026-10-28- Added `graph.link@1` (`from`, `rel`, `to` OVRN relation) for the planned object graph. The `graph.link.created` event waits for a producing service.@@@ -7722,6 +7722,330 @@"compatibility": "backward","status": "active"},++ {++ "id": "vision.image-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/image-request.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.ask-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/ask-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.ocr-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/ocr-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.compare-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/compare-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.find-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/find-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.receipt-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/receipt-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.product-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/product-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.watch-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/watch-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.moments-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/moments-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.answer",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/answer.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.answer-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/answer-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.alt-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/alt-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.find-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/find-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.receipt-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/receipt-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.product-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/product-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.watch-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/watch-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.moments-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/moments-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-detection",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-detection.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-blur",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-blur.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-enrol-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-enrol-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-verify",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-verify.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-find-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-find-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-cast-match-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-cast-match-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-cast-match",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-cast-match.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-tag",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-tag.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-tag-list",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-tag-list.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-library-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-library-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-library-result",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-library-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.face-library-group",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/face-library-group.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.consent",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/consent.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.consent-request",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/consent-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.consent-revocation",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/consent-revocation.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.models",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/models.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.benchmarks",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "vision/benchmarks.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.result.ready",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "events/payloads/vision.result.ready.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "vision.alert",++ "version": "1.0.0",++ "owner": "vision",++ "schema": "events/payloads/vision.alert.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "bench.kind","version": "1.0.0",@@@ -9,7 +9,7 @@"properties": {"event_id": { "type": "string", "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" },"trace_id": { "type": "string", "pattern": "^[0-9a-f]{32}$" },-- "event_type": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$" },++ "event_type": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$" },"version": { "type": "integer", "minimum": 1 },"source": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },"actor": { "$ref": "../identity/subject-ref.v1.json" },nnew file mode 100644@@@ -0,0 +1,57 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vision.alert.v1.json",++ "title": "VisionAlertPayload",++ "description": "vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "source_ref",++ "classes",++ "confidence",++ "detected_at"++ ],++ "properties": {++ "kind": {++ "enum": [++ "object",++ "scene_change",++ "face"++ ]++ },++ "source_ref": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ },++ "classes": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 60++ },++ "minItems": 1,++ "maxItems": 20++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0,++ "maximum": 1++ },++ "previous_ref": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 160++ },++ "detected_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,108 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vision.result.ready.v1.json",++ "title": "VisionResultReadyPayload",++ "description": "vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "result_id",++ "task",++ "source",++ "model",++ "provider",++ "route",++ "cost_usd",++ "latency_ms",++ "confidence",++ "retention",++ "consent_id",++ "created_at"++ ],++ "properties": {++ "result_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^(vis|vuc|fme|ftg|fco)_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr",++ "compare",++ "alt",++ "find",++ "receipt",++ "product",++ "watch",++ "moments",++ "ui_check",++ "face_detect",++ "face_blur"++ ]++ },++ "source": {++ "enum": [++ "upload",++ "url",++ "data",++ "html",++ "screenshot",++ "none"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string",++ "maxLength": 200++ },++ "consent_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,76 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/alt-result.v1.json",++ "title": "VisionAltResult",++ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "alt_text",++ "editable",++ "receipt"++ ],++ "properties": {++ "alt_text": {++ "type": "string",++ "maxLength": 240++ },++ "editable": {++ "const": true++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,117 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/answer-result.v1.json",++ "title": "VisionAnswerResult",++ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "$ref": "./answer.v1.json"++ },++ "compare": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "ok",++ "cost_usd",++ "latency_ms"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "requested_model": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "ok": {++ "type": "boolean"++ },++ "text": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ },++ "local": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ok",++ "model",++ "confidence",++ "error_code"++ ],++ "properties": {++ "ok": {++ "type": "boolean"++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,140 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/answer.v1.json",++ "title": "VisionAnswer",++ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "task",++ "mode",++ "source",++ "image_sha256",++ "image_mime",++ "image_bytes",++ "cost_usd",++ "latency_ms",++ "created_at",++ "answer"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr",++ "compare"++ ]++ },++ "question": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 2000++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "source": {++ "enum": [++ "upload",++ "url",++ "data"++ ]++ },++ "source_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri"++ },++ "image_sha256": {++ "type": "string",++ "pattern": "^[0-9a-f]{64}$"++ },++ "image_mime": {++ "type": "string",++ "pattern": "^image/[a-z0-9.+-]+$"++ },++ "image_bytes": {++ "type": "integer",++ "minimum": 1,++ "maximum": 20971520++ },++ "text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 20000++ },++ "answer": {++ "type": "object",++ "description": "Engine details, OCR words, provenance and any compare rows; never an image."++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "engine": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0,++ "maximum": 1++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,62 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/ask-request.v1.json",++ "title": "VisionAskRequest",++ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,79 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/benchmarks.v1.json",++ "title": "VisionBenchmarks",++ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "benchmarks"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "benchmarks": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "task",++ "engine",++ "provider",++ "model",++ "samples",++ "avg_latency_ms",++ "avg_cost_usd",++ "min_cost_usd",++ "max_latency_ms"++ ],++ "properties": {++ "task": {++ "type": "string"++ },++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "samples": {++ "type": "integer",++ "minimum": 0++ },++ "avg_latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "avg_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "min_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "max_latency_ms": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,59 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/compare-request.v1.json",++ "title": "VisionCompareRequest",++ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "models": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 3++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,50 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent-request.v1.json",++ "title": "VisionConsentRequest",++ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "signed_name",++ "text_version"++ ],++ "properties": {++ "signed_name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ],++ "description": "Echo of the :feature path segment."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "adult": {++ "type": "boolean"++ },++ "region": {++ "type": "string",++ "maxLength": 10++ }++ }++}nnew file mode 100644@@@ -0,0 +1,42 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent-revocation.v1.json",++ "title": "VisionConsentRevocation",++ "description": "vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "feature",++ "scope",++ "revoked_at",++ "revoked_by",++ "erased"++ ],++ "properties": {++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ]++ },++ "scope": {++ "type": "string",++ "maxLength": 160++ },++ "revoked_at": {++ "type": "string",++ "format": "date-time"++ },++ "revoked_by": {++ "enum": [++ "subject"++ ]++ },++ "erased": {++ "const": true++ }++ }++}nnew file mode 100644@@@ -0,0 +1,130 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent.v1.json",++ "title": "VisionConsent",++ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "subject",++ "modality",++ "feature",++ "scope",++ "purpose",++ "text_version",++ "granted_by",++ "granted_at",++ "expires_at",++ "last_used_at",++ "revoked_at",++ "revoked_by",++ "purpose_ends_at",++ "retention_until",++ "status",++ "erased"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose face or voice this consent covers."++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ]++ },++ "scope": {++ "type": "string",++ "maxLength": 160,++ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."++ },++ "purpose": {++ "type": "string",++ "maxLength": 200++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "granted_by": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person who signed; the subject themself, or a guardian where the law allows."++ },++ "granted_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the consent stops applying on its own; null when it lasts until revoked."++ },++ "last_used_at": {++ "type": "string",++ "format": "date-time"++ },++ "revoked_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "revoked_by": {++ "type": [++ "string",++ "null"++ ],++ "enum": [++ "subject",++ "retention",++ "account_deletion",++ null++ ],++ "description": "How it was revoked: by the person, by the retention job, or with the account."++ },++ "purpose_ends_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "retention_until": {++ "type": "string",++ "format": "date-time",++ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."++ },++ "status": {++ "enum": [++ "active",++ "revoked",++ "expired"++ ]++ },++ "erased": {++ "type": "boolean",++ "description": "True once the template and derived results this consent covered are gone."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-blur.v1.json",++ "title": "VisionFaceBlur",++ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "media_type",++ "face_count"++ ],++ "properties": {++ "media_type": {++ "const": "image/png"++ },++ "face_count": {++ "type": "integer",++ "minimum": 0++ }++ }++}nnew file mode 100644@@@ -0,0 +1,45 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-cast-match-request.v1.json",++ "title": "VisionFaceCastMatchRequest",++ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "media_ref"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "media_ref": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,70 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-cast-match.v1.json",++ "title": "VisionFaceCastMatch",++ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "matches",++ "liveness"++ ],++ "properties": {++ "channel": {++ "type": "string"++ },++ "matches": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "score"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++}nnew file mode 100644@@@ -0,0 +1,51 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-detection.v1.json",++ "title": "VisionFaceDetection",++ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "boxes"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "boxes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height",++ "score"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,47 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-enrol-request.v1.json",++ "title": "VisionFaceEnrolRequest",++ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "feature"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast"++ ]++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,36 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-find-result.v1.json",++ "title": "VisionFaceFindResult",++ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 500,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "private_link",++ "score"++ ],++ "properties": {++ "private_link": {++ "type": "string",++ "pattern": "^/api/v1/faces/media/"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,43 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-group.v1.json",++ "title": "VisionFaceLibraryGroup",++ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "media_id",++ "group_id",++ "excluded",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "media_id": {++ "type": "string",++ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "group_id": {++ "type": "string",++ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "label": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 120++ },++ "excluded": {++ "type": "boolean"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,34 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-request.v1.json",++ "title": "VisionFaceLibraryRequest",++ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action"++ ],++ "properties": {++ "action": {++ "enum": [++ "cluster",++ "exclude",++ "merge",++ "split"++ ]++ },++ "region": {++ "type": "string",++ "maxLength": 10++ },++ "ids": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 40++ },++ "minItems": 1,++ "maxItems": 100++ }++ }++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-result.v1.json",++ "title": "VisionFaceLibraryResult",++ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",++ "oneOf": [++ {++ "type": "array",++ "items": {++ "$ref": "./face-library-group.v1.json"++ }++ },++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "updated"++ ],++ "properties": {++ "action": {++ "enum": [++ "exclude",++ "merge",++ "split"++ ]++ },++ "updated": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-tag-list.v1.json",++ "title": "VisionFaceTagList",++ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",++ "type": "array",++ "items": {++ "$ref": "./face-tag.v1.json"++ }++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-tag.v1.json",++ "title": "VisionFaceTag",++ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "channel",++ "media_ref",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "channel": {++ "type": "string"++ },++ "media_ref": {++ "type": "string"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-verify.v1.json",++ "title": "VisionFaceVerify",++ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "score",++ "match",++ "threshold",++ "liveness"++ ],++ "properties": {++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "match": {++ "type": "boolean"++ },++ "threshold": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++}nnew file mode 100644@@@ -0,0 +1,64 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/find-request.v1.json",++ "title": "VisionFindRequest",++ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "q": {++ "type": "string",++ "minLength": 1,++ "maxLength": 500++ },++ "item_id": {++ "type": "string",++ "pattern": "^[\\w:.-]{1,200}$"++ },++ "result_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ }++ },++ "anyOf": [++ {++ "required": [++ "q"++ ]++ },++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ },++ {++ "required": [++ "item_id",++ "image"++ ]++ },++ {++ "required": [++ "item_id",++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,96 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/find-result.v1.json",++ "title": "VisionFindResult",++ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results",++ "receipt"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 5000,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "item_id",++ "score"++ ],++ "properties": {++ "item_id": {++ "type": "string"++ },++ "result_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri",++ "pattern": "^https://"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/image-request.v1.json",++ "title": "VisionImageRequest",++ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,41 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/models.v1.json",++ "title": "VisionModels",++ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ai",++ "configured",++ "offered",++ "local",++ "note"++ ],++ "properties": {++ "ai": {++ "type": "boolean"++ },++ "configured": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "offered": {++ "type": "array",++ "items": {++ "type": "object"++ }++ },++ "local": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "note": {++ "type": "string"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,60 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/moments-request.v1.json",++ "title": "VisionMomentsRequest",++ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "frames"++ ],++ "properties": {++ "frames": {++ "type": "array",++ "minItems": 1,++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "image": {++ "type": "string",++ "pattern": "^data:image/"++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "prompts": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 10++ }++ }++}nnew file mode 100644@@@ -0,0 +1,114 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/moments-result.v1.json",++ "title": "VisionMomentsResult",++ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "scene_changes",++ "candidate_moments",++ "receipt"++ ],++ "properties": {++ "scene_changes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "kind"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "kind": {++ "const": "scene_change"++ }++ }++ }++ },++ "candidate_moments": {++ "type": "array",++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "prompt",++ "score"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "prompt": {++ "type": "string"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,44 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/ocr-request.v1.json",++ "title": "VisionOcrRequest",++ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "fallback": {++ "type": "boolean"++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,36 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/product-request.v1.json",++ "title": "VisionProductRequest",++ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "allow_rerank": {++ "type": "boolean"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,123 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/product-result.v1.json",++ "title": "VisionProductResult",++ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "description",++ "embedding",++ "offers",++ "warnings",++ "receipt"++ ],++ "properties": {++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "embedding": {++ "type": "array",++ "items": {++ "type": "number"++ }++ },++ "offers": {++ "type": "array",++ "maxItems": 10,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "source",++ "title",++ "url"++ ],++ "properties": {++ "source": {++ "type": "string"++ },++ "title": {++ "type": "string",++ "maxLength": 200++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "price": {++ "type": [++ "number",++ "null"++ ]++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "warnings": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,36 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/receipt-request.v1.json",++ "title": "VisionReceiptRequest",++ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "fallback": {++ "type": "boolean"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,115 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/receipt-result.v1.json",++ "title": "VisionReceiptResult",++ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "store",++ "items",++ "total",++ "receipt"++ ],++ "properties": {++ "store": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 150++ },++ "items": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "qty",++ "price"++ ],++ "properties": {++ "name": {++ "type": "string",++ "maxLength": 200++ },++ "qty": {++ "type": "number",++ "minimum": 0++ },++ "price": {++ "type": "number",++ "minimum": 0++ }++ }++ }++ },++ "total": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0++ },++ "raw_text": {++ "type": "string",++ "maxLength": 20000++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,49 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/watch-request.v1.json",++ "title": "VisionWatchRequest",++ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "classes"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "classes": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 60++ },++ "minItems": 1,++ "maxItems": 20++ },++ "previous_image": {++ "type": "string",++ "pattern": "^data:image/"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,116 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/watch-result.v1.json",++ "title": "VisionWatchResult",++ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "detections",++ "receipt"++ ],++ "properties": {++ "detections": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "class",++ "confidence"++ ],++ "properties": {++ "class": {++ "type": "string"++ },++ "confidence": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": [++ "object",++ "null"++ ],++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "event_type": "vision.Alert",++ "version": 1,++ "source": "media",++ "actor": {++ "type": "service",++ "id": "media"++ },++ "timestamp": "2026-09-22T12:00:00Z",++ "subject": {++ "type": "vod",++ "id": "1"++ },++ "payload": {}++}ddeleted file mode 100644@@@ -1,16 +0,0 @@--{-- "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ",-- "event_type": "vod.ready",-- "version": 1,-- "source": "media",-- "actor": {-- "type": "service",-- "id": "media"-- },-- "timestamp": "2026-09-22T12:00:00Z",-- "subject": {-- "type": "vod",-- "id": "1"-- },-- "payload": {}--}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "kind": "motion",++ "source_ref": "cam:front-door",++ "classes": [],++ "confidence": 2,++ "detected_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "kind": "object",++ "source_ref": "cam:front-door",++ "classes": [++ "package"++ ],++ "confidence": 0.88,++ "detected_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "answer": {++ "alt_text": "x",++ "editable": false,++ "receipt": {++ "engine": "routed",++ "model": null,++ "cost_usd": 0,++ "ms": 1,++ "confidence": null,++ "retention": "x"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "answer": {++ "alt_text": "A kitchen counter with a bowl and a spoon.",++ "editable": true,++ "receipt": {++ "engine": "routed",++ "model": "gpt-4o-mini",++ "cost_usd": 0.0002,++ "ms": 900,++ "confidence": null,++ "retention": "not stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "answer": {++ "id": "vis_short"++ }++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "answer": {++ "id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "task": "ask",++ "mode": "auto",++ "source": "upload",++ "image_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",++ "image_mime": "image/png",++ "image_bytes": 1024,++ "text": "A desk with a keyboard and a mug.",++ "answer": {},++ "cost_usd": 0,++ "latency_ms": 42,++ "created_at": "2026-10-29T10:00:00.000Z"++ }++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "id": "vis_short",++ "task": "ask",++ "mode": "auto",++ "source": "upload",++ "cost_usd": 0,++ "latency_ms": 0,++ "created_at": "2026-10-29T10:00:00.000Z",++ "answer": {}++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "task": "ask",++ "mode": "auto",++ "source": "upload",++ "image_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",++ "image_mime": "image/png",++ "image_bytes": 1024,++ "text": "A desk with a keyboard and a mug.",++ "answer": {++ "engine": "local",++ "route": "local.ocr"++ },++ "model": "tesseract",++ "provider": "local",++ "route": "local.ocr",++ "reason": "the local OCR reader answered the read task; no model was called",++ "cost_usd": 0,++ "latency_ms": 42,++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "image_url": "https://example.com/a.png",++ "task": "compare"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "question": "What is on the desk?",++ "task": "ask",++ "mode": "auto"++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "count": 1,++ "benchmarks": [++ {++ "task": "ask",++ "engine": "cloud",++ "provider": null,++ "model": null,++ "samples": 3,++ "avg_latency_ms": 900,++ "avg_cost_usd": 0.0002,++ "min_cost_usd": 0.0001,++ "max_latency_ms": 1200++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "count": 1,++ "benchmarks": [++ {++ "task": "ask",++ "engine": "ai",++ "provider": "openai",++ "model": "gpt-4o-mini",++ "samples": 3,++ "avg_latency_ms": 900,++ "avg_cost_usd": 0.0002,++ "min_cost_usd": 0.0001,++ "max_latency_ms": 1200++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "models": [++ "a",++ "b",++ "c",++ "d"++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "task": "describe",++ "models": [++ "openai:gpt-4o-mini",++ "anthropic:claude-sonnet-4"++ ]++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "signed_name": "",++ "text_version": "face-cast-2026-10-28"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "signed_name": "Ana Ruiz",++ "text_version": "face-cast-2026-10-28",++ "feature": "cast",++ "channel": "live:one",++ "adult": true++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "feature": "cast",++ "scope": "live:one",++ "revoked_at": "2026-10-29T11:00:00.000Z",++ "revoked_by": "admin",++ "erased": true++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "feature": "cast",++ "scope": "live:one",++ "revoked_at": "2026-10-29T11:00:00.000Z",++ "revoked_by": "subject",++ "erased": true++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "fco_1",++ "subject": "ana",++ "modality": "face",++ "feature": "cast",++ "scope": "live:one",++ "purpose": "x",++ "text_version": "v1",++ "granted_by": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "granted_at": "2026-10-29T10:00:00.000Z",++ "expires_at": null,++ "last_used_at": "2026-10-29T10:00:00.000Z",++ "revoked_at": null,++ "revoked_by": null,++ "purpose_ends_at": null,++ "retention_until": "2029-10-29T10:00:00.000Z",++ "status": "active",++ "erased": false++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "fco_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "subject": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "modality": "face",++ "feature": "cast",++ "scope": "live:one",++ "purpose": "tag on named streams",++ "text_version": "face-cast-2026-10-28",++ "granted_by": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "granted_at": "2026-10-29T10:00:00.000Z",++ "expires_at": "2027-10-29T10:00:00.000Z",++ "last_used_at": "2026-10-29T10:00:00.000Z",++ "revoked_at": null,++ "revoked_by": null,++ "purpose_ends_at": null,++ "retention_until": "2029-10-29T10:00:00.000Z",++ "status": "active",++ "erased": false++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "media_type": "image/jpeg",++ "face_count": 2++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "media_type": "image/png",++ "face_count": 2++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "channel": "live:one",++ "media_ref": "media:a"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "channel": "live:one",++ "media_ref": "media:a",++ "image": "data:image/png;base64,iVBORw0KGgo="++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "channel": "live:one",++ "matches": [++ {++ "subject": "ana",++ "score": 0.71++ }++ ],++ "liveness": "not_checked"++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "channel": "live:one",++ "matches": [++ {++ "subject": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "score": 0.71,++ "box": {++ "x": 1,++ "y": 2,++ "width": 30,++ "height": 40++ }++ }++ ],++ "liveness": "not_checked"++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "count": 1,++ "boxes": [++ {++ "x": 20,++ "y": 20,++ "width": 80,++ "height": 80,++ "score": 2++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "count": 1,++ "boxes": [++ {++ "x": 20,++ "y": 20,++ "width": 80,++ "height": 80,++ "score": 0.99++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "feature": "library",++ "image": "data:image/png;base64,iVBORw0KGgo="++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "feature": "cast",++ "channel": "live:one",++ "image": "data:image/png;base64,iVBORw0KGgo="++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "results": [++ {++ "private_link": "https://openvibe.vision/public/x",++ "score": 0.66++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "results": [++ {++ "private_link": "/api/v1/faces/media/fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "score": 0.66++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "id": "fcl_1",++ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "excluded": false,++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "id": "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "label": "Ana",++ "excluded": false,++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "action": "delete",++ "ids": [++ "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4"++ ]++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "action": "cluster",++ "region": "US-IL"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "action": "exclude",++ "updated": -1++}nnew file mode 100644@@@ -0,0 +1,10 @@++[++ {++ "id": "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "label": null,++ "excluded": false,++ "created_at": "2026-10-29T10:00:00.000Z"++ }++]nnew file mode 100644@@@ -0,0 +1,8 @@++[++ {++ "id": "ftg_1",++ "channel": "live:one",++ "media_ref": "media:a",++ "created_at": "2026-10-29T10:00:00.000Z"++ }++]nnew file mode 100644@@@ -0,0 +1,8 @@++[++ {++ "id": "ftg_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "channel": "live:one",++ "media_ref": "media:a",++ "created_at": "2026-10-29T10:00:00.000Z"++ }++]nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "id": "ftg_1",++ "channel": "live:one",++ "media_ref": "media:a",++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "id": "ftg_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "channel": "live:one",++ "media_ref": "media:a",++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "score": 1.2,++ "match": true,++ "threshold": 0.363,++ "liveness": "not_checked"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "score": 0.72,++ "match": true,++ "threshold": 0.363,++ "liveness": "not_checked"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "q": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "q": "red bike by the pier"++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "answer": {++ "results": [++ {++ "item_id": "x",++ "result_url": "http://insecure.example/a",++ "score": 0.5++ }++ ],++ "receipt": {++ "engine": "local",++ "model": null,++ "cost_usd": 0,++ "ms": 1,++ "confidence": null,++ "retention": "x"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "answer": {++ "results": [++ {++ "item_id": "media:42",++ "result_url": "https://openvibe.media/m/42",++ "score": 0.81++ }++ ],++ "receipt": {++ "engine": "local",++ "model": "CLIP ViT-B/32 q8",++ "cost_usd": 0,++ "ms": 12,++ "confidence": 0.81,++ "retention": "query not stored; indexed embeddings stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "image": "https://example.com/a.png"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo="++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "ai": true,++ "configured": "openai:gpt-4o-mini",++ "offered": [],++ "local": [],++ "note": "x"++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "ai": true,++ "configured": [++ "openai:gpt-4o-mini"++ ],++ "offered": [++ {++ "provider": "openai",++ "model": "gpt-4o-mini"++ }++ ],++ "local": [++ "tesseract",++ "histogram"++ ],++ "note": "provider:model hints"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "frames": [++ {++ "image": "data:image/png;base64,iVBORw0KGgo="++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "frames": [++ {++ "timestamp_ms": 0,++ "image": "data:image/png;base64,iVBORw0KGgo="++ },++ {++ "timestamp_ms": 5000,++ "image_url": "https://example.com/f.jpg"++ }++ ],++ "prompts": [++ "a goal"++ ]++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "answer": {++ "scene_changes": [++ {++ "timestamp_ms": 1,++ "kind": "cut"++ }++ ],++ "candidate_moments": [],++ "receipt": {++ "engine": "local",++ "model": null,++ "cost_usd": 0,++ "ms": 1,++ "confidence": null,++ "retention": "x"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "answer": {++ "scene_changes": [++ {++ "timestamp_ms": 5000,++ "kind": "scene_change"++ }++ ],++ "candidate_moments": [++ {++ "timestamp_ms": 5000,++ "prompt": "a goal",++ "score": 0.31++ }++ ],++ "receipt": {++ "engine": "local",++ "model": "CLIP ViT-B/32 q8 + histogram",++ "cost_usd": 0,++ "ms": 90,++ "confidence": 0.31,++ "retention": "not stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "lang": "eng"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "image_url": "https://example.com/sign.png",++ "lang": "eng",++ "fallback": true++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "image_url": "https://example.com/shoe.png",++ "allow_rerank": true++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "answer": {++ "description": "A red running shoe.",++ "embedding": [],++ "offers": [++ {++ "source": "deals",++ "title": "Red runner",++ "url": "http://insecure.example/x"++ }++ ],++ "warnings": [],++ "receipt": {++ "engine": "local",++ "model": null,++ "cost_usd": 0,++ "ms": 1,++ "confidence": null,++ "retention": "x"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "answer": {++ "description": "A red running shoe.",++ "embedding": [++ 0.1,++ 0.2++ ],++ "offers": [++ {++ "source": "deals",++ "title": "Red runner",++ "url": "https://openvibe.deals/x",++ "price": 79.99,++ "score": 0.9++ }++ ],++ "warnings": [],++ "receipt": {++ "engine": "local+routed description",++ "model": "gpt-4o-mini",++ "cost_usd": 0.0001,++ "ms": 800,++ "confidence": 0.9,++ "retention": "not stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "fallback": "yes"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "fallback": false++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "answer": {++ "store": "Corner Market",++ "items": [++ {++ "name": "Milk",++ "qty": "two",++ "price": 3.5++ }++ ],++ "total": 7,++ "receipt": {++ "engine": "local",++ "model": "Tesseract 5",++ "cost_usd": 0,++ "ms": 120,++ "confidence": 0.9,++ "retention": "not stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "answer": {++ "store": "Corner Market",++ "items": [++ {++ "name": "Milk",++ "qty": 2,++ "price": 3.5++ }++ ],++ "total": 7,++ "receipt": {++ "engine": "local",++ "model": "Tesseract 5",++ "cost_usd": 0,++ "ms": 120,++ "confidence": 0.9,++ "retention": "not stored"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "result_id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "task": "look",++ "source": "upload",++ "model": null,++ "provider": null,++ "route": null,++ "cost_usd": 0,++ "latency_ms": 0,++ "confidence": null,++ "retention": "x",++ "consent_id": null,++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "result_id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",++ "task": "ask",++ "source": "upload",++ "model": "gpt-4o-mini",++ "provider": "openai",++ "route": "ai.fast",++ "cost_usd": 0.0002,++ "latency_ms": 900,++ "confidence": null,++ "retention": "not stored",++ "consent_id": null,++ "created_at": "2026-10-29T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "classes": []++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "image": "data:image/png;base64,iVBORw0KGgo=",++ "classes": [++ "package",++ "scene change"++ ],++ "previous_image": "data:image/png;base64,iVBORw0KGgo="++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "answer": {++ "detections": [++ {++ "class": "package",++ "confidence": 1.5++ }++ ],++ "receipt": {++ "engine": "local",++ "model": null,++ "cost_usd": 0,++ "ms": 1,++ "confidence": null,++ "retention": "x"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "answer": {++ "detections": [++ {++ "class": "package",++ "confidence": 0.88,++ "box": {++ "x": 1,++ "y": 2,++ "width": 30,++ "height": 40++ }++ },++ {++ "class": "scene change",++ "confidence": 0.4,++ "box": null++ }++ ],++ "receipt": {++ "engine": "local",++ "model": "DETR ResNet-50 q8 + histogram",++ "cost_usd": 0,++ "ms": 80,++ "confidence": 0.88,++ "retention": "not stored"++ }++ }++}@@@ -1772,7 +1772,7 @@},"event_type": {"type": "string",-- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$"},"version": {"type": "integer",@@@ -87007,6 +87007,2490 @@}}},++ "vision.image-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/image-request.v1.json",++ "title": "VisionImageRequest",++ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.ask-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/ask-request.v1.json",++ "title": "VisionAskRequest",++ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.ocr-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/ocr-request.v1.json",++ "title": "VisionOcrRequest",++ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "fallback": {++ "type": "boolean"++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.compare-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/compare-request.v1.json",++ "title": "VisionCompareRequest",++ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "models": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 3++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.find-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/find-request.v1.json",++ "title": "VisionFindRequest",++ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "q": {++ "type": "string",++ "minLength": 1,++ "maxLength": 500++ },++ "item_id": {++ "type": "string",++ "pattern": "^[\\w:.-]{1,200}$"++ },++ "result_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ }++ },++ "anyOf": [++ {++ "required": [++ "q"++ ]++ },++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ },++ {++ "required": [++ "item_id",++ "image"++ ]++ },++ {++ "required": [++ "item_id",++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.receipt-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/receipt-request.v1.json",++ "title": "VisionReceiptRequest",++ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "fallback": {++ "type": "boolean"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.product-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/product-request.v1.json",++ "title": "VisionProductRequest",++ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "allow_rerank": {++ "type": "boolean"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.watch-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/watch-request.v1.json",++ "title": "VisionWatchRequest",++ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "classes"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "classes": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 60++ },++ "minItems": 1,++ "maxItems": 20++ },++ "previous_image": {++ "type": "string",++ "pattern": "^data:image/"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.moments-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/moments-request.v1.json",++ "title": "VisionMomentsRequest",++ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "frames"++ ],++ "properties": {++ "frames": {++ "type": "array",++ "minItems": 1,++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "image": {++ "type": "string",++ "pattern": "^data:image/"++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "prompts": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 10++ }++ }++ }++ },++ "vision.answer": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/answer.v1.json",++ "title": "VisionAnswer",++ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "task",++ "mode",++ "source",++ "image_sha256",++ "image_mime",++ "image_bytes",++ "cost_usd",++ "latency_ms",++ "created_at",++ "answer"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr",++ "compare"++ ]++ },++ "question": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 2000++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "source": {++ "enum": [++ "upload",++ "url",++ "data"++ ]++ },++ "source_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri"++ },++ "image_sha256": {++ "type": "string",++ "pattern": "^[0-9a-f]{64}$"++ },++ "image_mime": {++ "type": "string",++ "pattern": "^image/[a-z0-9.+-]+$"++ },++ "image_bytes": {++ "type": "integer",++ "minimum": 1,++ "maximum": 20971520++ },++ "text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 20000++ },++ "answer": {++ "type": "object",++ "description": "Engine details, OCR words, provenance and any compare rows; never an image."++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "engine": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0,++ "maximum": 1++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "vision.answer-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/answer-result.v1.json",++ "title": "VisionAnswerResult",++ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "$ref": "./answer.v1.json"++ },++ "compare": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "ok",++ "cost_usd",++ "latency_ms"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "requested_model": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "ok": {++ "type": "boolean"++ },++ "text": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ },++ "local": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ok",++ "model",++ "confidence",++ "error_code"++ ],++ "properties": {++ "ok": {++ "type": "boolean"++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ }++ }++ },++ "vision.alt-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/alt-result.v1.json",++ "title": "VisionAltResult",++ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "alt_text",++ "editable",++ "receipt"++ ],++ "properties": {++ "alt_text": {++ "type": "string",++ "maxLength": 240++ },++ "editable": {++ "const": true++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.find-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/find-result.v1.json",++ "title": "VisionFindResult",++ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results",++ "receipt"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 5000,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "item_id",++ "score"++ ],++ "properties": {++ "item_id": {++ "type": "string"++ },++ "result_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri",++ "pattern": "^https://"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.receipt-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/receipt-result.v1.json",++ "title": "VisionReceiptResult",++ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "store",++ "items",++ "total",++ "receipt"++ ],++ "properties": {++ "store": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 150++ },++ "items": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "qty",++ "price"++ ],++ "properties": {++ "name": {++ "type": "string",++ "maxLength": 200++ },++ "qty": {++ "type": "number",++ "minimum": 0++ },++ "price": {++ "type": "number",++ "minimum": 0++ }++ }++ }++ },++ "total": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0++ },++ "raw_text": {++ "type": "string",++ "maxLength": 20000++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.product-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/product-result.v1.json",++ "title": "VisionProductResult",++ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "description",++ "embedding",++ "offers",++ "warnings",++ "receipt"++ ],++ "properties": {++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "embedding": {++ "type": "array",++ "items": {++ "type": "number"++ }++ },++ "offers": {++ "type": "array",++ "maxItems": 10,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "source",++ "title",++ "url"++ ],++ "properties": {++ "source": {++ "type": "string"++ },++ "title": {++ "type": "string",++ "maxLength": 200++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "price": {++ "type": [++ "number",++ "null"++ ]++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "warnings": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.watch-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/watch-result.v1.json",++ "title": "VisionWatchResult",++ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "detections",++ "receipt"++ ],++ "properties": {++ "detections": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "class",++ "confidence"++ ],++ "properties": {++ "class": {++ "type": "string"++ },++ "confidence": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": [++ "object",++ "null"++ ],++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.moments-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/moments-result.v1.json",++ "title": "VisionMomentsResult",++ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "scene_changes",++ "candidate_moments",++ "receipt"++ ],++ "properties": {++ "scene_changes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "kind"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "kind": {++ "const": "scene_change"++ }++ }++ }++ },++ "candidate_moments": {++ "type": "array",++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "prompt",++ "score"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "prompt": {++ "type": "string"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ }++ },++ "vision.face-detection": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-detection.v1.json",++ "title": "VisionFaceDetection",++ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "boxes"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "boxes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height",++ "score"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++ }++ },++ "vision.face-blur": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-blur.v1.json",++ "title": "VisionFaceBlur",++ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "media_type",++ "face_count"++ ],++ "properties": {++ "media_type": {++ "const": "image/png"++ },++ "face_count": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ },++ "vision.face-enrol-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-enrol-request.v1.json",++ "title": "VisionFaceEnrolRequest",++ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "feature"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast"++ ]++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.face-verify": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-verify.v1.json",++ "title": "VisionFaceVerify",++ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "score",++ "match",++ "threshold",++ "liveness"++ ],++ "properties": {++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "match": {++ "type": "boolean"++ },++ "threshold": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++ }++ },++ "vision.face-find-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-find-result.v1.json",++ "title": "VisionFaceFindResult",++ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 500,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "private_link",++ "score"++ ],++ "properties": {++ "private_link": {++ "type": "string",++ "pattern": "^/api/v1/faces/media/"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++ }++ },++ "vision.face-cast-match-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-cast-match-request.v1.json",++ "title": "VisionFaceCastMatchRequest",++ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "media_ref"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "media_ref": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "vision.face-cast-match": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-cast-match.v1.json",++ "title": "VisionFaceCastMatch",++ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "matches",++ "liveness"++ ],++ "properties": {++ "channel": {++ "type": "string"++ },++ "matches": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "score"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++ }++ },++ "vision.face-tag": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-tag.v1.json",++ "title": "VisionFaceTag",++ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "channel",++ "media_ref",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "channel": {++ "type": "string"++ },++ "media_ref": {++ "type": "string"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "vision.face-tag-list": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-tag-list.v1.json",++ "title": "VisionFaceTagList",++ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",++ "type": "array",++ "items": {++ "$ref": "./face-tag.v1.json"++ }++ }++ },++ "vision.face-library-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-request.v1.json",++ "title": "VisionFaceLibraryRequest",++ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action"++ ],++ "properties": {++ "action": {++ "enum": [++ "cluster",++ "exclude",++ "merge",++ "split"++ ]++ },++ "region": {++ "type": "string",++ "maxLength": 10++ },++ "ids": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 40++ },++ "minItems": 1,++ "maxItems": 100++ }++ }++ }++ },++ "vision.face-library-result": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-result.v1.json",++ "title": "VisionFaceLibraryResult",++ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",++ "oneOf": [++ {++ "type": "array",++ "items": {++ "$ref": "./face-library-group.v1.json"++ }++ },++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "updated"++ ],++ "properties": {++ "action": {++ "enum": [++ "exclude",++ "merge",++ "split"++ ]++ },++ "updated": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++ }++ },++ "vision.face-library-group": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/face-library-group.v1.json",++ "title": "VisionFaceLibraryGroup",++ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "media_id",++ "group_id",++ "excluded",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "media_id": {++ "type": "string",++ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "group_id": {++ "type": "string",++ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "label": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 120++ },++ "excluded": {++ "type": "boolean"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "vision.consent": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent.v1.json",++ "title": "VisionConsent",++ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "subject",++ "modality",++ "feature",++ "scope",++ "purpose",++ "text_version",++ "granted_by",++ "granted_at",++ "expires_at",++ "last_used_at",++ "revoked_at",++ "revoked_by",++ "purpose_ends_at",++ "retention_until",++ "status",++ "erased"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose face or voice this consent covers."++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ]++ },++ "scope": {++ "type": "string",++ "maxLength": 160,++ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."++ },++ "purpose": {++ "type": "string",++ "maxLength": 200++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "granted_by": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person who signed; the subject themself, or a guardian where the law allows."++ },++ "granted_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the consent stops applying on its own; null when it lasts until revoked."++ },++ "last_used_at": {++ "type": "string",++ "format": "date-time"++ },++ "revoked_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "revoked_by": {++ "type": [++ "string",++ "null"++ ],++ "enum": [++ "subject",++ "retention",++ "account_deletion",++ null++ ],++ "description": "How it was revoked: by the person, by the retention job, or with the account."++ },++ "purpose_ends_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "retention_until": {++ "type": "string",++ "format": "date-time",++ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."++ },++ "status": {++ "enum": [++ "active",++ "revoked",++ "expired"++ ]++ },++ "erased": {++ "type": "boolean",++ "description": "True once the template and derived results this consent covered are gone."++ }++ }++ }++ },++ "vision.consent-request": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent-request.v1.json",++ "title": "VisionConsentRequest",++ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "signed_name",++ "text_version"++ ],++ "properties": {++ "signed_name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ],++ "description": "Echo of the :feature path segment."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "adult": {++ "type": "boolean"++ },++ "region": {++ "type": "string",++ "maxLength": 10++ }++ }++ }++ },++ "vision.consent-revocation": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/consent-revocation.v1.json",++ "title": "VisionConsentRevocation",++ "description": "vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "feature",++ "scope",++ "revoked_at",++ "revoked_by",++ "erased"++ ],++ "properties": {++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ]++ },++ "scope": {++ "type": "string",++ "maxLength": 160++ },++ "revoked_at": {++ "type": "string",++ "format": "date-time"++ },++ "revoked_by": {++ "enum": [++ "subject"++ ]++ },++ "erased": {++ "const": true++ }++ }++ }++ },++ "vision.models": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/models.v1.json",++ "title": "VisionModels",++ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ai",++ "configured",++ "offered",++ "local",++ "note"++ ],++ "properties": {++ "ai": {++ "type": "boolean"++ },++ "configured": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "offered": {++ "type": "array",++ "items": {++ "type": "object"++ }++ },++ "local": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "note": {++ "type": "string"++ }++ }++ }++ },++ "vision.benchmarks": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/vision/benchmarks.v1.json",++ "title": "VisionBenchmarks",++ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "benchmarks"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "benchmarks": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "task",++ "engine",++ "provider",++ "model",++ "samples",++ "avg_latency_ms",++ "avg_cost_usd",++ "min_cost_usd",++ "max_latency_ms"++ ],++ "properties": {++ "task": {++ "type": "string"++ },++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "samples": {++ "type": "integer",++ "minimum": 0++ },++ "avg_latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "avg_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "min_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "max_latency_ms": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ }++ },++ "vision.result.ready": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vision.result.ready.v1.json",++ "title": "VisionResultReadyPayload",++ "description": "vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "result_id",++ "task",++ "source",++ "model",++ "provider",++ "route",++ "cost_usd",++ "latency_ms",++ "confidence",++ "retention",++ "consent_id",++ "created_at"++ ],++ "properties": {++ "result_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^(vis|vuc|fme|ftg|fco)_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr",++ "compare",++ "alt",++ "find",++ "receipt",++ "product",++ "watch",++ "moments",++ "ui_check",++ "face_detect",++ "face_blur"++ ]++ },++ "source": {++ "enum": [++ "upload",++ "url",++ "data",++ "html",++ "screenshot",++ "none"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string",++ "maxLength": 200++ },++ "consent_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "vision.alert": {++ "version": "1.0.0",++ "owner": "vision",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vision.alert.v1.json",++ "title": "VisionAlertPayload",++ "description": "vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "source_ref",++ "classes",++ "confidence",++ "detected_at"++ ],++ "properties": {++ "kind": {++ "enum": [++ "object",++ "scene_change",++ "face"++ ]++ },++ "source_ref": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ },++ "classes": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 60++ },++ "minItems": 1,++ "maxItems": 20++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0,++ "maximum": 1++ },++ "previous_ref": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 160++ },++ "detected_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },"bench.kind": {"version": "1.0.0","owner": "bench",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bench API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Domains API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for 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"@@@ -1503,7 +1503,7 @@},"event_type": {"type": "string",-- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$"},"version": {"type": "integer",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Food API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for 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"@@@ -250,8 +250,8 @@"service": "vision","name": "OpenVibe.Vision","origin": "https://openvibe.vision",-- "operations": 2,-- "capabilities": 1++ "operations": 32,++ "capabilities": 20},{"service": "watch",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.MediaHub API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRestream API",-- "version": "0.239.0",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Play API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Quest API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Run API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Services API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Vision API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -16,25 +16,25 @@}],"paths": {-- "/api/v1/ui-check": {++ "/api/v1/alt": {"post": {-- "operationId": "post_api_v1_ui_check",-- "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",-- "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",++ "operationId": "post_api_v1_alt",++ "summary": "Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person.",++ "description": "**vision.alt.write** (first-party): Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person. The answer has passed noLooks; the receipt names the engine, cost and latency. Rooms use it for Pics, Media and Chat uploads.","tags": [-- "vision.ui"++ "vision.alt"],"x-openvibe-capabilities": [-- "vision.ui.check"++ "vision.alt.write"],"x-openvibe-visibility": ["first-party"],-- "x-openvibe-quota-class": "vision-ui-check",++ "x-openvibe-quota-class": "vision-alt","security": [{"openvibeToken": [-- "vision.ui.check"++ "vision.alt.write"]}],@@@ -43,7 +43,7 @@"content": {"application/json": {"schema": {-- "$ref": "#/components/schemas/vision.ui-check-request.v1"++ "$ref": "#/components/schemas/vision.image-request.v1"}}}@@@ -54,7 +54,277 @@"content": {"application/json": {"schema": {-- "$ref": "#/components/schemas/vision.ui-check-result.v1"++ "$ref": "#/components/schemas/vision.alt-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/ask": {++ "post": {++ "operationId": "post_api_v1_ask",++ "summary": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr",++ "description": "**vision.image.ask** (first-party): Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",++ "tags": [++ "vision.image"++ ],++ "x-openvibe-capabilities": [++ "vision.image.ask"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-ask",++ "security": [++ {++ "openvibeToken": [++ "vision.image.ask"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ask-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.answer-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/benchmarks": {++ "get": {++ "operationId": "get_api_v1_benchmarks",++ "summary": "Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, ",++ "description": "**vision.benchmarks.read** (first-party): Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, image or answer text. Public.",++ "tags": [++ "vision.benchmarks"++ ],++ "x-openvibe-capabilities": [++ "vision.benchmarks.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-read",++ "security": [++ {++ "openvibeToken": [++ "vision.benchmarks.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.benchmarks.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/compare": {++ "post": {++ "operationId": "post_api_v1_compare",++ "summary": "Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency.",++ "description": "**vision.model.compare** (first-party): Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency. The requested model is a hint; OpenVibe.AI's own provenance is what each row reports.",++ "tags": [++ "vision.model"++ ],++ "x-openvibe-capabilities": [++ "vision.model.compare"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-compare",++ "security": [++ {++ "openvibeToken": [++ "vision.model.compare"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.compare-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.answer-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/describe": {++ "post": {++ "operationId": "post_api_v1_describe",++ "summary": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr",++ "description": "**vision.image.ask** (first-party): Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",++ "tags": [++ "vision.image"++ ],++ "x-openvibe-capabilities": [++ "vision.image.ask"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-ask",++ "security": [++ {++ "openvibeToken": [++ "vision.image.ask"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ask-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.answer-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/blur": {++ "post": {++ "operationId": "post_api_v1_faces_blur",++ "summary": "Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur.",++ "description": "**vision.face.blur** (first-party): Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur. No template is stored and the original is discarded; anyone may use it.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.blur"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.blur"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.image-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-blur.v1"}}}@@@ -70,133 +340,3619 @@}}}-- }-- },-- "/api/v1/ui-checks/{id}": {-- "get": {-- "operationId": "get_api_v1_ui_checks_id",-- "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",-- "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",-- "tags": [-- "vision.ui"-- ],-- "x-openvibe-capabilities": [-- "vision.ui.check"-- ],-- "x-openvibe-visibility": [-- "first-party"-- ],-- "x-openvibe-quota-class": "vision-ui-check",-- "security": [-- {-- "openvibeToken": [-- "vision.ui.check"-- ]++ }++ },++ "/api/v1/faces/cast/match": {++ "post": {++ "operationId": "post_api_v1_faces_cast_match",++ "summary": "Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match.",++ "description": "**vision.face.cast.match** (first-party): Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match. The caller is the Live service token scoped to that channel (its channel_id claim must equal the body's channel) and must hold this capability. A non-consented person is never matched and the answer does not say whether they exist.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.cast.match"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.cast.match"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-cast-match-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-cast-match.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/cast/tags": {++ "get": {++ "operationId": "get_api_v1_faces_cast_tags",++ "summary": "Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced.",++ "description": "**vision.face.cast.read** (first-party): Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced. Revoking the consent removes them.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.cast.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.cast.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-tag-list.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/consents": {++ "get": {++ "operationId": "get_api_v1_faces_consents",++ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",++ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.consent.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.consent.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.consent-request.v1"++ },++ "parameters": [++ {++ "name": "signed_name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ }++ },++ {++ "name": "text_version",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "maxLength": 80++ }++ },++ {++ "name": "modality",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "face",++ "voice"++ ]++ }++ },++ {++ "name": "feature",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ],++ "description": "Echo of the :feature path segment."++ },++ "description": "Echo of the :feature path segment."++ },++ {++ "name": "channel",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ {++ "name": "adult",++ "in": "query",++ "required": false,++ "schema": {++ "type": "boolean"++ }++ },++ {++ "name": "region",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "maxLength": 10++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.consent.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/consents/{feature}": {++ "delete": {++ "operationId": "delete_api_v1_faces_consents_feature",++ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",++ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.consent.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.consent.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.consent-request.v1"++ },++ "parameters": [++ {++ "name": "feature",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "signed_name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ }++ },++ {++ "name": "text_version",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "maxLength": 80++ }++ },++ {++ "name": "modality",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "face",++ "voice"++ ]++ }++ },++ {++ "name": "channel",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ {++ "name": "adult",++ "in": "query",++ "required": false,++ "schema": {++ "type": "boolean"++ }++ },++ {++ "name": "region",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "maxLength": 10++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.consent.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "post": {++ "operationId": "post_api_v1_faces_consents_feature",++ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",++ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.consent.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.consent.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.consent-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "feature",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.consent.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/consents/text": {++ "get": {++ "operationId": "get_api_v1_faces_consents_text",++ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",++ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.consent.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.consent.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.consent-request.v1"++ },++ "parameters": [++ {++ "name": "signed_name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ }++ },++ {++ "name": "text_version",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "maxLength": 80++ }++ },++ {++ "name": "modality",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "face",++ "voice"++ ]++ }++ },++ {++ "name": "feature",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ],++ "description": "Echo of the :feature path segment."++ },++ "description": "Echo of the :feature path segment."++ },++ {++ "name": "channel",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ {++ "name": "adult",++ "in": "query",++ "required": false,++ "schema": {++ "type": "boolean"++ }++ },++ {++ "name": "region",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "maxLength": 10++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.consent.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/detect": {++ "post": {++ "operationId": "post_api_v1_faces_detect",++ "summary": "Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet.",++ "description": "**vision.face.detect** (first-party): Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet. It stores no template, names no one and needs no consent; anyone may use it.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.detect"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.detect"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.image-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-detection.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/enrol/{feature}": {++ "post": {++ "operationId": "post_api_v1_faces_enrol_feature",++ "summary": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify.",++ "description": "**vision.face.verify.self** (first-party): Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.verify.self"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.verify.self"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-enrol-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "feature",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-verify.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/find": {++ "post": {++ "operationId": "post_api_v1_faces_find",++ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",++ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.find.self"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.find.self"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/library/cluster": {++ "post": {++ "operationId": "post_api_v1_faces_library_cluster",++ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",++ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.library"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.library"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-library-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-library-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/library/correct": {++ "post": {++ "operationId": "post_api_v1_faces_library_correct",++ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",++ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.library"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.library"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-library-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-library-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/library/groups": {++ "get": {++ "operationId": "get_api_v1_faces_library_groups",++ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",++ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.library"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.library"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.face-library-request.v1"++ },++ "parameters": [++ {++ "name": "action",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "cluster",++ "exclude",++ "merge",++ "split"++ ]++ }++ },++ {++ "name": "region",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "maxLength": 10++ }++ },++ {++ "name": "ids",++ "in": "query",++ "required": false,++ "schema": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 40++ },++ "minItems": 1,++ "maxItems": 100++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-library-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/media": {++ "post": {++ "operationId": "post_api_v1_faces_media",++ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",++ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.find.self"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.find.self"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/media/{id}": {++ "get": {++ "operationId": "get_api_v1_faces_media_id",++ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",++ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.find.self"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.find.self"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/faces/verify": {++ "post": {++ "operationId": "post_api_v1_faces_verify",++ "summary": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify.",++ "description": "**vision.face.verify.self** (first-party): Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",++ "tags": [++ "vision.face"++ ],++ "x-openvibe-capabilities": [++ "vision.face.verify.self"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-face",++ "security": [++ {++ "openvibeToken": [++ "vision.face.verify.self"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-enrol-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.face-verify.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/find": {++ "get": {++ "operationId": "get_api_v1_find",++ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",++ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",++ "tags": [++ "vision.media"++ ],++ "x-openvibe-capabilities": [++ "vision.media.find"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-find",++ "security": [++ {++ "openvibeToken": [++ "vision.media.find"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "post": {++ "operationId": "post_api_v1_find",++ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",++ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",++ "tags": [++ "vision.media"++ ],++ "x-openvibe-capabilities": [++ "vision.media.find"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-find",++ "security": [++ {++ "openvibeToken": [++ "vision.media.find"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/index": {++ "post": {++ "operationId": "post_api_v1_index",++ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",++ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",++ "tags": [++ "vision.media"++ ],++ "x-openvibe-capabilities": [++ "vision.media.find"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-find",++ "security": [++ {++ "openvibeToken": [++ "vision.media.find"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.find-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/models": {++ "get": {++ "operationId": "get_api_v1_models",++ "summary": "Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data.",++ "description": "**vision.models.read** (first-party): Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data. Public; OpenVibe.AI's router is still the authority.",++ "tags": [++ "vision.models"++ ],++ "x-openvibe-capabilities": [++ "vision.models.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-read",++ "security": [++ {++ "openvibeToken": [++ "vision.models.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.models.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/moments": {++ "post": {++ "operationId": "post_api_v1_moments",++ "summary": "Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and",++ "description": "**vision.video.moments** (first-party): Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and candidate moments. A candidate is a suggestion for Live or Media to accept.",++ "tags": [++ "vision.video"++ ],++ "x-openvibe-capabilities": [++ "vision.video.moments"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-moments",++ "security": [++ {++ "openvibeToken": [++ "vision.video.moments"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.moments-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.moments-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/ocr": {++ "post": {++ "operationId": "post_api_v1_ocr",++ "summary": "Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously.",++ "description": "**vision.text.read** (first-party): Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously. `fallback` asks OpenVibe.AI only when the local pass found no usable text, and that fallback needs a signed-in person or this capability.",++ "tags": [++ "vision.text"++ ],++ "x-openvibe-capabilities": [++ "vision.text.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-ocr",++ "security": [++ {++ "openvibeToken": [++ "vision.text.read"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ocr-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.answer-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/product": {++ "post": {++ "operationId": "post_api_v1_product",++ "summary": "Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoe",++ "description": "**vision.product.match** (first-party): Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoes with their score and URL. A candidate is not a verified price.",++ "tags": [++ "vision.product"++ ],++ "x-openvibe-capabilities": [++ "vision.product.match"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-product",++ "security": [++ {++ "openvibeToken": [++ "vision.product.match"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.product-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.product-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/receipt": {++ "post": {++ "operationId": "post_api_v1_receipt",++ "summary": "Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce b",++ "description": "**vision.receipt.read** (first-party): Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce both. OpenVibe.Food decides what enters a pantry.",++ "tags": [++ "vision.receipt"++ ],++ "x-openvibe-capabilities": [++ "vision.receipt.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-receipt",++ "security": [++ {++ "openvibeToken": [++ "vision.receipt.read"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.receipt-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.receipt-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/ui-check": {++ "post": {++ "operationId": "post_api_v1_ui_check",++ "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",++ "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",++ "tags": [++ "vision.ui"++ ],++ "x-openvibe-capabilities": [++ "vision.ui.check"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-ui-check",++ "security": [++ {++ "openvibeToken": [++ "vision.ui.check"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ui-check-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ui-check-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/ui-checks/{id}": {++ "get": {++ "operationId": "get_api_v1_ui_checks_id",++ "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",++ "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",++ "tags": [++ "vision.ui"++ ],++ "x-openvibe-capabilities": [++ "vision.ui.check"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-ui-check",++ "security": [++ {++ "openvibeToken": [++ "vision.ui.check"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/vision.ui-check-request.v1"++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.ui-check-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/watch": {++ "post": {++ "operationId": "post_api_v1_watch",++ "summary": "Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and retu",++ "description": "**vision.camera.watch** (first-party): Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and returns detections with confidence and boxes. Owner-granted camera alerting only; a watched class can raise vision.alert.",++ "tags": [++ "vision.camera"++ ],++ "x-openvibe-capabilities": [++ "vision.camera.watch"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "vision-watch",++ "security": [++ {++ "openvibeToken": [++ "vision.camera.watch"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.watch-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/vision.watch-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}."++ }++ }++ },++ "vision.alt-result.v1": {++ "title": "VisionAltResult",++ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "alt_text",++ "editable",++ "receipt"++ ],++ "properties": {++ "alt_text": {++ "type": "string",++ "maxLength": 240++ },++ "editable": {++ "const": true++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ },++ "vision.answer-result.v1": {++ "title": "VisionAnswerResult",++ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "$ref": "#/components/schemas/vision.answer.v1"++ },++ "compare": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "ok",++ "cost_usd",++ "latency_ms"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "requested_model": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "ok": {++ "type": "boolean"++ },++ "text": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ },++ "local": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ok",++ "model",++ "confidence",++ "error_code"++ ],++ "properties": {++ "ok": {++ "type": "boolean"++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "error_code": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ }++ },++ "vision.answer.v1": {++ "title": "VisionAnswer",++ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "task",++ "mode",++ "source",++ "image_sha256",++ "image_mime",++ "image_bytes",++ "cost_usd",++ "latency_ms",++ "created_at",++ "answer"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr",++ "compare"++ ]++ },++ "question": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 2000++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "source": {++ "enum": [++ "upload",++ "url",++ "data"++ ]++ },++ "source_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri"++ },++ "image_sha256": {++ "type": "string",++ "pattern": "^[0-9a-f]{64}$"++ },++ "image_mime": {++ "type": "string",++ "pattern": "^image/[a-z0-9.+-]+$"++ },++ "image_bytes": {++ "type": "integer",++ "minimum": 1,++ "maximum": 20971520++ },++ "text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 20000++ },++ "answer": {++ "type": "object",++ "description": "Engine details, OCR words, provenance and any compare rows; never an image."++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "engine": {++ "type": [++ "string",++ "null"++ ]++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0,++ "maximum": 1++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "vision.ask-request.v1": {++ "title": "VisionAskRequest",++ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "mode": {++ "enum": [++ "auto",++ "local",++ "ai"++ ]++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.benchmarks.v1": {++ "title": "VisionBenchmarks",++ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "benchmarks"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "benchmarks": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "task",++ "engine",++ "provider",++ "model",++ "samples",++ "avg_latency_ms",++ "avg_cost_usd",++ "min_cost_usd",++ "max_latency_ms"++ ],++ "properties": {++ "task": {++ "type": "string"++ },++ "engine": {++ "enum": [++ "local",++ "ai"++ ]++ },++ "provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "samples": {++ "type": "integer",++ "minimum": 0++ },++ "avg_latency_ms": {++ "type": "integer",++ "minimum": 0++ },++ "avg_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "min_cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "max_latency_ms": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },++ "vision.compare-request.v1": {++ "title": "VisionCompareRequest",++ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "task": {++ "enum": [++ "ask",++ "describe",++ "identify",++ "count",++ "translate",++ "ocr"++ ]++ },++ "question": {++ "type": "string",++ "maxLength": 2000++ },++ "models": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 3++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.consent-request.v1": {++ "title": "VisionConsentRequest",++ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "signed_name",++ "text_version"++ ],++ "properties": {++ "signed_name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 160++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ],++ "description": "Echo of the :feature path segment."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "adult": {++ "type": "boolean"++ },++ "region": {++ "type": "string",++ "maxLength": 10++ }++ }++ },++ "vision.consent.v1": {++ "title": "VisionConsent",++ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "subject",++ "modality",++ "feature",++ "scope",++ "purpose",++ "text_version",++ "granted_by",++ "granted_at",++ "expires_at",++ "last_used_at",++ "revoked_at",++ "revoked_by",++ "purpose_ends_at",++ "retention_until",++ "status",++ "erased"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose face or voice this consent covers."++ },++ "modality": {++ "enum": [++ "face",++ "voice"++ ]++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast",++ "library",++ "voice"++ ]++ },++ "scope": {++ "type": "string",++ "maxLength": 160,++ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."++ },++ "purpose": {++ "type": "string",++ "maxLength": 200++ },++ "text_version": {++ "type": "string",++ "maxLength": 80++ },++ "granted_by": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person who signed; the subject themself, or a guardian where the law allows."++ },++ "granted_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the consent stops applying on its own; null when it lasts until revoked."++ },++ "last_used_at": {++ "type": "string",++ "format": "date-time"++ },++ "revoked_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "revoked_by": {++ "type": [++ "string",++ "null"++ ],++ "enum": [++ "subject",++ "retention",++ "account_deletion",++ null++ ],++ "description": "How it was revoked: by the person, by the retention job, or with the account."++ },++ "purpose_ends_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "retention_until": {++ "type": "string",++ "format": "date-time",++ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."++ },++ "status": {++ "enum": [++ "active",++ "revoked",++ "expired"++ ]++ },++ "erased": {++ "type": "boolean",++ "description": "True once the template and derived results this consent covered are gone."++ }++ }++ },++ "vision.face-blur.v1": {++ "title": "VisionFaceBlur",++ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "media_type",++ "face_count"++ ],++ "properties": {++ "media_type": {++ "const": "image/png"++ },++ "face_count": {++ "type": "integer",++ "minimum": 0++ }++ }++ },++ "vision.face-cast-match-request.v1": {++ "title": "VisionFaceCastMatchRequest",++ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "media_ref"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ },++ "media_ref": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.face-cast-match.v1": {++ "title": "VisionFaceCastMatch",++ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "channel",++ "matches",++ "liveness"++ ],++ "properties": {++ "channel": {++ "type": "string"++ },++ "matches": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "score"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++ },++ "vision.face-detection.v1": {++ "title": "VisionFaceDetection",++ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "count",++ "boxes"++ ],++ "properties": {++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "boxes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height",++ "score"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++ },++ "vision.face-enrol-request.v1": {++ "title": "VisionFaceEnrolRequest",++ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "feature"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "feature": {++ "enum": [++ "verify",++ "find",++ "cast"++ ]++ },++ "channel": {++ "type": "string",++ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.face-find-result.v1": {++ "title": "VisionFaceFindResult",++ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 500,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "private_link",++ "score"++ ],++ "properties": {++ "private_link": {++ "type": "string",++ "pattern": "^/api/v1/faces/media/"++ },++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ }++ }++ }++ }++ }++ },++ "vision.face-library-group.v1": {++ "title": "VisionFaceLibraryGroup",++ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "media_id",++ "group_id",++ "excluded",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "media_id": {++ "type": "string",++ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "group_id": {++ "type": "string",++ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "label": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 120++ },++ "excluded": {++ "type": "boolean"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "vision.face-library-request.v1": {++ "title": "VisionFaceLibraryRequest",++ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action"++ ],++ "properties": {++ "action": {++ "enum": [++ "cluster",++ "exclude",++ "merge",++ "split"++ ]++ },++ "region": {++ "type": "string",++ "maxLength": 10++ },++ "ids": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 40++ },++ "minItems": 1,++ "maxItems": 100++ }++ }++ },++ "vision.face-library-result.v1": {++ "title": "VisionFaceLibraryResult",++ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",++ "oneOf": [++ {++ "type": "array",++ "items": {++ "$ref": "#/components/schemas/vision.face-library-group.v1"++ }++ },++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "updated"++ ],++ "properties": {++ "action": {++ "enum": [++ "exclude",++ "merge",++ "split"++ ]++ },++ "updated": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ ]++ },++ "vision.face-tag-list.v1": {++ "title": "VisionFaceTagList",++ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",++ "type": "array",++ "items": {++ "$ref": "#/components/schemas/vision.face-tag.v1"++ }++ },++ "vision.face-tag.v1": {++ "title": "VisionFaceTag",++ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "channel",++ "media_ref",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "channel": {++ "type": "string"++ },++ "media_ref": {++ "type": "string"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "vision.face-verify.v1": {++ "title": "VisionFaceVerify",++ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "score",++ "match",++ "threshold",++ "liveness"++ ],++ "properties": {++ "score": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "match": {++ "type": "boolean"++ },++ "threshold": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "liveness": {++ "enum": [++ "not_checked",++ "checked"++ ]++ }++ }++ },++ "vision.find-request.v1": {++ "title": "VisionFindRequest",++ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "q": {++ "type": "string",++ "minLength": 1,++ "maxLength": 500++ },++ "item_id": {++ "type": "string",++ "pattern": "^[\\w:.-]{1,200}$"++ },++ "result_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ }++ },++ "anyOf": [++ {++ "required": [++ "q"++ ]++ },++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ },++ {++ "required": [++ "item_id",++ "image"++ ]++ },++ {++ "required": [++ "item_id",++ "image_url"++ ]++ }++ ]++ },++ "vision.find-result.v1": {++ "title": "VisionFindResult",++ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "results",++ "receipt"++ ],++ "properties": {++ "results": {++ "type": "array",++ "maxItems": 5000,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "item_id",++ "score"++ ],++ "properties": {++ "item_id": {++ "type": "string"++ },++ "result_url": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri",++ "pattern": "^https://"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ },++ "vision.image-request.v1": {++ "title": "VisionImageRequest",++ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.models.v1": {++ "title": "VisionModels",++ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ai",++ "configured",++ "offered",++ "local",++ "note"++ ],++ "properties": {++ "ai": {++ "type": "boolean"++ },++ "configured": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "offered": {++ "type": "array",++ "items": {++ "type": "object"++ }++ },++ "local": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "note": {++ "type": "string"++ }++ }++ },++ "vision.moments-request.v1": {++ "title": "VisionMomentsRequest",++ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "frames"++ ],++ "properties": {++ "frames": {++ "type": "array",++ "minItems": 1,++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "image": {++ "type": "string",++ "pattern": "^data:image/"++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ }++ },++ "prompts": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 120++ },++ "maxItems": 10++ }++ }++ },++ "vision.moments-result.v1": {++ "title": "VisionMomentsResult",++ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "scene_changes",++ "candidate_moments",++ "receipt"++ ],++ "properties": {++ "scene_changes": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "kind"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "kind": {++ "const": "scene_change"++ }++ }++ }++ },++ "candidate_moments": {++ "type": "array",++ "maxItems": 30,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "timestamp_ms",++ "prompt",++ "score"++ ],++ "properties": {++ "timestamp_ms": {++ "type": "integer",++ "minimum": 0++ },++ "prompt": {++ "type": "string"++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ },++ "vision.ocr-request.v1": {++ "title": "VisionOcrRequest",++ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "lang": {++ "type": "string",++ "maxLength": 40++ },++ "fallback": {++ "type": "boolean"++ },++ "model": {++ "type": "string",++ "maxLength": 120++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.product-request.v1": {++ "title": "VisionProductRequest",++ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "allow_rerank": {++ "type": "boolean"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.product-result.v1": {++ "title": "VisionProductResult",++ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "description",++ "embedding",++ "offers",++ "warnings",++ "receipt"++ ],++ "properties": {++ "description": {++ "type": "string",++ "maxLength": 300++ },++ "embedding": {++ "type": "array",++ "items": {++ "type": "number"++ }++ },++ "offers": {++ "type": "array",++ "maxItems": 10,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "source",++ "title",++ "url"++ ],++ "properties": {++ "source": {++ "type": "string"++ },++ "title": {++ "type": "string",++ "maxLength": 200++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "price": {++ "type": [++ "number",++ "null"++ ]++ },++ "score": {++ "type": "number"++ }++ }++ }++ },++ "warnings": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ },++ "vision.receipt-request.v1": {++ "title": "VisionReceiptRequest",++ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "fallback": {++ "type": "boolean"}-- ],-- "x-openvibe-input": {-- "$ref": "#/components/schemas/vision.ui-check-request.v1"},-- "parameters": [++ "anyOf": [{-- "name": "id",-- "in": "path",-- "required": true,-- "schema": {-- "type": "string"-- }-- }-- ],-- "responses": {-- "2XX": {-- "description": "Success",-- "content": {-- "application/json": {-- "schema": {-- "$ref": "#/components/schemas/vision.ui-check-result.v1"-- }-- }-- }++ "required": [++ "image"++ ]},-- "default": {-- "description": "An error (RFC 9457 problem details)",-- "content": {-- "application/problem+json": {-- "schema": {-- "$ref": "#/components/schemas/errors.problem.v1"-- }-- }-- }++ {++ "required": [++ "image_url"++ ]}-- }-- }-- }-- },-- "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.",++ ]++ },++ "vision.receipt-result.v1": {++ "title": "VisionReceiptResult",++ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.","type": "object",++ "additionalProperties": false,"required": [-- "type",-- "title",-- "status",-- "code"++ "answer"],-- "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"++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "store",++ "items",++ "total",++ "receipt"++ ],++ "properties": {++ "store": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 150++ },++ "items": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "qty",++ "price"++ ],++ "properties": {++ "name": {++ "type": "string",++ "maxLength": 200++ },++ "qty": {++ "type": "number",++ "minimum": 0++ },++ "price": {++ "type": "number",++ "minimum": 0++ }++ }++ }++ },++ "total": {++ "type": [++ "number",++ "null"++ ],++ "minimum": 0++ },++ "raw_text": {++ "type": "string",++ "maxLength": 20000++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }}}}-- },-- "error": {-- "type": "string",-- "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."}}},@@@ -417,6 +4173,167 @@}}}++ },++ "vision.watch-request.v1": {++ "title": "VisionWatchRequest",++ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "classes"++ ],++ "properties": {++ "image": {++ "type": "string",++ "pattern": "^data:image/",++ "description": "The image as a data URL (base64)."++ },++ "image_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "description": "A public image URL, fetched through Vision's private-address guard."++ },++ "classes": {++ "type": "array",++ "items": {++ "type": "string",++ "maxLength": 60++ },++ "minItems": 1,++ "maxItems": 20++ },++ "previous_image": {++ "type": "string",++ "pattern": "^data:image/"++ }++ },++ "anyOf": [++ {++ "required": [++ "image"++ ]++ },++ {++ "required": [++ "image_url"++ ]++ }++ ]++ },++ "vision.watch-result.v1": {++ "title": "VisionWatchResult",++ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "answer"++ ],++ "properties": {++ "answer": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "detections",++ "receipt"++ ],++ "properties": {++ "detections": {++ "type": "array",++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "class",++ "confidence"++ ],++ "properties": {++ "class": {++ "type": "string"++ },++ "confidence": {++ "type": "number",++ "minimum": 0,++ "maximum": 1++ },++ "box": {++ "type": [++ "object",++ "null"++ ],++ "additionalProperties": false,++ "required": [++ "x",++ "y",++ "width",++ "height"++ ],++ "properties": {++ "x": {++ "type": "number"++ },++ "y": {++ "type": "number"++ },++ "width": {++ "type": "number"++ },++ "height": {++ "type": "number"++ }++ }++ }++ }++ }++ },++ "receipt": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "engine",++ "model",++ "cost_usd",++ "ms",++ "confidence",++ "retention"++ ],++ "properties": {++ "engine": {++ "enum": [++ "local",++ "routed",++ "local+routed",++ "local+routed description"++ ]++ },++ "model": {++ "type": [++ "string",++ "null"++ ]++ },++ "cost_usd": {++ "type": "number",++ "minimum": 0++ },++ "ms": {++ "type": "integer",++ "minimum": 0++ },++ "confidence": {++ "type": [++ "number",++ "null"++ ]++ },++ "retention": {++ "type": "string"++ }++ }++ }++ }++ }++ }}},"securitySchemes": {@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Watch API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Work API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Zone API",-- "version": "0.239.0",-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for 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"@@@ -213431,6 +213431,1406 @@ export interface VisionUiCheckResult {};}++/** [email protected] (owner: vision) */++/**++ * vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.++ */++export type VisionImageRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.++ */++export type VisionAskRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ question?: string;++ task?: "ask" | "describe" | "identify" | "count" | "translate" | "ocr";++ mode?: "auto" | "local" | "ai";++ lang?: string;++ model?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.++ */++export type VisionOcrRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ lang?: string;++ fallback?: boolean;++ model?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.++ */++export type VisionCompareRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ task?: "ask" | "describe" | "identify" | "count" | "translate" | "ocr";++ question?: string;++ /**++ * @maxItems 3++ */++ models?: [] | [string] | [string, string] | [string, string, string];++ lang?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.++ */++export type VisionFindRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ q?: string;++ item_id?: string;++ result_url?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.++ */++export type VisionReceiptRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ fallback?: boolean;++};++++/** [email protected] (owner: vision) */++/**++ * vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.++ */++export type VisionProductRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ allow_rerank?: boolean;++};++++/** [email protected] (owner: vision) */++/**++ * vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or "scene change"); `previous_image` is required for a scene change and is compared by histogram.++ */++export type VisionWatchRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ /**++ * @minItems 1++ * @maxItems 20++ */++ classes:++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ previous_image?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.++ */++export interface VisionMomentsRequest {++ /**++ * @minItems 1++ * @maxItems 30++ */++ frames: [++ (++ | {++ [k: string]: unknown | undefined;++ }++ | {++ [k: string]: unknown | undefined;++ }++ ),++ ...(++ | {++ [k: string]: unknown | undefined;++ }++ | {++ [k: string]: unknown | undefined;++ }++ )[]++ ];++ /**++ * @maxItems 10++ */++ prompts?:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string];++}++++/** [email protected] (owner: vision) */++/**++ * vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.++ */++export interface VisionAnswer {++ id: string;++ task: "ask" | "describe" | "identify" | "count" | "translate" | "ocr" | "compare";++ question?: string | null;++ mode: "auto" | "local" | "ai";++ source: "upload" | "url" | "data";++ source_url?: string | null;++ image_sha256: string;++ image_mime: string;++ image_bytes: number;++ text?: string | null;++ /**++ * Engine details, OCR words, provenance and any compare rows; never an image.++ */++ answer: {};++ model?: string | null;++ provider?: string | null;++ route?: string | null;++ reason?: string | null;++ engine?: string | null;++ confidence?: number | null;++ cost_usd: number;++ latency_ms: number;++ created_at: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.++ */++export interface VisionAnswerResult {++ answer: VisionAnswer;++ compare?: {++ engine: "local" | "ai";++ requested_model?: string | null;++ model?: string | null;++ provider?: string | null;++ route?: string | null;++ ok: boolean;++ text?: string | null;++ cost_usd: number;++ latency_ms: number;++ error_code?: string | null;++ }[];++ local?: {++ ok: boolean;++ model: string | null;++ confidence: number | null;++ error_code: string | null;++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.++ */++export interface VisionAltResult {++ answer: {++ alt_text: string;++ editable: true;++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.++ */++export interface VisionFindResult {++ answer: {++ /**++ * @maxItems 5000++ */++ results: {++ item_id: string;++ result_url?: string | null;++ score: number;++ }[];++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.++ */++export interface VisionReceiptResult {++ answer: {++ store: string | null;++ /**++ * @maxItems 100++ */++ items: {++ name: string;++ qty: number;++ price: number;++ }[];++ total: number | null;++ raw_text?: string;++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.++ */++export interface VisionProductResult {++ answer: {++ description: string;++ embedding: number[];++ /**++ * @maxItems 10++ */++ offers:++ | []++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ]++ | [++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ },++ {++ source: string;++ title: string;++ url: string;++ price?: number | null;++ score?: number;++ }++ ];++ warnings: string[];++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.++ */++export interface VisionWatchResult {++ answer: {++ detections: {++ class: string;++ confidence: number;++ box?: {++ x: number;++ y: number;++ width: number;++ height: number;++ } | null;++ }[];++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.++ */++export interface VisionMomentsResult {++ answer: {++ scene_changes: {++ timestamp_ms: number;++ kind: "scene_change";++ }[];++ /**++ * @maxItems 30++ */++ candidate_moments: {++ timestamp_ms: number;++ prompt: string;++ score: number;++ }[];++ receipt: {++ engine: "local" | "routed" | "local+routed" | "local+routed description";++ model: string | null;++ cost_usd: number;++ ms: number;++ confidence: number | null;++ retention: string;++ };++ };++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.++ */++export interface VisionFaceDetection {++ count: number;++ boxes: {++ x: number;++ y: number;++ width: number;++ height: number;++ score: number;++ }[];++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.++ */++export interface VisionFaceBlur {++ media_type: "image/png";++ face_count: number;++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.++ */++export type VisionFaceEnrolRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ feature: "verify" | "find" | "cast";++ channel?: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.++ */++export interface VisionFaceVerify {++ score: number;++ match: boolean;++ threshold: number;++ liveness: "not_checked" | "checked";++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.++ */++export interface VisionFaceFindResult {++ /**++ * @maxItems 500++ */++ results: {++ private_link: string;++ score: number;++ }[];++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.++ */++export type VisionFaceCastMatchRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The image as a data URL (base64).++ */++ image?: string;++ /**++ * A public image URL, fetched through Vision's private-address guard.++ */++ image_url?: string;++ channel: string;++ media_ref: string;++};++++/** [email protected] (owner: vision) */++/**++ * vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.++ */++export interface VisionFaceCastMatch {++ channel: string;++ matches: {++ subject: string;++ score: number;++ box?: {++ x: number;++ y: number;++ width: number;++ height: number;++ };++ }[];++ liveness: "not_checked" | "checked";++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.++ */++export interface VisionFaceTag {++ id: string;++ channel: string;++ media_ref: string;++ created_at: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.++ */++export type VisionFaceTagList = VisionFaceTag[];++++/** [email protected] (owner: vision) */++/**++ * vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.++ */++export interface VisionFaceLibraryRequest {++ action: "cluster" | "exclude" | "merge" | "split";++ region?: string;++ /**++ * @minItems 1++ * @maxItems 100++ */++ ids?: [string, ...string[]];++}++++/** [email protected] (owner: vision) */++/**++ * vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.++ */++export type VisionFaceLibraryResult =++ | VisionFaceLibraryGroup[]++ | {++ action: "exclude" | "merge" | "split";++ updated: number;++ };++++/** [email protected] (owner: vision) */++/**++ * vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.++ */++export interface VisionFaceLibraryGroup {++ id: string;++ media_id: string;++ group_id: string;++ label?: string | null;++ excluded: boolean;++ created_at: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).++ */++export interface VisionConsent {++ id: string;++ /**++ * The person whose face or voice this consent covers.++ */++ subject: string;++ modality: "face" | "voice";++ feature: "verify" | "find" | "cast" | "library" | "voice";++ /**++ * Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to.++ */++ scope: string;++ purpose: string;++ text_version: string;++ /**++ * The person who signed; the subject themself, or a guardian where the law allows.++ */++ granted_by: string;++ granted_at: string;++ /**++ * When the consent stops applying on its own; null when it lasts until revoked.++ */++ expires_at: string | null;++ last_used_at: string;++ revoked_at: string | null;++ /**++ * How it was revoked: by the person, by the retention job, or with the account.++ */++ revoked_by: "subject" | "retention" | "account_deletion" | null;++ purpose_ends_at: string | null;++ /**++ * The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended.++ */++ retention_until: string;++ status: "active" | "revoked" | "expired";++ /**++ * True once the template and derived results this consent covered are gone.++ */++ erased: boolean;++}++++/** [email protected] (owner: vision) */++/**++ * vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.++ */++export interface VisionConsentRequest {++ signed_name: string;++ text_version: string;++ modality?: "face" | "voice";++ /**++ * Echo of the :feature path segment.++ */++ feature?: "verify" | "find" | "cast" | "library" | "voice";++ channel?: string;++ adult?: boolean;++ region?: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.++ */++export interface VisionConsentRevocation {++ feature: "verify" | "find" | "cast" | "library" | "voice";++ scope: string;++ revoked_at: string;++ revoked_by: "subject";++ erased: true;++}++++/** [email protected] (owner: vision) */++/**++ * vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.++ */++export interface VisionModels {++ ai: boolean;++ configured: string[];++ offered: {}[];++ local: string[];++ note: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.++ */++export interface VisionBenchmarks {++ count: number;++ benchmarks: {++ task: string;++ engine: "local" | "ai";++ provider: string | null;++ model: string | null;++ samples: number;++ avg_latency_ms: number;++ avg_cost_usd: number;++ min_cost_usd: number;++ max_latency_ms: number;++ }[];++}++++/** [email protected] (owner: vision) */++/**++ * vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.++ */++export interface VisionResultReadyPayload {++ result_id: string | null;++ task:++ | "ask"++ | "describe"++ | "identify"++ | "count"++ | "translate"++ | "ocr"++ | "compare"++ | "alt"++ | "find"++ | "receipt"++ | "product"++ | "watch"++ | "moments"++ | "ui_check"++ | "face_detect"++ | "face_blur";++ source: "upload" | "url" | "data" | "html" | "screenshot" | "none";++ model: string | null;++ provider: string | null;++ route: string | null;++ cost_usd: number;++ latency_ms: number;++ confidence: number | null;++ retention: string;++ consent_id: string | null;++ created_at: string;++}++++/** [email protected] (owner: vision) */++/**++ * vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.++ */++export interface VisionAlertPayload {++ kind: "object" | "scene_change" | "face";++ source_ref: string;++ /**++ * @minItems 1++ * @maxItems 20++ */++ classes:++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ confidence: number | null;++ previous_ref?: string | null;++ detected_at: string;++}++/** [email protected] (owner: bench) *//*** bench.kind@1: a benchmark kind on OpenVibe.Bench. ROOM is the room that owns the kind, ENTRY the shape a submitted entry takes, JUDGE the default judge (pair, rubric, automatic_grader, test_suite or game_outcome), NAME the display name, SCHEMA the entry JSON Schema, and METRICS the board metrics (elo, pareto, cost, or a room's own).nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.alt.write",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person. The answer has passed noLooks; the receipt names the engine, cost and latency. Rooms use it for Pics, Media and Chat uploads.",++ "permissions": [++ "vision:alt"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-alt",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/alt"++ ],++ "inputSchema": "vision.image-request@1",++ "outputSchema": "vision.alt-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "vision.benchmarks.read",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, image or answer text. Public.",++ "permissions": [++ "vision:benchmarks:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-read",++ "events": [],++ "implementedBy": [++ "GET /api/v1/benchmarks"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "vision.benchmarks@1"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "vision.camera.watch",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and returns detections with confidence and boxes. Owner-granted camera alerting only; a watched class can raise vision.alert.",++ "permissions": [++ "vision:watch"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-watch",++ "events": [++ "vision.result.ready",++ "vision.alert"++ ],++ "implementedBy": [++ "POST /api/v1/watch"++ ],++ "inputSchema": "vision.watch-request@1",++ "outputSchema": "vision.watch-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.face.blur",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur. No template is stored and the original is discarded; anyone may use it.",++ "permissions": [++ "vision:face:blur"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/blur"++ ],++ "inputSchema": "vision.image-request@1",++ "outputSchema": "vision.face-blur@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.face.cast.match",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match. The caller is the Live service token scoped to that channel (its channel_id claim must equal the body's channel) and must hold this capability. A non-consented person is never matched and the answer does not say whether they exist.",++ "permissions": [++ "vision:face:cast:match"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/cast/match"++ ],++ "inputSchema": "vision.face-cast-match-request@1",++ "outputSchema": "vision.face-cast-match@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "vision.face.cast.read",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced. Revoking the consent removes them.",++ "permissions": [++ "vision:face:cast:read"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-face",++ "events": [],++ "implementedBy": [++ "GET /api/v1/faces/cast/tags"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "vision.face-tag-list@1"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "vision.face.consent.manage",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",++ "permissions": [++ "vision:face:consent"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-face",++ "events": [],++ "implementedBy": [++ "GET /api/v1/faces/consents",++ "GET /api/v1/faces/consents/text",++ "POST /api/v1/faces/consents/:feature",++ "DELETE /api/v1/faces/consents/:feature"++ ],++ "inputSchema": "vision.consent-request@1",++ "outputSchema": "vision.consent@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.face.detect",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet. It stores no template, names no one and needs no consent; anyone may use it.",++ "permissions": [++ "vision:face:detect"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/detect"++ ],++ "inputSchema": "vision.image-request@1",++ "outputSchema": "vision.face-detection@1"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "id": "vision.face.find.self",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",++ "permissions": [++ "vision:face:find"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/media",++ "GET /api/v1/faces/media/:id",++ "POST /api/v1/faces/find"++ ],++ "inputSchema": "vision.find-request@1",++ "outputSchema": "vision.face-find-result@1"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "id": "vision.face.library",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",++ "permissions": [++ "vision:face:library"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/library/cluster",++ "GET /api/v1/faces/library/groups",++ "POST /api/v1/faces/library/correct"++ ],++ "inputSchema": "vision.face-library-request@1",++ "outputSchema": "vision.face-library-result@1"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "vision.face.verify.self",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",++ "permissions": [++ "vision:face:verify"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-face",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/faces/enrol/:feature",++ "POST /api/v1/faces/verify"++ ],++ "inputSchema": "vision.face-enrol-request@1",++ "outputSchema": "vision.face-verify@1"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "vision.image.ask",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",++ "permissions": [++ "vision:ask"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-ask",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/ask",++ "POST /api/v1/describe"++ ],++ "inputSchema": "vision.ask-request@1",++ "outputSchema": "vision.answer-result@1"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "id": "vision.media.find",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",++ "permissions": [++ "vision:find"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-find",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/index",++ "GET /api/v1/find",++ "POST /api/v1/find"++ ],++ "inputSchema": "vision.find-request@1",++ "outputSchema": "vision.find-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.model.compare",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency. The requested model is a hint; OpenVibe.AI's own provenance is what each row reports.",++ "permissions": [++ "vision:compare"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-compare",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/compare"++ ],++ "inputSchema": "vision.compare-request@1",++ "outputSchema": "vision.answer-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "vision.models.read",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data. Public; OpenVibe.AI's router is still the authority.",++ "permissions": [++ "vision:models:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-read",++ "events": [],++ "implementedBy": [++ "GET /api/v1/models"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "vision.models@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.product.match",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoes with their score and URL. A candidate is not a verified price.",++ "permissions": [++ "vision:product"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-product",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/product"++ ],++ "inputSchema": "vision.product-request@1",++ "outputSchema": "vision.product-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.receipt.read",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce both. OpenVibe.Food decides what enters a pantry.",++ "permissions": [++ "vision:receipt"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-receipt",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/receipt"++ ],++ "inputSchema": "vision.receipt-request@1",++ "outputSchema": "vision.receipt-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.text.read",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously. `fallback` asks OpenVibe.AI only when the local pass found no usable text, and that fallback needs a signed-in person or this capability.",++ "permissions": [++ "vision:ocr"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "vision-ocr",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/ocr"++ ],++ "inputSchema": "vision.ocr-request@1",++ "outputSchema": "vision.answer-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "vision.video.moments",++ "version": "1.0",++ "owner": "vision",++ "status": "active",++ "visibility": "first-party",++ "description": "Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and candidate moments. A candidate is a suggestion for Live or Media to accept.",++ "permissions": [++ "vision:moments"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "vision-moments",++ "events": [++ "vision.result.ready"++ ],++ "implementedBy": [++ "POST /api/v1/moments"++ ],++ "inputSchema": "vision.moments-request@1",++ "outputSchema": "vision.moments-result@1"++}@@@ -1,7 +1,7 @@{"version": "0.1.0","contractRanges": {-- "openvibe-contracts": ">=0.229.0 <1.0.0"++ "openvibe-contracts": ">=0.240.0 <1.0.0"},"id": "vision","name": "OpenVibe.Vision",@@@ -16,9 +16,31 @@"publicSite": "service"},"capabilities": [-- "vision.ui.check"++ "vision.ui.check",++ "vision.image.ask",++ "vision.text.read",++ "vision.model.compare",++ "vision.alt.write",++ "vision.media.find",++ "vision.receipt.read",++ "vision.product.match",++ "vision.camera.watch",++ "vision.video.moments",++ "vision.face.detect",++ "vision.face.blur",++ "vision.face.consent.manage",++ "vision.face.verify.self",++ "vision.face.find.self",++ "vision.face.library",++ "vision.face.cast.match",++ "vision.face.cast.read",++ "vision.models.read",++ "vision.benchmarks.read"++ ],++ "eventsProduced": [++ "vision.result.ready",++ "vision.alert"],-- "eventsProduced": [],"eventsConsumed": ["network.account.export_requested","network.account.deleted"@@@ -37,6 +59,8 @@"drains": ["HTTP server closed and in-flight requests drained","JWKS refresher stopped",++ "face retention timer stopped",++ "outbox publisher stopped (its send in progress awaited; unsent rows stay for the next start)","telemetry interval and retention stopped, then the telemetry buffer flushed","PostgreSQL pool closed, then the Events subscriptions stopped"]@@@ -47,6 +71,10 @@"kind": "state","what": "answers, compare samples and UI checks live in PostgreSQL; a restart reopens the pool and clears only the in-process rate limits and telemetry buffer, so saved rows stay readable by id"},++ {++ "kind": "outbox",++ "what": "unsent vision.result.ready and vision.alert events in the outbox table go to OpenVibe.Events once the publisher starts (at least once; Events dedupes by event_id)"++ },{"kind": "consumer","what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop; account_data_events applies each once, so a redelivery changes nothing"@@@ -60,17 +88,17 @@],"window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies","blockers": {-- "none": "migration 0001 only creates Vision's own tables (answers, compare samples, UI checks, account-data receipts, telemetry); an earlier release reads none of them"++ "none": "migration 0001 only creates Vision's own tables (answers, compare samples, UI checks, account-data receipts, telemetry); migration 0003 adds the face tables and 0004 adds consent expiry/revocation columns and the event outbox, all additive, so an earlier release reads none of them"}},"contracts": {-- "range": ">=0.229.0 <1.0.0"++ "range": ">=0.240.0 <1.0.0"},"leases": {"none": "no claims: one process serves the site and the API"}},-- "notes": "Port 5130, unit openvibe-vision.service, database ov_vision. The Vision room (plan 2026-10-28 §5.8): ask a picture, read text with the local Tesseract 5 engine first, compare the local reader with up to three AI hints, and check HTML, a public URL or a screenshot for overflow, cut-off, low contrast, overlap and layering faults. OpenVibe.AI is the only model gateway (ai.run.create; the OpenAI-compatible POST /v1/chat/completions is workflow ai.complete); no provider key is held, no face recognition is used and no image bytes are stored. OAuth client `vision`; answers account export and deletion (ADR-033) and subscribes to the two account Events topics. v1 has no public records, so it has no Search outbox. Of Vision's four machine capabilities (vision.ask, vision.ocr, vision.compare, vision.ui_check), 0.235.0 registers only vision.ui.check, the route Design's Arena calls today; the other three are served but wait for a caller.",++ "notes": "Port 5130, unit openvibe-vision.service, database ov_vision. The Vision room (plan 2026-10-28 §5.8b): ask a picture, read text with the local Tesseract 5 engine first, compare the local reader with up to three AI hints, check HTML, a public URL or a screenshot for UI faults, write alt text, search only your own images by CLIP, read receipts, match products to offers, watch camera stills and find VOD moments. Faces are local (YuNet/SFace): detect and blur are identity-free, and verify, find, cast and library grouping each need a separate signed consent record (vision.consent@1) with a scope, expiry and a revocation that erases the derived data. Refusal is the same whether or not the person exists. OpenVibe.AI is the only model gateway (ai.run.create); no provider key is held, no image bytes are stored for general answers and no face template enters Search, a prompt or Bench. Public AI text passes noLooks. OAuth client `vision`; MCP at POST /mcp (RFC 9728 protected-resource metadata) exposes the image tools and never enrolment, verification or face-group mutation. Answers account export and deletion (ADR-033), subscribes to the two account Events topics and publishes vision.result.ready and vision.alert through its outbox. v1 has no public records, so it has no Search outbox.","health": "/api/health","ready": "/api/ready","internalOrigin": "http://127.0.0.1:5130",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.239.0",++ "version": "0.240.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",@@@ -121,7 +121,7 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscriptfor (const c of payloads) {const major = Number(c.version.split('.')[0]);ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);-- ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(c.id), `${c.id} is an event type`);++ ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){1,}$/.test(c.id), `${c.id} is an event type`);ok(services.get(c.owner) && (c.status === 'retired' || services.get(c.owner).eventsProduced.includes(c.id)), `${c.owner} manifest lists current ${c.id} in eventsProduced`);ok(['active', 'planned', 'retired'].includes(c.status), `${c.id} status ${c.status}`);if (c.status === 'retired') ok(!services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} no longer produces retired ${c.id}`);
@@ -4,6 +4,31 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,31 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.240.0 — 2026-10-29
+
+- **OpenVibe.Vision's API** (plan §5.8b): the service manifest now lists the 20 capabilities its routes serve today —
+ `vision.image.ask`, `vision.text.read`, `vision.model.compare`, `vision.ui.check`, `vision.alt.write`,
+ `vision.media.find`, `vision.receipt.read`, `vision.product.match`, `vision.camera.watch`, `vision.video.moments`,
+ `vision.face.detect`, `vision.face.blur`, `vision.face.consent.manage`, `vision.face.verify.self`,
+ `vision.face.find.self`, `vision.face.library`, `vision.face.cast.match`, `vision.face.cast.read`,
+ `vision.models.read` and `vision.benchmarks.read` — with their input and output schemas: `vision.image-request@1`,
+ `vision.ask-request@1`, `vision.ocr-request@1`, `vision.compare-request@1`, `vision.find-request@1`,
+ `vision.receipt-request@1`, `vision.product-request@1`, `vision.watch-request@1`, `vision.moments-request@1`,
+ `vision.answer@1`, `vision.answer-result@1`, `vision.alt-result@1`, `vision.find-result@1`,
+ `vision.receipt-result@1`, `vision.product-result@1`, `vision.watch-result@1`, `vision.moments-result@1`,
+ `vision.face-detection@1`, `vision.face-blur@1`, `vision.face-enrol-request@1`, `vision.face-verify@1`,
+ `vision.face-find-result@1`, `vision.face-cast-match-request@1`, `vision.face-cast-match@1`, `vision.face-tag@1`,
+ `vision.face-tag-list@1`, `vision.face-library-request@1`, `vision.face-library-result@1`,
+ `vision.face-library-group@1`, `vision.models@1` and `vision.benchmarks@1`.
+- **Consent** `vision.consent@1`: whose face or voice (subject and modality), the purpose-scoped feature and scope,
+ who signed which text version, when it expires, when and how it was revoked (subject, retention, account deletion),
+ the retention limit and whether the template and derived data were erased. `vision.consent-request@1` and
+ `vision.consent-revocation@1` are the grant and revoke bodies.
+- **Events** `vision.result.ready@1` (a result's identifiers, engine, provenance, cost, latency, retention and
+ consent id; never an image or answer text) and `vision.alert@1` (an owner-granted camera alert: source reference,
+ classes and confidence). `events.event-envelope@1` now accepts a two-segment event type (`vision.alert` is the
+ first), a backward-compatible widening; every capability id still needs three segments.
+
## 0.239.0 — 2026-10-28
## 0.239.0 — 2026-10-28
- Added `graph.link@1` (`from`, `rel`, `to` OVRN relation) for the planned object graph. The `graph.link.created` event waits for a producing service.
- Added `graph.link@1` (`from`, `rel`, `to` OVRN relation) for the planned object graph. The `graph.link.created` event waits for a producing service.
@@ -7722,6 +7722,330 @@
@@ -7722,6 +7722,330 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "vision.image-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/image-request.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.ask-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/ask-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.ocr-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/ocr-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.compare-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/compare-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.find-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/find-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.receipt-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/receipt-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.product-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/product-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.watch-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/watch-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.moments-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/moments-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.answer",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/answer.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.answer-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/answer-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.alt-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/alt-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.find-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/find-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.receipt-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/receipt-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.product-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/product-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.watch-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/watch-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.moments-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/moments-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-detection",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-detection.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-blur",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-blur.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-enrol-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-enrol-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-verify",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-verify.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-find-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-find-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-cast-match-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-cast-match-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-cast-match",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-cast-match.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-tag",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-tag.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-tag-list",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-tag-list.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-library-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-library-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-library-result",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-library-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.face-library-group",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/face-library-group.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.consent",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/consent.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.consent-request",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/consent-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.consent-revocation",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/consent-revocation.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.models",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/models.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.benchmarks",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "vision/benchmarks.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.result.ready",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "events/payloads/vision.result.ready.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "vision.alert",
+ "version": "1.0.0",
+ "owner": "vision",
+ "schema": "events/payloads/vision.alert.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "bench.kind",
"id": "bench.kind",
"version": "1.0.0",
"version": "1.0.0",
@@ -9,7 +9,7 @@
@@ -9,7 +9,7 @@
"properties": {
"properties": {
"event_id": { "type": "string", "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" },
"event_id": { "type": "string", "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" },
"trace_id": { "type": "string", "pattern": "^[0-9a-f]{32}$" },
"trace_id": { "type": "string", "pattern": "^[0-9a-f]{32}$" },
- "event_type": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$" },
+ "event_type": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$" },
"version": { "type": "integer", "minimum": 1 },
"version": { "type": "integer", "minimum": 1 },
"source": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },
"source": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },
"actor": { "$ref": "../identity/subject-ref.v1.json" },
"actor": { "$ref": "../identity/subject-ref.v1.json" },
new file mode 100644
new file mode 100644
@@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vision.alert.v1.json",
+ "title": "VisionAlertPayload",
+ "description": "vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "source_ref",
+ "classes",
+ "confidence",
+ "detected_at"
+ ],
+ "properties": {
+ "kind": {
+ "enum": [
+ "object",
+ "scene_change",
+ "face"
+ ]
+ },
+ "source_ref": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "minItems": 1,
+ "maxItems": 20
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0,
+ "maximum": 1
+ },
+ "previous_ref": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 160
+ },
+ "detected_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vision.result.ready.v1.json",
+ "title": "VisionResultReadyPayload",
+ "description": "vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "result_id",
+ "task",
+ "source",
+ "model",
+ "provider",
+ "route",
+ "cost_usd",
+ "latency_ms",
+ "confidence",
+ "retention",
+ "consent_id",
+ "created_at"
+ ],
+ "properties": {
+ "result_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^(vis|vuc|fme|ftg|fco)_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr",
+ "compare",
+ "alt",
+ "find",
+ "receipt",
+ "product",
+ "watch",
+ "moments",
+ "ui_check",
+ "face_detect",
+ "face_blur"
+ ]
+ },
+ "source": {
+ "enum": [
+ "upload",
+ "url",
+ "data",
+ "html",
+ "screenshot",
+ "none"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "consent_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/alt-result.v1.json",
+ "title": "VisionAltResult",
+ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "alt_text",
+ "editable",
+ "receipt"
+ ],
+ "properties": {
+ "alt_text": {
+ "type": "string",
+ "maxLength": 240
+ },
+ "editable": {
+ "const": true
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/answer-result.v1.json",
+ "title": "VisionAnswerResult",
+ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "$ref": "./answer.v1.json"
+ },
+ "compare": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "ok",
+ "cost_usd",
+ "latency_ms"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "requested_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "ok": {
+ "type": "boolean"
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ },
+ "local": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ok",
+ "model",
+ "confidence",
+ "error_code"
+ ],
+ "properties": {
+ "ok": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/answer.v1.json",
+ "title": "VisionAnswer",
+ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "task",
+ "mode",
+ "source",
+ "image_sha256",
+ "image_mime",
+ "image_bytes",
+ "cost_usd",
+ "latency_ms",
+ "created_at",
+ "answer"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr",
+ "compare"
+ ]
+ },
+ "question": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 2000
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "source": {
+ "enum": [
+ "upload",
+ "url",
+ "data"
+ ]
+ },
+ "source_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "image_sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "image_mime": {
+ "type": "string",
+ "pattern": "^image/[a-z0-9.+-]+$"
+ },
+ "image_bytes": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20971520
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 20000
+ },
+ "answer": {
+ "type": "object",
+ "description": "Engine details, OCR words, provenance and any compare rows; never an image."
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "engine": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0,
+ "maximum": 1
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/ask-request.v1.json",
+ "title": "VisionAskRequest",
+ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/benchmarks.v1.json",
+ "title": "VisionBenchmarks",
+ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "benchmarks"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "benchmarks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "task",
+ "engine",
+ "provider",
+ "model",
+ "samples",
+ "avg_latency_ms",
+ "avg_cost_usd",
+ "min_cost_usd",
+ "max_latency_ms"
+ ],
+ "properties": {
+ "task": {
+ "type": "string"
+ },
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "samples": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "min_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "max_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/compare-request.v1.json",
+ "title": "VisionCompareRequest",
+ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "models": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 3
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent-request.v1.json",
+ "title": "VisionConsentRequest",
+ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "signed_name",
+ "text_version"
+ ],
+ "properties": {
+ "signed_name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ],
+ "description": "Echo of the :feature path segment."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "adult": {
+ "type": "boolean"
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent-revocation.v1.json",
+ "title": "VisionConsentRevocation",
+ "description": "vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "feature",
+ "scope",
+ "revoked_at",
+ "revoked_by",
+ "erased"
+ ],
+ "properties": {
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ]
+ },
+ "scope": {
+ "type": "string",
+ "maxLength": 160
+ },
+ "revoked_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "revoked_by": {
+ "enum": [
+ "subject"
+ ]
+ },
+ "erased": {
+ "const": true
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent.v1.json",
+ "title": "VisionConsent",
+ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "subject",
+ "modality",
+ "feature",
+ "scope",
+ "purpose",
+ "text_version",
+ "granted_by",
+ "granted_at",
+ "expires_at",
+ "last_used_at",
+ "revoked_at",
+ "revoked_by",
+ "purpose_ends_at",
+ "retention_until",
+ "status",
+ "erased"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose face or voice this consent covers."
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ]
+ },
+ "scope": {
+ "type": "string",
+ "maxLength": 160,
+ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."
+ },
+ "purpose": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "granted_by": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person who signed; the subject themself, or a guardian where the law allows."
+ },
+ "granted_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the consent stops applying on its own; null when it lasts until revoked."
+ },
+ "last_used_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "revoked_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "revoked_by": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "enum": [
+ "subject",
+ "retention",
+ "account_deletion",
+ null
+ ],
+ "description": "How it was revoked: by the person, by the retention job, or with the account."
+ },
+ "purpose_ends_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "retention_until": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."
+ },
+ "status": {
+ "enum": [
+ "active",
+ "revoked",
+ "expired"
+ ]
+ },
+ "erased": {
+ "type": "boolean",
+ "description": "True once the template and derived results this consent covered are gone."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-blur.v1.json",
+ "title": "VisionFaceBlur",
+ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "media_type",
+ "face_count"
+ ],
+ "properties": {
+ "media_type": {
+ "const": "image/png"
+ },
+ "face_count": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-cast-match-request.v1.json",
+ "title": "VisionFaceCastMatchRequest",
+ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "media_ref"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "media_ref": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-cast-match.v1.json",
+ "title": "VisionFaceCastMatch",
+ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "matches",
+ "liveness"
+ ],
+ "properties": {
+ "channel": {
+ "type": "string"
+ },
+ "matches": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "score"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-detection.v1.json",
+ "title": "VisionFaceDetection",
+ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "boxes"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "boxes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height",
+ "score"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-enrol-request.v1.json",
+ "title": "VisionFaceEnrolRequest",
+ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "feature"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast"
+ ]
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-find-result.v1.json",
+ "title": "VisionFaceFindResult",
+ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 500,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "private_link",
+ "score"
+ ],
+ "properties": {
+ "private_link": {
+ "type": "string",
+ "pattern": "^/api/v1/faces/media/"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-group.v1.json",
+ "title": "VisionFaceLibraryGroup",
+ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "media_id",
+ "group_id",
+ "excluded",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "media_id": {
+ "type": "string",
+ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "group_id": {
+ "type": "string",
+ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "label": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 120
+ },
+ "excluded": {
+ "type": "boolean"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-request.v1.json",
+ "title": "VisionFaceLibraryRequest",
+ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "cluster",
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ },
+ "ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "minItems": 1,
+ "maxItems": 100
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-result.v1.json",
+ "title": "VisionFaceLibraryResult",
+ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "$ref": "./face-library-group.v1.json"
+ }
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "updated"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "updated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-tag-list.v1.json",
+ "title": "VisionFaceTagList",
+ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",
+ "type": "array",
+ "items": {
+ "$ref": "./face-tag.v1.json"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-tag.v1.json",
+ "title": "VisionFaceTag",
+ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "channel",
+ "media_ref",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "channel": {
+ "type": "string"
+ },
+ "media_ref": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-verify.v1.json",
+ "title": "VisionFaceVerify",
+ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "score",
+ "match",
+ "threshold",
+ "liveness"
+ ],
+ "properties": {
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "match": {
+ "type": "boolean"
+ },
+ "threshold": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/find-request.v1.json",
+ "title": "VisionFindRequest",
+ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "q": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500
+ },
+ "item_id": {
+ "type": "string",
+ "pattern": "^[\\w:.-]{1,200}$"
+ },
+ "result_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "q"
+ ]
+ },
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/find-result.v1.json",
+ "title": "VisionFindResult",
+ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results",
+ "receipt"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 5000,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "item_id",
+ "score"
+ ],
+ "properties": {
+ "item_id": {
+ "type": "string"
+ },
+ "result_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/image-request.v1.json",
+ "title": "VisionImageRequest",
+ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/models.v1.json",
+ "title": "VisionModels",
+ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ai",
+ "configured",
+ "offered",
+ "local",
+ "note"
+ ],
+ "properties": {
+ "ai": {
+ "type": "boolean"
+ },
+ "configured": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "offered": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "local": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "note": {
+ "type": "string"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/moments-request.v1.json",
+ "title": "VisionMomentsRequest",
+ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "frames"
+ ],
+ "properties": {
+ "frames": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "prompts": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 10
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/moments-result.v1.json",
+ "title": "VisionMomentsResult",
+ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "scene_changes",
+ "candidate_moments",
+ "receipt"
+ ],
+ "properties": {
+ "scene_changes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "kind"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "kind": {
+ "const": "scene_change"
+ }
+ }
+ }
+ },
+ "candidate_moments": {
+ "type": "array",
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "prompt",
+ "score"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "prompt": {
+ "type": "string"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/ocr-request.v1.json",
+ "title": "VisionOcrRequest",
+ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "fallback": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/product-request.v1.json",
+ "title": "VisionProductRequest",
+ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "allow_rerank": {
+ "type": "boolean"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/product-result.v1.json",
+ "title": "VisionProductResult",
+ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "description",
+ "embedding",
+ "offers",
+ "warnings",
+ "receipt"
+ ],
+ "properties": {
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "embedding": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ },
+ "offers": {
+ "type": "array",
+ "maxItems": 10,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "source",
+ "title",
+ "url"
+ ],
+ "properties": {
+ "source": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "price": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "warnings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/receipt-request.v1.json",
+ "title": "VisionReceiptRequest",
+ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "fallback": {
+ "type": "boolean"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/receipt-result.v1.json",
+ "title": "VisionReceiptResult",
+ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "store",
+ "items",
+ "total",
+ "receipt"
+ ],
+ "properties": {
+ "store": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 150
+ },
+ "items": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "qty",
+ "price"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qty": {
+ "type": "number",
+ "minimum": 0
+ },
+ "price": {
+ "type": "number",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "total": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0
+ },
+ "raw_text": {
+ "type": "string",
+ "maxLength": 20000
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/watch-request.v1.json",
+ "title": "VisionWatchRequest",
+ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "classes"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "minItems": 1,
+ "maxItems": 20
+ },
+ "previous_image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/watch-result.v1.json",
+ "title": "VisionWatchResult",
+ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "detections",
+ "receipt"
+ ],
+ "properties": {
+ "detections": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "class",
+ "confidence"
+ ],
+ "properties": {
+ "class": {
+ "type": "string"
+ },
+ "confidence": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "event_type": "vision.Alert",
+ "version": 1,
+ "source": "media",
+ "actor": {
+ "type": "service",
+ "id": "media"
+ },
+ "timestamp": "2026-09-22T12:00:00Z",
+ "subject": {
+ "type": "vod",
+ "id": "1"
+ },
+ "payload": {}
+}
deleted file mode 100644
deleted file mode 100644
@@ -1,16 +0,0 @@
@@ -1,16 +0,0 @@
-{
- "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
- "event_type": "vod.ready",
- "version": 1,
- "source": "media",
- "actor": {
- "type": "service",
- "id": "media"
- },
- "timestamp": "2026-09-22T12:00:00Z",
- "subject": {
- "type": "vod",
- "id": "1"
- },
- "payload": {}
-}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "kind": "motion",
+ "source_ref": "cam:front-door",
+ "classes": [],
+ "confidence": 2,
+ "detected_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "kind": "object",
+ "source_ref": "cam:front-door",
+ "classes": [
+ "package"
+ ],
+ "confidence": 0.88,
+ "detected_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "answer": {
+ "alt_text": "x",
+ "editable": false,
+ "receipt": {
+ "engine": "routed",
+ "model": null,
+ "cost_usd": 0,
+ "ms": 1,
+ "confidence": null,
+ "retention": "x"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "answer": {
+ "alt_text": "A kitchen counter with a bowl and a spoon.",
+ "editable": true,
+ "receipt": {
+ "engine": "routed",
+ "model": "gpt-4o-mini",
+ "cost_usd": 0.0002,
+ "ms": 900,
+ "confidence": null,
+ "retention": "not stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "answer": {
+ "id": "vis_short"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "answer": {
+ "id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "task": "ask",
+ "mode": "auto",
+ "source": "upload",
+ "image_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "image_mime": "image/png",
+ "image_bytes": 1024,
+ "text": "A desk with a keyboard and a mug.",
+ "answer": {},
+ "cost_usd": 0,
+ "latency_ms": 42,
+ "created_at": "2026-10-29T10:00:00.000Z"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "id": "vis_short",
+ "task": "ask",
+ "mode": "auto",
+ "source": "upload",
+ "cost_usd": 0,
+ "latency_ms": 0,
+ "created_at": "2026-10-29T10:00:00.000Z",
+ "answer": {}
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "task": "ask",
+ "mode": "auto",
+ "source": "upload",
+ "image_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "image_mime": "image/png",
+ "image_bytes": 1024,
+ "text": "A desk with a keyboard and a mug.",
+ "answer": {
+ "engine": "local",
+ "route": "local.ocr"
+ },
+ "model": "tesseract",
+ "provider": "local",
+ "route": "local.ocr",
+ "reason": "the local OCR reader answered the read task; no model was called",
+ "cost_usd": 0,
+ "latency_ms": 42,
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "image_url": "https://example.com/a.png",
+ "task": "compare"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "question": "What is on the desk?",
+ "task": "ask",
+ "mode": "auto"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "count": 1,
+ "benchmarks": [
+ {
+ "task": "ask",
+ "engine": "cloud",
+ "provider": null,
+ "model": null,
+ "samples": 3,
+ "avg_latency_ms": 900,
+ "avg_cost_usd": 0.0002,
+ "min_cost_usd": 0.0001,
+ "max_latency_ms": 1200
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "count": 1,
+ "benchmarks": [
+ {
+ "task": "ask",
+ "engine": "ai",
+ "provider": "openai",
+ "model": "gpt-4o-mini",
+ "samples": 3,
+ "avg_latency_ms": 900,
+ "avg_cost_usd": 0.0002,
+ "min_cost_usd": 0.0001,
+ "max_latency_ms": 1200
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "models": [
+ "a",
+ "b",
+ "c",
+ "d"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "task": "describe",
+ "models": [
+ "openai:gpt-4o-mini",
+ "anthropic:claude-sonnet-4"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "signed_name": "",
+ "text_version": "face-cast-2026-10-28"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "signed_name": "Ana Ruiz",
+ "text_version": "face-cast-2026-10-28",
+ "feature": "cast",
+ "channel": "live:one",
+ "adult": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "feature": "cast",
+ "scope": "live:one",
+ "revoked_at": "2026-10-29T11:00:00.000Z",
+ "revoked_by": "admin",
+ "erased": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "feature": "cast",
+ "scope": "live:one",
+ "revoked_at": "2026-10-29T11:00:00.000Z",
+ "revoked_by": "subject",
+ "erased": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "fco_1",
+ "subject": "ana",
+ "modality": "face",
+ "feature": "cast",
+ "scope": "live:one",
+ "purpose": "x",
+ "text_version": "v1",
+ "granted_by": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "granted_at": "2026-10-29T10:00:00.000Z",
+ "expires_at": null,
+ "last_used_at": "2026-10-29T10:00:00.000Z",
+ "revoked_at": null,
+ "revoked_by": null,
+ "purpose_ends_at": null,
+ "retention_until": "2029-10-29T10:00:00.000Z",
+ "status": "active",
+ "erased": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "fco_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "subject": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "modality": "face",
+ "feature": "cast",
+ "scope": "live:one",
+ "purpose": "tag on named streams",
+ "text_version": "face-cast-2026-10-28",
+ "granted_by": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "granted_at": "2026-10-29T10:00:00.000Z",
+ "expires_at": "2027-10-29T10:00:00.000Z",
+ "last_used_at": "2026-10-29T10:00:00.000Z",
+ "revoked_at": null,
+ "revoked_by": null,
+ "purpose_ends_at": null,
+ "retention_until": "2029-10-29T10:00:00.000Z",
+ "status": "active",
+ "erased": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "media_type": "image/jpeg",
+ "face_count": 2
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "media_type": "image/png",
+ "face_count": 2
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "channel": "live:one",
+ "media_ref": "media:a"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "channel": "live:one",
+ "media_ref": "media:a",
+ "image": "data:image/png;base64,iVBORw0KGgo="
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "channel": "live:one",
+ "matches": [
+ {
+ "subject": "ana",
+ "score": 0.71
+ }
+ ],
+ "liveness": "not_checked"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "channel": "live:one",
+ "matches": [
+ {
+ "subject": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "score": 0.71,
+ "box": {
+ "x": 1,
+ "y": 2,
+ "width": 30,
+ "height": 40
+ }
+ }
+ ],
+ "liveness": "not_checked"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "count": 1,
+ "boxes": [
+ {
+ "x": 20,
+ "y": 20,
+ "width": 80,
+ "height": 80,
+ "score": 2
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "count": 1,
+ "boxes": [
+ {
+ "x": 20,
+ "y": 20,
+ "width": 80,
+ "height": 80,
+ "score": 0.99
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "feature": "library",
+ "image": "data:image/png;base64,iVBORw0KGgo="
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "feature": "cast",
+ "channel": "live:one",
+ "image": "data:image/png;base64,iVBORw0KGgo="
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "results": [
+ {
+ "private_link": "https://openvibe.vision/public/x",
+ "score": 0.66
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "results": [
+ {
+ "private_link": "/api/v1/faces/media/fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "score": 0.66
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "id": "fcl_1",
+ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "excluded": false,
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "id": "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "label": "Ana",
+ "excluded": false,
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "action": "delete",
+ "ids": [
+ "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "action": "cluster",
+ "region": "US-IL"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "action": "exclude",
+ "updated": -1
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+[
+ {
+ "id": "fcl_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "media_id": "fme_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "group_id": "fgr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "label": null,
+ "excluded": false,
+ "created_at": "2026-10-29T10:00:00.000Z"
+ }
+]
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+[
+ {
+ "id": "ftg_1",
+ "channel": "live:one",
+ "media_ref": "media:a",
+ "created_at": "2026-10-29T10:00:00.000Z"
+ }
+]
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+[
+ {
+ "id": "ftg_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "channel": "live:one",
+ "media_ref": "media:a",
+ "created_at": "2026-10-29T10:00:00.000Z"
+ }
+]
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "id": "ftg_1",
+ "channel": "live:one",
+ "media_ref": "media:a",
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "id": "ftg_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "channel": "live:one",
+ "media_ref": "media:a",
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "score": 1.2,
+ "match": true,
+ "threshold": 0.363,
+ "liveness": "not_checked"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "score": 0.72,
+ "match": true,
+ "threshold": 0.363,
+ "liveness": "not_checked"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "q": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "q": "red bike by the pier"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "answer": {
+ "results": [
+ {
+ "item_id": "x",
+ "result_url": "http://insecure.example/a",
+ "score": 0.5
+ }
+ ],
+ "receipt": {
+ "engine": "local",
+ "model": null,
+ "cost_usd": 0,
+ "ms": 1,
+ "confidence": null,
+ "retention": "x"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "answer": {
+ "results": [
+ {
+ "item_id": "media:42",
+ "result_url": "https://openvibe.media/m/42",
+ "score": 0.81
+ }
+ ],
+ "receipt": {
+ "engine": "local",
+ "model": "CLIP ViT-B/32 q8",
+ "cost_usd": 0,
+ "ms": 12,
+ "confidence": 0.81,
+ "retention": "query not stored; indexed embeddings stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "image": "https://example.com/a.png"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo="
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "ai": true,
+ "configured": "openai:gpt-4o-mini",
+ "offered": [],
+ "local": [],
+ "note": "x"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "ai": true,
+ "configured": [
+ "openai:gpt-4o-mini"
+ ],
+ "offered": [
+ {
+ "provider": "openai",
+ "model": "gpt-4o-mini"
+ }
+ ],
+ "local": [
+ "tesseract",
+ "histogram"
+ ],
+ "note": "provider:model hints"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "frames": [
+ {
+ "image": "data:image/png;base64,iVBORw0KGgo="
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "frames": [
+ {
+ "timestamp_ms": 0,
+ "image": "data:image/png;base64,iVBORw0KGgo="
+ },
+ {
+ "timestamp_ms": 5000,
+ "image_url": "https://example.com/f.jpg"
+ }
+ ],
+ "prompts": [
+ "a goal"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "answer": {
+ "scene_changes": [
+ {
+ "timestamp_ms": 1,
+ "kind": "cut"
+ }
+ ],
+ "candidate_moments": [],
+ "receipt": {
+ "engine": "local",
+ "model": null,
+ "cost_usd": 0,
+ "ms": 1,
+ "confidence": null,
+ "retention": "x"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "answer": {
+ "scene_changes": [
+ {
+ "timestamp_ms": 5000,
+ "kind": "scene_change"
+ }
+ ],
+ "candidate_moments": [
+ {
+ "timestamp_ms": 5000,
+ "prompt": "a goal",
+ "score": 0.31
+ }
+ ],
+ "receipt": {
+ "engine": "local",
+ "model": "CLIP ViT-B/32 q8 + histogram",
+ "cost_usd": 0,
+ "ms": 90,
+ "confidence": 0.31,
+ "retention": "not stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "lang": "eng"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "image_url": "https://example.com/sign.png",
+ "lang": "eng",
+ "fallback": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "image_url": "https://example.com/shoe.png",
+ "allow_rerank": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "answer": {
+ "description": "A red running shoe.",
+ "embedding": [],
+ "offers": [
+ {
+ "source": "deals",
+ "title": "Red runner",
+ "url": "http://insecure.example/x"
+ }
+ ],
+ "warnings": [],
+ "receipt": {
+ "engine": "local",
+ "model": null,
+ "cost_usd": 0,
+ "ms": 1,
+ "confidence": null,
+ "retention": "x"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "answer": {
+ "description": "A red running shoe.",
+ "embedding": [
+ 0.1,
+ 0.2
+ ],
+ "offers": [
+ {
+ "source": "deals",
+ "title": "Red runner",
+ "url": "https://openvibe.deals/x",
+ "price": 79.99,
+ "score": 0.9
+ }
+ ],
+ "warnings": [],
+ "receipt": {
+ "engine": "local+routed description",
+ "model": "gpt-4o-mini",
+ "cost_usd": 0.0001,
+ "ms": 800,
+ "confidence": 0.9,
+ "retention": "not stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "fallback": "yes"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "fallback": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "answer": {
+ "store": "Corner Market",
+ "items": [
+ {
+ "name": "Milk",
+ "qty": "two",
+ "price": 3.5
+ }
+ ],
+ "total": 7,
+ "receipt": {
+ "engine": "local",
+ "model": "Tesseract 5",
+ "cost_usd": 0,
+ "ms": 120,
+ "confidence": 0.9,
+ "retention": "not stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "answer": {
+ "store": "Corner Market",
+ "items": [
+ {
+ "name": "Milk",
+ "qty": 2,
+ "price": 3.5
+ }
+ ],
+ "total": 7,
+ "receipt": {
+ "engine": "local",
+ "model": "Tesseract 5",
+ "cost_usd": 0,
+ "ms": 120,
+ "confidence": 0.9,
+ "retention": "not stored"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "result_id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "task": "look",
+ "source": "upload",
+ "model": null,
+ "provider": null,
+ "route": null,
+ "cost_usd": 0,
+ "latency_ms": 0,
+ "confidence": null,
+ "retention": "x",
+ "consent_id": null,
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "result_id": "vis_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
+ "task": "ask",
+ "source": "upload",
+ "model": "gpt-4o-mini",
+ "provider": "openai",
+ "route": "ai.fast",
+ "cost_usd": 0.0002,
+ "latency_ms": 900,
+ "confidence": null,
+ "retention": "not stored",
+ "consent_id": null,
+ "created_at": "2026-10-29T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "classes": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "image": "data:image/png;base64,iVBORw0KGgo=",
+ "classes": [
+ "package",
+ "scene change"
+ ],
+ "previous_image": "data:image/png;base64,iVBORw0KGgo="
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "answer": {
+ "detections": [
+ {
+ "class": "package",
+ "confidence": 1.5
+ }
+ ],
+ "receipt": {
+ "engine": "local",
+ "model": null,
+ "cost_usd": 0,
+ "ms": 1,
+ "confidence": null,
+ "retention": "x"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "answer": {
+ "detections": [
+ {
+ "class": "package",
+ "confidence": 0.88,
+ "box": {
+ "x": 1,
+ "y": 2,
+ "width": 30,
+ "height": 40
+ }
+ },
+ {
+ "class": "scene change",
+ "confidence": 0.4,
+ "box": null
+ }
+ ],
+ "receipt": {
+ "engine": "local",
+ "model": "DETR ResNet-50 q8 + histogram",
+ "cost_usd": 0,
+ "ms": 80,
+ "confidence": 0.88,
+ "retention": "not stored"
+ }
+ }
+}
@@ -1772,7 +1772,7 @@
@@ -1772,7 +1772,7 @@
},
},
"event_type": {
"event_type": {
"type": "string",
"type": "string",
- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"
+ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$"
},
},
"version": {
"version": {
"type": "integer",
"type": "integer",
@@ -87007,6 +87007,2490 @@
@@ -87007,6 +87007,2490 @@
}
}
}
}
},
},
+ "vision.image-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/image-request.v1.json",
+ "title": "VisionImageRequest",
+ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.ask-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/ask-request.v1.json",
+ "title": "VisionAskRequest",
+ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.ocr-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/ocr-request.v1.json",
+ "title": "VisionOcrRequest",
+ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "fallback": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.compare-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/compare-request.v1.json",
+ "title": "VisionCompareRequest",
+ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "models": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 3
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.find-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/find-request.v1.json",
+ "title": "VisionFindRequest",
+ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "q": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500
+ },
+ "item_id": {
+ "type": "string",
+ "pattern": "^[\\w:.-]{1,200}$"
+ },
+ "result_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "q"
+ ]
+ },
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.receipt-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/receipt-request.v1.json",
+ "title": "VisionReceiptRequest",
+ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "fallback": {
+ "type": "boolean"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.product-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/product-request.v1.json",
+ "title": "VisionProductRequest",
+ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "allow_rerank": {
+ "type": "boolean"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.watch-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/watch-request.v1.json",
+ "title": "VisionWatchRequest",
+ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "classes"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "minItems": 1,
+ "maxItems": 20
+ },
+ "previous_image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.moments-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/moments-request.v1.json",
+ "title": "VisionMomentsRequest",
+ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "frames"
+ ],
+ "properties": {
+ "frames": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "prompts": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 10
+ }
+ }
+ }
+ },
+ "vision.answer": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/answer.v1.json",
+ "title": "VisionAnswer",
+ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "task",
+ "mode",
+ "source",
+ "image_sha256",
+ "image_mime",
+ "image_bytes",
+ "cost_usd",
+ "latency_ms",
+ "created_at",
+ "answer"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr",
+ "compare"
+ ]
+ },
+ "question": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 2000
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "source": {
+ "enum": [
+ "upload",
+ "url",
+ "data"
+ ]
+ },
+ "source_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "image_sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "image_mime": {
+ "type": "string",
+ "pattern": "^image/[a-z0-9.+-]+$"
+ },
+ "image_bytes": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20971520
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 20000
+ },
+ "answer": {
+ "type": "object",
+ "description": "Engine details, OCR words, provenance and any compare rows; never an image."
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "engine": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0,
+ "maximum": 1
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "vision.answer-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/answer-result.v1.json",
+ "title": "VisionAnswerResult",
+ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "$ref": "./answer.v1.json"
+ },
+ "compare": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "ok",
+ "cost_usd",
+ "latency_ms"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "requested_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "ok": {
+ "type": "boolean"
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ },
+ "local": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ok",
+ "model",
+ "confidence",
+ "error_code"
+ ],
+ "properties": {
+ "ok": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.alt-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/alt-result.v1.json",
+ "title": "VisionAltResult",
+ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "alt_text",
+ "editable",
+ "receipt"
+ ],
+ "properties": {
+ "alt_text": {
+ "type": "string",
+ "maxLength": 240
+ },
+ "editable": {
+ "const": true
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.find-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/find-result.v1.json",
+ "title": "VisionFindResult",
+ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results",
+ "receipt"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 5000,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "item_id",
+ "score"
+ ],
+ "properties": {
+ "item_id": {
+ "type": "string"
+ },
+ "result_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.receipt-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/receipt-result.v1.json",
+ "title": "VisionReceiptResult",
+ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "store",
+ "items",
+ "total",
+ "receipt"
+ ],
+ "properties": {
+ "store": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 150
+ },
+ "items": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "qty",
+ "price"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qty": {
+ "type": "number",
+ "minimum": 0
+ },
+ "price": {
+ "type": "number",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "total": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0
+ },
+ "raw_text": {
+ "type": "string",
+ "maxLength": 20000
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.product-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/product-result.v1.json",
+ "title": "VisionProductResult",
+ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "description",
+ "embedding",
+ "offers",
+ "warnings",
+ "receipt"
+ ],
+ "properties": {
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "embedding": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ },
+ "offers": {
+ "type": "array",
+ "maxItems": 10,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "source",
+ "title",
+ "url"
+ ],
+ "properties": {
+ "source": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "price": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "warnings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.watch-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/watch-result.v1.json",
+ "title": "VisionWatchResult",
+ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "detections",
+ "receipt"
+ ],
+ "properties": {
+ "detections": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "class",
+ "confidence"
+ ],
+ "properties": {
+ "class": {
+ "type": "string"
+ },
+ "confidence": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.moments-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/moments-result.v1.json",
+ "title": "VisionMomentsResult",
+ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "scene_changes",
+ "candidate_moments",
+ "receipt"
+ ],
+ "properties": {
+ "scene_changes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "kind"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "kind": {
+ "const": "scene_change"
+ }
+ }
+ }
+ },
+ "candidate_moments": {
+ "type": "array",
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "prompt",
+ "score"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "prompt": {
+ "type": "string"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.face-detection": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-detection.v1.json",
+ "title": "VisionFaceDetection",
+ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "boxes"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "boxes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height",
+ "score"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.face-blur": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-blur.v1.json",
+ "title": "VisionFaceBlur",
+ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "media_type",
+ "face_count"
+ ],
+ "properties": {
+ "media_type": {
+ "const": "image/png"
+ },
+ "face_count": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "vision.face-enrol-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-enrol-request.v1.json",
+ "title": "VisionFaceEnrolRequest",
+ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "feature"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast"
+ ]
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.face-verify": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-verify.v1.json",
+ "title": "VisionFaceVerify",
+ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "score",
+ "match",
+ "threshold",
+ "liveness"
+ ],
+ "properties": {
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "match": {
+ "type": "boolean"
+ },
+ "threshold": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+ }
+ },
+ "vision.face-find-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-find-result.v1.json",
+ "title": "VisionFaceFindResult",
+ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 500,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "private_link",
+ "score"
+ ],
+ "properties": {
+ "private_link": {
+ "type": "string",
+ "pattern": "^/api/v1/faces/media/"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.face-cast-match-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-cast-match-request.v1.json",
+ "title": "VisionFaceCastMatchRequest",
+ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "media_ref"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "media_ref": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "vision.face-cast-match": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-cast-match.v1.json",
+ "title": "VisionFaceCastMatch",
+ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "matches",
+ "liveness"
+ ],
+ "properties": {
+ "channel": {
+ "type": "string"
+ },
+ "matches": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "score"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+ }
+ },
+ "vision.face-tag": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-tag.v1.json",
+ "title": "VisionFaceTag",
+ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "channel",
+ "media_ref",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "channel": {
+ "type": "string"
+ },
+ "media_ref": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "vision.face-tag-list": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-tag-list.v1.json",
+ "title": "VisionFaceTagList",
+ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",
+ "type": "array",
+ "items": {
+ "$ref": "./face-tag.v1.json"
+ }
+ }
+ },
+ "vision.face-library-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-request.v1.json",
+ "title": "VisionFaceLibraryRequest",
+ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "cluster",
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ },
+ "ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "minItems": 1,
+ "maxItems": 100
+ }
+ }
+ }
+ },
+ "vision.face-library-result": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-result.v1.json",
+ "title": "VisionFaceLibraryResult",
+ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "$ref": "./face-library-group.v1.json"
+ }
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "updated"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "updated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ "vision.face-library-group": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/face-library-group.v1.json",
+ "title": "VisionFaceLibraryGroup",
+ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "media_id",
+ "group_id",
+ "excluded",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "media_id": {
+ "type": "string",
+ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "group_id": {
+ "type": "string",
+ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "label": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 120
+ },
+ "excluded": {
+ "type": "boolean"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "vision.consent": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent.v1.json",
+ "title": "VisionConsent",
+ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "subject",
+ "modality",
+ "feature",
+ "scope",
+ "purpose",
+ "text_version",
+ "granted_by",
+ "granted_at",
+ "expires_at",
+ "last_used_at",
+ "revoked_at",
+ "revoked_by",
+ "purpose_ends_at",
+ "retention_until",
+ "status",
+ "erased"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose face or voice this consent covers."
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ]
+ },
+ "scope": {
+ "type": "string",
+ "maxLength": 160,
+ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."
+ },
+ "purpose": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "granted_by": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person who signed; the subject themself, or a guardian where the law allows."
+ },
+ "granted_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the consent stops applying on its own; null when it lasts until revoked."
+ },
+ "last_used_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "revoked_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "revoked_by": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "enum": [
+ "subject",
+ "retention",
+ "account_deletion",
+ null
+ ],
+ "description": "How it was revoked: by the person, by the retention job, or with the account."
+ },
+ "purpose_ends_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "retention_until": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."
+ },
+ "status": {
+ "enum": [
+ "active",
+ "revoked",
+ "expired"
+ ]
+ },
+ "erased": {
+ "type": "boolean",
+ "description": "True once the template and derived results this consent covered are gone."
+ }
+ }
+ }
+ },
+ "vision.consent-request": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent-request.v1.json",
+ "title": "VisionConsentRequest",
+ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "signed_name",
+ "text_version"
+ ],
+ "properties": {
+ "signed_name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ],
+ "description": "Echo of the :feature path segment."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "adult": {
+ "type": "boolean"
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+ }
+ },
+ "vision.consent-revocation": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/consent-revocation.v1.json",
+ "title": "VisionConsentRevocation",
+ "description": "vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "feature",
+ "scope",
+ "revoked_at",
+ "revoked_by",
+ "erased"
+ ],
+ "properties": {
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ]
+ },
+ "scope": {
+ "type": "string",
+ "maxLength": 160
+ },
+ "revoked_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "revoked_by": {
+ "enum": [
+ "subject"
+ ]
+ },
+ "erased": {
+ "const": true
+ }
+ }
+ }
+ },
+ "vision.models": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/models.v1.json",
+ "title": "VisionModels",
+ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ai",
+ "configured",
+ "offered",
+ "local",
+ "note"
+ ],
+ "properties": {
+ "ai": {
+ "type": "boolean"
+ },
+ "configured": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "offered": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "local": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "note": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "vision.benchmarks": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/vision/benchmarks.v1.json",
+ "title": "VisionBenchmarks",
+ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "benchmarks"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "benchmarks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "task",
+ "engine",
+ "provider",
+ "model",
+ "samples",
+ "avg_latency_ms",
+ "avg_cost_usd",
+ "min_cost_usd",
+ "max_latency_ms"
+ ],
+ "properties": {
+ "task": {
+ "type": "string"
+ },
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "samples": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "min_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "max_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.result.ready": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vision.result.ready.v1.json",
+ "title": "VisionResultReadyPayload",
+ "description": "vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "result_id",
+ "task",
+ "source",
+ "model",
+ "provider",
+ "route",
+ "cost_usd",
+ "latency_ms",
+ "confidence",
+ "retention",
+ "consent_id",
+ "created_at"
+ ],
+ "properties": {
+ "result_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^(vis|vuc|fme|ftg|fco)_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr",
+ "compare",
+ "alt",
+ "find",
+ "receipt",
+ "product",
+ "watch",
+ "moments",
+ "ui_check",
+ "face_detect",
+ "face_blur"
+ ]
+ },
+ "source": {
+ "enum": [
+ "upload",
+ "url",
+ "data",
+ "html",
+ "screenshot",
+ "none"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "consent_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "vision.alert": {
+ "version": "1.0.0",
+ "owner": "vision",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vision.alert.v1.json",
+ "title": "VisionAlertPayload",
+ "description": "vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "source_ref",
+ "classes",
+ "confidence",
+ "detected_at"
+ ],
+ "properties": {
+ "kind": {
+ "enum": [
+ "object",
+ "scene_change",
+ "face"
+ ]
+ },
+ "source_ref": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "minItems": 1,
+ "maxItems": 20
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0,
+ "maximum": 1
+ },
+ "previous_ref": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 160
+ },
+ "detected_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
"bench.kind": {
"bench.kind": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "bench",
"owner": "bench",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Actor API",
"title": "OpenVibe.Actor API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bench API",
"title": "OpenVibe.Bench API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Domains API",
"title": "OpenVibe.Domains API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -1503,7 +1503,7 @@
@@ -1503,7 +1503,7 @@
},
},
"event_type": {
"event_type": {
"type": "string",
"type": "string",
- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"
+ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){1,}$"
},
},
"version": {
"version": {
"type": "integer",
"type": "integer",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Food API",
"title": "OpenVibe.Food API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -250,8 +250,8 @@
@@ -250,8 +250,8 @@
"service": "vision",
"service": "vision",
"name": "OpenVibe.Vision",
"name": "OpenVibe.Vision",
"origin": "https://openvibe.vision",
"origin": "https://openvibe.vision",
- "operations": 2,
- "capabilities": 1
+ "operations": 32,
+ "capabilities": 20
},
},
{
{
"service": "watch",
"service": "watch",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Inventory API",
"title": "OpenVibe.Inventory API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.MediaHub API",
"title": "OpenVibe.MediaHub API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRestream API",
"title": "OpenRestream API",
- "version": "0.239.0",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Play API",
"title": "OpenVibe.Play API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Quest API",
"title": "OpenVibe.Quest API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Run API",
"title": "OpenVibe.Run API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Services API",
"title": "OpenVibe.Services API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Vision API",
"title": "OpenVibe.Vision API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -16,25 +16,25 @@
@@ -16,25 +16,25 @@
}
}
],
],
"paths": {
"paths": {
- "/api/v1/ui-check": {
+ "/api/v1/alt": {
"post": {
"post": {
- "operationId": "post_api_v1_ui_check",
- "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",
- "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",
+ "operationId": "post_api_v1_alt",
+ "summary": "Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person.",
+ "description": "**vision.alt.write** (first-party): Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person. The answer has passed noLooks; the receipt names the engine, cost and latency. Rooms use it for Pics, Media and Chat uploads.",
"tags": [
"tags": [
- "vision.ui"
+ "vision.alt"
],
],
"x-openvibe-capabilities": [
"x-openvibe-capabilities": [
- "vision.ui.check"
+ "vision.alt.write"
],
],
"x-openvibe-visibility": [
"x-openvibe-visibility": [
"first-party"
"first-party"
],
],
- "x-openvibe-quota-class": "vision-ui-check",
+ "x-openvibe-quota-class": "vision-alt",
"security": [
"security": [
{
{
"openvibeToken": [
"openvibeToken": [
- "vision.ui.check"
+ "vision.alt.write"
]
]
}
}
],
],
@@ -43,7 +43,7 @@
@@ -43,7 +43,7 @@
"content": {
"content": {
"application/json": {
"application/json": {
"schema": {
"schema": {
- "$ref": "#/components/schemas/vision.ui-check-request.v1"
+ "$ref": "#/components/schemas/vision.image-request.v1"
}
}
}
}
}
}
@@ -54,7 +54,277 @@
@@ -54,7 +54,277 @@
"content": {
"content": {
"application/json": {
"application/json": {
"schema": {
"schema": {
- "$ref": "#/components/schemas/vision.ui-check-result.v1"
+ "$ref": "#/components/schemas/vision.alt-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ask": {
+ "post": {
+ "operationId": "post_api_v1_ask",
+ "summary": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr",
+ "description": "**vision.image.ask** (first-party): Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",
+ "tags": [
+ "vision.image"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.image.ask"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-ask",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.image.ask"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ask-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.answer-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/benchmarks": {
+ "get": {
+ "operationId": "get_api_v1_benchmarks",
+ "summary": "Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, ",
+ "description": "**vision.benchmarks.read** (first-party): Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, image or answer text. Public.",
+ "tags": [
+ "vision.benchmarks"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.benchmarks.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.benchmarks.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.benchmarks.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/compare": {
+ "post": {
+ "operationId": "post_api_v1_compare",
+ "summary": "Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency.",
+ "description": "**vision.model.compare** (first-party): Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency. The requested model is a hint; OpenVibe.AI's own provenance is what each row reports.",
+ "tags": [
+ "vision.model"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.model.compare"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-compare",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.model.compare"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.compare-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.answer-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/describe": {
+ "post": {
+ "operationId": "post_api_v1_describe",
+ "summary": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr",
+ "description": "**vision.image.ask** (first-party): Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",
+ "tags": [
+ "vision.image"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.image.ask"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-ask",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.image.ask"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ask-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.answer-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/blur": {
+ "post": {
+ "operationId": "post_api_v1_faces_blur",
+ "summary": "Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur.",
+ "description": "**vision.face.blur** (first-party): Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur. No template is stored and the original is discarded; anyone may use it.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.blur"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.blur"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.image-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-blur.v1"
}
}
}
}
}
}
@@ -70,133 +340,3619 @@
@@ -70,133 +340,3619 @@
}
}
}
}
}
}
- }
- },
- "/api/v1/ui-checks/{id}": {
- "get": {
- "operationId": "get_api_v1_ui_checks_id",
- "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",
- "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",
- "tags": [
- "vision.ui"
- ],
- "x-openvibe-capabilities": [
- "vision.ui.check"
- ],
- "x-openvibe-visibility": [
- "first-party"
- ],
- "x-openvibe-quota-class": "vision-ui-check",
- "security": [
- {
- "openvibeToken": [
- "vision.ui.check"
- ]
+ }
+ },
+ "/api/v1/faces/cast/match": {
+ "post": {
+ "operationId": "post_api_v1_faces_cast_match",
+ "summary": "Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match.",
+ "description": "**vision.face.cast.match** (first-party): Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match. The caller is the Live service token scoped to that channel (its channel_id claim must equal the body's channel) and must hold this capability. A non-consented person is never matched and the answer does not say whether they exist.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.cast.match"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.cast.match"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-cast-match-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-cast-match.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/cast/tags": {
+ "get": {
+ "operationId": "get_api_v1_faces_cast_tags",
+ "summary": "Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced.",
+ "description": "**vision.face.cast.read** (first-party): Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced. Revoking the consent removes them.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.cast.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.cast.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-tag-list.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/consents": {
+ "get": {
+ "operationId": "get_api_v1_faces_consents",
+ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",
+ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.consent.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.consent.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.consent-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "signed_name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ }
+ },
+ {
+ "name": "text_version",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "maxLength": 80
+ }
+ },
+ {
+ "name": "modality",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ }
+ },
+ {
+ "name": "feature",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ],
+ "description": "Echo of the :feature path segment."
+ },
+ "description": "Echo of the :feature path segment."
+ },
+ {
+ "name": "channel",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ {
+ "name": "adult",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "region",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.consent.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/consents/{feature}": {
+ "delete": {
+ "operationId": "delete_api_v1_faces_consents_feature",
+ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",
+ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.consent.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.consent.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.consent-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "feature",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "signed_name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ }
+ },
+ {
+ "name": "text_version",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "maxLength": 80
+ }
+ },
+ {
+ "name": "modality",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ }
+ },
+ {
+ "name": "channel",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ {
+ "name": "adult",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "region",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.consent.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "post_api_v1_faces_consents_feature",
+ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",
+ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.consent.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.consent.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.consent-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "feature",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.consent.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/consents/text": {
+ "get": {
+ "operationId": "get_api_v1_faces_consents_text",
+ "summary": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consent",
+ "description": "**vision.face.consent.manage** (first-party): Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.consent.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.consent.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.consent-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "signed_name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ }
+ },
+ {
+ "name": "text_version",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "maxLength": 80
+ }
+ },
+ {
+ "name": "modality",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ }
+ },
+ {
+ "name": "feature",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ],
+ "description": "Echo of the :feature path segment."
+ },
+ "description": "Echo of the :feature path segment."
+ },
+ {
+ "name": "channel",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ {
+ "name": "adult",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "region",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.consent.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/detect": {
+ "post": {
+ "operationId": "post_api_v1_faces_detect",
+ "summary": "Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet.",
+ "description": "**vision.face.detect** (first-party): Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet. It stores no template, names no one and needs no consent; anyone may use it.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.detect"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.detect"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.image-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-detection.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/enrol/{feature}": {
+ "post": {
+ "operationId": "post_api_v1_faces_enrol_feature",
+ "summary": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify.",
+ "description": "**vision.face.verify.self** (first-party): Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.verify.self"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.verify.self"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-enrol-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "feature",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-verify.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/find": {
+ "post": {
+ "operationId": "post_api_v1_faces_find",
+ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",
+ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.find.self"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.find.self"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/library/cluster": {
+ "post": {
+ "operationId": "post_api_v1_faces_library_cluster",
+ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",
+ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.library"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.library"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-library-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-library-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/library/correct": {
+ "post": {
+ "operationId": "post_api_v1_faces_library_correct",
+ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",
+ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.library"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.library"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-library-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-library-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/library/groups": {
+ "get": {
+ "operationId": "get_api_v1_faces_library_groups",
+ "summary": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split).",
+ "description": "**vision.face.library** (first-party): Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.library"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.library"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.face-library-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "action",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "cluster",
+ "exclude",
+ "merge",
+ "split"
+ ]
+ }
+ },
+ {
+ "name": "region",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "maxLength": 10
+ }
+ },
+ {
+ "name": "ids",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "minItems": 1,
+ "maxItems": 100
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-library-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/media": {
+ "post": {
+ "operationId": "post_api_v1_faces_media",
+ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",
+ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.find.self"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.find.self"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/media/{id}": {
+ "get": {
+ "operationId": "get_api_v1_faces_media_id",
+ "summary": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find.",
+ "description": "**vision.face.find.self** (first-party): Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.find.self"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.find.self"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/faces/verify": {
+ "post": {
+ "operationId": "post_api_v1_faces_verify",
+ "summary": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify.",
+ "description": "**vision.face.verify.self** (first-party): Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",
+ "tags": [
+ "vision.face"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.face.verify.self"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-face",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.face.verify.self"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-enrol-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.face-verify.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/find": {
+ "get": {
+ "operationId": "get_api_v1_find",
+ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",
+ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",
+ "tags": [
+ "vision.media"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.media.find"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-find",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.media.find"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "post_api_v1_find",
+ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",
+ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",
+ "tags": [
+ "vision.media"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.media.find"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-find",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.media.find"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/index": {
+ "post": {
+ "operationId": "post_api_v1_index",
+ "summary": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private i",
+ "description": "**vision.media.find** (first-party): Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",
+ "tags": [
+ "vision.media"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.media.find"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-find",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.media.find"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.find-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/models": {
+ "get": {
+ "operationId": "get_api_v1_models",
+ "summary": "Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data.",
+ "description": "**vision.models.read** (first-party): Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data. Public; OpenVibe.AI's router is still the authority.",
+ "tags": [
+ "vision.models"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.models.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.models.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.models.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/moments": {
+ "post": {
+ "operationId": "post_api_v1_moments",
+ "summary": "Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and",
+ "description": "**vision.video.moments** (first-party): Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and candidate moments. A candidate is a suggestion for Live or Media to accept.",
+ "tags": [
+ "vision.video"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.video.moments"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-moments",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.video.moments"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.moments-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.moments-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ocr": {
+ "post": {
+ "operationId": "post_api_v1_ocr",
+ "summary": "Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously.",
+ "description": "**vision.text.read** (first-party): Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously. `fallback` asks OpenVibe.AI only when the local pass found no usable text, and that fallback needs a signed-in person or this capability.",
+ "tags": [
+ "vision.text"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.text.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-ocr",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.text.read"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ocr-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.answer-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/product": {
+ "post": {
+ "operationId": "post_api_v1_product",
+ "summary": "Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoe",
+ "description": "**vision.product.match** (first-party): Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoes with their score and URL. A candidate is not a verified price.",
+ "tags": [
+ "vision.product"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.product.match"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-product",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.product.match"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.product-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.product-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/receipt": {
+ "post": {
+ "operationId": "post_api_v1_receipt",
+ "summary": "Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce b",
+ "description": "**vision.receipt.read** (first-party): Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce both. OpenVibe.Food decides what enters a pantry.",
+ "tags": [
+ "vision.receipt"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.receipt.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-receipt",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.receipt.read"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.receipt-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.receipt-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ui-check": {
+ "post": {
+ "operationId": "post_api_v1_ui_check",
+ "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",
+ "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",
+ "tags": [
+ "vision.ui"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.ui.check"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-ui-check",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.ui.check"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ui-check-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ui-check-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ui-checks/{id}": {
+ "get": {
+ "operationId": "get_api_v1_ui_checks_id",
+ "summary": "Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings",
+ "description": "**vision.ui.check** (first-party): Run OpenVibe.Vision's UI check: POST /api/v1/ui-check takes HTML, a public URL or a screenshot plus an optional viewport and returns overflow, cut-off text, low contrast, overlap and layering findings with counts and severity; GET /api/v1/ui-checks/:id reads a saved check. OpenVibe.Design's Arena is the caller today; a signed-in person may also use it. The check runs entirely on Vision's host (static HTML/CSS parsing, or OCR boxes and pixel sampling for a screenshot): it executes no JavaScript and fetches only a public URL through the SSRF guard.",
+ "tags": [
+ "vision.ui"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.ui.check"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-ui-check",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.ui.check"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/vision.ui-check-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.ui-check-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/watch": {
+ "post": {
+ "operationId": "post_api_v1_watch",
+ "summary": "Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and retu",
+ "description": "**vision.camera.watch** (first-party): Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and returns detections with confidence and boxes. Owner-granted camera alerting only; a watched class can raise vision.alert.",
+ "tags": [
+ "vision.camera"
+ ],
+ "x-openvibe-capabilities": [
+ "vision.camera.watch"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "vision-watch",
+ "security": [
+ {
+ "openvibeToken": [
+ "vision.camera.watch"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.watch-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/vision.watch-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}."
+ }
+ }
+ },
+ "vision.alt-result.v1": {
+ "title": "VisionAltResult",
+ "description": "vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "alt_text",
+ "editable",
+ "receipt"
+ ],
+ "properties": {
+ "alt_text": {
+ "type": "string",
+ "maxLength": 240
+ },
+ "editable": {
+ "const": true
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.answer-result.v1": {
+ "title": "VisionAnswerResult",
+ "description": "vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "$ref": "#/components/schemas/vision.answer.v1"
+ },
+ "compare": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "ok",
+ "cost_usd",
+ "latency_ms"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "requested_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "ok": {
+ "type": "boolean"
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ },
+ "local": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ok",
+ "model",
+ "confidence",
+ "error_code"
+ ],
+ "properties": {
+ "ok": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "error_code": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "vision.answer.v1": {
+ "title": "VisionAnswer",
+ "description": "vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "task",
+ "mode",
+ "source",
+ "image_sha256",
+ "image_mime",
+ "image_bytes",
+ "cost_usd",
+ "latency_ms",
+ "created_at",
+ "answer"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^vis_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr",
+ "compare"
+ ]
+ },
+ "question": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 2000
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "source": {
+ "enum": [
+ "upload",
+ "url",
+ "data"
+ ]
+ },
+ "source_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "image_sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "image_mime": {
+ "type": "string",
+ "pattern": "^image/[a-z0-9.+-]+$"
+ },
+ "image_bytes": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20971520
+ },
+ "text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 20000
+ },
+ "answer": {
+ "type": "object",
+ "description": "Engine details, OCR words, provenance and any compare rows; never an image."
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "engine": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0,
+ "maximum": 1
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "vision.ask-request.v1": {
+ "title": "VisionAskRequest",
+ "description": "vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "mode": {
+ "enum": [
+ "auto",
+ "local",
+ "ai"
+ ]
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.benchmarks.v1": {
+ "title": "VisionBenchmarks",
+ "description": "vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "benchmarks"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "benchmarks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "task",
+ "engine",
+ "provider",
+ "model",
+ "samples",
+ "avg_latency_ms",
+ "avg_cost_usd",
+ "min_cost_usd",
+ "max_latency_ms"
+ ],
+ "properties": {
+ "task": {
+ "type": "string"
+ },
+ "engine": {
+ "enum": [
+ "local",
+ "ai"
+ ]
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "samples": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "avg_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "min_cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "max_latency_ms": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.compare-request.v1": {
+ "title": "VisionCompareRequest",
+ "description": "vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "task": {
+ "enum": [
+ "ask",
+ "describe",
+ "identify",
+ "count",
+ "translate",
+ "ocr"
+ ]
+ },
+ "question": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "models": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 3
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.consent-request.v1": {
+ "title": "VisionConsentRequest",
+ "description": "vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "signed_name",
+ "text_version"
+ ],
+ "properties": {
+ "signed_name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 160
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ],
+ "description": "Echo of the :feature path segment."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "adult": {
+ "type": "boolean"
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+ },
+ "vision.consent.v1": {
+ "title": "VisionConsent",
+ "description": "vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "subject",
+ "modality",
+ "feature",
+ "scope",
+ "purpose",
+ "text_version",
+ "granted_by",
+ "granted_at",
+ "expires_at",
+ "last_used_at",
+ "revoked_at",
+ "revoked_by",
+ "purpose_ends_at",
+ "retention_until",
+ "status",
+ "erased"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fco_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose face or voice this consent covers."
+ },
+ "modality": {
+ "enum": [
+ "face",
+ "voice"
+ ]
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast",
+ "library",
+ "voice"
+ ]
+ },
+ "scope": {
+ "type": "string",
+ "maxLength": 160,
+ "description": "Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to."
+ },
+ "purpose": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "text_version": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "granted_by": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person who signed; the subject themself, or a guardian where the law allows."
+ },
+ "granted_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the consent stops applying on its own; null when it lasts until revoked."
+ },
+ "last_used_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "revoked_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "revoked_by": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "enum": [
+ "subject",
+ "retention",
+ "account_deletion",
+ null
+ ],
+ "description": "How it was revoked: by the person, by the retention job, or with the account."
+ },
+ "purpose_ends_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "retention_until": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended."
+ },
+ "status": {
+ "enum": [
+ "active",
+ "revoked",
+ "expired"
+ ]
+ },
+ "erased": {
+ "type": "boolean",
+ "description": "True once the template and derived results this consent covered are gone."
+ }
+ }
+ },
+ "vision.face-blur.v1": {
+ "title": "VisionFaceBlur",
+ "description": "vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "media_type",
+ "face_count"
+ ],
+ "properties": {
+ "media_type": {
+ "const": "image/png"
+ },
+ "face_count": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ "vision.face-cast-match-request.v1": {
+ "title": "VisionFaceCastMatchRequest",
+ "description": "vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "media_ref"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ },
+ "media_ref": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:/_-]{1,159}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.face-cast-match.v1": {
+ "title": "VisionFaceCastMatch",
+ "description": "vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "channel",
+ "matches",
+ "liveness"
+ ],
+ "properties": {
+ "channel": {
+ "type": "string"
+ },
+ "matches": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "score"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+ },
+ "vision.face-detection.v1": {
+ "title": "VisionFaceDetection",
+ "description": "vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "count",
+ "boxes"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "boxes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height",
+ "score"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.face-enrol-request.v1": {
+ "title": "VisionFaceEnrolRequest",
+ "description": "vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "feature"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "feature": {
+ "enum": [
+ "verify",
+ "find",
+ "cast"
+ ]
+ },
+ "channel": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9:_-]{1,119}$"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.face-find-result.v1": {
+ "title": "VisionFaceFindResult",
+ "description": "vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 500,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "private_link",
+ "score"
+ ],
+ "properties": {
+ "private_link": {
+ "type": "string",
+ "pattern": "^/api/v1/faces/media/"
+ },
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.face-library-group.v1": {
+ "title": "VisionFaceLibraryGroup",
+ "description": "vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "media_id",
+ "group_id",
+ "excluded",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^fcl_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "media_id": {
+ "type": "string",
+ "pattern": "^fme_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "group_id": {
+ "type": "string",
+ "pattern": "^fgr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "label": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 120
+ },
+ "excluded": {
+ "type": "boolean"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "vision.face-library-request.v1": {
+ "title": "VisionFaceLibraryRequest",
+ "description": "vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "cluster",
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "region": {
+ "type": "string",
+ "maxLength": 10
+ },
+ "ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "minItems": 1,
+ "maxItems": 100
+ }
+ }
+ },
+ "vision.face-library-result.v1": {
+ "title": "VisionFaceLibraryResult",
+ "description": "vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.",
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/vision.face-library-group.v1"
+ }
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "updated"
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "exclude",
+ "merge",
+ "split"
+ ]
+ },
+ "updated": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ ]
+ },
+ "vision.face-tag-list.v1": {
+ "title": "VisionFaceTagList",
+ "description": "vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/vision.face-tag.v1"
+ }
+ },
+ "vision.face-tag.v1": {
+ "title": "VisionFaceTag",
+ "description": "vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "channel",
+ "media_ref",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ftg_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "channel": {
+ "type": "string"
+ },
+ "media_ref": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "vision.face-verify.v1": {
+ "title": "VisionFaceVerify",
+ "description": "vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "score",
+ "match",
+ "threshold",
+ "liveness"
+ ],
+ "properties": {
+ "score": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "match": {
+ "type": "boolean"
+ },
+ "threshold": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "liveness": {
+ "enum": [
+ "not_checked",
+ "checked"
+ ]
+ }
+ }
+ },
+ "vision.find-request.v1": {
+ "title": "VisionFindRequest",
+ "description": "vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "q": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500
+ },
+ "item_id": {
+ "type": "string",
+ "pattern": "^[\\w:.-]{1,200}$"
+ },
+ "result_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "q"
+ ]
+ },
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "item_id",
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.find-result.v1": {
+ "title": "VisionFindResult",
+ "description": "vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "results",
+ "receipt"
+ ],
+ "properties": {
+ "results": {
+ "type": "array",
+ "maxItems": 5000,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "item_id",
+ "score"
+ ],
+ "properties": {
+ "item_id": {
+ "type": "string"
+ },
+ "result_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.image-request.v1": {
+ "title": "VisionImageRequest",
+ "description": "vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.models.v1": {
+ "title": "VisionModels",
+ "description": "vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ai",
+ "configured",
+ "offered",
+ "local",
+ "note"
+ ],
+ "properties": {
+ "ai": {
+ "type": "boolean"
+ },
+ "configured": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "offered": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "local": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "note": {
+ "type": "string"
+ }
+ }
+ },
+ "vision.moments-request.v1": {
+ "title": "VisionMomentsRequest",
+ "description": "vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "frames"
+ ],
+ "properties": {
+ "frames": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ }
+ },
+ "prompts": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 120
+ },
+ "maxItems": 10
+ }
+ }
+ },
+ "vision.moments-result.v1": {
+ "title": "VisionMomentsResult",
+ "description": "vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "scene_changes",
+ "candidate_moments",
+ "receipt"
+ ],
+ "properties": {
+ "scene_changes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "kind"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "kind": {
+ "const": "scene_change"
+ }
+ }
+ }
+ },
+ "candidate_moments": {
+ "type": "array",
+ "maxItems": 30,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "timestamp_ms",
+ "prompt",
+ "score"
+ ],
+ "properties": {
+ "timestamp_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "prompt": {
+ "type": "string"
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.ocr-request.v1": {
+ "title": "VisionOcrRequest",
+ "description": "vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "lang": {
+ "type": "string",
+ "maxLength": 40
+ },
+ "fallback": {
+ "type": "boolean"
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 120
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.product-request.v1": {
+ "title": "VisionProductRequest",
+ "description": "vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "allow_rerank": {
+ "type": "boolean"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.product-result.v1": {
+ "title": "VisionProductResult",
+ "description": "vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "description",
+ "embedding",
+ "offers",
+ "warnings",
+ "receipt"
+ ],
+ "properties": {
+ "description": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "embedding": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ },
+ "offers": {
+ "type": "array",
+ "maxItems": 10,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "source",
+ "title",
+ "url"
+ ],
+ "properties": {
+ "source": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "price": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "score": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "warnings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "vision.receipt-request.v1": {
+ "title": "VisionReceiptRequest",
+ "description": "vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "fallback": {
+ "type": "boolean"
}
}
- ],
- "x-openvibe-input": {
- "$ref": "#/components/schemas/vision.ui-check-request.v1"
},
},
- "parameters": [
+ "anyOf": [
{
{
- "name": "id",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "2XX": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/vision.ui-check-result.v1"
- }
- }
- }
+ "required": [
+ "image"
+ ]
},
},
- "default": {
- "description": "An error (RFC 9457 problem details)",
- "content": {
- "application/problem+json": {
- "schema": {
- "$ref": "#/components/schemas/errors.problem.v1"
- }
- }
- }
+ {
+ "required": [
+ "image_url"
+ ]
}
}
- }
- }
- }
- },
- "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.",
+ ]
+ },
+ "vision.receipt-result.v1": {
+ "title": "VisionReceiptResult",
+ "description": "vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.",
"type": "object",
"type": "object",
+ "additionalProperties": false,
"required": [
"required": [
- "type",
- "title",
- "status",
- "code"
+ "answer"
],
],
- "additionalProperties": true,
"properties": {
"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"
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "store",
+ "items",
+ "total",
+ "receipt"
+ ],
+ "properties": {
+ "store": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 150
+ },
+ "items": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "qty",
+ "price"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qty": {
+ "type": "number",
+ "minimum": 0
+ },
+ "price": {
+ "type": "number",
+ "minimum": 0
+ }
+ }
+ }
+ },
+ "total": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "minimum": 0
+ },
+ "raw_text": {
+ "type": "string",
+ "maxLength": 20000
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
}
}
}
}
}
}
- },
- "error": {
- "type": "string",
- "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
}
}
}
}
},
},
@@ -417,6 +4173,167 @@
@@ -417,6 +4173,167 @@
}
}
}
}
}
}
+ },
+ "vision.watch-request.v1": {
+ "title": "VisionWatchRequest",
+ "description": "vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or \"scene change\"); `previous_image` is required for a scene change and is compared by histogram.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "classes"
+ ],
+ "properties": {
+ "image": {
+ "type": "string",
+ "pattern": "^data:image/",
+ "description": "The image as a data URL (base64)."
+ },
+ "image_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "description": "A public image URL, fetched through Vision's private-address guard."
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "minItems": 1,
+ "maxItems": 20
+ },
+ "previous_image": {
+ "type": "string",
+ "pattern": "^data:image/"
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "image"
+ ]
+ },
+ {
+ "required": [
+ "image_url"
+ ]
+ }
+ ]
+ },
+ "vision.watch-result.v1": {
+ "title": "VisionWatchResult",
+ "description": "vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "detections",
+ "receipt"
+ ],
+ "properties": {
+ "detections": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "class",
+ "confidence"
+ ],
+ "properties": {
+ "class": {
+ "type": "string"
+ },
+ "confidence": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "box": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": false,
+ "required": [
+ "x",
+ "y",
+ "width",
+ "height"
+ ],
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ },
+ "width": {
+ "type": "number"
+ },
+ "height": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ },
+ "receipt": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "engine",
+ "model",
+ "cost_usd",
+ "ms",
+ "confidence",
+ "retention"
+ ],
+ "properties": {
+ "engine": {
+ "enum": [
+ "local",
+ "routed",
+ "local+routed",
+ "local+routed description"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cost_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "confidence": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "retention": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
},
},
"securitySchemes": {
"securitySchemes": {
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Watch API",
"title": "OpenVibe.Watch API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Work API",
"title": "OpenVibe.Work API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Zone API",
"title": "OpenVibe.Zone API",
- "version": "0.239.0",
- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.239.0): each operation lists the capabilities it performs; a token for 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.240.0",
+ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.240.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -213431,6 +213431,1406 @@ export interface VisionUiCheckResult {
@@ -213431,6 +213431,1406 @@ export interface VisionUiCheckResult {
};
};
}
}
+/** [email protected] (owner: vision) */
+/**
+ * vision.image-request@1: one image for OpenVibe.Vision's one-shot tools, as a data URL or a public URL. The multipart form field `image` carries the same bytes. Private tools (find, index, watch, moments) accept only uploaded bytes or a data URL, never an unverified source URL.
+ */
+export type VisionImageRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.ask-request@1: the body of POST /api/v1/ask and /api/v1/describe on OpenVibe.Vision. `task` is what the caller wants (ask, describe, identify, count, translate, ocr); `mode` is auto (local OCR first, then OpenVibe.AI), local (never a model) or ai. A read question (text, sign, receipt, label) is answered by the local OCR reader first in auto mode. `lang` is the Tesseract language and `model` is a provider:model hint AI may ignore.
+ */
+export type VisionAskRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ question?: string;
+ task?: "ask" | "describe" | "identify" | "count" | "translate" | "ocr";
+ mode?: "auto" | "local" | "ai";
+ lang?: string;
+ model?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.ocr-request@1: the body of POST /api/v1/ocr. The local Tesseract 5 reader runs on Vision's host and costs nothing; `fallback` asks OpenVibe.AI only when the local pass found no usable text, which needs a signed-in person or a service token holding vision.ocr.
+ */
+export type VisionOcrRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ lang?: string;
+ fallback?: boolean;
+ model?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.compare-request@1: the body of POST /api/v1/compare. One image runs through the local reader and up to three AI hints; the requested model is a hint only, and each row reports the provider and model OpenVibe.AI says answered.
+ */
+export type VisionCompareRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ task?: "ask" | "describe" | "identify" | "count" | "translate" | "ocr";
+ question?: string;
+ /**
+ * @maxItems 3
+ */
+ models?: [] | [string] | [string, string] | [string, string, string];
+ lang?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.find-request@1: the body of POST /api/v1/index and of GET or POST /api/v1/find. `index` stores a scoped CLIP embedding for one of the caller's own items (`item_id`, optional `result_url`); `find` searches only the caller's own private index, by words (`q`) or by an example image. No public image index is ever built.
+ */
+export type VisionFindRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ q?: string;
+ item_id?: string;
+ result_url?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.receipt-request@1: the body of POST /api/v1/receipt. The local OCR reader extracts line items and a total; `fallback` asks OpenVibe.AI only when the local pass could not produce both.
+ */
+export type VisionReceiptRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ fallback?: boolean;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.product-request@1: the body of POST /api/v1/product. The image is embedded locally with CLIP; `allow_rerank` lets one routed model reorder the candidate offers. Candidates come from OpenVibe.Deals and open.shoes; Vision never invents a price.
+ */
+export type VisionProductRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ allow_rerank?: boolean;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.watch-request@1: the body of POST /api/v1/watch (owner-granted camera alerts). `classes` names 1–20 things to look for (snow, delivery, package, a DETR label, or "scene change"); `previous_image` is required for a scene change and is compared by histogram.
+ */
+export type VisionWatchRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ /**
+ * @minItems 1
+ * @maxItems 20
+ */
+ classes:
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ previous_image?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.moments-request@1: the body of POST /api/v1/moments. Up to 30 timestamped frames are compared with CLIP prompt embeddings; a histogram jump marks a scene change. VOD and stream chapters come from this.
+ */
+export interface VisionMomentsRequest {
+ /**
+ * @minItems 1
+ * @maxItems 30
+ */
+ frames: [
+ (
+ | {
+ [k: string]: unknown | undefined;
+ }
+ | {
+ [k: string]: unknown | undefined;
+ }
+ ),
+ ...(
+ | {
+ [k: string]: unknown | undefined;
+ }
+ | {
+ [k: string]: unknown | undefined;
+ }
+ )[]
+ ];
+ /**
+ * @maxItems 10
+ */
+ prompts?:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string];
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.answer@1: one saved answer on OpenVibe.Vision. It names the engine, model, provider and route that actually answered (never a model hint), the reason it was chosen, the cost OpenVibe.AI reported, the measured latency and the confidence. No image bytes are stored: only a sha256 and the declared media type.
+ */
+export interface VisionAnswer {
+ id: string;
+ task: "ask" | "describe" | "identify" | "count" | "translate" | "ocr" | "compare";
+ question?: string | null;
+ mode: "auto" | "local" | "ai";
+ source: "upload" | "url" | "data";
+ source_url?: string | null;
+ image_sha256: string;
+ image_mime: string;
+ image_bytes: number;
+ text?: string | null;
+ /**
+ * Engine details, OCR words, provenance and any compare rows; never an image.
+ */
+ answer: {};
+ model?: string | null;
+ provider?: string | null;
+ route?: string | null;
+ reason?: string | null;
+ engine?: string | null;
+ confidence?: number | null;
+ cost_usd: number;
+ latency_ms: number;
+ created_at: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.answer-result@1: what POST /api/v1/ask, /describe and /ocr answer (`{ answer }`), and POST /api/v1/compare answers with `compare` rows and the local reader's summary added. Every AI-written text has passed noLooks: a look-naming answer is replaced, never repaired word by word.
+ */
+export interface VisionAnswerResult {
+ answer: VisionAnswer;
+ compare?: {
+ engine: "local" | "ai";
+ requested_model?: string | null;
+ model?: string | null;
+ provider?: string | null;
+ route?: string | null;
+ ok: boolean;
+ text?: string | null;
+ cost_usd: number;
+ latency_ms: number;
+ error_code?: string | null;
+ }[];
+ local?: {
+ ok: boolean;
+ model: string | null;
+ confidence: number | null;
+ error_code: string | null;
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.alt-result@1: POST /api/v1/alt answers one short, editable alt-text sentence about the scene, objects and action, never about a person. The text has passed noLooks; the receipt says which engine answered and at what cost.
+ */
+export interface VisionAltResult {
+ answer: {
+ alt_text: string;
+ editable: true;
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.find-result@1: POST /api/v1/index and GET/POST /api/v1/find answer a scoped private search over the caller's own indexed items: `results` (item_id, result_url, score) only, no one else's rows and no public index.
+ */
+export interface VisionFindResult {
+ answer: {
+ /**
+ * @maxItems 5000
+ */
+ results: {
+ item_id: string;
+ result_url?: string | null;
+ score: number;
+ }[];
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.receipt-result@1: POST /api/v1/receipt answers the store, the line items and the total the local OCR reader read (or the routed fallback read), with `raw_text` when the local pass produced it. OpenVibe.Food decides what enters a pantry; Vision returns the reading.
+ */
+export interface VisionReceiptResult {
+ answer: {
+ store: string | null;
+ /**
+ * @maxItems 100
+ */
+ items: {
+ name: string;
+ qty: number;
+ price: number;
+ }[];
+ total: number | null;
+ raw_text?: string;
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.product-result@1: POST /api/v1/product answers a description of the product only (never a person), the CLIP embedding, and candidate offers from OpenVibe.Deals and open.shoes with their score, price and URL. A room must not treat a candidate as a price it verified.
+ */
+export interface VisionProductResult {
+ answer: {
+ description: string;
+ embedding: number[];
+ /**
+ * @maxItems 10
+ */
+ offers:
+ | []
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ]
+ | [
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ },
+ {
+ source: string;
+ title: string;
+ url: string;
+ price?: number | null;
+ score?: number;
+ }
+ ];
+ warnings: string[];
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.watch-result@1: POST /api/v1/watch answers the requested classes found in a camera still (DETR labels, snow/delivery/package through CLIP, or a histogram scene change), each with a confidence and, for an object, a box.
+ */
+export interface VisionWatchResult {
+ answer: {
+ detections: {
+ class: string;
+ confidence: number;
+ box?: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ } | null;
+ }[];
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.moments-result@1: POST /api/v1/moments answers scene changes and candidate moments (timestamp, matching prompt, score) for up to 30 frames. A moment is a suggestion: the room decides whether it becomes a clip or a chapter.
+ */
+export interface VisionMomentsResult {
+ answer: {
+ scene_changes: {
+ timestamp_ms: number;
+ kind: "scene_change";
+ }[];
+ /**
+ * @maxItems 30
+ */
+ candidate_moments: {
+ timestamp_ms: number;
+ prompt: string;
+ score: number;
+ }[];
+ receipt: {
+ engine: "local" | "routed" | "local+routed" | "local+routed description";
+ model: string | null;
+ cost_usd: number;
+ ms: number;
+ confidence: number | null;
+ retention: string;
+ };
+ };
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-detection@1: POST /api/v1/faces/detect, an identity-free YuNet privacy tool. It counts faces and returns their boxes and scores; it stores no template, names no one and needs no consent. Anyone may use it.
+ */
+export interface VisionFaceDetection {
+ count: number;
+ boxes: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ score: number;
+ }[];
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-blur@1: POST /api/v1/faces/blur answers a redacted PNG derivative and the number of faces blurred in X-Faces-Blur. Identity-free, no template, and the original is never stored.
+ */
+export interface VisionFaceBlur {
+ media_type: "image/png";
+ face_count: number;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-enrol-request@1: POST /api/v1/faces/enrol/:feature. `feature` is verify (1:1 verification of yourself), find (search your own uploads) or cast (an adult tagging named streams); each needs its own signed consent first, and cast also names the channel scope.
+ */
+export type VisionFaceEnrolRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ feature: "verify" | "find" | "cast";
+ channel?: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-verify@1: POST /api/v1/faces/verify compares this person's own verification attempt with their enrolled template. It always requires that person's verify consent; without it the answer is faces.consent_required whether or not a template exists. A match alone never unlocks anything: liveness and a second factor are separate.
+ */
+export interface VisionFaceVerify {
+ score: number;
+ match: boolean;
+ threshold: number;
+ liveness: "not_checked" | "checked";
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-find-result@1: POST /api/v1/faces/find lists the caller's own private uploads that match their enrolled self-search template, as private links and scores. It requires find consent; without it the answer is faces.consent_required whether or not any media exists.
+ */
+export interface VisionFaceFindResult {
+ /**
+ * @maxItems 500
+ */
+ results: {
+ private_link: string;
+ score: number;
+ }[];
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-cast-match-request@1: POST /api/v1/faces/cast/match, called by OpenVibe.Live with a service token scoped to one channel (vision.face.cast.match). It matches only that channel's opted-in adult roster; a person who has not enrolled for this channel cannot appear, and the answer does not say whether they exist.
+ */
+export type VisionFaceCastMatchRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The image as a data URL (base64).
+ */
+ image?: string;
+ /**
+ * A public image URL, fetched through Vision's private-address guard.
+ */
+ image_url?: string;
+ channel: string;
+ media_ref: string;
+};
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-cast-match@1: the matches inside one channel's consented roster, each with a score and box. No other subject is named and no non-roster face is described.
+ */
+export interface VisionFaceCastMatch {
+ channel: string;
+ matches: {
+ subject: string;
+ score: number;
+ box?: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ };
+ }[];
+ liveness: "not_checked" | "checked";
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-tag@1: one cast tag on a stream: which subject, which channel, which private media reference and when. GET /api/v1/faces/cast/tags lists the caller's own tags; revoking the cast consent removes them.
+ */
+export interface VisionFaceTag {
+ id: string;
+ channel: string;
+ media_ref: string;
+ created_at: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-tag-list@1: GET /api/v1/faces/cast/tags answers the caller's own cast tags, newest first, as a bare array. Empty when they have not consented to cast matching.
+ */
+export type VisionFaceTagList = VisionFaceTag[];
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-library-request@1: POST /api/v1/faces/library/cluster (region-gated), GET /library/groups and POST /library/correct. `action` is cluster (default), exclude, merge or split; `ids` names up to 100 of the caller's own clusters; `region` is a jurisdiction code the host allows. Unknown faces stay unlabelled.
+ */
+export interface VisionFaceLibraryRequest {
+ action: "cluster" | "exclude" | "merge" | "split";
+ region?: string;
+ /**
+ * @minItems 1
+ * @maxItems 100
+ */
+ ids?: [string, ...string[]];
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-library-result@1: clustering and group reads answer the caller's own private face groups as an array; a correction answers the action and how many clusters it updated.
+ */
+export type VisionFaceLibraryResult =
+ | VisionFaceLibraryGroup[]
+ | {
+ action: "exclude" | "merge" | "split";
+ updated: number;
+ };
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.face-library-group@1: one private face cluster in a person's own Vision library: the media it came from, the group it was merged into, an optional label and whether the person excluded it. It never leaves that person's library.
+ */
+export interface VisionFaceLibraryGroup {
+ id: string;
+ media_id: string;
+ group_id: string;
+ label?: string | null;
+ excluded: boolean;
+ created_at: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.consent@1: one consent record on OpenVibe.Vision. It says whose face or voice it is (`subject`), the modality, the purpose-scoped feature and scope (a channel or a library), who signed which text version, when it was granted, when it expires, and how and when it was revoked. Face matching tools refuse without an active record for that person, with the same answer whether or not the person exists. Templates are deleted when the purpose ends and at the latest three years after the last use (BIPA); a revoked audit record is deleted one year after the purpose ends (CUBI).
+ */
+export interface VisionConsent {
+ id: string;
+ /**
+ * The person whose face or voice this consent covers.
+ */
+ subject: string;
+ modality: "face" | "voice";
+ feature: "verify" | "find" | "cast" | "library" | "voice";
+ /**
+ * Empty means the person's own account; a cast grant names one channel, a library grant the library it applies to.
+ */
+ scope: string;
+ purpose: string;
+ text_version: string;
+ /**
+ * The person who signed; the subject themself, or a guardian where the law allows.
+ */
+ granted_by: string;
+ granted_at: string;
+ /**
+ * When the consent stops applying on its own; null when it lasts until revoked.
+ */
+ expires_at: string | null;
+ last_used_at: string;
+ revoked_at: string | null;
+ /**
+ * How it was revoked: by the person, by the retention job, or with the account.
+ */
+ revoked_by: "subject" | "retention" | "account_deletion" | null;
+ purpose_ends_at: string | null;
+ /**
+ * The outer limit the record and derived data may exist: three years after last use, or one year after the purpose ended.
+ */
+ retention_until: string;
+ status: "active" | "revoked" | "expired";
+ /**
+ * True once the template and derived results this consent covered are gone.
+ */
+ erased: boolean;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.consent-request@1: POST /api/v1/faces/consents/:feature. The caller signs the current text (`text_version` must match this server's), names themself (`signed_name`), and for cast must confirm they are an adult; library grouping is refused outside the host's allowed regions. A repeat of an active consent changes nothing.
+ */
+export interface VisionConsentRequest {
+ signed_name: string;
+ text_version: string;
+ modality?: "face" | "voice";
+ /**
+ * Echo of the :feature path segment.
+ */
+ feature?: "verify" | "find" | "cast" | "library" | "voice";
+ channel?: string;
+ adult?: boolean;
+ region?: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.consent-revocation@1: DELETE /api/v1/faces/consents/:feature answers with the revoked feature and scope, when, and that the template and derived data were erased in the same transaction. Revocation removes past cast tags and cannot be undone by reusing an old text version.
+ */
+export interface VisionConsentRevocation {
+ feature: "verify" | "find" | "cast" | "library" | "voice";
+ scope: string;
+ revoked_at: string;
+ revoked_by: "subject";
+ erased: true;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.models@1: GET /api/v1/models names the AI route's offered provider:model hints, the configured hints and the local engines present, without any subject data. OpenVibe.AI's router remains the authority: this is a menu, not a promise.
+ */
+export interface VisionModels {
+ ai: boolean;
+ configured: string[];
+ offered: {}[];
+ local: string[];
+ note: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.benchmarks@1: GET /api/v1/benchmarks aggregates compare samples per task, engine, provider and model with sample counts and mean latency and cost. No subject, image or answer text is included.
+ */
+export interface VisionBenchmarks {
+ count: number;
+ benchmarks: {
+ task: string;
+ engine: "local" | "ai";
+ provider: string | null;
+ model: string | null;
+ samples: number;
+ avg_latency_ms: number;
+ avg_cost_usd: number;
+ min_cost_usd: number;
+ max_latency_ms: number;
+ }[];
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.result.ready v1 (OpenVibe.Vision): a visual result is ready for its owner or caller. Envelope: source vision, subject { type: user, id: <requester> } (or { type: service, id: <caller> } when no person asked), visibility subject for a person and internal otherwise. The payload carries the identifiers, the engine and provenance, cost, latency and retention, never an image, a template or an answer text.
+ */
+export interface VisionResultReadyPayload {
+ result_id: string | null;
+ task:
+ | "ask"
+ | "describe"
+ | "identify"
+ | "count"
+ | "translate"
+ | "ocr"
+ | "compare"
+ | "alt"
+ | "find"
+ | "receipt"
+ | "product"
+ | "watch"
+ | "moments"
+ | "ui_check"
+ | "face_detect"
+ | "face_blur";
+ source: "upload" | "url" | "data" | "html" | "screenshot" | "none";
+ model: string | null;
+ provider: string | null;
+ route: string | null;
+ cost_usd: number;
+ latency_ms: number;
+ confidence: number | null;
+ retention: string;
+ consent_id: string | null;
+ created_at: string;
+}
+
+/** [email protected] (owner: vision) */
+/**
+ * vision.alert v1 (OpenVibe.Vision): an owner-granted camera alert. Envelope: source vision, subject { type: user, id: <source owner> }, visibility subject. The payload names the watched source reference and the classes found with their confidence; it carries no image bytes and no person's identity.
+ */
+export interface VisionAlertPayload {
+ kind: "object" | "scene_change" | "face";
+ source_ref: string;
+ /**
+ * @minItems 1
+ * @maxItems 20
+ */
+ classes:
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ confidence: number | null;
+ previous_ref?: string | null;
+ detected_at: string;
+}
+
/** [email protected] (owner: bench) */
/** [email protected] (owner: bench) */
/**
/**
* bench.kind@1: a benchmark kind on OpenVibe.Bench. ROOM is the room that owns the kind, ENTRY the shape a submitted entry takes, JUDGE the default judge (pair, rubric, automatic_grader, test_suite or game_outcome), NAME the display name, SCHEMA the entry JSON Schema, and METRICS the board metrics (elo, pareto, cost, or a room's own).
* bench.kind@1: a benchmark kind on OpenVibe.Bench. ROOM is the room that owns the kind, ENTRY the shape a submitted entry takes, JUDGE the default judge (pair, rubric, automatic_grader, test_suite or game_outcome), NAME the display name, SCHEMA the entry JSON Schema, and METRICS the board metrics (elo, pareto, cost, or a room's own).
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.alt.write",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Write editable alt text for an image on OpenVibe.Vision: POST /api/v1/alt returns one short sentence about the scene, objects and action, never about a person. The answer has passed noLooks; the receipt names the engine, cost and latency. Rooms use it for Pics, Media and Chat uploads.",
+ "permissions": [
+ "vision:alt"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-alt",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/alt"
+ ],
+ "inputSchema": "vision.image-request@1",
+ "outputSchema": "vision.alt-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "vision.benchmarks.read",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read OpenVibe.Vision's aggregate comparison results: GET /api/v1/benchmarks groups compare samples by task, engine, provider and model with sample counts, mean latency and mean cost, with no subject, image or answer text. Public.",
+ "permissions": [
+ "vision:benchmarks:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-read",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/benchmarks"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "vision.benchmarks@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "vision.camera.watch",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Watch a camera still on OpenVibe.Vision: POST /api/v1/watch finds the requested classes (DETR labels, snow, delivery, package through CLIP, or a histogram scene change against previous_image) and returns detections with confidence and boxes. Owner-granted camera alerting only; a watched class can raise vision.alert.",
+ "permissions": [
+ "vision:watch"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-watch",
+ "events": [
+ "vision.result.ready",
+ "vision.alert"
+ ],
+ "implementedBy": [
+ "POST /api/v1/watch"
+ ],
+ "inputSchema": "vision.watch-request@1",
+ "outputSchema": "vision.watch-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.face.blur",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Identity-free face blurring on OpenVibe.Vision: POST /api/v1/faces/blur returns a redacted PNG derivative and the blurred count in X-Faces-Blur. No template is stored and the original is discarded; anyone may use it.",
+ "permissions": [
+ "vision:face:blur"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/blur"
+ ],
+ "inputSchema": "vision.image-request@1",
+ "outputSchema": "vision.face-blur@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.face.cast.match",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Match faces against one channel's opted-in adult cast roster for OpenVibe.Live: POST /api/v1/faces/cast/match. The caller is the Live service token scoped to that channel (its channel_id claim must equal the body's channel) and must hold this capability. A non-consented person is never matched and the answer does not say whether they exist.",
+ "permissions": [
+ "vision:face:cast:match"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/cast/match"
+ ],
+ "inputSchema": "vision.face-cast-match-request@1",
+ "outputSchema": "vision.face-cast-match@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "vision.face.cast.read",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read your own cast tags on OpenVibe.Vision: GET /api/v1/faces/cast/tags lists the named channel and media reference for each tag the person's own cast consent produced. Revoking the consent removes them.",
+ "permissions": [
+ "vision:face:cast:read"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-face",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/faces/cast/tags"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "vision.face-tag-list@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "vision.face.consent.manage",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read, grant and revoke purpose-scoped face or voice consent on OpenVibe.Vision: GET /api/v1/faces/consents and /consents/text, POST /consents/:feature with the signed current text, and DELETE /consents/:feature, which revokes and erases the template and derived data in one transaction. A person manages only their own record; the record says whose face or voice, the scope, expiry and how it was revoked (vision.consent@1).",
+ "permissions": [
+ "vision:face:consent"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-face",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/faces/consents",
+ "GET /api/v1/faces/consents/text",
+ "POST /api/v1/faces/consents/:feature",
+ "DELETE /api/v1/faces/consents/:feature"
+ ],
+ "inputSchema": "vision.consent-request@1",
+ "outputSchema": "vision.consent@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.face.detect",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Identity-free face detection on OpenVibe.Vision: POST /api/v1/faces/detect counts faces and returns their boxes and scores with local YuNet. It stores no template, names no one and needs no consent; anyone may use it.",
+ "permissions": [
+ "vision:face:detect"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/detect"
+ ],
+ "inputSchema": "vision.image-request@1",
+ "outputSchema": "vision.face-detection@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "id": "vision.face.find.self",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Find yourself in your own private uploads on OpenVibe.Vision: POST /api/v1/faces/media, GET /api/v1/faces/media/:id and POST /api/v1/faces/find. Refused with faces.consent_required without find consent, the same answer whether or not any media exists; results are private links to the caller's own rows.",
+ "permissions": [
+ "vision:face:find"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/media",
+ "GET /api/v1/faces/media/:id",
+ "POST /api/v1/faces/find"
+ ],
+ "inputSchema": "vision.find-request@1",
+ "outputSchema": "vision.face-find-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "id": "vision.face.library",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Group faces in your own Vision library: POST /api/v1/faces/library/cluster (region-gated), GET /api/v1/faces/library/groups and POST /api/v1/faces/library/correct (exclude, merge, split). Requires library consent; unknown faces stay unlabelled and nothing leaves that person's library.",
+ "permissions": [
+ "vision:face:library"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/library/cluster",
+ "GET /api/v1/faces/library/groups",
+ "POST /api/v1/faces/library/correct"
+ ],
+ "inputSchema": "vision.face-library-request@1",
+ "outputSchema": "vision.face-library-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "vision.face.verify.self",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Verify yourself against your own enrolled template on OpenVibe.Vision: POST /api/v1/faces/enrol/verify and /api/v1/faces/verify. Refused with faces.consent_required without that person's verify consent, the same answer whether or not a template exists; a match alone unlocks nothing.",
+ "permissions": [
+ "vision:face:verify"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-face",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/faces/enrol/:feature",
+ "POST /api/v1/faces/verify"
+ ],
+ "inputSchema": "vision.face-enrol-request@1",
+ "outputSchema": "vision.face-verify@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "vision.image.ask",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Ask or describe an image on OpenVibe.Vision: POST /api/v1/ask and /api/v1/describe take an image (multipart, data URL or public URL), a question, a task (ask, describe, identify, count, translate, ocr) and a mode (auto, local, ai). A read question is answered by the local OCR reader first; everything else goes through OpenVibe.AI, whose provenance the answer reports. In auto mode a read question also works anonymously; any other task needs a signed-in person or this capability.",
+ "permissions": [
+ "vision:ask"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-ask",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/ask",
+ "POST /api/v1/describe"
+ ],
+ "inputSchema": "vision.ask-request@1",
+ "outputSchema": "vision.answer-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "id": "vision.media.find",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Index and search a person's own images by CLIP embedding on OpenVibe.Vision: POST /api/v1/index stores one scoped embedding per item and GET/POST /api/v1/find searches only that person's own private index, by words or by an example image. No public image index is made; every read is owner-checked.",
+ "permissions": [
+ "vision:find"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-find",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/index",
+ "GET /api/v1/find",
+ "POST /api/v1/find"
+ ],
+ "inputSchema": "vision.find-request@1",
+ "outputSchema": "vision.find-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.model.compare",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Run OpenVibe.Vision's side-by-side comparison: POST /api/v1/compare runs the local reader and up to three AI model hints on the same image and returns each row with its success, text, cost and latency. The requested model is a hint; OpenVibe.AI's own provenance is what each row reports.",
+ "permissions": [
+ "vision:compare"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-compare",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/compare"
+ ],
+ "inputSchema": "vision.compare-request@1",
+ "outputSchema": "vision.answer-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "vision.models.read",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read OpenVibe.Vision's model menu: GET /api/v1/models returns the AI route's offered provider:model hints, the configured hints and the local engines present, with no subject data. Public; OpenVibe.AI's router is still the authority.",
+ "permissions": [
+ "vision:models:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-read",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/models"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "vision.models@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.product.match",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Match a product photo to offers on OpenVibe.Vision: POST /api/v1/product describes the product only (never a person), embeds it with CLIP and returns candidate offers from OpenVibe.Deals and open.shoes with their score and URL. A candidate is not a verified price.",
+ "permissions": [
+ "vision:product"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-product",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/product"
+ ],
+ "inputSchema": "vision.product-request@1",
+ "outputSchema": "vision.product-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.receipt.read",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read a receipt on OpenVibe.Vision: POST /api/v1/receipt returns the store, line items and total the local OCR reader extracted; `fallback` asks OpenVibe.AI only when the local pass could not produce both. OpenVibe.Food decides what enters a pantry.",
+ "permissions": [
+ "vision:receipt"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-receipt",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/receipt"
+ ],
+ "inputSchema": "vision.receipt-request@1",
+ "outputSchema": "vision.receipt-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.text.read",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read text from an image with OpenVibe.Vision's local Tesseract 5 reader: POST /api/v1/ocr returns the text, mean confidence and word boxes at zero cost, anonymously. `fallback` asks OpenVibe.AI only when the local pass found no usable text, and that fallback needs a signed-in person or this capability.",
+ "permissions": [
+ "vision:ocr"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "vision-ocr",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/ocr"
+ ],
+ "inputSchema": "vision.ocr-request@1",
+ "outputSchema": "vision.answer-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "vision.video.moments",
+ "version": "1.0",
+ "owner": "vision",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Find moments in a stream or VOD on OpenVibe.Vision: POST /api/v1/moments compares up to 30 timestamped frames with CLIP prompt embeddings and a histogram scene-change pass, returning scene changes and candidate moments. A candidate is a suggestion for Live or Media to accept.",
+ "permissions": [
+ "vision:moments"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "vision-moments",
+ "events": [
+ "vision.result.ready"
+ ],
+ "implementedBy": [
+ "POST /api/v1/moments"
+ ],
+ "inputSchema": "vision.moments-request@1",
+ "outputSchema": "vision.moments-result@1"
+}
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
{
{
"version": "0.1.0",
"version": "0.1.0",
"contractRanges": {
"contractRanges": {
- "openvibe-contracts": ">=0.229.0 <1.0.0"
+ "openvibe-contracts": ">=0.240.0 <1.0.0"
},
},
"id": "vision",
"id": "vision",
"name": "OpenVibe.Vision",
"name": "OpenVibe.Vision",
@@ -16,9 +16,31 @@
@@ -16,9 +16,31 @@
"publicSite": "service"
"publicSite": "service"
},
},
"capabilities": [
"capabilities": [
- "vision.ui.check"
+ "vision.ui.check",
+ "vision.image.ask",
+ "vision.text.read",
+ "vision.model.compare",
+ "vision.alt.write",
+ "vision.media.find",
+ "vision.receipt.read",
+ "vision.product.match",
+ "vision.camera.watch",
+ "vision.video.moments",
+ "vision.face.detect",
+ "vision.face.blur",
+ "vision.face.consent.manage",
+ "vision.face.verify.self",
+ "vision.face.find.self",
+ "vision.face.library",
+ "vision.face.cast.match",
+ "vision.face.cast.read",
+ "vision.models.read",
+ "vision.benchmarks.read"
+ ],
+ "eventsProduced": [
+ "vision.result.ready",
+ "vision.alert"
],
],
- "eventsProduced": [],
"eventsConsumed": [
"eventsConsumed": [
"network.account.export_requested",
"network.account.export_requested",
"network.account.deleted"
"network.account.deleted"
@@ -37,6 +59,8 @@
@@ -37,6 +59,8 @@
"drains": [
"drains": [
"HTTP server closed and in-flight requests drained",
"HTTP server closed and in-flight requests drained",
"JWKS refresher stopped",
"JWKS refresher stopped",
+ "face retention timer stopped",
+ "outbox publisher stopped (its send in progress awaited; unsent rows stay for the next start)",
"telemetry interval and retention stopped, then the telemetry buffer flushed",
"telemetry interval and retention stopped, then the telemetry buffer flushed",
"PostgreSQL pool closed, then the Events subscriptions stopped"
"PostgreSQL pool closed, then the Events subscriptions stopped"
]
]
@@ -47,6 +71,10 @@
@@ -47,6 +71,10 @@
"kind": "state",
"kind": "state",
"what": "answers, compare samples and UI checks live in PostgreSQL; a restart reopens the pool and clears only the in-process rate limits and telemetry buffer, so saved rows stay readable by id"
"what": "answers, compare samples and UI checks live in PostgreSQL; a restart reopens the pool and clears only the in-process rate limits and telemetry buffer, so saved rows stay readable by id"
},
},
+ {
+ "kind": "outbox",
+ "what": "unsent vision.result.ready and vision.alert events in the outbox table go to OpenVibe.Events once the publisher starts (at least once; Events dedupes by event_id)"
+ },
{
{
"kind": "consumer",
"kind": "consumer",
"what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop; account_data_events applies each once, so a redelivery changes nothing"
"what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop; account_data_events applies each once, so a redelivery changes nothing"
@@ -60,17 +88,17 @@
@@ -60,17 +88,17 @@
],
],
"window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
"window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
"blockers": {
"blockers": {
- "none": "migration 0001 only creates Vision's own tables (answers, compare samples, UI checks, account-data receipts, telemetry); an earlier release reads none of them"
+ "none": "migration 0001 only creates Vision's own tables (answers, compare samples, UI checks, account-data receipts, telemetry); migration 0003 adds the face tables and 0004 adds consent expiry/revocation columns and the event outbox, all additive, so an earlier release reads none of them"
}
}
},
},
"contracts": {
"contracts": {
- "range": ">=0.229.0 <1.0.0"
+ "range": ">=0.240.0 <1.0.0"
},
},
"leases": {
"leases": {
"none": "no claims: one process serves the site and the API"
"none": "no claims: one process serves the site and the API"
}
}
},
},
- "notes": "Port 5130, unit openvibe-vision.service, database ov_vision. The Vision room (plan 2026-10-28 §5.8): ask a picture, read text with the local Tesseract 5 engine first, compare the local reader with up to three AI hints, and check HTML, a public URL or a screenshot for overflow, cut-off, low contrast, overlap and layering faults. OpenVibe.AI is the only model gateway (ai.run.create; the OpenAI-compatible POST /v1/chat/completions is workflow ai.complete); no provider key is held, no face recognition is used and no image bytes are stored. OAuth client `vision`; answers account export and deletion (ADR-033) and subscribes to the two account Events topics. v1 has no public records, so it has no Search outbox. Of Vision's four machine capabilities (vision.ask, vision.ocr, vision.compare, vision.ui_check), 0.235.0 registers only vision.ui.check, the route Design's Arena calls today; the other three are served but wait for a caller.",
+ "notes": "Port 5130, unit openvibe-vision.service, database ov_vision. The Vision room (plan 2026-10-28 §5.8b): ask a picture, read text with the local Tesseract 5 engine first, compare the local reader with up to three AI hints, check HTML, a public URL or a screenshot for UI faults, write alt text, search only your own images by CLIP, read receipts, match products to offers, watch camera stills and find VOD moments. Faces are local (YuNet/SFace): detect and blur are identity-free, and verify, find, cast and library grouping each need a separate signed consent record (vision.consent@1) with a scope, expiry and a revocation that erases the derived data. Refusal is the same whether or not the person exists. OpenVibe.AI is the only model gateway (ai.run.create); no provider key is held, no image bytes are stored for general answers and no face template enters Search, a prompt or Bench. Public AI text passes noLooks. OAuth client `vision`; MCP at POST /mcp (RFC 9728 protected-resource metadata) exposes the image tools and never enrolment, verification or face-group mutation. Answers account export and deletion (ADR-033), subscribes to the two account Events topics and publishes vision.result.ready and vision.alert through its outbox. v1 has no public records, so it has no Search outbox.",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
"internalOrigin": "http://127.0.0.1:5130",
"internalOrigin": "http://127.0.0.1:5130",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.239.0",
+ "version": "0.240.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -121,7 +121,7 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
@@ -121,7 +121,7 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
for (const c of payloads) {
for (const c of payloads) {
const major = Number(c.version.split('.')[0]);
const major = Number(c.version.split('.')[0]);
ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);
ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);
- ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(c.id), `${c.id} is an event type`);
+ ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){1,}$/.test(c.id), `${c.id} is an event type`);
ok(services.get(c.owner) && (c.status === 'retired' || services.get(c.owner).eventsProduced.includes(c.id)), `${c.owner} manifest lists current ${c.id} in eventsProduced`);
ok(services.get(c.owner) && (c.status === 'retired' || services.get(c.owner).eventsProduced.includes(c.id)), `${c.owner} manifest lists current ${c.id} in eventsProduced`);
ok(['active', 'planned', 'retired'].includes(c.status), `${c.id} status ${c.status}`);
ok(['active', 'planned', 'retired'].includes(c.status), `${c.id} status ${c.status}`);
if (c.status === 'retired') ok(!services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} no longer produces retired ${c.id}`);
if (c.status === 'retired') ok(!services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} no longer produces retired ${c.id}`);