openapi: 3.1.0 info: title: VerseVDI control contract version: 1.0.0 description: Bounded control-plane wire contract. The current SessionRequest shape is control wire version 2; no provider or media endpoints. servers: - url: / tags: - name: authentication - name: resources - name: broker - name: events - name: session-quality - name: gateway paths: /api/v1/auth/login: post: tags: [authentication] operationId: loginBrowserSession requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/LoginRequest responses: '200': description: Browser session established through secure cookies; native identity is structurally forbidden. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/BrowserAuthenticatedSession '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '503': {$ref: '#/components/responses/Unavailable'} /api/v1/auth/refresh: post: tags: [authentication] operationId: rotateNativeCredential requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/RefreshRequest responses: '200': description: Rotated opaque native credential. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/NativeCredential '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/session: get: tags: [authentication] operationId: getAuthenticatedSession security: - browserSession: [] - nativeBearer: [] responses: '200': description: Route-context-specific authenticated subject summary. Browser responses forbid native identity; native bearer responses require it. content: application/json: schema: oneOf: - $ref: ../schemas/control-v1.schema.json#/$defs/BrowserAuthenticatedSession - $ref: ../schemas/control-v1.schema.json#/$defs/NativeAuthenticatedSession '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/tunnel-credentials: post: tags: [authentication] operationId: issueNativeTunnelCredential security: - nativeBearer: [] responses: '200': description: Short-lived native tunnel credential. The response must not be stored. headers: Cache-Control: description: Prevent storage of the credential response. required: true schema: {type: string, const: no-store} content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/NativeTunnelCredential '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/reauth: post: tags: [authentication] operationId: issueReauthenticationGrant security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ReauthRequest responses: '200': description: Short-lived opaque step-up grant. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ReauthGrant '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/logout: post: tags: [authentication] operationId: logoutSession security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] - nativeBearer: [] responses: '204': {description: Session revoked and browser cookies cleared.} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/devices: post: tags: [authentication] operationId: registerDevice security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/DeviceRegistrationRequest responses: '201': description: Single-use device challenge. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/DeviceChallenge '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/devices/{device_id}/prove: post: tags: [authentication] operationId: proveDevice security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] parameters: - $ref: '#/components/parameters/DeviceID' requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/DeviceProofRequest responses: '200': description: Opaque native credential. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/NativeCredential '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/auth/devices/{device_id}: delete: tags: [authentication] operationId: revokeDevice security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] parameters: - $ref: '#/components/parameters/DeviceID' responses: '204': {description: Device and its native authority revoked.} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/resources: get: tags: [resources] operationId: listResources security: - browserSession: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Cursor' responses: '200': description: Subject-scoped bounded resources and entitlements. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ResourceList '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/broker/sessions: post: tags: [broker] operationId: requestBrokerSession description: Control wire version 2 endpoint. Legacy version-1 SessionRequest payloads containing client-supplied policy_snapshot are rejected. security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/SessionRequest responses: '201': description: Durable broker session. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession '202': description: Durable session waiting for an eligible desktop or capacity. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '403': {$ref: '#/components/responses/Forbidden'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}: get: tags: [broker] operationId: getBrokerSession security: - browserSession: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/SessionID' responses: '200': description: Subject-scoped broker session projection. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/broker/sessions/{session_id}/allocate: post: tags: [broker] operationId: allocateBrokerSession security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/SessionID' responses: '200': description: Gateway-only launch manifest with a single-use grant. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ConnectionManifest '202': description: Session remains waiting for an eligible desktop or capacity. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}/reconnect: post: tags: [broker] operationId: reconnectBrokerSession security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/SessionID' requestBody: required: true content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ReconnectRequest responses: '200': description: Gateway-only reconnect manifest with a monotonic sequence. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ConnectionManifest '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}/cancel: post: tags: [broker] operationId: cancelBrokerSession security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] - nativeBearer: [] parameters: - $ref: '#/components/parameters/SessionID' responses: '204': {description: Cancellation accepted idempotently.} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/session-quality-limits: get: tags: [session-quality] operationId: getSessionQualityLimits security: [{nativeBearer: []}] responses: '200': {description: 'Current global native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '401': {$ref: '#/components/responses/Unauthorized'} /api/v1/session-quality-limits/assignments/{assignment_id}: get: tags: [session-quality] operationId: getAssignmentSessionQualityLimits security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/AssignmentID'}] responses: '200': {description: 'Current assignment-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/session-quality-limits/pools/{pool_id}: get: tags: [session-quality] operationId: getPoolSessionQualityLimits security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/PoolID'}] responses: '200': {description: 'Current pool-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/admin/entitlements/{entitlement_id}/display-limit-override: patch: tags: [session-quality] operationId: updateEntitlementDisplayLimitOverride security: - browserSession: [] browserCsrfCookie: [] browserCsrfHeader: [] parameters: [{$ref: '#/components/parameters/EntitlementID'}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/DisplayLimitOverrideRequest}}}} responses: '204': {description: Direct entitlement display override updated.} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '403': {$ref: '#/components/responses/Forbidden'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}/quality-changes: post: tags: [session-quality] operationId: createSessionQualityChange description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes. security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeRequest}}}} responses: '201': {description: 'Durable quality change created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '200': {description: 'Existing idempotent quality change returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}/quality-changes/{operation_id}: get: tags: [session-quality] operationId: getSessionQualityChange description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes. security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}] responses: '200': {description: 'Durable quality change state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/broker/sessions/{session_id}/stop-operations: post: tags: [session-quality] operationId: createSessionStopOperation description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes. security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}] responses: '201': {description: 'Durable Stop operation created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '200': {description: 'Existing idempotent Stop operation returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/broker/sessions/{session_id}/stop-operations/{operation_id}: get: tags: [session-quality] operationId: getSessionStopOperation description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes. security: [{nativeBearer: []}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}] responses: '200': {description: 'Durable Stop operation state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '401': {$ref: '#/components/responses/Unauthorized'} '404': {$ref: '#/components/responses/NotFound'} /api/v1/gateway/quality-work: post: tags: [gateway] operationId: acquireGatewayQualityWork description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and supplied operation/revision/lease generation coordinates. A `poll` acquisition omits unknown operation coordinates and discovers pending session work after a lost prompt; `prompt` and `observation` coordinates MUST match exactly. Maximum JSON body: 16384 bytes. security: [{gatewayMutualTLS: []}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWorkRequest}}}} responses: '200': {description: 'Leased quality work or reconciliation observation. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWork}}}} '204': {description: No matching work is available.} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/gateway/quality-ack: post: tags: [gateway] operationId: acknowledgeGatewayQualityWork description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, operation, revision, and monotonic lease generation tuple. Stale lease generations MUST be rejected. `applied` requires `current_applied_revision == revision`; `proven_prior` requires `current_applied_revision < revision`; `unknown` forbids `current_applied_revision` and makes no applied-revision assertion. Maximum JSON body: 16384 bytes. security: [{gatewayMutualTLS: []}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityAck}}}} responses: '204': {description: Quality acknowledgement committed idempotently.} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/gateway/stop-work: post: tags: [gateway] operationId: acquireGatewayStopWork description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and any supplied operation coordinate. A `poll` acquisition omits the unknown operation ID and discovers pending session work after a lost prompt; a `prompt` coordinate MUST match exactly. Maximum JSON body: 16384 bytes. security: [{gatewayMutualTLS: []}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWorkRequest}}}} responses: '200': {description: 'One-way non-reissued Stop work claim. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWork}}}} '204': {description: No matching Stop work is available.} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/gateway/stop-ack: post: tags: [gateway] operationId: acknowledgeGatewayStopWork description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, and operation tuple. Maximum JSON body: 16384 bytes. security: [{gatewayMutualTLS: []}] requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopAck}}}} responses: '204': {description: Stop acknowledgement committed idempotently.} '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} /api/v1/events: get: tags: [events] operationId: resumeUserEvents security: - browserSession: [] - nativeBearer: [] parameters: - name: version in: query required: false schema: {type: string, enum: ['1']} - name: cursor in: query required: false schema: {type: string, maxLength: 512} - name: Upgrade in: header required: false schema: {type: string, enum: [websocket]} - name: Sec-WebSocket-Version in: header required: false schema: {type: string, enum: ['13']} responses: '200': description: Bounded resumable NDJSON event snapshot; WebSocket upgrades emit the same envelopes as text frames. headers: X-VerseVDI-Event-Cursor: schema: {type: string, maxLength: 512} content: application/x-ndjson: schema: $ref: ../schemas/control-v1.schema.json#/$defs/EventEnvelope '400': {$ref: '#/components/responses/InvalidRequest'} '401': {$ref: '#/components/responses/Unauthorized'} '409': {$ref: '#/components/responses/Conflict'} '426': {description: WebSocket version 13 is required.} '503': {$ref: '#/components/responses/Unavailable'} components: securitySchemes: browserSession: type: apiKey in: cookie name: versevdi_session browserCsrfCookie: type: apiKey in: cookie name: versevdi_csrf description: Must be identical to X-CSRF-Token and is checked against Server session state. browserCsrfHeader: type: apiKey in: header name: X-CSRF-Token description: Must be identical to the versevdi_csrf cookie and is checked against Server session state. nativeBearer: type: http scheme: bearer bearerFormat: opaque gatewayMutualTLS: type: mutualTLS parameters: DeviceID: name: device_id in: path required: true schema: {type: string, minLength: 1, maxLength: 128} SessionID: name: session_id in: path required: true schema: {type: string, minLength: 1, maxLength: 128} AssignmentID: name: assignment_id in: path required: true schema: {type: string, minLength: 1, maxLength: 128} PoolID: name: pool_id in: path required: true schema: {type: string, minLength: 1, maxLength: 128} EntitlementID: name: entitlement_id in: path required: true schema: {type: string, minLength: 1, maxLength: 128} OperationID: name: operation_id in: path required: true schema: {type: string, format: uuid, pattern: '^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'} Limit: name: limit in: query required: false schema: {type: integer, minimum: 1, maximum: 100, default: 100} Cursor: name: cursor in: query required: false schema: {type: string, maxLength: 512} IdempotencyKey: name: Idempotency-Key in: header required: true schema: {type: string, minLength: 1, maxLength: 256} responses: InvalidRequest: description: The request is invalid. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope Unauthorized: description: Authentication or credential validation failed without revealing credential state. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope Forbidden: description: The authenticated subject is not entitled to the requested action. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope NotFound: description: The resource is unavailable to this subject. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope Conflict: description: The expected version, protocol version, idempotency, or event retention boundary conflicts. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope Unavailable: description: The authoritative service is temporarily unavailable. content: application/json: schema: $ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope