{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "DeviceMirror Cloud API",
    "version": "0.2.0",
    "description": "DeviceMirror cloud workspace API. Phone frame generation and action execution may use a clearly labeled simulated adapter; identity, ownership, credentials, usage, WebRTC signaling, real model inference and sandbox billing remain real. Protected routes require a verified Google browser session or a scoped revocable API Bearer credential. Browser mutations require an Origin header exactly equal to the server origin. Requests from other explicit origins are rejected; this API does not offer cross-origin browser CORS access. Server-side callers may omit Origin. No implicit action retry/idempotency guarantee exists. All authenticated API traffic is additionally limited to 180 requests/minute/account; limits are shared across devices. UTC monthly account allowances: Free 2 devices / 3,000 operations / 60 video minutes / 10 model requests; Pro sandbox 10 / 100,000 / 1,000 / 300. Deployment-wide public-pilot caps additionally limit all accounts together to 20 undeleted simulated devices, 2,000 reserved video minutes per UTC month and 20 model requests per UTC day; capacity is persisted in D1. A screenshot or action attempt reserves one operation before downstream execution. Video startup admission reserves five minutes from the account and deployment; failed creation rolls back these reservations, while successful leases are not refunded on early close. A plan downgrade can return 402 for device use beyond the current plan's oldest allowed registrations. General JSON body limit 64 KiB; SDP 120,000 bytes. API errors use Error; Better Auth and MCP maintain their own protocol error forms. Default model uses a server-native Workers AI binding with @cf/mistralai/mistral-small-3.1-24b-instruct; optional server-only external MODEL_API_KEY plus CHAT_MODEL takes priority. The confirmed Workers Free account has a shared 10,000-neuron daily hard allowance, not a guaranteed number of agent tasks; exhaustion rejects inference and exact remaining usage is unknown. Realtime SFU and TURN were activated after explicit user approval and their secrets are configured. Real publisher VP8 connection has been established; complete subscriber/browser cloud video acceptance is still pending. Fresh receiving sessions use an SFU-generated offer, not an initial client offer.",
    "contact": {
      "name": "DeviceMirror documentation",
      "url": "https://device-mirror.jiulong.hu/docs"
    }
  },
  "servers": [
    {
      "url": "https://device-mirror.jiulong.hu",
      "description": "Primary Cloudflare deployment"
    },
    {
      "url": "http://127.0.0.1:8787",
      "description": "Local Wrangler development with local D1 and approved OAuth callback"
    }
  ],
  "tags": [
    {
      "name": "Workspace",
      "description": "Current authenticated identity, entitlements, usage and provider configuration."
    },
    {
      "name": "Devices",
      "description": "Owned device registration, connection state, screen capture and acknowledged input."
    },
    {
      "name": "Video",
      "description": "Bounded receiving leases and Cloudflare Realtime SDP signaling; media/control are separate."
    },
    {
      "name": "Credentials",
      "description": "Browser-managed scoped, revocable and expiring API credentials."
    },
    {
      "name": "MCP",
      "description": "Standard JSON-RPC Streamable HTTP interface for external MCP clients."
    },
    {
      "name": "Agent",
      "description": "Server-side vision-model loop and persistent per-user conversations."
    },
    {
      "name": "Billing",
      "description": "Stripe sandbox Checkout, customer portal and authoritative subscription synchronization."
    },
    {
      "name": "Administration",
      "description": "Server-authorized bounded operational views; no secret data."
    },
    {
      "name": "Connector",
      "description": "Private outbound source protocol and server-proxied SFU publication."
    },
    {
      "name": "Authentication",
      "description": "Google OAuth and secure sessions managed by Better Auth."
    },
    {
      "name": "Health",
      "description": "Public liveness and configuration-readiness checks."
    }
  ],
  "security": [
    {
      "ApiCredential": []
    },
    {
      "BrowserSession": []
    },
    {
      "LocalBrowserSession": []
    }
  ],
  "paths": {
    "/v1/me": {
      "get": {
        "operationId": "getWorkspace",
        "summary": "Read current identity, entitlement, usage and readiness",
        "tags": ["Workspace"],
        "description": "A verified browser session or any valid API credential can inspect its own account. Readiness flags reflect configuration, not a completed end-to-end test. Monthly counters use UTC YYYY-MM.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/Principal"
                    },
                    "entitlement": {
                      "$ref": "#/components/schemas/Entitlement"
                    },
                    "usage": {
                      "$ref": "#/components/schemas/Usage"
                    },
                    "readiness": {
                      "$ref": "#/components/schemas/Readiness"
                    }
                  },
                  "required": ["user", "entitlement", "usage", "readiness"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        }
      }
    },
    "/v1/devices": {
      "get": {
        "operationId": "listDevices",
        "summary": "List authorized devices",
        "tags": ["Devices"],
        "description": "Lists only the user's undeleted devices, additionally filtered by a scoped credential's deviceIds. Online means a connector heartbeat was seen within 45 seconds.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    }
                  },
                  "required": ["devices"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "x-required-scopes": ["devices:read"]
      },
      "post": {
        "operationId": "enrollDevice",
        "summary": "Register a physical or simulated device",
        "tags": ["Devices"],
        "description": "Browser session only. The one-time connectorToken authenticates only this device's outbound bridge; it is not an API credential. Enrollment atomically enforces the account's device allowance and the deployment-wide cap of 20 undeleted simulated registrations. Capture the token immediately; it cannot be retrieved later. Enrollment is limited to 10/minute.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device": {
                      "$ref": "#/components/schemas/Device"
                    },
                    "connectorToken": {
                      "type": "string",
                      "description": "Returned once on successful creation; store privately. Never retrievable later."
                    }
                  },
                  "required": ["device", "connectorToken"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceEnrollment"
              },
              "example": {
                "name": "Pacific Demo",
                "kind": "simulated"
              }
            }
          }
        }
      }
    },
    "/v1/devices/{deviceId}": {
      "get": {
        "operationId": "getDevice",
        "summary": "Read one device and its online status",
        "tags": ["Devices"],
        "description": "",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device": {
                      "$ref": "#/components/schemas/Device"
                    }
                  },
                  "required": ["device"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "x-required-scopes": ["devices:read"],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      },
      "delete": {
        "operationId": "deleteDevice",
        "summary": "Delete a device registration and revoke its connector",
        "tags": ["Devices"],
        "description": "Soft-deletes the owned registration, changes its connector hash, and disconnects its current bridge. Reconnecting requires a new enrollment.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true
                    }
                  },
                  "required": ["deleted"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/connector-token": {
      "post": {
        "operationId": "rotateConnectorToken",
        "summary": "Rotate a device connector credential",
        "tags": ["Devices"],
        "description": "Browser session only. Disconnects the current bridge and returns a new token once. Update the source configuration before reconnecting.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connectorToken": {
                      "type": "string",
                      "description": "Returned once on successful creation; store privately. Never retrievable later."
                    }
                  },
                  "required": ["connectorToken"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/screenshot": {
      "get": {
        "operationId": "captureScreenshot",
        "summary": "Request a fresh device screenshot",
        "tags": ["Devices"],
        "description": "Makes a real RPC to the connected bridge. Costs one operation, including an attempt whose downstream execution fails. Limited to 30 captures/minute/account. This endpoint is not the video transport.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Screenshot"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "504": {
            "$ref": "#/components/responses/Error504"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:read"],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/actions": {
      "post": {
        "operationId": "executeDeviceAction",
        "summary": "Execute a tap, swipe, text or key action",
        "tags": ["Devices"],
        "description": "Coordinates use native device pixels, not normalized percentages. x/endX must be below reported width; y/endY below height. Costs one operation and is limited to 60/minute/account. Input is not idempotent: a timeout can occur after execution. Inspect a new screenshot before retrying an uncertain action. Additional action properties are rejected.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "504": {
            "$ref": "#/components/responses/Error504"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:control"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceAction"
              },
              "example": {
                "type": "tap",
                "x": 195,
                "y": 422
              }
            }
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/credentials": {
      "get": {
        "operationId": "listCredentials",
        "summary": "List API credential metadata",
        "tags": ["Credentials"],
        "description": "Returns at most 100 credential metadata records, including revoked/expired records. Secret values and hashes are never returned.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credentials": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Credential"
                      }
                    }
                  },
                  "required": ["credentials"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        }
      },
      "post": {
        "operationId": "createCredential",
        "summary": "Create a scoped API credential",
        "tags": ["Credentials"],
        "description": "Secret token is returned once. Default expiry 90 days; at most 20 active unexpired credentials. All requested device restrictions must be owned by this account. An empty deviceIds array grants all owned devices. Limited to 10 creations/minute.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credential": {
                      "$ref": "#/components/schemas/Credential"
                    },
                    "token": {
                      "type": "string",
                      "description": "Returned once on successful creation; store privately. Never retrievable later."
                    }
                  },
                  "required": ["credential", "token"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCredential"
              },
              "example": {
                "name": "My MCP client",
                "scopes": ["devices:read", "devices:control"],
                "deviceIds": ["dev_3a38975408cf42188e4f754cf4ca3a37"],
                "expiresInDays": 90
              }
            }
          }
        }
      }
    },
    "/v1/credentials/{credentialId}": {
      "delete": {
        "operationId": "revokeCredential",
        "summary": "Revoke one API credential",
        "tags": ["Credentials"],
        "description": "Only this user's active credentials can be revoked. New requests then fail authentication and associated media leases are revoked/closed, with any unconfirmed upstream cleanup queued for retry. A second revocation returns 404.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "const": true
                    }
                  },
                  "required": ["revoked"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "parameters": [
          {
            "name": "credentialId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Credential id returned by createCredential/listCredentials."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/video-sessions": {
      "post": {
        "operationId": "createVideoSession",
        "summary": "Start a five-minute authorized WebRTC viewing lease",
        "tags": ["Video"],
        "description": "Atomically admits at most three viewers/device and six/account, then reserves five minutes from both the account monthly allowance and the deployment-wide 2,000-minute UTC monthly cap. The source may publish only after a charged open viewer admission. Creation failures close the admission and roll back charged video reservations; an established lease is not refunded on early close. Starts/reuses this device's screen publication and creates a separate receiving SFU session. Limited to 6 creations/minute/account. Requires activated Cloudflare Realtime and configured secrets; activation and secrets are now configured after user approval. Subscriber playback is not yet accepted. Control remains separate from media. Receiving negotiation is server-offer; POST {} to tracks, then answer its offer through PUT renegotiate.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoLease"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "504": {
            "$ref": "#/components/responses/Error504"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:video"],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/video-sessions/{leaseId}/tracks": {
      "post": {
        "operationId": "subscribeVideoTrack",
        "summary": "Negotiate the remote screen track",
        "tags": ["Video"],
        "description": "For a fresh receiving-only PeerConnection with its track handler installed, POST {}. Do not add a local transceiver, create a local offer, send a MID, or send sessionDescription in this request. The backend requests the authorized remote screen publication from Cloudflare without a local offer and returns the SFU offer plus receiving MID. Apply that offer, create/set a local answer, wait for ICE gathering, then PUT the answer to this lease's renegotiate endpoint. A lease permits one track negotiation: concurrent/repeated requests return 409 already_negotiated. The server rechecks lease, credential and device validity and queues cleanup on failure/revocation. A new lease is required after an uncertain negotiation.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:video"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {}
              },
              "example": {}
            }
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          },
          {
            "name": "leaseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Application video lease id returned by createVideoSession, not the Cloudflare sessionId."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/video-sessions/{leaseId}/renegotiate": {
      "put": {
        "operationId": "renegotiateVideo",
        "summary": "Complete an immediate SDP renegotiation",
        "tags": ["Video"],
        "description": "Required completion of server-offer subscriber setup: after applying the SFU offer from POST tracks, create/set the local SDP answer, await ICE gathering and PUT only that answer here. Continue only after this API accepts it; local signalingState stable alone is not server confirmation. Lease must be owned, open, unexpired and within plan capacity. Limited to four renegotiation requests/minute/lease. Serialize SDP mutations.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:video"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sessionDescription": {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "answer"
                      },
                      "sdp": {
                        "type": "string",
                        "minLength": 10,
                        "maxLength": 100000
                      }
                    },
                    "required": ["type", "sdp"],
                    "additionalProperties": false
                  }
                },
                "required": ["sessionDescription"],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          },
          {
            "name": "leaseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Application video lease id returned by createVideoSession, not the Cloudflare sessionId."
          }
        ]
      }
    },
    "/v1/devices/{deviceId}/video-sessions/{leaseId}": {
      "delete": {
        "operationId": "closeVideoSession",
        "summary": "Close a viewing lease",
        "tags": ["Video"],
        "description": "Revokes an owned lease locally before any upstream cleanup. Returns closed:true and cleanupPending; true cleanupPending means remote SFU closure remains unconfirmed and will be retried by an alarm/scheduled cleanup. Repeated close can retry pending cleanup. The source publisher stops after the last open viewer closes. Deleting a device, rotating its connector or revoking the API credential also closes associated leases. Successful lease reservations are not refunded on early stop.",
        "security": [
          {
            "ApiCredential": []
          },
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["closed", "cleanupPending"],
                  "properties": {
                    "closed": {
                      "const": true
                    },
                    "cleanupPending": {
                      "type": "boolean",
                      "description": "True means the local lease is revoked but SFU track closure is not yet confirmed. A scheduled retry remains queued; this is not a claim that upstream cleanup succeeded."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:video"],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          },
          {
            "name": "leaseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Application video lease id returned by createVideoSession, not the Cloudflare sessionId."
          }
        ]
      }
    },
    "/v1/billing": {
      "get": {
        "operationId": "getBilling",
        "summary": "Read subscription records and entitlement",
        "tags": ["Billing"],
        "description": "Browser session only. Reads persisted provider-synchronized subscriptions; does not itself call Stripe. Only a valid active/trialing, unexpired configured price grants Pro.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entitlement": {
                      "$ref": "#/components/schemas/Entitlement"
                    },
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    },
                    "ready": {
                      "type": "boolean"
                    },
                    "sandbox": {
                      "const": true
                    }
                  },
                  "required": [
                    "entitlement",
                    "subscriptions",
                    "ready",
                    "sandbox"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "operationId": "createCheckout",
        "summary": "Create a Stripe sandbox subscription Checkout session",
        "tags": ["Billing"],
        "description": "Browser session only; no price or account can be chosen by a client. Uses the configured monthly sandbox price and a five-minute-window idempotency key. An active Pro subscription returns 409. A success redirect alone grants no entitlement.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostedBillingPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/v1/billing/portal": {
      "post": {
        "operationId": "createBillingPortal",
        "summary": "Open the Stripe sandbox customer portal",
        "tags": ["Billing"],
        "description": "Uses the server-mapped Stripe customer only. Requires a previously created customer; otherwise 409. Portal supports configured subscription cancellation and billing details.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostedBillingPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/v1/billing/refresh": {
      "post": {
        "operationId": "refreshBilling",
        "summary": "Reconcile subscription state from Stripe",
        "tags": ["Billing"],
        "description": "Fetches up to 100 current/historical subscriptions for the mapped customer and re-evaluates entitlement. Useful after Checkout or portal changes. No customer is a successful free entitlement.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entitlement": {
                      "$ref": "#/components/schemas/Entitlement"
                    },
                    "sandbox": {
                      "const": true
                    }
                  },
                  "required": ["entitlement", "sandbox"]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/v1/billing/webhook": {
      "post": {
        "operationId": "receiveStripeWebhook",
        "summary": "Receive a signed Stripe sandbox event",
        "tags": ["Billing"],
        "description": "Stripe delivery endpoint, not a user API. Verifies Stripe-Signature with Web Crypto and a 300-second tolerance; rejects live events. Handles customer.subscription.created/updated/deleted and checkout.session.completed/async_payment_succeeded. Fetches authoritative current subscription state instead of trusting delayed payload state. Event IDs are persisted only after success; duplicate processed IDs return received:true,duplicate:true. Retry delivery after a non-2xx response.",
        "security": [
          {
            "StripeSignature": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "const": true
                    },
                    "duplicate": {
                      "type": "boolean"
                    }
                  },
                  "required": ["received"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StripeEvent"
              }
            }
          },
          "description": "Exact raw Stripe JSON bytes must be preserved. Re-encoding an event invalidates its signature."
        }
      }
    },
    "/v1/chat": {
      "post": {
        "operationId": "runDeviceAgent",
        "summary": "Run the server-side screenshot/model/action loop",
        "tags": ["Agent"],
        "description": "Browser session only. Default real vision model is native Cloudflare Workers AI via AI binding and WORKERS_AI_MODEL=@cf/mistralai/mistral-small-3.1-24b-instruct. Optional server-only MODEL_API_KEY plus CHAT_MODEL takes priority; MODEL_BASE_URL may select an external OpenAI-compatible endpoint. Captures the initial screen, then a serial vision/tool loop may request screenshots and at most four actions, with five model steps, a 1,200-token output cap and a 90-second deadline. Subsequent requests may send conversationId with the same device. Normal device ownership/plan/operation quotas apply. Per-account model quota is 10/month Free or 300/month Pro, additionally capped at 20 requests/UTC day for the whole pilot. The confirmed Workers Free account rejects inference after its shared 10,000-neuron daily allowance; this is not a guarantee that all 20 daily pilot tasks fit, and remaining neurons are unknown. No configured native or external model returns 503; provider failure/exhaustion returns a real error, never a fabricated reply. A failed request may already have applied actions; inspect before retrying.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "504": {
            "$ref": "#/components/responses/Error504"
          },
          "402": {
            "$ref": "#/components/responses/Error402"
          }
        },
        "x-required-scopes": ["devices:control", "devices:read"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              },
              "example": {
                "deviceId": "dev_3a38975408cf42188e4f754cf4ca3a37",
                "message": "Describe the screen and tap the center once."
              }
            }
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "operationId": "listConversations",
        "summary": "List saved device-agent conversations",
        "tags": ["Agent"],
        "description": "At most 50 conversations owned by the signed-in user, newest updated first.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "conversations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      }
                    }
                  },
                  "required": ["conversations"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        }
      }
    },
    "/v1/conversations/{conversationId}": {
      "get": {
        "operationId": "getConversation",
        "summary": "Read saved conversation messages",
        "tags": ["Agent"],
        "description": "At most 100 messages in chronological creation order; only the owning browser user can read them.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    }
                  },
                  "required": ["messages"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Conversation owned by the signed-in user."
          }
        ]
      },
      "delete": {
        "operationId": "deleteConversation",
        "summary": "Delete an owned conversation",
        "tags": ["Agent"],
        "description": "Deletes only a row owned by the signed-in user, with message cascade. Returns deleted:true even when no owned row matched; it never deletes another user's conversation.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true
                    }
                  },
                  "required": ["deleted"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Conversation owned by the signed-in user."
          }
        ]
      }
    },
    "/v1/admin/overview": {
      "get": {
        "operationId": "getAdminOverview",
        "summary": "Read bounded operational records",
        "tags": ["Administration"],
        "description": "Requires verified Google email on server ADMIN_EMAILS allowlist. API credentials cannot access this route. Returns at most 100 rows per user/device/subscription/usage collection, 20 webhook metadata records, total counts and configuration readiness. It excludes OAuth tokens, session values, credential hashes and provider secrets.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOverview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          }
        }
      }
    },
    "/v1/connect/{deviceId}": {
      "get": {
        "operationId": "connectDeviceBridge",
        "summary": "Upgrade an authenticated outbound device connection",
        "tags": ["Connector"],
        "description": "Device bridge only, using its dm_device_ token. Send Upgrade:websocket. Cloud messages are {id,type,payload}, type screenshot/action/video.start/video.stop/status; respond {id,result} or {id,error:{code,message}}. Send {type:\"heartbeat\",state,media} every 15 seconds. A missing heartbeat for 45 seconds makes status offline. Reported state.kind must match registration; integer width/height must be 1..8192. Persisted lastSeenAt writes are coalesced to 60 seconds unless geometry changes, while live liveness updates each accepted heartbeat. A heartbeat faster than five seconds or more than 120 inbound messages/minute closes the socket. One active bridge/device; replacement disconnects the older socket. Normal RPC deadline 15 seconds; video.start 35 seconds. At most eight pending cloud RPCs/device. Connector REST calls are limited to 120/minute/device.",
        "security": [
          {
            "ConnectorCredential": []
          }
        ],
        "responses": {
          "101": {
            "description": "Switching Protocols. Subsequent frames follow the connector JSON protocol documented in the operation description."
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "426": {
            "description": "A WebSocket Upgrade request is required."
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/connect/{deviceId}/media/session": {
      "post": {
        "operationId": "createPublisherSession",
        "summary": "Create an SFU publisher session for this device",
        "tags": ["Connector"],
        "description": "Only the enrolled source may request this. Server-side SFU secrets are never disclosed. The resulting publisher session is bound to the device and current connector hash. Requires a live charged viewer admission; an unadmitted publisher receives 403 viewer_required. Shared publisher admission rate is 6/minute/device.",
        "security": [
          {
            "ConnectorCredential": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sessionId": {
                      "type": "string"
                    },
                    "iceServers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IceServer"
                      }
                    }
                  },
                  "required": ["sessionId", "iceServers"],
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/connect/{deviceId}/media/tracks": {
      "post": {
        "operationId": "publishScreenTrack",
        "summary": "Publish the authorized screen track",
        "tags": ["Connector"],
        "description": "Exactly one local track named screen. sessionId must belong to this device and connector credential. Body max 120,000 bytes. Requires a live charged viewer admission; an unadmitted publisher receives 403 viewer_required. Shared publisher admission rate is 6/minute/device.",
        "security": [
          {
            "ConnectorCredential": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "sessionDescription": {
                    "$ref": "#/components/schemas/SessionDescription"
                  },
                  "tracks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "location": {
                          "const": "local"
                        },
                        "mid": {
                          "type": "string",
                          "maxLength": 10
                        },
                        "trackName": {
                          "const": "screen"
                        }
                      },
                      "required": ["location", "mid", "trackName"],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 1
                  }
                },
                "required": ["sessionId", "sessionDescription", "tracks"],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/v1/connect/{deviceId}/media/renegotiate": {
      "put": {
        "operationId": "renegotiatePublisher",
        "summary": "Complete publisher SDP renegotiation",
        "tags": ["Connector"],
        "description": "Publisher session must be owned by the current connector. Serialize negotiations per session. Body max 120,000 bytes. Requires a live charged viewer admission; an unadmitted publisher receives 403 viewer_required. Shared publisher admission rate is 6/minute/device.",
        "security": [
          {
            "ConnectorCredential": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "502": {
            "$ref": "#/components/responses/Error502"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "sessionDescription": {
                    "$ref": "#/components/schemas/SessionDescription"
                  }
                },
                "required": ["sessionId", "sessionDescription"],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Owned device ID returned by enrollment or list_devices. A credential may further restrict it."
          }
        ]
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpRequest",
        "summary": "Send a standard Streamable HTTP MCP message",
        "tags": ["MCP"],
        "description": "Standard MCP JSON-RPC over stateless Streamable HTTP. Use a real MCP SDK/client; send Accept: application/json, text/event-stream and let it handle initialization and notifications. No server-initiated GET stream or DELETE session endpoint. Tools: list_devices, get_device, screenshot, device_action. Tool scope/ownership/quota match HTTP; screenshot returns an MCP image content block. Session cookies are rejected. Per-account MCP limit 60/minute; body max 64 KiB. Tool failures may be protocol-level result.isError with HTTP 200.",
        "security": [
          {
            "ApiCredential": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcMessage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "415": {
            "$ref": "#/components/responses/Error415"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "202": {
            "description": "Accepted MCP notification with no response body."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcMessage"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list",
                "params": {}
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "application/json, text/event-stream"
            }
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Negotiated protocol version sent by the MCP client after initialization."
          }
        ]
      },
      "get": {
        "operationId": "mcpUnsupportedGet",
        "summary": "Unsupported for this stateless MCP deployment",
        "tags": ["MCP"],
        "security": [
          {
            "ApiCredential": []
          }
        ],
        "responses": {
          "405": {
            "description": "Authenticated callers receive a JSON-RPC method-not-supported error. Use POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcMessage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          }
        }
      },
      "delete": {
        "operationId": "mcpUnsupportedDelete",
        "summary": "Unsupported for this stateless MCP deployment",
        "tags": ["MCP"],
        "security": [
          {
            "ApiCredential": []
          }
        ],
        "responses": {
          "405": {
            "description": "Authenticated callers receive a JSON-RPC method-not-supported error. Use POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcMessage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          }
        }
      }
    },
    "/api/auth/sign-in/social": {
      "post": {
        "operationId": "signInWithGoogle",
        "summary": "Start Google OAuth through Better Auth",
        "tags": ["Authentication"],
        "description": "Browser authentication endpoint managed by Better Auth. The normal console starts Google redirect OAuth and follows its trusted callback; cookies and state protect the flow. Better Auth also accepts a genuine Google idToken and verifies its provider signature/audience server-side, establishing a normal session without a redirect round trip. This is not a test login or permission bypass. Use the exact configured origin. Password sign-in/registration are disabled. Authentication errors retain Better Auth's own representation.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "redirect": {
                      "type": "boolean"
                    },
                    "token": {
                      "type": "string",
                      "description": "Provider-verification sign-in may return a session token; keep private. Browser authentication uses the HttpOnly cookie."
                    },
                    "user": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "403": {
            "description": "Forbidden browser origin or authentication request in Better Auth/application error format.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "description": "Better Auth persistent authentication rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "provider": {
                    "const": "google"
                  },
                  "callbackURL": {
                    "type": "string",
                    "example": "https://device-mirror.jiulong.hu/console"
                  },
                  "idToken": {
                    "type": "object",
                    "required": ["token"],
                    "properties": {
                      "token": {
                        "type": "string",
                        "description": "Genuine Google-signed ID JWT for the configured client audience. Sensitive; never log or commit."
                      },
                      "nonce": {
                        "type": "string"
                      },
                      "accessToken": {
                        "type": "string"
                      },
                      "refreshToken": {
                        "type": "string"
                      },
                      "expiresAt": {
                        "type": "number"
                      }
                    }
                  }
                },
                "required": ["provider"]
              }
            }
          }
        }
      }
    },
    "/api/auth/get-session": {
      "get": {
        "operationId": "getBrowserSession",
        "summary": "Inspect the Better Auth browser session",
        "tags": ["Authentication"],
        "description": "Returns null when unauthenticated, otherwise Better Auth user/session metadata. Protected application APIs additionally require verified email. Better Auth owns this endpoint and its error representation.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "user": {
                          "type": "object",
                          "additionalProperties": true
                        },
                        "session": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "required": ["user", "session"]
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden browser origin or authentication request in Better Auth/application error format.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "description": "Better Auth persistent authentication rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/sign-out": {
      "post": {
        "operationId": "signOut",
        "summary": "End the current browser session",
        "tags": ["Authentication"],
        "description": "Better Auth invalidates the current session and clears its cookies. Use same-origin browser requests. Better Auth owns this endpoint and its error representation.",
        "security": [
          {
            "BrowserSession": []
          },
          {
            "LocalBrowserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "403": {
            "description": "Forbidden browser origin or authentication request in Better Auth/application error format.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "description": "Better Auth persistent authentication rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/callback/google": {
      "get": {
        "operationId": "googleOAuthCallback",
        "summary": "Google OAuth callback managed by Better Auth",
        "tags": ["Authentication"],
        "security": [],
        "description": "Register this exact URL with Google OAuth. The browser reaches it from Google with code/state parameters; callers should not invoke it directly. State and provider credentials are verified by Better Auth. Redirect destination is the trusted callbackURL chosen during sign-in.",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "error",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the selected trusted callback URL after successful login, or an authentication error destination. Session cookies are set on success."
          },
          "400": {
            "description": "Invalid provider callback or OAuth state, in Better Auth error format."
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Read Worker liveness",
        "tags": ["Health"],
        "description": "",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "ok"
                    },
                    "service": {
                      "const": "DeviceMirror"
                    },
                    "version": {
                      "type": "string"
                    }
                  },
                  "required": ["status", "service", "version"]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "operationId": "getReadiness",
        "summary": "Read database and provider configuration readiness",
        "tags": ["Health"],
        "description": "Performs a D1 SELECT 1 and reports presence of required provider configuration. True configuration flags do not prove external permissions, current provider availability or completed end-to-end acceptance.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-Request-ID": {
                "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "database": {
                      "type": "boolean"
                    },
                    "google": {
                      "type": "boolean"
                    },
                    "billing": {
                      "type": "boolean"
                    },
                    "video": {
                      "type": "boolean"
                    },
                    "model": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "database",
                    "google",
                    "billing",
                    "video",
                    "model"
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiCredential": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dm_api_<43 base64url characters>",
        "description": "Scoped revocable credential created in the console. Stored as a hash; shown once. May restrict devices and expires by default after 90 days."
      },
      "BrowserSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Secure-better-auth.session_token",
        "description": "HTTPS Better Auth HttpOnly session cookie. Browser owns cookie handling; do not extract/share it. Session mutations also require exact same-origin Origin."
      },
      "LocalBrowserSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "better-auth.session_token",
        "description": "Local HTTP development variant of the browser cookie; production uses BrowserSession."
      },
      "ConnectorCredential": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dm_device_<base64url>",
        "description": "Device-specific connector secret. Cannot authenticate user/MCP endpoints. Deleted or rotated credentials cannot reconnect."
      },
      "StripeSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "Stripe-Signature",
        "description": "Stripe-generated timestamped HMAC over exact raw JSON body; verified with server webhook secret and 300-second tolerance. Not a reusable Bearer token."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": ["code", "message"]
          },
          "requestId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": ["error"],
        "example": {
          "error": {
            "code": "device_offline",
            "message": "Device connector is offline"
          },
          "requestId": "b3e9061c-a812-4b10-b4d5-2ae6b1a6e5aa"
        }
      },
      "Principal": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "admin": {
            "type": "boolean"
          },
          "tokenId": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": ["id", "email", "name", "admin"]
      },
      "Scope": {
        "type": "string",
        "enum": ["devices:read", "devices:control", "devices:video"],
        "description": "read permits discovery/status/screenshots; control permits actions; video permits media leases. Scopes are not OAuth scopes: set them when creating an API credential."
      },
      "Entitlement": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "enum": ["free", "pro"]
          },
          "status": {
            "type": "string"
          },
          "deviceLimit": {
            "type": "integer"
          },
          "operationLimit": {
            "type": "integer"
          },
          "videoMinutesLimit": {
            "type": "integer"
          },
          "currentPeriodEnd": {
            "type": ["integer", "null"],
            "description": "Stripe period end as Unix seconds."
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "plan",
          "status",
          "deviceLimit",
          "operationLimit",
          "videoMinutesLimit",
          "currentPeriodEnd",
          "sandbox"
        ]
      },
      "Usage": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}$",
            "example": "2026-10"
          },
          "operations": {
            "type": "integer"
          },
          "videoMinutes": {
            "type": "integer"
          },
          "modelRequests": {
            "type": "integer"
          }
        },
        "required": ["month", "operations", "videoMinutes", "modelRequests"]
      },
      "Readiness": {
        "type": "object",
        "properties": {
          "google": {
            "type": "boolean"
          },
          "billing": {
            "type": "boolean"
          },
          "video": {
            "type": "boolean"
          },
          "model": {
            "type": "boolean"
          }
        },
        "required": ["google", "billing", "video", "model"]
      },
      "Device": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "dev_3a38975408cf42188e4f754cf4ca3a37"
          },
          "name": {
            "type": "string",
            "example": "Pacific Demo"
          },
          "kind": {
            "type": "string",
            "enum": ["simulated", "physical"]
          },
          "width": {
            "type": "integer",
            "example": 390,
            "minimum": 1,
            "maximum": 8192
          },
          "height": {
            "type": "integer",
            "example": 844,
            "minimum": 1,
            "maximum": 8192
          },
          "status": {
            "type": "string",
            "enum": ["online", "offline"]
          },
          "lastSeenAt": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Persisted last-seen metadata is coalesced to approximately one write/minute unless geometry changes; online status uses live 45-second heartbeat freshness."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "kind",
          "width",
          "height",
          "status",
          "lastSeenAt",
          "createdAt"
        ]
      },
      "DeviceEnrollment": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "kind": {
            "type": "string",
            "enum": ["simulated", "physical"]
          }
        },
        "required": ["name", "kind"],
        "additionalProperties": false
      },
      "Screenshot": {
        "type": "object",
        "properties": {
          "mimeType": {
            "type": "string",
            "enum": ["image/png", "image/jpeg"]
          },
          "data": {
            "type": "string",
            "contentEncoding": "base64",
            "maxLength": 2700000,
            "description": "Base64-encoded complete image without a data URI prefix."
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "state": {
            "type": "object",
            "additionalProperties": true,
            "description": "Adapter-reported state. Simulated devices include revision/tap/text/scroll information."
          }
        },
        "required": ["mimeType", "data", "width", "height"]
      },
      "Tap": {
        "type": "object",
        "properties": {
          "type": {
            "const": "tap"
          },
          "x": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          },
          "y": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          }
        },
        "required": ["type", "x", "y"],
        "additionalProperties": false
      },
      "Swipe": {
        "type": "object",
        "properties": {
          "type": {
            "const": "swipe"
          },
          "x": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          },
          "y": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          },
          "endX": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          },
          "endY": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8191,
            "description": "Native device pixels. Must also be strictly less than the device width or height for the relevant axis."
          },
          "durationMs": {
            "type": "integer",
            "minimum": 50,
            "maximum": 3000,
            "default": 300
          }
        },
        "required": ["type", "x", "y", "endX", "endY"],
        "additionalProperties": false
      },
      "TypeText": {
        "type": "object",
        "properties": {
          "type": {
            "const": "type"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        },
        "required": ["type", "text"],
        "additionalProperties": false
      },
      "Key": {
        "type": "object",
        "properties": {
          "type": {
            "const": "key"
          },
          "key": {
            "type": "string",
            "enum": [
              "HOME",
              "BACK",
              "ENTER",
              "BACKSPACE",
              "ESCAPE",
              "VOLUME_UP",
              "VOLUME_DOWN"
            ]
          }
        },
        "required": ["type", "key"],
        "additionalProperties": false
      },
      "DeviceAction": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/Tap"
          },
          {
            "$ref": "#/components/schemas/Swipe"
          },
          {
            "$ref": "#/components/schemas/TypeText"
          },
          {
            "$ref": "#/components/schemas/Key"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "tap": "#/components/schemas/Tap",
            "swipe": "#/components/schemas/Swipe",
            "type": "#/components/schemas/TypeText",
            "key": "#/components/schemas/Key"
          }
        }
      },
      "ActionResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "state": {
            "type": "object",
            "additionalProperties": true
          },
          "executedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true,
        "description": "Successful adapter acknowledgement. Adapter-specific fields are preserved; callers must not assume an unacknowledged action executed. The simulated adapter returns ok/state/executedAt."
      },
      "Credential": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "revokedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "prefix",
          "scopes",
          "deviceIds",
          "createdAt",
          "expiresAt"
        ]
      },
      "CreateCredential": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "minItems": 1,
            "maxItems": 3
          },
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 80
            },
            "maxItems": 10,
            "default": [],
            "description": "Empty means all devices owned by the user, including future registrations. A nonempty array limits access to those owned devices."
          },
          "expiresInDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "default": 90
          }
        },
        "required": ["name", "scopes"],
        "additionalProperties": false
      },
      "SessionDescription": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["offer", "answer"]
          },
          "sdp": {
            "type": "string",
            "minLength": 10,
            "maxLength": 100000
          }
        },
        "required": ["type", "sdp"],
        "additionalProperties": false
      },
      "IceServer": {
        "type": "object",
        "properties": {
          "urls": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "username": {
            "type": "string"
          },
          "credential": {
            "type": "string"
          }
        },
        "required": ["urls"]
      },
      "VideoLease": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sessionId": {
            "type": "string"
          },
          "mode": {
            "const": "sfu"
          },
          "iceServers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IceServer"
            }
          },
          "publication": {
            "type": "object",
            "properties": {
              "sessionId": {
                "type": "string"
              },
              "trackName": {
                "const": "screen"
              }
            },
            "required": ["sessionId", "trackName"]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "negotiation": {
            "const": "server-offer",
            "description": "For a fresh receiver, POST an empty JSON object to the lease tracks endpoint. Apply its SFU offer, create/gather a local answer and PUT that answer to renegotiate."
          }
        },
        "required": [
          "id",
          "sessionId",
          "mode",
          "iceServers",
          "publication",
          "expiresAt",
          "negotiation"
        ],
        "description": "id is the application lease ID used in follow-up URLs; sessionId is a Cloudflare SFU session ID. Leases last five minutes and reserve five minutes from both account and deployment quotas. At most three viewers/device and six/account. Each lease permits one track negotiation; start a new lease after an uncertain negotiation. Publisher admission requires an open, charged viewer lease. Fresh subscriber signaling is server-offer: do not create a local offer or pre-add a video transceiver before requesting the remote track."
      },
      "MediaResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "sessionDescription": {
            "$ref": "#/components/schemas/SessionDescription"
          },
          "requiresImmediateRenegotiation": {
            "type": "boolean"
          },
          "tracks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "mid": {
                  "type": "string"
                },
                "trackName": {
                  "type": "string"
                },
                "location": {
                  "type": "string"
                },
                "sessionId": {
                  "type": "string"
                },
                "errorCode": {
                  "type": "string"
                },
                "errorDescription": {
                  "type": "string"
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true,
        "description": "Cloudflare Realtime SFU response. Apply sessionDescription to your PeerConnection. When requiresImmediateRenegotiation is true and the description is an offer, create an answer and PUT it to renegotiate. Check per-track errors as well as HTTP status."
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "price_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "current_period_end": {
            "type": "integer"
          },
          "cancel_at_period_end": {
            "type": "integer",
            "enum": [0, 1]
          },
          "livemode": {
            "type": "integer",
            "enum": [0, 1]
          }
        },
        "required": [
          "id",
          "price_id",
          "status",
          "current_period_end",
          "cancel_at_period_end",
          "livemode"
        ]
      },
      "HostedBillingPage": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "sandbox": {
            "const": true
          }
        },
        "required": ["url", "sandbox"]
      },
      "ChatRequest": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string",
            "maxLength": 80
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000
          },
          "conversationId": {
            "type": "string",
            "maxLength": 80
          }
        },
        "required": ["deviceId", "message"],
        "additionalProperties": false
      },
      "ChatResult": {
        "type": "object",
        "properties": {
          "conversationId": {
            "type": "string"
          },
          "reply": {
            "type": "string"
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "action": {
                  "$ref": "#/components/schemas/DeviceAction"
                },
                "result": {
                  "$ref": "#/components/schemas/ActionResult"
                },
                "width": {
                  "type": "integer"
                },
                "height": {
                  "type": "integer"
                }
              },
              "additionalProperties": true
            }
          }
        },
        "required": ["conversationId", "reply", "steps"]
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "device_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "device_id", "created_at", "updated_at"]
      },
      "Message": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": ["user", "assistant"]
          },
          "content": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["role", "content", "created_at"]
      },
      "StripeEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "livemode": {
            "const": false
          },
          "data": {
            "type": "object",
            "properties": {
              "object": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "required": ["object"]
          }
        },
        "required": ["id", "type", "livemode", "data"],
        "additionalProperties": true
      },
      "JsonRpcMessage": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "type": ["string", "integer", "null"]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          },
          "result": {},
          "error": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": ["jsonrpc"],
        "additionalProperties": true
      },
      "AdminOverview": {
        "type": "object",
        "properties": {
          "users": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "devices": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "subscriptions": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "webhooks": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "usage": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "users": {
                "type": "integer"
              },
              "devices": {
                "type": "integer"
              },
              "activeSubscriptions": {
                "type": "integer"
              }
            }
          },
          "runtime": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string"
              },
              "database": {
                "type": "string"
              },
              "control": {
                "type": "string"
              },
              "billing": {
                "type": "string"
              },
              "media": {
                "type": "boolean"
              },
              "model": {
                "type": "boolean"
              }
            }
          },
          "limit": {
            "type": "integer",
            "const": 100
          }
        },
        "required": [
          "users",
          "devices",
          "subscriptions",
          "webhooks",
          "usage",
          "totals",
          "runtime",
          "limit"
        ]
      }
    },
    "responses": {
      "Error400": {
        "description": "Invalid JSON, action, coordinates, SDP, or request parameters.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error401": {
        "description": "Missing, invalid, expired or revoked credential; verified Google session required.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error403": {
        "description": "Missing scope/device grant, forbidden browser Origin, browser-session-only resource or administrator requirement.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error404": {
        "description": "Resource does not exist within the caller's authorized ownership boundary, or lease is expired.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error409": {
        "description": "Device offline, publisher missing, active subscription conflict or customer not initialized.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error413": {
        "description": "Request exceeds the endpoint body limit (64 KiB normally, 120,000 bytes for SDP).",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error415": {
        "description": "Use Content-Type: application/json for JSON requests.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error429": {
        "description": "Per-account rate or quota, deployment-wide pilot capacity, device/credential/viewer concurrency, pending RPC or model request limit exceeded. deployment_capacity uses UTC day/month reset; device_limit can mean the shared simulated-device cap.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error500": {
        "description": "Unexpected service or persistence error; inspect the request ID.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error502": {
        "description": "Adapter/media/model upstream failure; any completed action can remain applied.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error503": {
        "description": "Cloudflare Realtime or model configuration is missing/invalid, or deployment capacity configuration is invalid. A model needs AI binding plus WORKERS_AI_MODEL, or optional server-only MODEL_API_KEY plus CHAT_MODEL (and optional MODEL_BASE_URL).",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error504": {
        "description": "Device RPC timed out or disconnected. Execution status may be unknown; inspect before retrying.",
        "headers": {
          "X-Request-ID": {
            "description": "Correlation ID for diagnostics; errors include the same ID in the response body.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error402": {
        "description": "The device lies outside the current plan device allowance after a downgrade. Upgrade or remove another registration.",
        "headers": {
          "X-Request-ID": {
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Quickstart, MCP client configuration, scopes and video negotiation",
    "url": "https://device-mirror.jiulong.hu/docs"
  }
}
