{
  "openapi": "3.1.0",
  "info": {
    "title": "Cloud Computer AI API",
    "version": "1.0.0",
    "description": "REST endpoints used by the console and the MCP server at /mcp. Send a Clerk session JWT, or a personal access token (ccai_pat_…) when MCP is enabled. Token management requires the Clerk session. No secrets are included in this document. When OWNER_LOCK_ENABLED=1, control routes return 403 code not_owner for a non-owner. Every 429 sets Retry-After and retryAfter. GET /api/computers/{id}/screen-text and the MCP tool screen_text exist when DESKTOP_OCR_ENABLED=1. There is no move_computer tool. Canvas position is PATCH { x, y } on the shared computer record, and that PATCH is owner-locked."
  },
  "servers": [
    { "url": "https://app.cloudcomputerai.com" }
  ],
  "security": [
    { "bearerAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clerk session JWT, or a personal access token ccai_pat_… when MCP_ENABLED=1."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" },
          "retryAfter": { "type": "integer", "description": "Present on HTTP 429. Seconds, matching the Retry-After header. railway_daily_cap is 3600, railway_active_cap is 300, any other 429 is 60." }
        },
        "required": ["error"]
      },
      "Computer": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "status": { "type": "string" },
          "templateId": { "type": ["string", "null"] },
          "templateName": { "type": ["string", "null"] },
          "provider": { "type": ["string", "null"] },
          "ownerUserId": { "type": ["string", "null"] },
          "x": { "type": "number", "description": "Canvas x on the shared computer record" },
          "y": { "type": "number", "description": "Canvas y on the shared computer record" },
          "services": {
            "type": "array",
            "description": "Guest services on this computer when COMPOSE_SERVICES_ENABLED=1.",
            "items": { "$ref": "#/components/schemas/ServiceState" }
          }
        }
      },
      "Template": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "desc": { "type": "string" }
        }
      },
      "ServiceCatalogEntry": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "icon": { "type": "string" },
          "description": { "type": "string" },
          "dependsOn": { "type": "array", "items": { "type": "string" } },
          "ports": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "port": { "type": "integer" },
                "protocol": { "type": "string" }
              }
            }
          },
          "url": { "type": ["string", "null"] }
        },
        "required": ["id", "name"]
      },
      "ServiceState": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "status": { "type": "string", "enum": ["queued", "installing", "healthy", "failed"] },
          "ports": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "addedAt": { "type": ["string", "null"] },
          "healthAt": { "type": ["string", "null"] },
          "error": { "type": ["string", "null"] },
          "dependsOn": { "type": "array", "items": { "type": "string" } }
        },
        "required": ["id", "status"]
      },
      "Pat": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "userId": { "type": "string" },
          "name": { "type": "string" },
          "prefix": { "type": "string" },
          "createdAt": { "type": "string" },
          "lastUsedAt": { "type": ["string", "null"] },
          "revokedAt": { "type": ["string", "null"] },
          "expiresAt": { "type": ["string", "null"] }
        },
        "required": ["id", "userId", "name", "prefix", "createdAt"]
      }
    },
    "responses": {
      "NotOwner": {
        "description": "OWNER_LOCK_ENABLED=1 and the caller is not the owner, a platform admin, or auth bypass. Body is { \"error\": \"Only the owner or an admin can control this computer\", \"code\": \"not_owner\" }. The MCP server surfaces this as isError text. Shared computers stay listable and screenshot-able.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "TooMany": {
        "description": "Rate limited. Retry-After is seconds and matches JSON retryAfter. railway_daily_cap is 3600, railway_active_cap is 300, any other 429 is 60.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the client may retry",
            "schema": { "type": "integer" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    }
  },
  "paths": {
    "/api/flags": {
      "get": {
        "operationId": "getFlags",
        "summary": "Feature flags",
        "security": [],
        "description": "Public. Each boolean is true only when its env var is 1. Defaults are false.",
        "responses": {
          "200": {
            "description": "Flags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agentView": { "type": "boolean", "description": "AGENT_VIEW_ENABLED=1" },
                    "a11yFocusSafe": { "type": "boolean", "description": "A11Y_P3_FOCUS_SAFE=1" },
                    "mcp": { "type": "boolean", "description": "MCP_ENABLED=1" },
                    "ownerLock": { "type": "boolean", "description": "OWNER_LOCK_ENABLED=1" },
                    "desktopFrameA11y": { "type": "boolean", "description": "DESKTOP_FRAME_A11Y=1" },
                    "a11yP2CanvasAlt": { "type": "boolean", "description": "A11Y_P2_CANVAS_ALT=1" },
                    "desktopOcr": { "type": "boolean", "description": "DESKTOP_OCR_ENABLED=1" },
                    "canvasListView": { "type": "boolean", "description": "CANVAS_LIST_VIEW=1" },
                    "composeServices": { "type": "boolean", "description": "COMPOSE_SERVICES_ENABLED=1" }
                  },
                  "required": ["agentView", "a11yFocusSafe", "mcp", "ownerLock", "desktopFrameA11y", "a11yP2CanvasAlt", "desktopOcr", "canvasListView", "composeServices"]
                }
              }
            }
          }
        }
      }
    },
    "/api/computers": {
      "get": {
        "operationId": "listComputers",
        "summary": "List computers",
        "responses": {
          "200": {
            "description": "Computers visible to the caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "computers": { "type": "array", "items": { "$ref": "#/components/schemas/Computer" } }
                  },
                  "required": ["computers"]
                }
              }
            }
          },
          "401": { "description": "Sign in required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "operationId": "launchComputer",
        "summary": "Launch a computer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "templateId": { "type": "string" },
                  "provider": { "type": "string" },
                  "size": { "type": "string" },
                  "vcpu": { "type": "number" },
                  "memoryMb": { "type": "number" },
                  "diskGb": { "type": "number" },
                  "ttlHours": { "type": "number" },
                  "x": { "type": "number" },
                  "y": { "type": "number" },
                  "disclosureAck": { "type": "boolean", "description": "Required true for the Railway template" },
                  "services": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Optional catalog service ids. Queued after the computer is running when COMPOSE_SERVICES_ENABLED=1. Ignored when the flag is off. The 201 body stays { computer }."
                  }
                },
                "required": ["templateId"]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Computer created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "computer": { "$ref": "#/components/schemas/Computer" } },
                  "required": ["computer"]
                }
              }
            }
          },
          "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Sign in required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/TooMany" }
        }
      }
    },
    "/api/computers/{id}": {
      "get": {
        "operationId": "getComputer",
        "summary": "Get a computer",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Computer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "computer": { "$ref": "#/components/schemas/Computer" } },
                  "required": ["computer"]
                }
              }
            }
          },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "patch": {
        "operationId": "updateComputer",
        "summary": "Rename a computer or set its canvas position",
        "description": "Writes name, x, and y onto the shared computer record. Drag, the Move dialog, Alt+Arrow (40px), and Auto-arrange all PATCH these fields. There is no per-user layout and no MCP move_computer tool. When OWNER_LOCK_ENABLED=1 a layout-only { x, y } body is still owner-locked.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "x": { "type": "number" },
                  "y": { "type": "number" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated computer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "computer": { "$ref": "#/components/schemas/Computer" } },
                  "required": ["computer"]
                }
              }
            }
          },
          "403": { "$ref": "#/components/responses/NotOwner" },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "operationId": "deleteComputer",
        "summary": "Delete a computer",
        "description": "Destructive. Confirm with the user before calling. The MCP delete_computer tool also requires confirm to equal exactly \"DELETE <id>\".",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "id": { "type": "string" }
                  }
                }
              }
            }
          },
          "403": { "$ref": "#/components/responses/NotOwner" },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/computers/{id}/start": {
      "post": {
        "operationId": "startComputer",
        "summary": "Start a computer",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "computer": { "$ref": "#/components/schemas/Computer" } }
                }
              }
            }
          },
          "403": { "$ref": "#/components/responses/NotOwner" }
        }
      }
    },
    "/api/computers/{id}/stop": {
      "post": {
        "operationId": "stopComputer",
        "summary": "Stop a computer",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Stopped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "computer": { "$ref": "#/components/schemas/Computer" } }
                }
              }
            }
          },
          "403": { "$ref": "#/components/responses/NotOwner" }
        }
      }
    },
    "/api/computers/{id}/screenshot": {
      "get": {
        "operationId": "screenshotComputer",
        "summary": "Screenshot a computer",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Raw base64 PNG, without a data: prefix",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "pngBase64": { "type": "string" } },
                  "required": ["pngBase64"]
                }
              }
            }
          }
        }
      }
    },
    "/api/computers/{id}/screen-text": {
      "get": {
        "operationId": "screenText",
        "summary": "Read on-screen text with OCR",
        "description": "Enabled when DESKTOP_OCR_ENABLED=1. Uses the same visibility check as screenshot (not the owner lock). Runs tesseract stdin stdout on the PNG, 15s timeout, max image 10MB, cached 60s per computer id and sha1 (max 200 entries). Returns 404 when the flag is off. Returns 501 code ocr_unavailable when tesseract is missing. MCP tool screen_text { id } (readOnlyHint) calls this route and is listed only when the flag is on.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Recognized text",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "text": { "type": "string" },
                    "cachedAt": { "type": "string", "format": "date-time" },
                    "engine": { "type": "string", "const": "tesseract" }
                  },
                  "required": ["text", "cachedAt", "engine"]
                }
              }
            }
          },
          "404": { "description": "Not found, or DESKTOP_OCR_ENABLED is not 1", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "501": { "description": "tesseract is not installed (code ocr_unavailable)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/services": {
      "get": {
        "operationId": "listServices",
        "summary": "List the service catalog",
        "description": "Enabled when COMPOSE_SERVICES_ENABLED=1. Returns 404 { error: \"services disabled\" } when the flag is unset or 0. Install scripts are not included. MCP tool list_services with no id calls this route and is omitted from tools/list when the flag is off.",
        "responses": {
          "200": {
            "description": "Catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceCatalogEntry" } }
                  },
                  "required": ["services"]
                }
              }
            }
          },
          "404": { "description": "COMPOSE_SERVICES_ENABLED is not 1", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/computers/{id}/services": {
      "get": {
        "operationId": "listComputerServices",
        "summary": "List services on a computer",
        "description": "Enabled when COMPOSE_SERVICES_ENABLED=1. MCP tool list_services { id } calls this route.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Services installed or queued on the computer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceState" } }
                  },
                  "required": ["services"]
                }
              }
            }
          },
          "404": { "description": "Computer not found, or services are disabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "operationId": "addService",
        "summary": "Queue a catalog service",
        "description": "Enabled when COMPOSE_SERVICES_ENABLED=1. Body { serviceId }. Dependencies are queued first. Returns 202 with the computer and its services. 400 unknown service. 409 when the computer is not running. MCP tool add_service { id, serviceId } calls this route and is omitted from tools/list when the flag is off. Guest output that contains =, password, or token is dropped from stored errors.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "serviceId": { "type": "string" } },
                "required": ["serviceId"]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Service queued. Install continues in the background.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "computer": { "$ref": "#/components/schemas/Computer" },
                    "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceState" } }
                  },
                  "required": ["computer", "services"]
                }
              }
            }
          },
          "400": { "description": "Unknown service", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Computer not found, or services are disabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Computer is not running", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/computers/{id}/services/{sid}/health": {
      "post": {
        "operationId": "checkServiceHealth",
        "summary": "Re-check one service",
        "description": "Enabled when COMPOSE_SERVICES_ENABLED=1. Runs the catalog health check on a service already added to the computer.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "sid", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Updated service list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceState" } }
                  },
                  "required": ["services"]
                }
              }
            }
          },
          "400": { "description": "Unknown service", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Computer or service not found, or services are disabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Computer is not running", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/computers/{id}/click": {
      "post": {
        "operationId": "clickComputer",
        "summary": "Click on a computer desktop",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "x": { "type": "number" },
                  "y": { "type": "number" },
                  "button": { "type": "string", "enum": ["left", "middle", "right"] },
                  "nx": { "type": "number" },
                  "ny": { "type": "number" },
                  "double": { "type": "boolean" },
                  "move": { "type": "boolean" }
                },
                "required": ["x", "y"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Click accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" },
                    "action": { "type": "string" }
                  }
                }
              }
            }
          },
          "403": { "$ref": "#/components/responses/NotOwner" }
        }
      }
    },
    "/api/computers/{id}/type": {
      "post": {
        "operationId": "typeText",
        "summary": "Type text on a computer desktop",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "text": { "type": "string" } },
                "required": ["text"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Typed", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "action": { "type": "string" } } } } } },
          "403": { "$ref": "#/components/responses/NotOwner" }
        }
      }
    },
    "/api/computers/{id}/key": {
      "post": {
        "operationId": "pressKey",
        "summary": "Send a key to a computer desktop",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "key": { "type": "string", "description": "Example: ctrl+c or Return" } },
                "required": ["key"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Key sent", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "action": { "type": "string" } } } } } },
          "403": { "$ref": "#/components/responses/NotOwner" }
        }
      }
    },
    "/api/workspace": {
      "get": {
        "operationId": "listTemplates",
        "summary": "Workspace, including launch templates",
        "description": "Template ids in templates are the source for templateId. There is no separate /api/templates route.",
        "responses": {
          "200": {
            "description": "Workspace payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": { "type": "array", "items": { "$ref": "#/components/schemas/Template" } }
                  },
                  "required": ["templates"]
                }
              }
            }
          }
        }
      }
    },
    "/api/tokens": {
      "get": {
        "operationId": "listTokens",
        "summary": "List personal access tokens",
        "description": "Requires a Clerk session. A personal access token receives 403. Enabled only when MCP_ENABLED=1. Hashes and plaintext tokens are omitted.",
        "responses": {
          "200": {
            "description": "Active tokens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tokens": { "type": "array", "items": { "$ref": "#/components/schemas/Pat" } }
                  },
                  "required": ["tokens"]
                }
              }
            }
          },
          "403": { "description": "PAT cannot manage tokens", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "operationId": "createToken",
        "summary": "Create a personal access token",
        "description": "Returns the plaintext token once. Requires a Clerk session. Enabled only when MCP_ENABLED=1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "expiresDays": { "type": ["number", "null"], "description": "Omit or null for no expiry. 1 to 3650." }
                },
                "required": ["name"]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token created. token is shown only in this response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": { "type": "string" },
                    "pat": { "$ref": "#/components/schemas/Pat" }
                  },
                  "required": ["token", "pat"]
                }
              }
            }
          },
          "400": { "description": "Invalid name or expiry", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "PAT cannot manage tokens", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/tokens/{id}": {
      "delete": {
        "operationId": "revokeToken",
        "summary": "Revoke a personal access token",
        "description": "Requires a Clerk session. Enabled only when MCP_ENABLED=1.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "ok": { "type": "boolean" } },
                  "required": ["ok"]
                }
              }
            }
          },
          "403": { "description": "PAT cannot manage tokens", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  }
}
