{
  "openapi": "3.1.0",
  "info": {
    "title": "Infrastructure Hoster API",
    "version": "1.0.0",
    "description": "Static site and SPA hosting. A project is a subdomain (`<name>.hoster.myinfrastructure.click`); a deploy uploads a new immutable version and switches to it atomically, with history and rollback. Custom domains, public environment variables and cache strategy are per project.\n\nErrors always use `{\"error\":{\"code\",\"message\",\"details?\"}}` with a stable `code`; collections use `{items, page}`. User-facing `message` strings are in Portuguese.\n\nAuthentication: account routes accept an Infrastructure Auth JWT (session or OAuth access token) or an `hk_` API key; `/deploy-ticket` accepts only a single-use `ht_` upload ticket. The MCP endpoint (`/mcp`) is documented separately in llms-mcp.txt.",
    "contact": {
      "name": "Infrastructure",
      "url": "https://myinfrastructure.click/contact"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://hoster.worker.myinfrastructure.click"
    }
  ],
  "externalDocs": {
    "description": "Agent guide (llms.txt)",
    "url": "https://myinfrastructure.click/products/hoster/llms.txt"
  },
  "tags": [
    {
      "name": "Projects",
      "description": "Create, list, rename and delete projects."
    },
    {
      "name": "Deploys",
      "description": "Publish versions, list history and roll back."
    },
    {
      "name": "Upload tickets",
      "description": "Single-use `ht_` tickets (issued by the MCP) that publish one project once."
    },
    {
      "name": "Variables",
      "description": "Public environment variables injected into HTML."
    },
    {
      "name": "Cache",
      "description": "Cache-Control strategy and edge purge."
    },
    {
      "name": "Domains",
      "description": "Custom domain and certificates."
    },
    {
      "name": "API keys",
      "description": "`hk_` machine keys for the CLI and CI."
    },
    {
      "name": "Account",
      "description": "Plan state and account data deletion."
    },
    {
      "name": "Service",
      "description": "Public service health."
    },
    {
      "name": "OAuth discovery",
      "description": "OAuth protected resource metadata."
    }
  ],
  "security": [
    {
      "sessionJwt": []
    },
    {
      "apiKeyBearer": []
    },
    {
      "apiKeyHeader": []
    },
    {
      "sessionCookie": []
    },
    {
      "oauth2": []
    }
  ],
  "paths": {
    "/status": {
      "get": {
        "operationId": "getServiceStatus",
        "tags": [
          "Service"
        ],
        "summary": "Service health",
        "description": "Anonymous health of the Hoster service (database and file storage probes). Feeds the public status page.",
        "security": [],
        "responses": {
          "200": {
            "description": "Health report. Always 200; `status` carries the verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceStatus"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "getProtectedResourceMetadata",
        "tags": [
          "OAuth discovery"
        ],
        "summary": "OAuth protected resource metadata",
        "description": "RFC 9728 document pointing at the authorization server (Infrastructure Auth) and the supported scopes. Used by OAuth/MCP clients to discover where to log in.",
        "security": [],
        "responses": {
          "200": {
            "description": "Metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/check-project/{id}": {
      "get": {
        "operationId": "checkProjectName",
        "tags": [
          "Projects"
        ],
        "summary": "Check project name availability",
        "description": "Public. Tells whether a name is free AND valid. An invalid or reserved name answers 200 with `available: false` and a `reason`.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Candidate project name.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NameCheck"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Every project owned by the caller. Not paginated: the whole set is returned (`page.total` and `page.hasMore` are null).",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "The projects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "tags": [
          "Projects"
        ],
        "summary": "Create (or link) a project",
        "description": "Creates a project and registers its subdomain `<projectId>.hoster.myinfrastructure.click` (certificate issued in the background). Idempotent for the owner: if the project already exists and is yours, returns 200 with `alreadyLinked: true` (updating `mainFile` if sent). Creating beyond the plan limit returns 402; an existing project whose owner has no valid subscription also returns 402.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The project already existed and belongs to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedProject"
                }
              }
            }
          },
          "201": {
            "description": "Project created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}": {
      "delete": {
        "operationId": "deleteProject",
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Deletes every file version, the subdomain and custom-domain hostnames, and the project row. Irreversible.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/name": {
      "patch": {
        "operationId": "renameProject",
        "tags": [
          "Projects"
        ],
        "summary": "Rename a project",
        "description": "Changes the name and therefore the subdomain. Files, history, variables, cache and custom domain carry over. The new subdomain certificate starts `pending`.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New project name (same rules as creation)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renamed (or `unchanged: true`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenamedProject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "NAME_TAKEN — the new name is in use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/files": {
      "delete": {
        "operationId": "deleteProjectFiles",
        "tags": [
          "Projects"
        ],
        "summary": "Delete all project files",
        "description": "Deletes EVERY version, including the live one, and clears `activeDeployId`; the site then shows the holding page. Not part of a normal redeploy — deploys are versioned and atomic.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Files deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cleared": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/deploy-zip": {
      "post": {
        "operationId": "deployZip",
        "tags": [
          "Deploys"
        ],
        "summary": "Publish a ZIP",
        "description": "Uploads a ZIP of the built site into a new version folder and switches the project to it atomically (the previous version keeps serving until the switch, and on failure). Used by the CLI. Records the version in the history with `source: cli` and returns the bundle audit.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/DeployRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/deploy-ticket": {
      "post": {
        "operationId": "deployWithTicket",
        "tags": [
          "Upload tickets"
        ],
        "summary": "Publish a ZIP with an upload ticket",
        "description": "Same as `/deploy-zip`, authorized by a single-use `ht_` upload ticket instead of an account credential. Tickets are issued by the MCP tool `hoster_deploy_ticket` (there is no REST route that issues them), are bound to one project and expire after 15 minutes. `projectId` must match the ticket. The ticket is consumed only on success, so a failed attempt may be retried with the same ticket. Ownership and subscription are re-checked at redemption.",
        "security": [
          {
            "uploadTicket": []
          },
          {
            "uploadTicketHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/DeployRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Published (source `mcp`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "UNAUTHORIZED — ticket refused. `details.reason` is `malformed`, `unknown`, `used` or `expired`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "Ticket expirado.",
                    "details": {
                      "reason": "expired"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/deploy-ticket/info": {
      "get": {
        "operationId": "getDeployTicketInfo",
        "tags": [
          "Upload tickets"
        ],
        "summary": "Inspect an upload ticket",
        "description": "Read-only: tells which project a ticket publishes to and when it expires, without consuming it. The CLI calls this before compressing a directory.",
        "security": [
          {
            "uploadTicket": []
          },
          {
            "uploadTicketHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "What the ticket authorizes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketInfo"
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED — ticket refused. `details.reason` is `malformed`, `unknown`, `used` or `expired`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/upload": {
      "post": {
        "operationId": "uploadFile",
        "tags": [
          "Deploys"
        ],
        "summary": "Upload one file into a version under construction",
        "description": "File-by-file upload into a version folder that is NOT live yet. Send the same `deployId` for every file of a batch, then call `POST /projects/{id}/deploy/{deployId}/commit` to put it live. Nothing uploaded here is visible without the commit.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/projects/{id}/deploy/{deployId}/commit": {
      "post": {
        "operationId": "commitDeploy",
        "tags": [
          "Deploys"
        ],
        "summary": "Put an uploaded version live",
        "description": "Atomically switches the project to the version assembled with `/upload`, records it in the history (`source: dashboard`) and returns the bundle audit. Refuses an empty version.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          },
          {
            "name": "deployId",
            "in": "path",
            "required": true,
            "description": "The version folder id returned by `/upload`, `/deploy-zip` or `/deploy-ticket`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommitRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommitResult"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR — no files were uploaded to this version (`details.reason: empty_deploy`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/projects/{id}/deployments": {
      "get": {
        "operationId": "listDeployments",
        "tags": [
          "Deploys"
        ],
        "summary": "List versions",
        "description": "The publication history, newest first, with the live one flagged `active`. Retention depends on the plan. Not paginated.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeploymentList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/projects/{id}/deployments/{deployId}/activate": {
      "post": {
        "operationId": "activateDeployment",
        "tags": [
          "Deploys"
        ],
        "summary": "Roll back to a version",
        "description": "Moves the live pointer to a previous version from the history. Instant: no files are copied. Requires a valid subscription, like publishing.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          },
          {
            "name": "deployId",
            "in": "path",
            "required": true,
            "description": "The version folder id returned by `/upload`, `/deploy-zip` or `/deploy-ticket`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The version now live.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deployId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR — the version is already live (`details.reason: already_active`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "EXPIRED — the version files are no longer available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}/variables": {
      "get": {
        "operationId": "getProjectVariables",
        "tags": [
          "Variables"
        ],
        "summary": "Get environment variables",
        "description": "The public variables injected as `window.ENV` into every HTML response of the site.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The variables.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Variables"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "setProjectVariables",
        "tags": [
          "Variables"
        ],
        "summary": "Replace environment variables",
        "description": "Replaces the WHOLE set (no merge). Takes effect on the next page load, no redeploy needed. Values are public — never send secrets.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Variables"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Variables"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/cache": {
      "get": {
        "operationId": "getCacheStrategy",
        "tags": [
          "Cache"
        ],
        "summary": "Get cache strategy",
        "description": "The Cache-Control strategy of the project (defaults to `development` when unset).",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The strategy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CacheStrategy"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "setCacheStrategy",
        "tags": [
          "Cache"
        ],
        "summary": "Set cache strategy",
        "description": "Sets the Cache-Control strategy for the whole project. Does not purge the edge by itself — call `purge-cache`.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CacheStrategy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CacheStrategy"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/purge-cache": {
      "post": {
        "operationId": "purgeProjectCache",
        "tags": [
          "Cache"
        ],
        "summary": "Purge edge cache",
        "description": "Invalidates the edge cache for the project's subdomain and its custom domain (never the whole zone).",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Purge accepted by the edge.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "purging": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/domain": {
      "post": {
        "operationId": "setCustomDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Set custom domain",
        "description": "Attaches a custom domain, registers it for certificate issuance and returns the DNS record to create (CNAME to `proxy.hoster.myinfrastructure.click`; apex domains get ALIAS/ANAME alternatives). Status starts `pending`.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetDomainRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Domain attached, pending DNS and certificate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetDomainResult"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR — invalid domain. `details.reason`: `required`, `too_long`, `sem_tld`, `invalid_format`, `tld_invalido`, `tld_reservado`, `tld_desconhecido`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "CONFLICT — the domain is already used by another project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "removeCustomDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Remove custom domain",
        "description": "Detaches the custom domain and removes its hostname registration. The official subdomain keeps working.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/domain/verify": {
      "get": {
        "operationId": "verifyCustomDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Verify custom domain",
        "description": "Checks DNS and certificate live and updates `customDomainStatus`. `active` requires DNS reaching the service AND an issued certificate. When pending, returns a diagnosis and the DNS instruction; may trigger hostname registration or a validation restart in the background.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainVerification"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{id}/ssl-status": {
      "get": {
        "operationId": "getSslStatus",
        "tags": [
          "Domains"
        ],
        "summary": "Official subdomain certificate status",
        "description": "Certificate state of `<id>.hoster.myinfrastructure.click`. Registers the hostname if it was never registered.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The project name (`projectId`), which is also its subdomain: `<id>.hoster.myinfrastructure.click`.",
            "schema": {
              "type": "string",
              "example": "my-site"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SslStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "API keys"
        ],
        "summary": "List API keys",
        "description": "The caller's `hk_` keys, with `keyPrefix` only — the secret is never listed. Not paginated.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "The keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Create API key",
        "description": "Creates an `hk_` key with full account access (no scopes). The plaintext `key` is returned once. Pro and Scale plans only; otherwise 402.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "ci-deploy"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Copy `key` now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedApiKey"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api-keys/{id}": {
      "delete": {
        "operationId": "deleteApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Delete API key",
        "description": "Revokes a key. Other edge isolates may accept it for up to one minute (cache TTL).",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "API key id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/plan": {
      "get": {
        "operationId": "getPlanNotice",
        "tags": [
          "Account"
        ],
        "summary": "Plan notice",
        "description": "The caller's plan state for display (billing banner). Never fails on billing outages: answers `state: unknown` instead.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "apiKeyBearer": []
          },
          {
            "apiKeyHeader": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "Plan state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanNotice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/me/deletion-preview": {
      "get": {
        "operationId": "previewAccountDeletion",
        "tags": [
          "Account"
        ],
        "summary": "Preview account deletion",
        "description": "What exists under the account in Hoster. Requires an Infrastructure Auth token; `hk_` keys get 403.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "Inventory.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountInventory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/me": {
      "delete": {
        "operationId": "deleteAccountData",
        "tags": [
          "Account"
        ],
        "summary": "Delete the account's Hoster data",
        "description": "Deletes API keys and deploy history. Refused with 409 HAS_RESOURCES while any project exists — delete each with `DELETE /projects/{id}` first. Requires an Infrastructure Auth token; `hk_` keys get 403.",
        "security": [
          {
            "sessionJwt": []
          },
          {
            "sessionCookie": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "apiKeys": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "HAS_RESOURCES — projects still exist; `details` carries the inventory.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionJwt": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Infrastructure Auth JWT (RS256, verified against https://auth.worker.myinfrastructure.click/.well-known/jwks.json; needs `sub` and a future `exp`). Sessions last 7 days, no refresh endpoint."
      },
      "apiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hk_",
        "description": "An `hk_` API key sent as `Authorization: Bearer hk_…`. Full account access; not accepted on `/me` routes."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Hoster-Key",
        "description": "An `hk_` API key (CLI/CI). Full account access; not accepted on `/me` routes."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "auth-token",
        "description": "The Infrastructure Auth JWT in the `auth-token` cookie (dashboard). Checked before the Authorization header."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE (S256), issued by Infrastructure Auth. Use it to act on behalf of a person; scopes are listed in the authorization server metadata.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.worker.myinfrastructure.click/oauth/authorize",
            "tokenUrl": "https://auth.worker.myinfrastructure.click/oauth/token",
            "scopes": {
              "auth:read": "Read access (MCP read tools).",
              "auth:write": "Write access (MCP write tools)."
            }
          }
        }
      },
      "uploadTicket": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ht_",
        "description": "Single-use `ht_` upload ticket (one project, one successful use, 15 minutes), issued by the MCP tool `hoster_deploy_ticket`."
      },
      "uploadTicketHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Hoster-Ticket",
        "description": "The `ht_` upload ticket in a header, used when `Authorization` is not a Bearer."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The single error envelope of the stack. `code` is stable and machine-readable; `message` is human text (Portuguese) and may change.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "UNAUTHORIZED",
                  "FORBIDDEN",
                  "NOT_FOUND",
                  "VALIDATION_ERROR",
                  "CONFLICT",
                  "NAME_TAKEN",
                  "EXPIRED",
                  "HAS_RESOURCES",
                  "PLAN_LIMIT",
                  "SUBSCRIPTION_REQUIRED",
                  "INTERNAL_ERROR",
                  "SERVICE_UNAVAILABLE",
                  "UPSTREAM_ERROR"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "What the caller can act on, e.g. `{reason}` for validation and ticket refusals, `{resource, plan, current, limit}` for plan refusals."
              },
              "retriable": {
                "type": "boolean",
                "description": "Present and `true` on SERVICE_UNAVAILABLE and UPSTREAM_ERROR: retrying may succeed."
              }
            }
          }
        },
        "example": {
          "error": {
            "code": "NOT_FOUND",
            "message": "Project not found"
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "Pagination block. Hoster collections are NOT paginated: they return the whole set, `limit` equals the number of items, `offset` is 0, and `total`/`hasMore` are `null` (nobody counted).",
        "required": [
          "limit",
          "offset",
          "total",
          "hasMore"
        ],
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": [
              "integer",
              "null"
            ]
          },
          "hasMore": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "Project": {
        "type": "object",
        "description": "A project row as stored. Timestamps are serialized as ISO 8601 strings.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Internal row id."
          },
          "projectId": {
            "type": "string",
            "description": "Project name and subdomain.",
            "example": "my-site"
          },
          "userId": {
            "type": "string",
            "description": "Owner id (`sub` from Infrastructure Auth)."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "variables": {
            "type": [
              "string",
              "null"
            ],
            "description": "Environment variables as a JSON-encoded string (use `GET /projects/{id}/variables` for the parsed object)."
          },
          "customDomain": {
            "type": [
              "string",
              "null"
            ]
          },
          "customDomainStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "`pending`, `active` or `error`."
          },
          "mainFile": {
            "type": [
              "string",
              "null"
            ],
            "description": "Entry file, e.g. `index.html`."
          },
          "cacheStrategy": {
            "type": [
              "string",
              "null"
            ],
            "description": "`development`, `balanced`, `extreme`, or a literal Cache-Control string."
          },
          "sslStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "Certificate state of the official subdomain: `pending`, `active` or `error`."
          },
          "activeDeployId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The version currently live."
          },
          "storageRoot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Immutable storage prefix of the project files."
          },
          "deployingSince": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Epoch ms of a deploy in progress, or null."
          }
        }
      },
      "ProjectList": {
        "type": "object",
        "required": [
          "items",
          "page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "CreateProjectRequest": {
        "type": "object",
        "required": [
          "projectId"
        ],
        "properties": {
          "projectId": {
            "type": "string",
            "description": "Desired name/subdomain. Normalized, then must be 3–63 chars of `a-z`, `0-9` and `-` (DNS label) and not reserved.",
            "example": "my-site"
          },
          "mainFile": {
            "type": "string",
            "description": "Entry file. On an existing project, updates it.",
            "example": "index.html"
          }
        }
      },
      "LinkedProject": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Project"
          },
          {
            "type": "object",
            "properties": {
              "alreadyLinked": {
                "type": "boolean",
                "const": true
              }
            }
          }
        ]
      },
      "RenamedProject": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Project"
          },
          {
            "type": "object",
            "properties": {
              "previousId": {
                "type": "string",
                "description": "The old name (present when the name changed)."
              },
              "unchanged": {
                "type": "boolean",
                "description": "`true` when the new name equals the current one (nothing done)."
              }
            }
          }
        ]
      },
      "NameCheck": {
        "type": "object",
        "required": [
          "exists",
          "available",
          "name"
        ],
        "properties": {
          "exists": {
            "type": "boolean",
            "description": "A project with this name exists."
          },
          "available": {
            "type": "boolean",
            "description": "The name can be used (valid format, not reserved, not taken)."
          },
          "name": {
            "type": "string",
            "description": "The normalized name."
          },
          "reason": {
            "type": "string",
            "enum": [
              "required",
              "too_short",
              "too_long",
              "invalid_format",
              "reserved",
              "taken"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "AuditFinding": {
        "type": "object",
        "description": "One bundle-audit finding. Field names are in Portuguese and are part of the contract.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable rule id, e.g. `js-monolitico`."
          },
          "gravidade": {
            "type": "string",
            "description": "Severity, e.g. `alto`, `medio`."
          },
          "titulo": {
            "type": "string"
          },
          "detalhe": {
            "type": "string"
          },
          "acao": {
            "type": "string"
          }
        }
      },
      "Audit": {
        "type": [
          "array",
          "null"
        ],
        "items": {
          "$ref": "#/components/schemas/AuditFinding"
        },
        "description": "Bundle audit of the published files; `null` when the audit could not run."
      },
      "DeployRequest": {
        "type": "object",
        "required": [
          "file",
          "projectId"
        ],
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/zip",
            "description": "ZIP of the built site (paths relative to the site root)."
          },
          "projectId": {
            "type": "string",
            "description": "Target project name."
          },
          "label": {
            "type": "string",
            "description": "Display label of the version (e.g. commit message). Truncated to 160 chars."
          },
          "gitBranch": {
            "type": "string",
            "description": "Truncated to 120 chars."
          },
          "gitCommit": {
            "type": "string",
            "description": "Truncated to 40 chars."
          }
        }
      },
      "DeployResult": {
        "type": "object",
        "required": [
          "fileCount",
          "deployId"
        ],
        "properties": {
          "fileCount": {
            "type": "integer"
          },
          "deployId": {
            "type": "string"
          },
          "auditoria": {
            "$ref": "#/components/schemas/Audit"
          }
        }
      },
      "TicketInfo": {
        "type": "object",
        "required": [
          "projectId",
          "expiresAt",
          "url"
        ],
        "properties": {
          "projectId": {
            "type": "string"
          },
          "expiresAt": {
            "type": "integer",
            "description": "Epoch ms."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://my-site.hoster.myinfrastructure.click"
          }
        }
      },
      "UploadRequest": {
        "type": "object",
        "required": [
          "file",
          "projectId",
          "path"
        ],
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "description": "The file bytes. Content-Type served is derived from the path extension, not from the part."
          },
          "projectId": {
            "type": "string",
            "description": "Target project name."
          },
          "path": {
            "type": "string",
            "description": "Path RELATIVE to the site root, e.g. `assets/app.js`. `..`, NUL and empty paths are refused."
          },
          "deployId": {
            "type": "string",
            "description": "Version folder to write into. Send the same value for every file of a batch; generated by the server when omitted."
          }
        }
      },
      "UploadResult": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "The storage key written."
          },
          "deployId": {
            "type": "string",
            "description": "The version folder; pass it to the commit route."
          }
        }
      },
      "CommitRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Display label of the version. Truncated to 160 chars."
          }
        }
      },
      "CommitResult": {
        "type": "object",
        "required": [
          "deployId"
        ],
        "properties": {
          "deployId": {
            "type": "string"
          },
          "auditoria": {
            "$ref": "#/components/schemas/Audit"
          }
        }
      },
      "Deployment": {
        "type": "object",
        "properties": {
          "deployId": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Where it came from: `cli` (/deploy-zip), `mcp` (/deploy-ticket), `dashboard` (/upload + commit)."
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "gitBranch": {
            "type": [
              "string",
              "null"
            ]
          },
          "gitCommit": {
            "type": [
              "string",
              "null"
            ]
          },
          "fileCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "totalBytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "active": {
            "type": "boolean",
            "description": "This version is the one live."
          }
        }
      },
      "DeploymentList": {
        "type": "object",
        "required": [
          "items",
          "page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          },
          "activeDeployId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Variables": {
        "type": "object",
        "required": [
          "variables"
        ],
        "properties": {
          "variables": {
            "type": "object",
            "additionalProperties": true,
            "description": "Public key/value pairs injected as `window.ENV` into every HTML response. Never put secrets here.",
            "example": {
              "API_URL": "https://api.example.com"
            }
          }
        }
      },
      "CacheStrategy": {
        "type": "object",
        "required": [
          "cacheStrategy"
        ],
        "properties": {
          "cacheStrategy": {
            "type": "string",
            "description": "`development` (no-store), `balanced` (max-age=3600), `extreme` (max-age=2592000), or a literal Cache-Control string.",
            "example": "balanced"
          }
        }
      },
      "DnsInstruction": {
        "type": "object",
        "description": "The DNS record the customer must create.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "cname",
              "apex"
            ]
          },
          "type": {
            "type": "string",
            "const": "CNAME"
          },
          "name": {
            "type": "string",
            "description": "Record name (`@` for an apex domain)."
          },
          "value": {
            "type": "string",
            "example": "proxy.hoster.myinfrastructure.click"
          },
          "host": {
            "type": "string"
          },
          "apex": {
            "type": "boolean"
          },
          "alternatives": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Apex only: `ALIAS`, `ANAME`, `CNAME_FLATTENING`."
          },
          "fallback": {
            "type": "string",
            "description": "Apex only: `use-subdomain-or-move-dns`."
          }
        }
      },
      "SetDomainRequest": {
        "type": "object",
        "required": [
          "domain"
        ],
        "properties": {
          "domain": {
            "type": "string",
            "example": "example.com"
          }
        }
      },
      "SetDomainResult": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "pending"
          },
          "dns_instruction": {
            "$ref": "#/components/schemas/DnsInstruction"
          }
        }
      },
      "DomainVerification": {
        "type": "object",
        "description": "Live check of the custom domain. Field set varies with the outcome.",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "active",
              "pending"
            ]
          },
          "message": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "dns": {
            "type": "string",
            "description": "`cname`, `cname-elsewhere`, `a-record-no-cname` or `no-record`."
          },
          "ssl": {
            "type": "string",
            "description": "Certificate state (`zone`, `no-record`, `not-registered`, or the Cloudflare SSL status)."
          },
          "proxied": {
            "type": "boolean"
          },
          "coveredByZone": {
            "type": "boolean"
          },
          "ownership": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "TXT ownership proof `{name, value, ...}` when available."
          },
          "target": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the CNAME currently points."
          },
          "diagnosis": {
            "type": "object",
            "properties": {
              "reason": {
                "type": "string"
              },
              "blocked": {
                "type": "boolean"
              },
              "detail": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "dns_instruction": {
            "$ref": "#/components/schemas/DnsInstruction"
          }
        }
      },
      "SslStatus": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "active",
              "pending"
            ]
          },
          "message": {
            "type": "string"
          },
          "sslStatus": {
            "type": "string",
            "description": "Raw Cloudflare SSL status."
          },
          "hostnameStatus": {
            "type": "string",
            "description": "Raw Cloudflare hostname status."
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "description": "An `hk_` API key as listed. The secret is never returned after creation.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "keyPrefix": {
            "type": [
              "string",
              "null"
            ],
            "description": "First 11 characters, for identification."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "items",
          "page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "CreatedApiKey": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "key"
            ],
            "properties": {
              "key": {
                "type": "string",
                "description": "The full `hk_` secret. Returned ONCE."
              }
            }
          }
        ]
      },
      "PlanNotice": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "active",
              "free",
              "none",
              "unknown"
            ]
          },
          "plan": {
            "type": [
              "string",
              "null"
            ]
          },
          "limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Project limit; null when unlimited or unknown."
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Banner text, only when state is `none`."
          }
        }
      },
      "AccountInventory": {
        "type": "object",
        "properties": {
          "projects": {
            "type": "integer"
          },
          "customDomains": {
            "type": "integer"
          },
          "apiKeys": {
            "type": "integer"
          },
          "projectIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ServiceStatus": {
        "type": "object",
        "properties": {
          "service": {
            "type": "string",
            "const": "hoster"
          },
          "status": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "down"
            ]
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "operational",
                    "degraded",
                    "down"
                  ]
                },
                "latencyMs": {
                  "type": "integer"
                },
                "metrics": {
                  "type": "object",
                  "additionalProperties": true
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "description": "RFC 9728 protected resource metadata.",
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri",
            "example": "https://hoster.worker.myinfrastructure.click/mcp"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "resource_documentation": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "VALIDATION_ERROR — malformed or missing input. `details.reason` may say which rule failed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "VALIDATION_ERROR",
                "message": "Missing projectId"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "UNAUTHORIZED — missing, invalid or expired credential.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "UNAUTHORIZED",
                "message": "Sessão inválida ou expirada. Por favor, faça login novamente."
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "PLAN_LIMIT or SUBSCRIPTION_REQUIRED — the plan does not cover this operation. Sites already live keep being served.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "SUBSCRIPTION_REQUIRED",
                "message": "Publicar exige uma assinatura ativa.",
                "details": {
                  "resource": "deploy",
                  "plan": null
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "FORBIDDEN — the project/key does not exist or belongs to another account.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "FORBIDDEN",
                "message": "Project not found or access denied"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "NOT_FOUND — the resource does not exist or is not visible to the caller.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "NOT_FOUND",
                "message": "Project not found"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "INTERNAL_ERROR — unexpected failure on the server side.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadGateway": {
        "description": "UPSTREAM_ERROR — Cloudflare refused or failed. `retriable: true`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "SERVICE_UNAVAILABLE — transient infrastructure failure. `retriable: true`; retry shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
