{ "openapi": "3.1.0", "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "YoVision Sense Control API", "version": "1.0.0", "description": "受控的设备期望态与收敛查询契约。Tenant 只从认证上下文确定;Site、Area、RBAC 和配额真相仍由 Bell 持有。" }, "servers": [ { "url": "/", "description": "由部署入口提供 scheme、host 和认证终止" } ], "security": [ { "bearerAuth": [] } ], "tags": [ { "name": "Devices", "description": "设备台账、期望态和收敛状态" }, { "name": "Operations", "description": "批量写入的逐项结果" } ], "paths": { "/api/v1/sites/{site_id}/devices": { "get": { "tags": ["Devices"], "operationId": "listDevices", "summary": "分页查询当前认证租户下的站点设备", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}, {"$ref": "#/components/parameters/ModalityFilter"}, {"$ref": "#/components/parameters/CapabilityFilter"}, {"$ref": "#/components/parameters/DesiredStateFilter"}, {"$ref": "#/components/parameters/ActualStateFilter"} ], "responses": { "200": { "description": "按 created_at、id 稳定升序返回的一页设备", "headers": {"X-Trace-ID": {"$ref": "#/components/headers/TraceID"}}, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/DevicePage"}} } }, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "500": {"$ref": "#/components/responses/ErrorResponse"} } }, "post": { "tags": ["Devices"], "operationId": "createDevice", "summary": "创建待探测设备", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/IdempotencyKey"} ], "requestBody": { "required": true, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/DeviceCreate"}} } }, "responses": { "201": {"$ref": "#/components/responses/CreatedDevice"}, "400": {"$ref": "#/components/responses/ErrorResponse"}, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "409": {"$ref": "#/components/responses/ErrorResponse"}, "422": {"$ref": "#/components/responses/ErrorResponse"}, "503": {"$ref": "#/components/responses/ErrorResponse"} } } }, "/api/v1/sites/{site_id}/devices/{device_id}": { "get": { "tags": ["Devices"], "operationId": "getDevice", "summary": "读取设备期望态、实际态和收敛进度", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/DeviceID"} ], "responses": { "200": {"$ref": "#/components/responses/CurrentDevice"}, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "500": {"$ref": "#/components/responses/ErrorResponse"} } }, "patch": { "tags": ["Devices"], "operationId": "updateDevice", "summary": "按 ETag 修改设备可编辑配置", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/DeviceID"}, {"$ref": "#/components/parameters/IfMatch"} ], "requestBody": { "required": true, "content": { "application/merge-patch+json": {"schema": {"$ref": "#/components/schemas/DevicePatch"}} } }, "responses": { "202": {"$ref": "#/components/responses/AcceptedMutation"}, "400": {"$ref": "#/components/responses/ErrorResponse"}, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "409": {"$ref": "#/components/responses/ErrorResponse"}, "412": {"$ref": "#/components/responses/ErrorResponse"}, "422": {"$ref": "#/components/responses/ErrorResponse"}, "428": {"$ref": "#/components/responses/ErrorResponse"}, "503": {"$ref": "#/components/responses/ErrorResponse"} } } }, "/api/v1/sites/{site_id}/devices/{device_id}/desired-state": { "put": { "tags": ["Devices"], "operationId": "setDeviceDesiredState", "summary": "启用或停用设备接入期望态", "description": "成功只表示期望态已持久化并进入对账,不表示实际态已经收敛。重复提交不会重复改变 generation,但每次请求均可产生审计事实。", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/DeviceID"}, {"$ref": "#/components/parameters/IfMatch"} ], "requestBody": { "required": true, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/DesiredStateChange"}} } }, "responses": { "202": {"$ref": "#/components/responses/AcceptedMutation"}, "400": {"$ref": "#/components/responses/ErrorResponse"}, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "412": {"$ref": "#/components/responses/ErrorResponse"}, "422": {"$ref": "#/components/responses/ErrorResponse"}, "428": {"$ref": "#/components/responses/ErrorResponse"}, "503": {"$ref": "#/components/responses/ErrorResponse"} } } }, "/api/v1/sites/{site_id}/devices:batchDesiredState": { "post": { "tags": ["Devices"], "operationId": "batchSetDeviceDesiredState", "summary": "批量提交最多 128 个设备期望态变更", "parameters": [ {"$ref": "#/components/parameters/SiteID"}, {"$ref": "#/components/parameters/IdempotencyKey"} ], "requestBody": { "required": true, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/BatchDesiredStateRequest"}} } }, "responses": { "202": { "description": "请求已接收;响应包含已知的逐项接受/拒绝结果,后续状态从 operation 读取", "headers": { "Location": {"$ref": "#/components/headers/OperationLocation"}, "X-Trace-ID": {"$ref": "#/components/headers/TraceID"} }, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/BatchOperation"}} } }, "400": {"$ref": "#/components/responses/ErrorResponse"}, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "409": {"$ref": "#/components/responses/ErrorResponse"}, "422": {"$ref": "#/components/responses/ErrorResponse"}, "503": {"$ref": "#/components/responses/ErrorResponse"} } } }, "/api/v1/operations/{operation_id}": { "get": { "tags": ["Operations"], "operationId": "getOperation", "summary": "查询批量写入和逐项结果", "parameters": [ {"$ref": "#/components/parameters/OperationID"} ], "responses": { "200": { "description": "批量操作状态", "headers": {"X-Trace-ID": {"$ref": "#/components/headers/TraceID"}}, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/BatchOperation"}} } }, "401": {"$ref": "#/components/responses/ErrorResponse"}, "404": {"$ref": "#/components/responses/ErrorResponse"}, "500": {"$ref": "#/components/responses/ErrorResponse"} } } } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "opaque", "description": "认证实现与 claim 格式不属于 v1 契约;服务端必须从已验证主体确定 tenant 与权限。" } }, "parameters": { "SiteID": { "name": "site_id", "in": "path", "required": true, "schema": {"$ref": "#/components/schemas/LogicalID"}, "description": "当前认证租户中的稳定站点 ID" }, "DeviceID": { "name": "device_id", "in": "path", "required": true, "schema": {"$ref": "#/components/schemas/LogicalID"} }, "OperationID": { "name": "operation_id", "in": "path", "required": true, "schema": {"type": "string", "pattern": "^op_[0-9A-HJKMNP-TV-Z]{26}$"} }, "Cursor": { "name": "cursor", "in": "query", "required": false, "schema": {"type": "string", "minLength": 16, "maxLength": 512}, "description": "不透明游标;客户端不得解析或构造" }, "Limit": { "name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50} }, "ModalityFilter": { "name": "modality", "in": "query", "required": false, "schema": {"$ref": "#/components/schemas/Modality"} }, "CapabilityFilter": { "name": "capability", "in": "query", "required": false, "schema": {"$ref": "#/components/schemas/Capability"} }, "DesiredStateFilter": { "name": "desired_state", "in": "query", "required": false, "schema": {"$ref": "#/components/schemas/DesiredState"} }, "ActualStateFilter": { "name": "actual_state", "in": "query", "required": false, "schema": {"$ref": "#/components/schemas/ActualState"} }, "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string", "minLength": 16, "maxLength": 128, "pattern": "^[A-Za-z0-9._:-]+$" }, "description": "同一认证主体、站点、operationId、key 和规范化 body 在 24 小时内返回同一结果;key 相同而 body 不同返回 idempotency_conflict。" }, "IfMatch": { "name": "If-Match", "in": "header", "required": true, "schema": {"type": "string", "minLength": 3, "maxLength": 128}, "description": "使用最近一次 GET/写响应的 opaque ETag;不匹配返回 412 etag_mismatch。" } }, "headers": { "ETag": { "description": "设备资源的不透明并发版本", "schema": {"type": "string"} }, "TraceID": { "description": "不含租户、主机或凭据的排查 ID", "schema": {"type": "string", "minLength": 8, "maxLength": 128} }, "DeviceLocation": { "description": "新建设备资源的相对路径", "schema": {"type": "string", "pattern": "^/api/v1/sites/[^/]+/devices/[^/]+$"} }, "OperationLocation": { "description": "批量操作资源的相对路径", "schema": {"type": "string", "pattern": "^/api/v1/operations/op_[0-9A-HJKMNP-TV-Z]{26}$"} } }, "responses": { "CurrentDevice": { "description": "设备当前表示;endpoint 与 credential ref 永不回显", "headers": { "ETag": {"$ref": "#/components/headers/ETag"}, "X-Trace-ID": {"$ref": "#/components/headers/TraceID"} }, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/Device"}} } }, "CreatedDevice": { "description": "设备已创建;启用设备仍需等待对账收敛", "headers": { "ETag": {"$ref": "#/components/headers/ETag"}, "Location": {"$ref": "#/components/headers/DeviceLocation"}, "X-Trace-ID": {"$ref": "#/components/headers/TraceID"} }, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/Device"}} } }, "AcceptedMutation": { "description": "期望态/配置写入已接受,实际态异步收敛", "headers": { "ETag": {"$ref": "#/components/headers/ETag"}, "X-Trace-ID": {"$ref": "#/components/headers/TraceID"} }, "content": { "application/json": {"schema": {"$ref": "#/components/schemas/MutationReceipt"}} } }, "ErrorResponse": { "description": "稳定错误;不返回内部堆栈、资源存在性或连接秘密", "headers": {"X-Trace-ID": {"$ref": "#/components/headers/TraceID"}}, "content": { "application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}} } } }, "schemas": { "LogicalID": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$" }, "Modality": { "type": "string", "enum": ["video", "radar", "contact", "button", "wearable", "other"] }, "Capability": { "type": "string", "enum": ["video_capture", "audio_capture", "spatial_rule", "telemetry"] }, "DesiredState": { "type": "string", "enum": ["disabled", "enabled"] }, "ActualState": { "type": "string", "enum": ["pending", "online", "offline", "failed"] }, "AdapterStatus": { "type": "string", "enum": ["pending", "ready", "adapter_not_ready", "authentication_failed", "unavailable"] }, "ErrorCode": { "type": "string", "enum": [ "invalid_request", "unauthenticated", "forbidden", "not_found", "conflict", "precondition_required", "etag_mismatch", "idempotency_conflict", "duplicate_serial_number", "quota_exceeded", "quota_projection_unavailable", "quota_projection_invalid", "area_policy_denied", "area_policy_unavailable", "adapter_not_ready", "authentication_failed", "endpoint_credentials_forbidden", "batch_too_large", "service_unavailable", "internal_error" ] }, "DeviceCreate": { "type": "object", "additionalProperties": false, "required": ["serial_number", "name", "modality", "capabilities", "area_id"], "properties": { "serial_number": {"type": "string", "minLength": 1, "maxLength": 128}, "name": {"type": "string", "minLength": 1, "maxLength": 200}, "modality": {"$ref": "#/components/schemas/Modality"}, "capabilities": { "type": "array", "minItems": 1, "maxItems": 16, "uniqueItems": true, "items": {"$ref": "#/components/schemas/Capability"}, "description": "请求接入的能力集合;适配器探测不支持时以稳定错误拒绝或标记 adapter_not_ready。" }, "area_id": {"$ref": "#/components/schemas/LogicalID"}, "endpoint_ref": { "type": "string", "format": "uri", "minLength": 1, "maxLength": 2048, "writeOnly": true, "description": "绝对 URI,禁止 userinfo;只在受控写路径使用。" }, "credential_ref": { "type": "string", "minLength": 1, "maxLength": 512, "writeOnly": true, "description": "不透明密钥引用,不是用户名、密码或 token。" }, "profile_token": { "type": "string", "minLength": 1, "maxLength": 256, "writeOnly": true }, "desired_state": { "allOf": [{"$ref": "#/components/schemas/DesiredState"}], "default": "disabled" } }, "allOf": [ { "if": { "required": ["modality"], "properties": {"modality": {"const": "video"}} }, "then": { "properties": { "capabilities": {"contains": {"const": "video_capture"}, "minContains": 1} } } }, { "if": { "required": ["capabilities"], "properties": { "capabilities": {"contains": {"const": "video_capture"}, "minContains": 1} } }, "then": {"required": ["endpoint_ref", "credential_ref"]} } ] }, "DevicePatch": { "type": "object", "additionalProperties": false, "minProperties": 1, "properties": { "name": {"type": "string", "minLength": 1, "maxLength": 200}, "area_id": {"$ref": "#/components/schemas/LogicalID"}, "endpoint_ref": { "type": "string", "format": "uri", "minLength": 1, "maxLength": 2048, "writeOnly": true, "description": "绝对 URI,禁止 userinfo。" }, "credential_ref": { "type": "string", "minLength": 1, "maxLength": 512, "writeOnly": true }, "profile_token": { "type": "string", "minLength": 1, "maxLength": 256, "writeOnly": true } } }, "DesiredStateChange": { "type": "object", "additionalProperties": false, "required": ["desired_state", "reason"], "properties": { "desired_state": {"$ref": "#/components/schemas/DesiredState"}, "reason": {"type": "string", "minLength": 1, "maxLength": 500} } }, "Device": { "type": "object", "additionalProperties": false, "required": [ "id", "tenant_id", "site_id", "serial_number", "name", "modality", "capabilities", "area_id", "desired_state", "actual_state", "adapter_status", "endpoint_configured", "credential_configured", "generation", "observed_generation", "converged", "failure_count", "projection_versions", "created_at", "updated_at" ], "properties": { "id": {"$ref": "#/components/schemas/LogicalID"}, "tenant_id": { "allOf": [{"$ref": "#/components/schemas/LogicalID"}], "readOnly": true, "description": "只用于回显已授权上下文,不得由客户端写入。" }, "site_id": {"$ref": "#/components/schemas/LogicalID"}, "serial_number": {"type": "string", "minLength": 1, "maxLength": 128}, "name": {"type": "string", "minLength": 1, "maxLength": 200}, "modality": {"$ref": "#/components/schemas/Modality"}, "capabilities": { "type": "array", "items": {"$ref": "#/components/schemas/Capability"}, "uniqueItems": true, "maxItems": 16, "readOnly": true }, "area_id": {"$ref": "#/components/schemas/LogicalID"}, "desired_state": {"$ref": "#/components/schemas/DesiredState"}, "actual_state": {"$ref": "#/components/schemas/ActualState"}, "adapter_status": {"$ref": "#/components/schemas/AdapterStatus"}, "endpoint_configured": {"type": "boolean", "readOnly": true}, "credential_configured": {"type": "boolean", "readOnly": true}, "generation": {"type": "integer", "format": "int64", "minimum": 1, "readOnly": true}, "observed_generation": {"type": "integer", "format": "int64", "minimum": 0, "readOnly": true}, "converged": { "type": "boolean", "readOnly": true, "description": "期望代已被观察且当前无待重试;不等价于所有外部系统健康。" }, "failure_count": {"type": "integer", "minimum": 0, "readOnly": true}, "next_attempt_at": {"type": ["string", "null"], "format": "date-time", "readOnly": true}, "last_error_code": {"type": ["string", "null"], "maxLength": 128, "readOnly": true}, "projection_versions": {"$ref": "#/components/schemas/ProjectionVersions"}, "created_at": {"type": "string", "format": "date-time", "readOnly": true}, "updated_at": {"type": "string", "format": "date-time", "readOnly": true} } }, "ProjectionVersions": { "type": "object", "additionalProperties": false, "required": ["quota_source_version", "area_policy_source_version", "synced_at"], "properties": { "quota_source_version": {"type": ["integer", "null"], "format": "int64", "minimum": 1, "readOnly": true}, "area_policy_source_version": {"type": ["integer", "null"], "format": "int64", "minimum": 1, "readOnly": true}, "synced_at": {"type": ["string", "null"], "format": "date-time", "readOnly": true} } }, "SiteQuotaStatus": { "type": "object", "additionalProperties": false, "required": ["status", "used_video_channels", "max_video_channels", "available_video_channels", "over_limit", "source_version", "synced_at"], "properties": { "status": {"type": "string", "enum": ["current", "unavailable", "invalid"]}, "used_video_channels": {"type": "integer", "minimum": 0, "maximum": 128}, "max_video_channels": {"type": ["integer", "null"], "minimum": 1, "maximum": 128}, "available_video_channels": {"type": ["integer", "null"], "minimum": 0, "maximum": 128}, "over_limit": {"type": "boolean"}, "source_version": {"type": ["integer", "null"], "format": "int64", "minimum": 1}, "synced_at": {"type": ["string", "null"], "format": "date-time"} } }, "PageInfo": { "type": "object", "additionalProperties": false, "required": ["limit", "has_more", "next_cursor"], "properties": { "limit": {"type": "integer", "minimum": 1, "maximum": 100}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "maxLength": 512} } }, "DevicePage": { "type": "object", "additionalProperties": false, "required": ["items", "page", "quota"], "properties": { "items": {"type": "array", "maxItems": 100, "items": {"$ref": "#/components/schemas/Device"}}, "page": {"$ref": "#/components/schemas/PageInfo"}, "quota": {"$ref": "#/components/schemas/SiteQuotaStatus"} } }, "MutationReceipt": { "type": "object", "additionalProperties": false, "required": ["device", "accepted_at", "trace_id"], "properties": { "device": {"$ref": "#/components/schemas/Device"}, "accepted_at": {"type": "string", "format": "date-time"}, "trace_id": {"type": "string", "minLength": 8, "maxLength": 128} } }, "BatchDesiredStateItem": { "type": "object", "additionalProperties": false, "required": ["device_id", "etag", "desired_state"], "properties": { "device_id": {"$ref": "#/components/schemas/LogicalID"}, "etag": {"type": "string", "minLength": 3, "maxLength": 128}, "desired_state": {"$ref": "#/components/schemas/DesiredState"} } }, "BatchDesiredStateRequest": { "type": "object", "additionalProperties": false, "required": ["items", "reason"], "properties": { "items": { "type": "array", "minItems": 1, "maxItems": 128, "items": {"$ref": "#/components/schemas/BatchDesiredStateItem"} }, "reason": {"type": "string", "minLength": 1, "maxLength": 500} } }, "BatchItemResult": { "type": "object", "additionalProperties": false, "required": ["device_id", "status", "error_code", "message", "generation"], "properties": { "device_id": {"$ref": "#/components/schemas/LogicalID"}, "status": {"type": "string", "enum": ["accepted", "rejected", "succeeded", "failed"]}, "error_code": {"oneOf": [{"$ref": "#/components/schemas/ErrorCode"}, {"type": "null"}]}, "message": {"type": ["string", "null"], "maxLength": 500}, "generation": {"type": ["integer", "null"], "format": "int64", "minimum": 1} } }, "BatchOperation": { "type": "object", "additionalProperties": false, "required": ["id", "status", "submitted_at", "completed_at", "results", "trace_id"], "properties": { "id": {"type": "string", "pattern": "^op_[0-9A-HJKMNP-TV-Z]{26}$"}, "status": {"type": "string", "enum": ["queued", "running", "succeeded", "partially_succeeded", "failed"]}, "submitted_at": {"type": "string", "format": "date-time"}, "completed_at": {"type": ["string", "null"], "format": "date-time"}, "results": {"type": "array", "maxItems": 128, "items": {"$ref": "#/components/schemas/BatchItemResult"}}, "trace_id": {"type": "string", "minLength": 8, "maxLength": 128} } }, "FieldError": { "type": "object", "additionalProperties": false, "required": ["field", "code", "message"], "properties": { "field": {"type": "string", "maxLength": 256}, "code": {"type": "string", "maxLength": 128}, "message": {"type": "string", "maxLength": 500} } }, "Problem": { "type": "object", "additionalProperties": false, "required": ["type", "title", "status", "code", "message", "trace_id", "field_errors"], "properties": { "type": {"type": "string", "format": "uri-reference"}, "title": {"type": "string", "maxLength": 200}, "status": {"type": "integer", "minimum": 400, "maximum": 599}, "code": {"$ref": "#/components/schemas/ErrorCode"}, "message": {"type": "string", "maxLength": 1000}, "trace_id": {"type": "string", "minLength": 8, "maxLength": 128}, "field_errors": {"type": "array", "maxItems": 100, "items": {"$ref": "#/components/schemas/FieldError"}} } } } } }