{"openapi":"3.0.3","info":{"title":"xCloud Public API","version":"1.0.0","description":"## xCloud Public API\n\nA self-service REST API for all xCloud customers. Use this API to programmatically\nmanage your servers, sites, databases, backups, cron jobs, and more \u2014 directly from\nyour own scripts, CI\/CD pipelines, or integrations.\n\nMachine-readable copies of this document (the host is rewritten for the\ndeployment you call):\n\n- `GET \/api\/v1\/openapi.yaml`\n- `GET \/api\/v1\/openapi.json`\n\n`GET \/api\/v1\/docs` renders the same document in a browser.\n\n---\n\n## Authentication\n\nAll API requests (except `GET \/health`) require a **Sanctum Personal Access Token**\npassed as a Bearer token in the `Authorization` header.\n\n### Creating a Token\n\n1. Log in to [app.xcloud.host](https:\/\/app.xcloud.host)\n2. Navigate to **Account \u2192 API Tokens** (`\/user\/api-tokens`)\n3. Click **Create New Token**\n4. Select the required scopes for your use case\n5. Copy the token \u2014 **it is shown only once**\n\n### Available Scopes\n\n| Scope | Access |\n|-------|--------|\n| `read:servers` | List and view servers, databases, cron jobs, PHP versions, monitoring, sudo users |\n| `write:servers` | Reboot servers, create WordPress sites, manage sudo users |\n| `read:sites` | List and view sites, backups, SSL, domain, git, deployment logs, SSH config |\n| `write:sites` | Trigger backups, rescue sites, purge cache, update SSH\/SFTP config |\n| `read:billing` | List and view billing: plan, overview, invoices, bills, packages, products, payment methods, subscriptions |\n| `read:addons` | List plans and view purchased mailboxes \/ addons \u2014 **metadata only, no credentials** |\n| `write:addons` | Purchase mailboxes\/mail-delivery, verify DNS, pay invoices, and **read sending credentials** (mailbox POP\/IMAP\/SMTP passwords, Mail Delivery `api_key`) |\n| `*` | Full access to all endpoints |\n\n### Required Headers\n\n```http\nAuthorization: Bearer <your-token>\nAccept: application\/json\nContent-Type: application\/json\n```\n\n---\n\n### Multi-Team Tokens\n\nA token is created with a **default team** and can optionally be granted\nadditional teams. Every request runs against exactly one team:\n\n- Without extra headers, requests run against the token's default team.\n- Pass `X-Team-Id: <team-uuid>` to run a request against another granted\n  team. `GET \/teams` lists the teams a token may act on.\n- Naming a team the token was not granted (or that no longer exists)\n  returns `403` \u2014 there is no silent fallback.\n\n## Response Format\n\nAll responses follow a consistent envelope:\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Operation completed successfully.\",\n  \"data\": { ... }\n}\n```\n\nFor paginated lists, `data` contains:\n\n```json\n{\n  \"data\": [ ... ],\n  \"meta\": {\n    \"current_page\": 1,\n    \"last_page\": 5,\n    \"per_page\": 15,\n    \"total\": 72\n  }\n}\n```\n\n---\n\n## HTTP Status Codes\n\n| Code | Meaning |\n|------|---------|\n| `200 OK` | Request successful |\n| `202 Accepted` | Async operation queued (e.g. reboot, backup) |\n| `400 Bad Request` | Validation error \u2014 check `errors` in response body |\n| `401 Unauthorized` | Missing or invalid token |\n| `403 Forbidden` | Token lacks the required scope |\n| `404 Not Found` | Resource not found or not accessible to your team |\n| `422 Unprocessable Entity` | Business logic validation failure |\n| `429 Too Many Requests` | Rate limit exceeded |\n| `500 Internal Server Error` | Server error \u2014 contact support |\n\n---\n\n## Rate Limits\n\n| Type | Limit |\n|------|-------|\n| Authenticated requests | **60 requests \/ minute** |\n| Unauthenticated requests | **10 requests \/ minute** |\n\nRate limit headers are included in every response:\n\n```http\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 58\nX-RateLimit-Reset: 1710000060\n```\n\nWhen you exceed the limit, the API returns `429 Too Many Requests` with a\n`Retry-After` header indicating how many seconds to wait.\n","contact":{"name":"xCloud Support","url":"https:\/\/xcloud.host\/support"},"license":{"name":"Proprietary","url":"https:\/\/xcloud.host\/terms"}},"servers":[{"url":"https:\/\/app.xcloud.host\/api\/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Alerts","description":"Authorized incident notification history and personal read receipts"},{"name":"Health","description":"API health check \u2014 no authentication required."},{"name":"Catalog","description":"The app \/ stack-compatibility catalog and active hosting SKU pricing. Both are already public elsewhere (marketing site, New Site UI) \u2014 no authentication required.\n"},{"name":"User","description":"Current authenticated user information and API token management.\n"},{"name":"Integrations","description":"Third-party integrations connected to the current team (Cloudflare, etc.). Requires the `read:servers` scope.\n"},{"name":"Servers","description":"List and inspect managed servers. Read-only operations require the `read:servers` scope. Write operations (reboot, site creation) require `write:servers`.\n"},{"name":"Blueprints","description":"WordPress site blueprints \u2014 predefined configurations of themes, plugins, and post-deployment scripts. Use blueprints when creating WordPress sites. Requires the `read:servers` scope.\n"},{"name":"Sites","description":"List and inspect sites across all servers. Read-only operations require the `read:sites` scope. Write operations (backup, cache purge) require `write:sites`.\n"},{"name":"Vulnerabilities","description":"Security vulnerability inventory merged from Patchstack (premium scanner) and Wordfence (built-in scanner). Per-site and team-wide rollup. Ignored entries are excluded by default. Requires the `read:sites` scope.\n"},{"name":"PageSpeed","description":"PageSpeed Insights performance data per site, including Core Web Vitals (LCP, CLS, INP, FCP) and Lighthouse scores. Latest snapshot plus paginated history. Requires the `read:sites` scope.\n"},{"name":"Broken Links","description":"Site-wide broken link and image scanning for WordPress sites \u2014 crawls the site, checks links and image sources, and reports dead links (404\/410), redirects, timeouts, and other failures. Read operations require the `read:sites` scope; triggering a scan requires `write:sites`. Both require the `site:manage-broken-links` team permission and are unavailable on the free plan.\n"},{"name":"WordPress Actions","description":"Asynchronous WordPress write operations \u2014 update plugins\/themes\/core, with optional pre-update backup. Returns a 202 with an operation UUID for polling. Requires the `write:sites` scope and the `site:manage-update` team permission.\n"},{"name":"OneClick Apps","description":"Discover and install OneClick apps (Docker-based one-click applications). Flow: browse the catalog, read an app's field schema, check per-server compatibility, install (202), poll the status endpoint until `is_terminal` is true, then fetch credentials. Catalog and schema require `read:sites`; the compatibility check requires `read:servers`; install requires `write:servers` plus the `site:create` team permission; status, credentials, and lifecycle actions require `read:sites`\/`write:sites`.\n"},{"name":"Billing","description":"Read-only access to the team's billing: current plan, invoices, bill line-items, purchased packages\/products, payment methods, and subscriptions. All operations require the `read:billing` scope and billing access on the team \u2014 Team Admin (or team owner) with the `billing:create` permission, matching the web billing page. Plans that hide the billing-read feature return 404.\n"},{"name":"Addons - Mailbox","description":"Purchase and manage branded mailbox addons. List available mailbox plans, buy a mailbox, verify its DNS records, and inspect purchased mailboxes. Read operations require the `read:addons` scope; write operations require the `write:addons` scope.\n"},{"name":"Addons - Mail Delivery","description":"Purchase and manage the Mail Delivery (\"xCloud Managed Email\") addon \u2014 outbound email sending backed by an Elastic Email subaccount. List the paid plans, buy sending capacity (charged inline), and retrieve a subscription with its sending credentials (API key + SMTP). Purchase is instant-on-payment with no DNS lifecycle; a team may hold many subscriptions and each purchase tops up the same subaccount's credits. Read operations require the `read:addons` scope; write operations require the `write:addons` scope.\n"},{"name":"Payments","description":"Settle or retry outstanding invoices generated by addon purchases (and other billable operations). Requires the `write:addons` scope and the `billing:create` team permission.\n"}],"paths":{"\/health":{"get":{"tags":["Health"],"summary":"API Health Check","description":"Returns the current health status of the API. No authentication required. Use this endpoint to verify connectivity before making authenticated requests.\n","operationId":"health.check","security":[],"responses":{"200":{"description":"API is healthy","content":{"application\/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"version":{"type":"string","example":"v1"}}},"example":{"status":"ok","version":"v1"}}}}}}},"\/catalog\/apps":{"get":{"tags":["Catalog"],"summary":"List App Catalog","description":"Returns the full one-click app \/ stack-compatibility catalog \u2014 every app a site can be created from, its supported server stacks, and its minimum resource requirements. Computed live from the same resolvers the New Site UI uses (manifest templates, native app types, and the legacy one-click set), not a static snapshot. No authentication required.\n","operationId":"catalog.apps.index","security":[],"responses":{"200":{"description":"App catalog","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/CatalogApp"}}}}}}]},"example":{"success":true,"message":"Success","data":{"items":[{"slug":"wordpress","name":"WordPress","description":"Launch a fresh WordPress site, or bring an existing one over by migration, upload, or backup restore.","icon":"img\/wordpress-blue.svg","group":"wordpress","category":"cms","supported_stacks":["nginx","openlitespeed"],"requirements":{"min_ram_mb":1024,"min_cpu_cores":1,"min_disk_gb":0},"methods":[],"entry_route_name":null,"entry_route_params":[],"is_active":true,"coming_soon":false,"is_beta":false,"is_template":false,"requires_new_server":false,"keywords":["wordpress","wp","cms","blog"]},{"slug":"n8n","name":"n8n","description":"Workflow automation platform.","icon":"img\/n8n.svg","group":"one_click","category":"automation","supported_stacks":["nginx","openlitespeed"],"requirements":{"min_ram_mb":4096,"min_cpu_cores":2,"min_disk_gb":0},"methods":[],"entry_route_name":"site.create.oneclick","entry_route_params":{"app_slug":"n8n"},"is_active":true,"coming_soon":false,"is_beta":false,"is_template":false,"requires_new_server":false,"keywords":[]}]}}}}}}}},"\/catalog\/pricing":{"get":{"tags":["Catalog"],"summary":"List Hosting Pricing","description":"Returns active xCloud-managed and xCloud-provider hosting plans \u2014 uuid, title, price, currency, renewal term, resources, and which stacks the plan allows. The backing infrastructure provider is never included in this payload, and the raw internal SKU is deliberately never published (it can be the provider's own public plan code for some plans). No authentication required. `server_family` identifies the customer-facing tab shown in the app (xCloud Managed or xCloud-billed Vultr), not the infrastructure behind xCloud Managed. `service_type` remains the billing family.\n\nSome plans carry an introductory discount that only applies to the FIRST bill for that term \u2014 `first_purchase_price` is what checkout actually charges today; `price` is what every renewal after that is charged. See the `CatalogPricingPlan` schema for the full caveat \u2014 do not treat `first_purchase_price` as an ongoing\/current price.\n","x-mcp-description":"Each plan includes server_family (xcloud_managed = xCloud Managed tab; xcloud_vultr = xCloud-billed Vultr tab) and geographic regions. service_type is billing only. Region IDs are informational slugs, not provisioning inputs or capacity guarantees.\n","operationId":"catalog.pricing.index","security":[],"responses":{"200":{"description":"Hosting pricing catalog","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/CatalogPricingPlan"}}}}}}]},"example":{"success":true,"message":"Success","data":{"items":[{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","title":"xCloud Managed \u2014 4GB","service_type":"xcloud_managed_hosting","server_family":"xcloud_vultr","regions":[{"id":"us-new-jersey","city":"New Jersey","country":"US","country_code":"US"}],"price":48,"first_purchase_price":48,"currency":"usd","renewal_type":"monthly","ram_gb":4,"disk_gb":80,"cpu_cores":2,"allowed_stacks":["nginx","openlitespeed"]},{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","title":"Cloud VPS 16GB Monthly","service_type":"xcloud_managed_hosting","server_family":"xcloud_managed","regions":[{"id":"nl-amsterdam","city":"Amsterdam","country":"Netherlands","country_code":"NL"}],"price":39.99,"first_purchase_price":19.99,"currency":"usd","renewal_type":"monthly","ram_gb":16,"disk_gb":200,"cpu_cores":6,"allowed_stacks":["openclaw","hermes","paperclip","deepseek_harness"]}]}}}}}}}},"\/auth\/config":{"get":{"tags":["User"],"operationId":"auth.config","summary":"Native sign-in configuration","description":"Public client configuration for this deployment. Returns 404 until native sign-in is enabled.","security":[],"responses":{"200":{"description":"Public OAuth configuration; never contains a client secret","content":{"application\/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/NativeAuthConfiguration"}}}}}},"404":{"description":"Native sign-in is not enabled"}}}},"\/auth\/token":{"post":{"tags":["User"],"operationId":"auth.token","summary":"Exchange or refresh a native session","description":"Standard OAuth token exchange for the registered native public client. Send authorization_code with code, code_verifier and the exact redirect_uri, or refresh_token with the last stored refresh token. Refresh rotates credentials; serialize exchanges and atomically replace the stored pair. No client secret.\n","security":[],"requestBody":{"required":true,"content":{"application\/x-www-form-urlencoded":{"schema":{"$ref":"#\/components\/schemas\/NativeTokenRequest"}}}},"responses":{"200":{"description":"Session credentials; never cache or log","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/NativeTokenResponse"}}}},"400":{"description":"Invalid grant or request; invalid_grant requires sign-in","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/NativeTokenError"}}}},"401":{"description":"Invalid client","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/NativeTokenError"}}}},"429":{"description":"Rate limited; respect Retry-After"}}}},"\/auth\/session":{"delete":{"tags":["User"],"operationId":"auth.session","summary":"Revoke the calling session","description":"Revokes the calling native authorization and all access\/refresh tokens in its rotation family, without affecting other device authorizations. For a PAT, deletes only the calling token. No account-wide token-management ability is required. Available even after default-team membership is revoked. A repeated request with the revoked bearer returns 401. Offline clients must clear local secrets without claiming remote revocation succeeded.\n","security":[{"bearerAuth":[]},{"nativeOAuth":[]}],"responses":{"204":{"description":"Calling session revoked"},"401":{"$ref":"#\/components\/responses\/Unauthorized"}}}},"\/notifications\/config":{"get":{"tags":["Alerts"],"operationId":"notifications.config","summary":"Get native push availability","description":"Requires the registered first-party native OAuth grant and read:servers or read:sites. Cookies, personal API keys and MCP tokens are rejected. X-Team-Id is not used. Registration is disabled by default (404); topics\/packages and environments are server-configured. Omit platform for the existing iOS behavior; Android clients must request android and verify the returned platform before registering. Availability does not confirm device delivery; APNs\/FCM device acceptance is required before release.","security":[{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"name":"platform","in":"query","required":false,"schema":{"type":"string","enum":["ios","android"],"default":"ios"}}],"responses":{"200":{"description":"Current authorized state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/NativePushConfiguration"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/notifications\/installations\/{installationUuid}":{"get":{"tags":["Alerts"],"operationId":"notifications.installations.show","summary":"Get this native push installation","description":"Requires the registered first-party native OAuth grant and read:servers or read:sites. Cookies, personal API keys and MCP tokens are rejected. X-Team-Id is not used. Registration is disabled by default (404); topics\/packages and delivery environments are server-configured. Only the same user and OAuth grant can read it. Refresh token rotation preserves access. Device tokens and internal identifiers are never returned.","security":[{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"name":"installationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current authorized state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/NativePushInstallation"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"tags":["Alerts"],"operationId":"notifications.installations.store","summary":"Register or update native push preferences","description":"Requires the registered first-party native OAuth grant and read:servers or read:sites. Cookies, personal API keys and MCP tokens are rejected. X-Team-Id is not used. Registration is disabled by default (404); topics\/packages and delivery environments are server-configured. The client generates an installation UUID per signed-in session. Explicit authorized teams and categories are required when enabled. This changes only this device preferences. A changed subscription or token starts enrollment at the newest event, without historical backfill. An unchanged heartbeat preserves its revision and queued events. Stale updates return 409; reload before editing. A revoked UUID cannot be reused. The same platform token on another installation retires the old registration.","security":[{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"name":"installationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current authorized state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/NativePushInstallation"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"The revision changed or a create request supplied an old revision. Reload preferences before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"description":"Too many active installations in this grant or rate limit reached. Respect Retry-After when present.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"503":{"description":"Registration topic is not configured for this brand.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}},"x-destructive":false,"x-required-scope":"read","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/NativePushInstallationRequest"}}}}},"delete":{"tags":["Alerts"],"operationId":"notifications.installations.destroy","summary":"Unlink this native push installation","description":"Requires the registered first-party native OAuth grant and read:servers or read:sites. Cookies, personal API keys and MCP tokens are rejected. X-Team-Id is not used. Registration is disabled by default (404); topics\/packages and delivery environments are server-configured. Clears the token and prevents late updates reviving it. Does not affect another device or grant. The current grant must still be valid; local logout must clear state even if offline.","security":[{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"name":"installationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"This installation is unlinked; repeated deletes are safe."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}},"x-destructive":false,"x-required-scope":"read"}},"\/notifications\/installations\/{installationUuid}\/alerts\/{alertUuid}":{"get":{"tags":["Alerts"],"operationId":"notifications.installations.alerts.show","summary":"Resolve an incident notification","description":"Requires the registered first-party native OAuth grant and read:servers or read:sites. Cookies, personal API keys and MCP tokens are rejected. X-Team-Id is not used. Registration is disabled by default (404); topics\/packages and delivery environments are server-configured. Resolves the event to its current authorized team and resource without trusting payload metadata. Rechecks current membership, grant, brand, resource scopes and access; deleted or transferred resources cannot be opened. Does not mark read or change the web current team. The app must validate the returned team against its current session before navigating.","security":[{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"name":"installationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"alertUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current authorized state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/IncidentAlert"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/alerts":{"get":{"tags":["Alerts"],"summary":"List Incident Alerts","operationId":"alerts.index","description":"Selected-team incident notification history from normalized backend producers. Requires read:servers or read:sites; each resource kind is filtered by its granted scope and current resource access, including unread_count. Ambiguous legacy records are excluded. This is not a list of currently open incidents. Unread count covers the authorized inbox before optional filters. Read state is shared by this user's devices and independent of the dashboard cursor.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"unread","in":"query","schema":{"type":"boolean"},"description":"Use true for unread only, false for read only; omit for all. Also accepts 1 or 0."},{"name":"severity","in":"query","schema":{"type":"string","enum":["info","warning","error"]}},{"name":"category","in":"query","schema":{"type":"string","enum":["availability","resources","deployments","backups","ssl","security"]}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}}],"responses":{"200":{"description":"Newest first by recorded event identity; empty is a valid inbox.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/IncidentAlertPage"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/alerts\/{alertUuid}":{"get":{"tags":["Alerts"],"summary":"Get Incident Alert","operationId":"alerts.show","description":"Revalidates selected team, resource scope and current resource access. Deleted, moved, unclassified or inaccessible resources return 404. Requires read:servers for a server or read:sites for a site.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"alertUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Authorized event with individual read state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/IncidentAlert"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/alerts\/{alertUuid}\/read":{"put":{"tags":["Alerts"],"summary":"Set Incident Alert Read State","operationId":"alerts.read","x-destructive":false,"x-required-scope":"read","description":"Idempotently updates only this user's receipt for this selected-team event. Does not resolve incidents, change resources or advance the dashboard cursor. Requires the same read:servers or read:sites access as viewing the resource. Other users' receipts are unaffected.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"alertUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["is_read"],"properties":{"is_read":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Event with updated read state.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/IncidentAlert"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/teams":{"get":{"tags":["User"],"summary":"List Granted Teams","description":"Lists the teams this token may act on: its default team, plus any extra teams granted to it. Pass a team's uuid in the `X-Team-Id` header to run a request against it instead of the default.\n","operationId":"teams.index","x-mcp-description":"Call this when the user names a team. Pass a returned uuid as the `team` argument on any other tool.\n","security":[{"bearerAuth":[]},{"nativeOAuth":[]}],"responses":{"200":{"description":"Teams granted to the calling token","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/GrantedTeam"}}}}]},"example":{"success":true,"message":"Success","data":[{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Acme","role":"owner","is_default":true},{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"Acme Agency","role":"admin","is_default":false}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"}}}},"\/user":{"get":{"tags":["User"],"summary":"Get Current User","description":"Returns profile information for the currently authenticated user.","operationId":"user.show","security":[{"bearerAuth":[]},{"nativeOAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Current user profile","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}}}]},"example":{"success":true,"message":"User retrieved successfully.","data":{"uuid":"3f2c9a1e-7b64-4d2a-9c1f-5e8a2b7d4c10","name":"Jane Smith","email":"jane@example.com","profile_photo_url":"https:\/\/app.xcloud.host\/storage\/profile-photos\/jane.jpg","current_team_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","current_team":{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Acme"},"default_team_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","teams":[{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Acme"},{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"Acme Agency"}],"created_at":"2024-01-15T10:30:00.000000Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"}}}},"\/user\/tokens":{"get":{"tags":["User"],"summary":"List API Tokens","description":"Lists all personal access tokens belonging to the current user. Requires the `*` scope.\n","operationId":"user.tokens.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"List of API tokens","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/ApiToken"}}}}]},"example":{"success":true,"message":"Tokens retrieved successfully.","data":[{"uuid":"8c1f3a89-2c4e-4a73-9d4c-8b1f2a3d4e5f","name":"My CI Token","abilities":["read:servers","read:sites"],"team_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","team_name":"Acme","last_used_at":"2025-03-10T08:22:00Z","created_at":"2025-01-20T14:00:00Z"}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/user\/tokens\/{tokenUuid}":{"delete":{"tags":["User"],"summary":"Revoke API Token","description":"Permanently revokes the specified API token (addressed by UUID). This action cannot be undone. Requires the `*` scope.\n","operationId":"user.tokens.revoke","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"tokenUuid","in":"path","required":true,"description":"UUID of the token to revoke","schema":{"type":"string","format":"uuid","example":"8c1f3a89-2c4e-4a73-9d4c-8b1f2a3d4e5f"}}],"responses":{"200":{"description":"Token revoked successfully","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"Token revoked successfully.","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/integrations\/cloudflare":{"get":{"tags":["Integrations"],"summary":"List Cloudflare Integrations","description":"Returns all Cloudflare accounts connected to the current team. Requires the `read:servers` scope.\n","operationId":"integrations.cloudflare.index","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"List of connected Cloudflare accounts","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/CloudflareIntegration"}}}}]},"example":{"success":true,"message":"Cloudflare integrations retrieved successfully.","data":[{"id":"cf_1","name":"Jane's Cloudflare","email":"jane@example.com","zone_count":12}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/servers":{"get":{"tags":["Servers"],"summary":"List Servers","description":"Returns a paginated list of all servers in your team. Supports filtering by search term and status. Requires the `read:servers` scope.\n","operationId":"servers.index","x-mcp-description":"Each server in the result includes a `dashboard_url` that opens it in the xCloud dashboard. Also call this when a deploy request names no target server: show the eligible servers and ask the human to choose \u2014 never select one silently. If they already named a server, do not ask again.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"search","in":"query","description":"Filter by server name or IP address","schema":{"type":"string","example":"xcloud-prod"}},{"name":"status","in":"query","description":"Filter by server status","schema":{"type":"string","enum":["new","creating","modifying","deleting","provisioning","created","provisioned","modified","deleted","disconnected","creation_failed","error","provisioning_failed","payment_failed","deletion_failed","modification_failed","low_storage","suspended"],"example":"provisioned"}},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of servers","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PaginatedServers"}}}]},"example":{"success":true,"message":"Servers retrieved successfully.","data":{"items":[{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"xcloud","status":"provisioned","status_readable":"Provisioned","ip_address":"203.0.113.42","provider":"digitalocean","stack":"lemp","php_version":"8.2","location":"New York","created_at":"2024-06-01T09:00:00Z","dashboard_url":"https:\/\/app.xcloud.host\/server\/a1b2c3d4-e5f6-7890-abcd-ef1234567890\/dashboard"}],"pagination":{"current_page":1,"last_page":3,"per_page":15,"total":42}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Servers"],"summary":"Create Server","description":"Purchases and provisions a new xCloud-managed (Vultr) server on your team's billing account. `size` must be a plan `slug` and `region` a region `id` offered by that plan \u2014 both from `GET \/servers\/plans`. Requires the `write:servers` scope.\n\nProvisioning is asynchronous: the server is returned immediately with an early status and then moves through `new` \u2192 `provisioning` \u2192 `provisioned` (or `provisioning_failed`). Poll `GET \/servers\/{uuid}` for the current status.\n\nPass an optional `Idempotency-Key` header to make retries safe \u2014 a completed create is replayed verbatim so a dropped response can't charge the team twice or create a second paid server.\n","x-mcp-description":"Purchases and provisions a new xCloud-managed (Vultr) server on your team's billing account \u2014 it CHARGES the team's default payment method immediately, so quote the plan, region and price from servers.plans, get the human's explicit approval, then set confirm: true. Pass an Idempotency-Key and reuse that same key on every retry, so a dropped response cannot buy a second server. Provisioning is async: poll servers.provisioning-progress for the step list, or servers.show for status.\n","operationId":"servers.store","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key that makes this create idempotent. If a previous request with the same key already completed, its stored response is replayed instead of provisioning (and charging for) a second server.\n\n**Use a UNIQUE value (e.g. a fresh UUID) for every distinct server you create.** Reusing a key replays the FIRST request's response verbatim and creates nothing \u2014 so if you send it with a different body you will get back the earlier server, not a new one. Reusing a key with a different request body now returns `422`. Omit the header entirely if you don't need retry-safety. (Note: an API client that auto-fills the example below will reuse it across requests \u2014 replace it per call.)\n","schema":{"type":"string","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PurchaseServerRequest"},"examples":{"create":{"summary":"Purchase a server","value":{"name":"my-app-server","size":"vc2-1c-1gb","region":"ewr","renewal_period":"monthly","stack":"nginx","database_type":"mysql","ubuntu_version":"24.04","backups":false,"tags":["production","api"]}}}}}},"responses":{"202":{"description":"Server provisioning has started. The response `data` is the server object with an early status; provisioning continues asynchronously. The `Location` header points at `GET \/servers\/{uuid}` \u2014 poll it for status (`new` \u2192 `provisioning` \u2192 `provisioned` \/ `provisioning_failed`).\n","headers":{"Location":{"description":"URL of the created server (`GET \/servers\/{uuid}`).","schema":{"type":"string","format":"uri","example":"https:\/\/app.xcloud.host\/api\/v1\/servers\/a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}},"content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerDetail"}}}]},"example":{"success":true,"message":"Server provisioning started.","data":{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"my-app-server","status":"provisioning","ip_address":"203.0.113.42","provider":"vultr","stack":"nginx","php_version":"8.2","ubuntu_version":"24.04","region":"ewr","monitoring":true,"created_at":"2026-07-12T10:30:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Payment required. Either the team has no active payment method \/ is not in good billing standing (pre-flight, before any server is created), or the charge was declined\/failed and the server was rolled back. Returns the standard error envelope with `data: null`.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"examples":{"no_payment_method":{"summary":"No active payment method (pre-flight)","value":{"success":false,"message":"Add an active payment method in the dashboard before creating a server.","data":null}},"charge_failed":{"summary":"Charge declined \u2014 server rolled back","value":{"success":false,"message":"Your payment could not be completed. No server was created.","data":null}}}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{uuid}":{"get":{"tags":["Servers"],"summary":"Get Server","description":"Returns full details for a specific server. Requires the `read:servers` scope.\n","operationId":"servers.show","x-mcp-description":"The server includes a `dashboard_url` that opens it in the xCloud dashboard.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Server detail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerDetail"}}}]},"example":{"success":true,"message":"Server retrieved successfully.","data":{"uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"xcloud","status":"provisioned","status_readable":"Provisioned","ip_address":"203.0.113.42","provider":"digitalocean","stack":"lemp","php_version":"8.2","ubuntu_version":"24.04","node_version":"20","database_type":"mysql","location":"New York","created_at":"2024-06-01T09:00:00Z","dashboard_url":"https:\/\/app.xcloud.host\/server\/a1b2c3d4-e5f6-7890-abcd-ef1234567890\/dashboard"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/sites":{"get":{"tags":["Servers"],"summary":"List Sites on Server","description":"Returns all sites hosted on the specified server. Requires the `read:servers` scope.\n","operationId":"servers.sites","x-mcp-description":"Each site in the result includes a `dashboard_url` that opens it in the xCloud dashboard.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"status","in":"query","description":"Filter by site status","schema":{"type":"string","enum":["new","migration_init","migration_in_queue","clone_init","provisioning","migrating","cloning","deleting","provisioned","deleted","provisioning_failed","deletion_failed","migration_failed","migration_cancelled","clone_failed","update_failed","suspended"]}},{"name":"type","in":"query","description":"Filter by site type","schema":{"type":"string","enum":["wordpress","laravel","custom-php","oneclick","phpmyadmin","n8n","uptime-kuma","mautic","nextcloud","librechat","openwebui","ollama","nodejs","docker-compose","umami","lovable","site-pro","supabase","wireguard","openclaw","paperclip","hermes","deepseek_harness"]}},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of sites on the server","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PaginatedSites"}}}]},"example":{"success":true,"message":"Sites retrieved successfully.","data":{"items":[{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"example.com","domain_name":"example.com","type":"wordpress","status":"provisioned","deploy_state":"deployed","php_version":"8.2","server_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","created_at":"2024-06-10T14:30:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/cron-jobs":{"get":{"tags":["Servers"],"summary":"List Cron Jobs","description":"Returns all cron jobs configured on the specified server. Requires the `read:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"List of cron jobs","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/CronJob"}}}}]},"example":{"success":true,"message":"Cron jobs retrieved successfully.","data":[{"id":101,"command":"php \/home\/xcloud\/example.com\/artisan schedule:run","frequency":"* * * * *","status":"active"}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Servers"],"summary":"Create Server Cron Job","description":"Creates a server-level cron job and installs it on the server synchronously. The user is validated against the server's actual Linux users via SSH. Requires the `write:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs.create","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ServerCronJobInput"}}}},"responses":{"201":{"description":"Cron job created and installed","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CronJobDetail"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{uuid}\/cron-jobs\/{cronJobUuid}":{"put":{"tags":["Servers"],"summary":"Update Server Cron Job","description":"Updates a server-level cron job and reinstalls it on the server. Requires the `write:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ServerCronJobInput"}}}},"responses":{"200":{"description":"Cron job updated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CronJobDetail"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Servers"],"summary":"Delete Server Cron Job","description":"Removes the cron job from the server (synchronous SSH) and deletes the record. Requires the `write:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cron job deleted","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"502":{"description":"Failed to remove cron job from the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/cron-jobs\/{cronJobUuid}\/execute":{"post":{"tags":["Servers"],"summary":"Execute Server Cron Job","description":"Triggers the cron command now. The command is launched in the background (detached over SSH) and returns 202 immediately, so long-running cron jobs do not time out the request. Retrieve the result via the cron-job output endpoint once it finishes. Requires the `write:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs.execute","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Cron job execution started","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"502":{"description":"Cron job failed to launch","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/cron-jobs\/{cronJobUuid}\/output":{"get":{"tags":["Servers"],"summary":"Get Server Cron Job Output","description":"Returns the last captured output for the cron job. Requires the `read:servers` scope and the `server:cron-job` permission.\n","operationId":"servers.cron-jobs.output","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cron job output","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"output":{"type":"string"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/firewall-rules":{"get":{"tags":["Servers"],"summary":"List Firewall Rules","description":"Returns every firewall rule configured on the server, with per-traffic counts (allow \/ deny) and the active-rule count. Requires the `read:servers` scope.\n","operationId":"servers.firewallRules","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"List of firewall rules","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/FirewallRuleList"}}}]},"example":{"success":true,"message":"Firewall rules retrieved successfully.","data":{"items":[{"uuid":"55ff66aa-77bb-88cc-99dd-00ee11ff2233","name":"ssh-admin","ip_address":"203.0.113.10","port":"22","protocol":"tcp","traffic":"allow","is_active":true,"description":"Office static IP","created_at":"2026-01-10T00:00:00Z","updated_at":"2026-01-10T00:00:00Z"}],"counts":{"allow":1,"deny":0,"active":1,"total":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Servers"],"summary":"Create Firewall Rule","description":"Creates a new firewall rule on the server. Synchronous \u2014 runs `InstallFirewall` over SSH inline and returns the new row at 201. Idempotent on (name, port, ip_address). Requires the `write:servers` scope.\n","operationId":"servers.firewallRules.create","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateFirewallRuleRequest"},"examples":{"allow_ssh_office":{"summary":"Allow SSH from an office IP","value":{"name":"ssh-office","port":"22","ip_address":"203.0.113.10","protocol":"tcp","traffic":"allow"}},"deny_bot_range":{"summary":"Block a port range from a known bot subnet","value":{"name":"block-bots","port":"8000:9000","ip_address":"198.51.100.0\/24","protocol":"tcp","traffic":"deny"}}}}}},"responses":{"201":{"description":"Firewall rule created","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/FirewallRule"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Validation failure or InstallFirewall SSH error.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/firewall-rules\/{firewallRuleUuid}":{"delete":{"tags":["Servers"],"summary":"Delete Firewall Rule","description":"Disables the rule on the server (DisableFirewall over SSH) then deletes the row. Synchronous. Requires the `write:servers` scope.\n","operationId":"servers.firewallRules.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"firewallRuleUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Firewall rule deleted","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/firewall-rules\/{firewallRuleUuid}\/enable":{"post":{"tags":["Servers"],"summary":"Enable Firewall Rule","description":"Marks the rule active and runs `InstallFirewall` over SSH. Synchronous. Requires the `write:servers` scope.\n","operationId":"servers.firewallRules.enable","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"firewallRuleUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Firewall rule enabled","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/FirewallRule"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"InstallFirewall SSH error.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/firewall-rules\/{firewallRuleUuid}\/disable":{"post":{"tags":["Servers"],"summary":"Disable Firewall Rule","description":"Marks the rule inactive and runs `DisableFirewall` over SSH. Synchronous. Requires the `write:servers` scope.\n","operationId":"servers.firewallRules.disable","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"firewallRuleUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Firewall rule disabled","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/FirewallRule"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"DisableFirewall SSH error.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/firewall\/whitelist-xcloud-ips":{"post":{"tags":["Servers"],"summary":"Whitelist xCloud Infrastructure IPs","description":"Adds (or extends) the \"xCloud Infrastructure\" rule so xCloud jumpbox, API, backup, and monitoring IPs can reach the server's SSH port. Safe to call repeatedly \u2014 already-present IPs are not duplicated. Requires the `write:servers` scope.\n","operationId":"servers.firewall.whitelist-xcloud-ips","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"xCloud IPs whitelisted","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"added_count":{"type":"integer","example":3}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Whitelisting failed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/firewall\/whitelist-caller-ip":{"post":{"tags":["Servers"],"summary":"Whitelist Caller IP","description":"Whitelists the API caller's current IP address on the server's SSH firewall. Useful before tightening SSH access rules so the caller does not lock themselves out. Requires the `write:servers` scope.\n","operationId":"servers.firewall.whitelist-caller-ip","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Caller IP whitelisted","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"caller_ip":{"type":"string","example":"203.0.113.42"},"added_count":{"type":"integer","example":1}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Whitelisting failed or caller IP not detectable.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/firewall\/ssh-restriction-status":{"get":{"tags":["Servers"],"summary":"Get SSH Restriction Status","description":"Returns whether xCloud infrastructure IPs are whitelisted and whether the API caller's IP is whitelisted on the server's SSH firewall. Use before any rule-tightening operation to check for lockout risk. Requires the `read:servers` scope.\n","operationId":"servers.firewall.ssh-restriction-status","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"SSH restriction status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SshRestrictionStatus"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/fail2ban\/banned-ips":{"get":{"tags":["Servers"],"summary":"List Banned IP Addresses","description":"Returns IP addresses currently banned by fail2ban on the server. Runs synchronously over SSH. Requires the `read:servers` scope and the `server:manage-firewall` permission.\n","operationId":"servers.fail2ban.banned-ips","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Banned IP addresses","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"banned_ips":{"type":"array","items":{"type":"string","example":"1.2.3.4"}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"502":{"description":"fail2ban fetch failed on the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}},"post":{"tags":["Servers"],"summary":"Ban IP Addresses","description":"Bans one or more IP addresses via fail2ban. Runs synchronously over SSH. Up to 100 IPs per call. Requires the `write:servers` scope and the `server:manage-firewall` permission.\n","operationId":"servers.fail2ban.ban","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["ip_addresses"],"properties":{"ip_addresses":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","format":"ipv4","example":"1.2.3.4"}}}}}}},"responses":{"201":{"description":"IP addresses banned","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"banned_ips":{"type":"array","items":{"type":"string","example":"1.2.3.4"}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"502":{"description":"fail2ban ban command failed on the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/fail2ban\/banned-ips\/{ip}":{"delete":{"tags":["Servers"],"summary":"Unban IP Address","description":"Unbans a single IP address via fail2ban. Runs synchronously over SSH. Requires the `write:servers` scope and the `server:manage-firewall` permission.\n","operationId":"servers.fail2ban.unban","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"ip","in":"path","required":true,"description":"IPv4 or IPv6 address to unban","schema":{"type":"string","example":"1.2.3.4"}}],"responses":{"200":{"description":"IP address unbanned","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Invalid IP address","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"502":{"description":"fail2ban unban command failed on the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/supervisor-processes":{"get":{"tags":["Servers"],"summary":"List Supervisor Processes","description":"Returns supervisor (process-manager) entries configured on the server, including processes owned by sites on that server. Paginated. Requires the `read:servers` scope.\n","operationId":"servers.supervisorProcesses","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of supervisor processes","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SupervisorProcessList"}}}]},"example":{"success":true,"message":"Supervisor processes retrieved successfully.","data":{"items":[{"uuid":"ab12cd34-5678-90ef-aabb-ccddeeff0011","command":"php \/var\/www\/example.com\/artisan queue:work","directory":"\/var\/www\/example.com","user":"xcloud_example","numprocs":2,"startsecs":1,"stopsecs":10,"stopsignal":"TERM","status":"running","site_uuid":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb","site_name":"example.com","created_at":"2026-02-10T00:00:00Z","updated_at":"2026-04-01T10:00:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/snapshots":{"get":{"tags":["Servers"],"summary":"List Site Snapshots on a Server","description":"Returns the **site** snapshots taken from sites hosted on this server (aggregated across every source site on the server). These are site-level backups\/snapshots \u2014 this is **not** a server-image or server-level snapshot. Each entry carries the originating site's UUID and name so callers can correlate back to the site. SSH keypair references, raw meta, and logs are stripped. The `public_url_token` is included only for public snapshots. Requires the `read:servers` scope.\n","operationId":"servers.snapshots","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of server snapshots","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerSnapshotList"}}}]},"example":{"success":true,"message":"Snapshots retrieved successfully.","data":{"items":[{"uuid":"9f8e7d6c-5b4a-3210-fedc-ba9876543210","name":"pre-deploy","description":"Snapshot before v2 release","type":"private","status":"ready","size_bytes":524288000,"formatted_size":"500 MB","public_url_token":null,"source_site_uuid":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb","source_site_name":"example.com","created_at":"2026-04-01T10:00:00Z","updated_at":"2026-04-01T10:15:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/monitoring\/history":{"get":{"tags":["Servers"],"summary":"Get Server Monitoring History","description":"Returns a time-series of server-wide CPU, RAM, and disk usage sampled from ServerMonitor snapshots. Supports `range=24h|7d` (default 7d). Requires Pro plan \u2014 returns 403 on free plans. Requires the `read:servers` scope.\n","operationId":"servers.monitoringHistory","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"range","in":"query","description":"Sampling window. 24h returns the last 24 hours; 7d returns the last week (default).","schema":{"type":"string","enum":["24h","7d"],"default":"7d","example":"7d"}}],"responses":{"200":{"description":"Server monitoring history","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerMonitoringHistory"}}}]},"example":{"success":true,"message":"Monitoring history retrieved successfully.","data":{"server":{"uuid":"55ff66aa-77bb-88cc-99dd-00ee11ff2233","name":"web-1"},"range":"7d","samples":[{"cpu_usage":12.4,"ram_usage":68.1,"disk_usage":34.7,"time_at":"10:00 AM","sampled_at":"2026-04-01T10:00:00Z"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"description":"Server is on a free plan or token lacks the required scope\/policy.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/monitoring":{"get":{"tags":["Servers"],"summary":"Get Server Monitoring Stats","description":"Returns the latest CPU, memory, and disk usage statistics for the server. Requires the `read:servers` scope.\n","operationId":"servers.monitoring","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Server monitoring stats","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerMonitoring"}}}]},"example":{"success":true,"message":"Monitoring stats retrieved successfully.","data":{"cpu_usage":12.4,"memory_usage":68.1,"disk_usage":34.7,"uptime":"15 days, 3:42:10","recorded_at":"2025-03-14T16:00:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/tasks":{"get":{"tags":["Servers"],"summary":"Get Recent Server Tasks","description":"Returns the recent task\/operation history for the server (e.g. provisioning, software installs, reboots). Requires the `read:servers` scope.\n","operationId":"servers.tasks","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated server task history","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerTaskList"}}}]},"example":{"success":true,"message":"Tasks retrieved successfully.","data":{"items":[{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","name":"Reboot Server","type":"reboot","status":"finished","output":"Server reboot completed","created_at":"2025-03-14T10:00:00Z","updated_at":"2025-03-14T10:02:15Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/php-versions":{"get":{"tags":["Servers"],"summary":"List PHP Versions","description":"Returns all PHP versions installed on the specified server, including which is set as the default. Requires the `read:servers` scope.\n","operationId":"servers.php-versions","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"List of installed PHP versions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PhpVersion"}}}}]},"example":{"success":true,"message":"PHP versions retrieved successfully.","data":[{"version":"8.2","is_default":true},{"version":"8.1","is_default":false},{"version":"7.4","is_default":false}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Servers"],"summary":"Install PHP Version","description":"Installs a PHP version on the server. Asynchronous \u2014 dispatches to the background queue. Poll `\/php-versions\/available` for the final status. Requires the `write:servers` scope and the `server:manage-php` permission.\n","operationId":"servers.php-versions.install","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["php_version"],"properties":{"php_version":{"type":"string","example":"8.3"}}}}}},"responses":{"202":{"description":"PHP install dispatched","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PhpInstallStatus"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Servers"],"summary":"Uninstall PHP Version","description":"Uninstalls a PHP version. Asynchronous \u2014 dispatches to the background queue. Requires the `write:servers` scope.\n","operationId":"servers.php-versions.uninstall","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["php_version"],"properties":{"php_version":{"type":"string","example":"8.3"}}}}}},"responses":{"202":{"description":"PHP uninstall dispatched","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"status":{"type":"string","example":"uninstalling"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{uuid}\/php-versions\/available":{"get":{"tags":["Servers"],"summary":"List Available PHP Versions","description":"Returns every PHP version available on the server's OS, with install status, default flag, opcache state, and patch info. Requires the `read:servers` scope.\n","operationId":"servers.php-versions.available","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Available PHP versions with install state","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PhpVersionAvailable"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/php-versions\/patch-info":{"get":{"tags":["Servers"],"summary":"Get PHP Patch Info","description":"Synchronously refreshes patch availability for installed PHP versions (runs UpdatePackages + CheckPatchVersions via SSH) and returns current patch metadata. Requires the `read:servers` scope.\n","operationId":"servers.php-versions.patch-info","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"PHP patch info","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","nullable":true},"php_versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"string"},"status":{"type":"string"},"current_version":{"type":"string","nullable":true},"patch_available":{"type":"string","nullable":true}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/php-versions\/{version}\/default":{"post":{"tags":["Servers"],"summary":"Set Default PHP Version","description":"Set the given PHP version as the server's default. If already installed flips the OS default synchronously (returns 200); otherwise dispatches an install (returns 202).\n","operationId":"servers.php-versions.default","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"version","in":"path","required":true,"schema":{"type":"string","example":"8.3","pattern":"^[0-9]+\\.[0-9]+$"}}],"responses":{"200":{"description":"Default PHP version updated (already installed)","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"is_default":{"type":"boolean"},"dispatched":{"type":"boolean","example":false}}}}}]}}}},"202":{"description":"PHP install dispatched as new default","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"is_default":{"type":"boolean"},"dispatched":{"type":"boolean","example":true}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Invalid PHP version","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/php-versions\/{version}\/patch":{"post":{"tags":["Servers"],"summary":"Patch PHP Version","description":"Apply patch (minor-version bump) for an installed PHP version. Asynchronous \u2014 dispatches PatchPHPVersion to the background queue.\n","operationId":"servers.php-versions.patch","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"version","in":"path","required":true,"schema":{"type":"string","example":"8.3","pattern":"^[0-9]+\\.[0-9]+$"}}],"responses":{"202":{"description":"PHP patch dispatched","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"status":{"type":"string","example":"patching"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"PHP version not installed or invalid","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/php-versions\/{version}\/opcache":{"post":{"tags":["Servers"],"summary":"Toggle OPCache for PHP Version","description":"Enable or disable OPCache for a PHP version. Synchronous over SSH. Requires the `write:servers` scope and the `server:manage-php` permission.\n","operationId":"servers.php-versions.opcache","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"version","in":"path","required":true,"schema":{"type":"string","example":"8.3","pattern":"^[0-9]+\\.[0-9]+$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OPCache state updated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"opcache_enabled":{"type":"boolean"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"502":{"description":"Failed to update OPCache state","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/reboot":{"post":{"tags":["Servers"],"summary":"Submit a server reboot command","description":"Synchronous acknowledgement: 200 with null data once the reboot command has been submitted over SSH. The 200 says the command was accepted, never that the server came back \u2014 nothing here reads a new boot identity.\n\nAsynchronous alternative. The verified flow is servers.reboots.store, which returns 202 carrying the operation as `data.uuid` before any SSH runs. Poll GET \/servers\/{uuid}\/reboots\/{operationUuid} until is_terminal; only `completed` with reboot_verified: true proves the server rebooted. POST on that same path with \/check resumes read-only verification of an operation left unconfirmed. This endpoint keeps its 200 contract so existing token and MCP clients are not broken; new integrations should use the tracked pair.\n\n422 when the server is not connected, when an unresolved reboot operation already exists \u2014 a second command is never sent while one is outstanding \u2014 or when submission of the command could not be confirmed, including an SSH authentication failure, connection failure or lost command response. These failures never receive a success acknowledgement; an uncertain submission remains guarded against duplicate commands.\n","x-mcp-description":"200 means submitted, not rebooted. For a verifiable result call servers.reboots.store and poll servers.reboots.show until is_terminal.\n","operationId":"servers.reboot","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Reboot command submitted; this response does not verify completion.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"Reboot command submitted; completion unverified","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Server not connected, an unresolved reboot already exists, or command submission could not be confirmed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/reboots":{"post":{"tags":["Servers"],"summary":"Request a verified server reboot","description":"Records one reboot operation, returned as `data.uuid`, before any SSH runs; poll it for the outcome. A repeat while one is unresolved returns it rather than rebooting again. Only a different boot identity, read back over SSH, counts as completion.\n","operationId":"servers.reboots.store","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"202":{"description":"Operation recorded, or an existing unresolved operation returned. Requires write:servers and server:manage-services. A repeat returns the same `data.uuid` only while the operation remains unresolved; after settlement it starts a new reboot. If this response is lost, do not automatically repeat the POST: without its UUID the client cannot safely determine whether the operation has settled. Keep the outcome unconfirmed and require operator reconciliation before another submission. If the UUID was received, poll that operation instead. Reboot-required flags clear only after verification, and an unconfirmed operation keeps blocking new submissions.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ServerRebootOperationResponse"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Server not connected.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/reboots\/{operationUuid}":{"get":{"tags":["Servers"],"summary":"Read one server reboot operation","description":"Reads one reboot operation on this server; runs no SSH. `is_terminal` only means checking stopped \u2014 only `completed` with `reboot_verified: true` proves the server rebooted.\n","operationId":"servers.reboots.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"operationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Exact reboot operation, without SSH output or private boot identities. Requires read:servers and server:manage-services, and returns only an operation on this server in the current team. Queued, preparing and verifying work has a 15-minute initial deadline; missing confirmation after the command was submitted becomes unconfirmed, never success.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ServerRebootOperationResponse"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/reboots\/{operationUuid}\/check":{"post":{"tags":["Servers"],"summary":"Recheck an unconfirmed reboot","description":"Resumes read-only verification of an unconfirmed operation for five minutes, within two hours of its creation. Never sends a reboot command; other states return unchanged.\n","operationId":"servers.reboots.check","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"operationUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current operation after a read-only verification request. Requires write:servers and server:manage-services, and only an unconfirmed operation with a saved baseline resumes verification. One still unconfirmed after this window needs operator investigation and keeps blocking new reboot submissions \u2014 a status check is not authorization to retry the reboot.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ServerRebootOperationResponse"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/services":{"get":{"tags":["Servers"],"summary":"List Server Services","description":"Returns the services managed on the server (Nginx, OpenLiteSpeed, PHP, Redis, Docker, Supervisor, MySQL\/MariaDB, etc.) with their current status, version, and action capability flags (`can_start`, `can_stop`, `can_restart`, `can_install`). `data.installable` is the catalog of services this stack can still install (from the dashboard Install a Service modal). A monitoring-only Redis row (`is_required: false`) does not remove Redis from `installable`. After `POST \/services\/install`, poll this endpoint until the new row is `active` or `failed`. Requires the `read:servers` scope and the `server:manage-services` permission.\n","operationId":"servers.services","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"List of server services","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerService"}},"count":{"type":"integer","example":6},"installable":{"type":"array","items":{"type":"string"},"description":"Service names that can still be installed on this server. PHP appears here on nginx\/OLS stacks but must be installed via `\/php-versions`, not `POST \/services\/install`.\n","example":["redis","nodejs"]}}}}}]},"example":{"success":true,"message":"Services retrieved successfully.","data":{"items":[{"name":"nginx","label":"NGINX","status":"active","version":null,"is_required":true,"auto_healing":true,"can_restart":true,"can_start":false,"can_stop":false,"can_install":false},{"name":"php","label":"PHP 8.2","status":"active","version":"8.2","is_required":false,"auto_healing":false,"can_restart":true,"can_start":false,"can_stop":true,"can_install":false}],"count":2,"installable":["redis","nodejs"]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/services\/restart":{"post":{"tags":["Servers"],"summary":"Restart a Server Service","description":"Restarts a single server service, addressed by name in the request body (e.g. `nginx`, `lsws`, `php`, `redis`, `docker`, `supervisor`, `mysql`, `mariadb`). For PHP servers running multiple versions, supply `version` to disambiguate. The restart runs synchronously over SSH. Node.js is a runtime and cannot be restarted; PHP cannot be restarted on OpenLiteSpeed servers (they use LSAPI). Requires the `write:servers` scope.\n","operationId":"servers.services.restart","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["mysql","mariadb","postgresql","nginx","redis","php","ssh","supervisor","docker","lsws","nodejs","openclaw","paperclip","hermes","deepseek_harness"],"description":"Service name to restart.","example":"nginx"},"version":{"type":"string","nullable":true,"description":"Optional version selector (PHP services only).","example":"8.2"}}}}}},"responses":{"200":{"description":"Service restarted","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"NGINX restarted successfully","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Server not found, or the requested service is not present on the server.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Server not connected, the service cannot be restarted in its current state, or validation failed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/services\/disable":{"post":{"tags":["Servers"],"summary":"Disable a Server Service","description":"Stop a service by name. For PHP, supply version to disambiguate. Requires can_stop=true: an active service with supported stop actions, including required services. Node.js and PHP on OpenLiteSpeed cannot be stopped. Runs synchronously over SSH. Requires the write:servers scope and server:manage-services permission.\n","operationId":"servers.services.disable","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["mysql","mariadb","postgresql","nginx","redis","php","ssh","supervisor","docker","lsws","nodejs","openclaw","paperclip","hermes","deepseek_harness"],"description":"Service name to disable.","example":"redis"},"version":{"type":"string","nullable":true,"description":"Optional version selector (PHP services only).","example":"8.2"}}}}}},"responses":{"200":{"description":"Service disabled","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"Redis disabled successfully","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Server not found, or the requested service is not present on the server.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Server not connected, the service is not currently active (cannot be disabled), or validation failed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/node-versions":{"get":{"tags":["Servers"],"summary":"Get Server Node.js Versions","description":"Read the current server-wide Node.js major, service status, supported majors, and version-operation history. Poll this endpoint after a version change until status is active or failed. current_version changes only after a successful switch; on failure it retains the last successful major and does not guarantee that the OS runtime is healthy. Historical installed entries do not imply side-by-side system runtimes. Requires read:servers and server:manage-php, matching dashboard Node access.\n","operationId":"servers.node-versions.index","x-mcp-description":"Poll until active or failed; verify current_version. Requires server:manage-php.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Current Node.js state and supported versions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["current_version","status","available_versions","items"],"properties":{"current_version":{"type":"string","nullable":true,"example":"22"},"status":{"type":"string","example":"active"},"available_versions":{"type":"array","items":{"type":"string"},"example":["14","16","18","20","21","22","24"]},"items":{"type":"array","items":{"type":"object","properties":{"version":{"type":"string","example":"22"},"status":{"type":"string","example":"installed"},"is_default":{"type":"boolean"}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/node-versions\/{version}\/default":{"post":{"tags":["Servers"],"summary":"Change Server Node.js Version","description":"Install the selected Node.js major and make it the server default on connected Nginx or OpenLiteSpeed servers. Supports upgrading or downgrading the major, including a first install. Replaces the server-wide Node runtime; this can affect existing Node applications and build tools. Does not install isolated per-site runtimes or perform a same-major patch update. Returns 202 while installing, or 200 if already active on that major. Poll GET \/servers\/{uuid}\/node-versions until status is active or failed. Requires write:servers and server:manage-php. Polling also requires read:servers.\n","operationId":"servers.node-versions.default","x-destructive":true,"x-mcp-description":"Replaces system Node. After 202 poll servers_node-versions_index. Requires server:manage-php.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"version","in":"path","required":true,"schema":{"type":"string","enum":["14","16","18","20","21","22","24"],"example":"24"}}],"responses":{"200":{"description":"Already active on the requested major; no work dispatched","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"status":{"type":"string","example":"active"},"dispatched":{"type":"boolean","example":false}}}}}]}}}},"202":{"description":"Version change queued; poll GET \/node-versions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"version":{"type":"string"},"status":{"type":"string","example":"installing"},"dispatched":{"type":"boolean","example":true}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Another Node.js version operation is in progress","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Unsupported Node.js major, disconnected server, or incompatible stack","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/services\/install":{"post":{"tags":["Servers"],"summary":"Install a Server Service","description":"Queues installation of a service that appears in `GET \/services` `installable` \u2014 Redis, first Node.js major, MySQL or MariaDB, or Docker. PHP is rejected (422); install PHP with `POST \/php-versions`. Extra Node majors and the default switch use `POST \/node-versions\/{version}\/default`. Same-major patch updates are not exposed by this endpoint.\nReturns 202 as soon as the install is queued. Poll `GET \/services` until the matching `name` is `active` or `failed`. Do not poll `GET \/tasks` (that requires a different permission). Never returns Redis or database passwords.\n`version` is a Node major (`22`) or a database engine key (`mysql84`, `mariadb11`) matching the dashboard Install a Service modal. Redis and Docker ignore `version`. Requires the `write:servers` scope and the `server:manage-services` permission.\n","operationId":"servers.services.install","x-mcp-description":"Install Redis, first Node.js, MySQL\/MariaDB, or Docker. PHP: use servers_php-versions_install. After 202 poll servers_services.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["mysql","mariadb","postgresql","nginx","redis","php","ssh","supervisor","docker","lsws","nodejs","openclaw","paperclip","hermes","deepseek_harness"],"description":"Service name to install. PHP is always 422.","example":"redis"},"version":{"type":"string","nullable":true,"description":"Node major (`14`\u2013`24`) or database engine key (`mysql8`, `mysql84`, `mariadb10`, `mariadb11`). Optional for Redis and Docker. Node defaults to `22` when omitted.\n","example":"22"}}}}}},"responses":{"202":{"description":"Install queued. Poll GET \/services for active or failed.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"service":{"type":"string","example":"redis"},"status":{"type":"string","example":"installing"},"version":{"type":"string","nullable":true,"example":"7"}}}}}]},"example":{"success":true,"message":"Redis installation has been queued. This may take a few minutes.","data":{"service":"redis","status":"installing","version":"7"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Server not connected, service not installable on this stack, PHP (use \/php-versions), already installed, live MySQL blocking MariaDB (or vice versa), invalid version, or validation failed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/services\/enable":{"post":{"tags":["Servers"],"summary":"Enable a Server Service","description":"Starts a stopped or failed server service, addressed by name in the request body (same shape as restart\/disable). Runs synchronously over SSH. Node.js is a runtime and cannot be started. Requires the `write:servers` scope and the `server:manage-services` permission.\n","operationId":"servers.services.enable","x-destructive":false,"x-mcp-description":"Start a stopped service. Same body as restart\/disable. Node.js cannot be started. 200 is terminal.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["mysql","mariadb","postgresql","nginx","redis","php","ssh","supervisor","docker","lsws","nodejs","openclaw","paperclip","hermes","deepseek_harness"],"description":"Service name to start.","example":"redis"},"version":{"type":"string","nullable":true,"description":"Optional version selector (PHP services only).","example":"8.2"}}}}}},"responses":{"200":{"description":"Service started","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"Redis started successfully","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Server not found, or the requested service is not present on the server.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Server not connected, the service cannot be started in its current state (already active, or a runtime such as Node.js), or validation failed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/servers\/{uuid}\/sudo-users":{"get":{"tags":["Servers"],"summary":"List Sudo Users","description":"Returns a paginated list of all sudo users on the specified server. Requires the `read:servers` scope.\n","operationId":"servers.sudoUsers.index","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of sudo users","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PaginatedSudoUsers"}}}]},"example":{"success":true,"message":"Sudo users retrieved successfully.","data":{"items":[{"uuid":"c3d4e5f6-a7b8-9012-cdef-234567890123","username":"deploy","status":"active","is_temporary":false,"expires_at":null,"created_at":"2025-06-01T12:00:00Z"},{"uuid":"d4e5f6a7-b8c9-0123-defa-345678901234","username":"auditor","status":"active","is_temporary":true,"expires_at":"2025-07-01T00:00:00Z","created_at":"2025-06-15T08:30:00Z"}],"pagination":{"current_page":1,"last_page":1,"per_page":15,"total":2}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Servers"],"summary":"Create or Update Sudo User","description":"Creates a new sudo user on the specified server, or updates an existing one if a user with the same username already exists (idempotent). This is an asynchronous operation \u2014 the user will be in `updating` status until ready. Requires the `write:servers` scope.\n","operationId":"servers.sudoUsers.store","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateSudoUserRequest"},"examples":{"permanent_user":{"summary":"Permanent sudo user","value":{"username":"deploy","password":"S3cur3P@ss!","ssh_public_keys":["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample deploy@workstation"],"is_temporary":false}},"temporary_user":{"summary":"Temporary sudo user","value":{"username":"auditor","password":"Aud1tP@ss!","ssh_public_keys":["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQExample auditor@laptop"],"is_temporary":true}}}}}},"responses":{"201":{"description":"Sudo user created or updated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SudoUser"}}}]},"example":{"success":true,"message":"Sudo user created successfully.","data":{"uuid":"c3d4e5f6-a7b8-9012-cdef-234567890123","username":"deploy","status":"updating","is_temporary":false,"expires_at":null,"created_at":"2025-06-01T12:00:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{uuid}\/sudo-users\/{sudo_user_uuid}":{"delete":{"tags":["Servers"],"summary":"Delete Sudo User","description":"Deletes the specified sudo user from the server. This is an asynchronous operation \u2014 the user will be in `deleting` status until fully removed. Requires the `write:servers` scope.\n","operationId":"servers.sudoUsers.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/SudoUserUuid"}],"responses":{"200":{"description":"Sudo user deletion initiated","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"},"example":{"success":true,"message":"Sudo user deletion initiated.","data":null}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/sites\/wordpress":{"post":{"tags":["Servers"],"summary":"Create WordPress Site","description":"Provisions a new WordPress site on the specified server. This is an asynchronous operation \u2014 the site will be in `provisioning` status until ready.\n**Notes:** - `domain` is required when `mode` is `live`; omit for `demo` sites (a subdomain is auto-generated) - `ssl.provider` is required for `live` mode - `demo` mode manages SSL itself and cannot use Cloudflare DNS or extra domains. Sending\n  `cloudflare: false`, `ssl: null` or `additional_domains: []` is accepted \u2014 only a\n  truthy\/non-empty value for `cloudflare`, `domain`, `ssl` or `additional_domains` is refused\n- If `multisite.enabled` is `true`, `multisite.type` defaults to `subdirectory` - Credentials (admin password, database password) are returned in `auto_generated` only once\n**Dry run.** Send `dry_run: true` to run every check this call runs \u2014 field validation, repository\/branch reachability, deploy-key checks, domain uniqueness, port and database checks, blueprint\/snapshot access, the Cloudflare lookup \u2014 and stop at the persist step. A valid payload answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site, no credentials); an invalid one answers the exact 4xx the real call would (same `errors.code`, `next_actions` and dashboard links). A dry run writes nothing: no site, no DNS record, no deploy key, no job \u2014 the one trace is the live host-port probe, which records a read-only \"List bound host ports\" task in the server's task history (found by local QA, 2026-09-19) \u2014 and it does not consume the `Idempotency-Key`, so the same key can be sent again with the real call.\nRequires the `write:servers` scope.\n","operationId":"servers.sites.wordpress.create","x-mcp-dry-run":true,"x-mcp-description":"Creates a WordPress site on an nginx\/OpenLiteSpeed server (never Docker). PROVISIONS A REAL, BILLABLE SITE \u2014 when unsure of the payload, send body.dry_run: true first: it runs every check, creates nothing and needs no confirm; then send the same body again without dry_run and with confirm: true. Prefer a server whose servers.index row has a database_type other than none; on one without, the default engine is installed first (choose with body.database.engine) and provisioning takes longer. Afterwards give the human data.dashboard_url (the progress page) and poll data.status_url until terminal \u2014 never hand out the domain before that, and never repeat auto_generated credentials unless asked. mode demo mints a subdomain (see servers.staging-hostname.suggest); mode live needs a domain you control.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateWordPressSiteRequest"},"examples":{"live_site":{"summary":"Production WordPress site","value":{"mode":"live","domain":"example.com","title":"My Awesome Site","php_version":"8.2","ssl":{"provider":"xcloud"},"wordpress_version":"6.7","cache":{"full_page":true,"object_cache":true},"cloudflare":true,"tags":["production","client-site"]}},"demo_site":{"summary":"Demo \/ staging site","value":{"mode":"demo","title":"Test Site","php_version":"8.2","cache":{"full_page":false}}},"multisite":{"summary":"WordPress multisite (subdomain)","value":{"mode":"live","domain":"network.example.com","title":"My Network","php_version":"8.2","ssl":{"provider":"xcloud"},"multisite":{"enabled":true,"type":"subdomain"}}}}}}},"responses":{"200":{"$ref":"#\/components\/responses\/DryRun"},"202":{"description":"WordPress site provisioning started","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CreatedWordPressSite"}}}]},"example":{"success":true,"message":"WordPress site provisioning started.","data":{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","domain":"example.com","title":"My Awesome Site","type":"wordpress","mode":"live","status":"provisioning","server_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","auto_generated":{"admin_password":"Xc!9pMnK2v#rT","database_password":"Db#7qWnZ4xA!p"}}}}}},"400":{"$ref":"#\/components\/responses\/ValidationError"},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Business rule violation (e.g. domain already in use)","content":{"application\/json":{"example":{"success":false,"message":"The domain example.com is already in use on this server.","data":null}}}}}}},"\/servers\/{uuid}\/sites\/git":{"post":{"tags":["Servers"],"summary":"Deploy Site from Git","description":"Creates a site on the server and deploys it from a git repository. This is an asynchronous operation \u2014 the response returns as soon as the deployment has been accepted, and the site remains in a provisioning state until the first deploy finishes. The whole creation is atomic: if any step fails, no partial site is left behind.\n\n**Choosing a repository source**\n- **Connected provider** \u2014 send `repository.provider_uuid` + `repository.full_name`.\n  Use this for private repositories: xCloud authorises the deploy key on the repo for\n  you, and can install a push-to-deploy webhook (`enable_push_deploy`).\n- **Public URL** \u2014 send `repository.url` (an `https:\/\/` clone URL). Cloned anonymously;\n  no key required. The `.git` suffix is added for you if you omit it.\n\n**Private SSH URLs (`git@\u2026`)** need a deploy key authorised on the remote before the first clone. Either connect a git provider, or run the deploy-key handshake (`POST \/servers\/{server_uuid}\/git\/deploy-keys` \u2192 add the public key \u2192 verify) and pass the resulting `repository.deploy_key_uuid`. A private SSH URL with no provider and no `deploy_key_uuid` is rejected (`errors.code: deploy_key_required`).\n\nThe supplied key is re-probed against the repository in this request, before anything is created \u2014 a key the remote does not accept fails with `errors.code: deploy_key_not_verified` rather than provisioning a site that cannot clone.\n\n**Defaults worth knowing**\n- `repository.branch` \u2014 omit to use the repository's default branch. - `database` \u2014 only WordPress gets a database by default. Everything else defaults to\n  none; pass `database.provider: in_server` to request one.\n- `php_version` \u2014 falls back to the server's version. - `site_user`, and the database name\/user\/password when not supplied, are derived for you.\n**Dry run.** Send `dry_run: true` to run every check this call runs \u2014 field validation, repository\/branch reachability, deploy-key checks, domain uniqueness, port and database checks, the runtime fallbacks \u2014 and stop at the persist step. A valid payload answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site, no credentials); an invalid one answers the exact 4xx the real call would (same `errors.code`, `next_actions` and dashboard links). A dry run writes nothing: no site, no DNS record, no deploy key, no job \u2014 the one trace is the live host-port probe, which records a read-only \"List bound host ports\" task in the server's task history (found by local QA, 2026-09-19) \u2014 and it does not consume the `Idempotency-Key`, so the same key can be sent again with the real call.\nRequires the `write:servers` scope.\n","operationId":"servers.sites.git.create","x-mcp-dry-run":true,"x-mcp-description":"Deploys a repo to an nginx\/OpenLiteSpeed server with an EXPLICIT app type and runtime config \u2014 no detection. Prefer servers_sites_git_auto (with git_detect) unless the human gave you the exact site_type and settings. PROVISIONS A REAL, BILLABLE SITE. When unsure of the payload, send body.dry_run: true first: it runs every check, creates nothing and needs no confirm; then get the human's approval and send the same body again without dry_run and with confirm: true. Private repos need repository.provider_uuid or a prepared+verified repository.deploy_key_uuid. Pass an Idempotency-Key header to make retries safe.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateGitSiteRequest"},"examples":{"public_repo_laravel":{"summary":"Public repository, live domain","value":{"site_type":"laravel","repository":{"url":"https:\/\/github.com\/laravel\/laravel.git","branch":"11.x"},"domain":{"mode":"live","name":"app.example.com","title":"My Laravel App","ssl_provider":"xcloud"},"php_version":"8.3","database":{"provider":"in_server"},"deploy_script":"$XCLOUD_COMPOSER install --no-dev --optimize-autoloader\n$XCLOUD_PHP artisan migrate --force\n"}},"private_repo_via_provider":{"summary":"Private repository via a connected provider, push-to-deploy on","value":{"site_type":"nodejs","repository":{"provider_uuid":"9f1c2f2e-3a5b-4c7d-8e9f-0a1b2c3d4e5f","full_name":"my-org\/my-node-app","branch":"main"},"domain":{"mode":"staging","name":"my-node-app"},"node_version":"20","serving_mode":"ssr","start_command":"npm start","port":3000,"build_command":"npm run build","enable_push_deploy":true,"env_file_content":"NODE_ENV=production\n"}}}}}},"responses":{"200":{"$ref":"#\/components\/responses\/DryRun"},"202":{"description":"Deployment accepted","content":{"application\/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Git site deployment initiated."},"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"domain":{"type":"string","example":"app.example.com"},"type":{"type":"string","example":"laravel"},"branch":{"type":"string","nullable":true,"example":"main"},"status":{"type":"string","example":"migration_init"},"server_uuid":{"type":"string","format":"uuid"},"poll_url":{"type":"string","description":"GET this URL to track deploy progress and terminal state. Following it requires the `read:sites` scope in addition to `write:servers`."},"warnings":{"type":"array","items":{"type":"string"}},"domain_setup":{"allOf":[{"$ref":"#\/components\/schemas\/DomainSetup"}],"description":"Present only for `domain.mode: \"live\"` \u2014 the DNS record the caller still has to add. Absent entirely for a staging hostname."}}}}}}}},"400":{"$ref":"#\/components\/responses\/ValidationError"},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Server or git provider not found"},"422":{"description":"Validation error, or a business rule violation \u2014 an unprovisioned server, a private SSH repository URL, or a Docker server (use docker-compose deployment).\n","content":{"application\/json":{"example":{"success":false,"message":"Private SSH repositories need a prepared deploy key. Prepare and verify one via \/servers\/{server}\/git\/deploy-keys, then pass repository.deploy_key_uuid \u2014 or connect a git provider and pass repository.provider_uuid.","errors":{"code":"deploy_key_required"},"data":null}}}}}}},"\/servers\/{uuid}\/sites\/git\/auto":{"post":{"tags":["Servers"],"summary":"Deploy Site from Git (auto-detect)","description":"Detect-then-deploy in one call. Accepts the same body as **Deploy Site from Git** (`POST \/servers\/{uuid}\/sites\/git`) but everything except `repository` is optional: repository analysis (and, for the deploy script, AI) fills any field you omit, and any value you *do* send always wins. If a field required to provision \u2014 most importantly `site_type` \u2014 is both omitted and undetectable, the call is refused (422) with a pointer to the manual endpoint rather than deploying a wrong-stack guess.\n\nConvenience defaults when omitted: `domain` \u2192 a staging hostname derived from the repository name; `branch` \u2192 the repository's real default branch; a Node ssr\/hybrid `port` \u2192 an auto-allocated free port. The response `warnings` array lists what was auto-filled. Same async, atomic behaviour and repository-source rules as the manual endpoint.\n\n**Works on Docker servers too.** This endpoint targets any stack: on a Docker server it resolves the container configuration from the repository \u2014 its docker-compose file plus a host port that file publishes, or (when there is no compose file) its Dockerfile plus the port it `EXPOSE`s \u2014 and runs the Docker deploy, reporting the chosen mode in `deploy_mode` and explaining the choice in `warnings`. Use `POST \/servers\/{uuid}\/sites\/git\/docker` only when you want to pin those values yourself. A repository that ships neither a compose file nor a Dockerfile cannot run on a Docker server and is refused with `incompatible_server`, whose `next_actions` name both remedies: `add_docker_files` or `choose_compatible_server`. A repository that could not be read at all is refused with `repository_access_not_found` instead \u2014 that is an access problem, not a missing-file one, and no explicit `site_type` bypasses it.\n\nSet `generate_ai_script: false` in the body to skip AI deploy-script generation (a model round-trip that adds latency); the deterministic default script is used instead. AI is on by default.\n**Dry run.** Send `dry_run: true` to run every check this call runs \u2014 field validation, repository\/branch reachability, deploy-key checks, domain uniqueness, port and database checks, detection and every auto-filled field (on a Docker server, the compose\/Dockerfile scan too) \u2014 and stop at the persist step. A valid payload answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site, no credentials); an invalid one answers the exact 4xx the real call would (same `errors.code`, `next_actions` and dashboard links). A dry run writes nothing: no site, no DNS record, no deploy key, no job \u2014 the one trace is the live host-port probe, which records a read-only \"List bound host ports\" task in the server's task history (found by local QA, 2026-09-19) \u2014 and it does not consume the `Idempotency-Key`, so the same key can be sent again with the real call.\nRequires the `write:servers` scope.\n","operationId":"servers.sites.git.auto","x-mcp-dry-run":true,"x-mcp-description":"Deploys a repo to ANY server \u2014 nginx\/OpenLiteSpeed OR Docker \u2014 auto-detecting what you omit (on Docker the container config too; servers_sites_git_docker only pins it; pass docker.allowed_dot_paths on a [dot_directory_assets] warning). Server: if the human named one, use it and do not ask again; else servers_index and let them choose \u2014 never pick silently. Run git_detect with that server_uuid first and clear any `repository_access.next_actions`. PROVISIONS A REAL, BILLABLE SITE \u2014 show the human the app type and domain, get approval, then confirm: true. Only `repository` is required; omit `domain` for a staging hostname. Poll `poll_url`. Send an Idempotency-Key so a retry never creates a second site. dry_run: true previews the resolved site without creating anything. On a live domain in a connected Cloudflare zone pass `cloudflare: true` and do not create the A record yourself.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/AutoGitSiteRequest"},"examples":{"minimal":{"summary":"Paste a repo \u2014 detect everything","value":{"repository":{"url":"https:\/\/github.com\/vercel\/next.js"}}},"override_domain":{"summary":"Detect the stack, deploy to my own domain","value":{"repository":{"provider_uuid":"9f1c2f2e-3a5b-4c7d-8e9f-0a1b2c3d4e5f","full_name":"my-org\/my-app"},"domain":{"mode":"live","name":"app.example.com","ssl_provider":"xcloud"}}}}}}},"responses":{"200":{"$ref":"#\/components\/responses\/DryRun"},"202":{"description":"Deployment accepted","content":{"application\/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Git site deployment initiated."},"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"domain":{"type":"string","example":"my-app.x-cloud.app"},"type":{"type":"string","example":"nodejs"},"branch":{"type":"string","nullable":true,"example":"main"},"status":{"type":"string","example":"migration_init"},"server_uuid":{"type":"string","format":"uuid"},"poll_url":{"type":"string","description":"GET this URL to track deploy progress and terminal state (see Get Site Status). Following it requires the `read:sites` scope in addition to the `write:servers` scope used to deploy."},"warnings":{"type":"array","items":{"type":"string"},"description":"Human-readable notes on what detection\/AI auto-filled."},"domain_setup":{"allOf":[{"$ref":"#\/components\/schemas\/DomainSetup"}],"description":"Present only for `domain.mode: \"live\"` \u2014 the DNS record the caller still has to add. Absent entirely for a staging hostname."}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Server or git provider not found"},"409":{"description":"A request with the same Idempotency-Key is already in progress."},"422":{"description":"Validation error, a business-rule violation (unprovisioned\/Docker\/agentic server, or a private SSH URL), or the app type could not be detected and none was supplied.\n","content":{"application\/json":{"example":{"success":false,"message":"Could not automatically detect the app type for this repository. Deploy with the manual endpoint (POST \/servers\/{server}\/sites\/git) and pass an explicit site_type.","data":null}}}}}}},"\/git\/detect":{"post":{"tags":["Servers"],"summary":"Analyse a Git repository (Deploy via Git preview)","description":"Side-effect-free repository analysis for the Deploy-via-Git flow. Detects the app type, serving mode, and build\/start guesses over the repository host's API \u2014 creating no site, no migration, and provisioning nothing. Use it to preview what `POST \/servers\/{uuid}\/sites\/git\/auto` would deploy, then optionally tweak and deploy.\n\nDetection is best-effort and honest: an unreadable repository (private without a connected provider, or an unsupported host) returns `reachable: false` with `detection: null` \u2014 branch on `repository_access` \u2014 rather than an error; never a 422.\n\nReachability and detectability are separate answers. When the repository host's API cannot be read (a rate limit, a 5xx) but an anonymous `git ls-remote` can read the repository, the response is `reachable: true` with `detection.supported: false` and a warning saying why the guess is missing and when to retry \u2014 pass `site_type` explicitly and deploy, or wait. When neither can answer, `repository_access.code` is `repository_access_probe_unavailable`: the CHECK failed, not the repository. It is never reported as a missing repository.\n\nSet `include_deploy_script: true` to also generate an AI deploy script (heavier; limited to 15 requests\/hour per user \u2014 a throttle or miss returns `deploy_script_source: default` and the deterministic script). Requires the `read:servers` scope.\n","operationId":"git.detect","x-destructive":false,"x-required-scope":"read","x-mcp-description":"Preview step for deploying a repo \u2014 call this BEFORE servers_sites_git_auto to see the detected app type, serving mode, build\/start commands and default branch. Creates nothing. Pass `server_uuid` for the chosen server so compatibility is judged against it. `repository_access.status: inaccessible` is an ACCESS problem \u2014 follow its `code`\/`next_actions`; site_type does NOT bypass it, so never fall back to servers_sites_git_create for it. A readable repo with `detection.supported: false` needs the human to name the app type. `repository_access_probe_unavailable` = the CHECK failed, not the repo: retry or pass site_type. On a Docker server `compatible:false` covers the native endpoints only \u2014 if `compatibility.docker_deployable` is true, servers_sites_git_auto deploys it via the container path (`deploy_via: docker_compose`): never call a docker-capable repo a dead end. Set include_deploy_script only when you need the AI script (rate-limited).","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"provider_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Connected git provider UUID. Required unless repository_url is given."},"repository_full_name":{"type":"string","nullable":true,"example":"my-org\/my-app","description":"owner\/repo. Required with provider_uuid."},"repository_url":{"type":"string","nullable":true,"example":"https:\/\/github.com\/vercel\/next.js","description":"Public repository URL. Required unless provider_uuid is given."},"branch":{"type":"string","nullable":true},"include_deploy_script":{"type":"boolean","nullable":true,"default":false},"app_type":{"type":"string","nullable":true,"enum":["laravel","nodejs","custom-php","wordpress","lovable"],"description":"Authoritative app-type hint for the AI deploy script."},"server_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Optional target server. When given, the response includes a `compatibility` verdict \u2014 e.g. a Docker server cannot run the native git deploy path \u2014 and the repository's declared Node version (engines.node\/.nvmrc), when present, is compared against this server's installed Node version.\n"},"start_command":{"type":"string","nullable":true,"description":"A start command being considered for this deploy. Checked for the same compound-shell-command advisory (chained with &&, ;, | or starting \"cd \") the detector applies to its own guess \u2014 see `detection.warnings`.\n"}}},"examples":{"public_url":{"summary":"Public URL","value":{"repository_url":"https:\/\/github.com\/vercel\/next.js"}},"provider_with_ai":{"summary":"Connected provider, with AI deploy script","value":{"provider_uuid":"9f1c2f2e-3a5b-4c7d-8e9f-0a1b2c3d4e5f","repository_full_name":"my-org\/my-app","include_deploy_script":true}}}}}},"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Analysis result (also returned, with reachable=false, for unreadable repositories)","content":{"application\/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Repository analysed."},"data":{"type":"object","properties":{"reachable":{"type":"boolean","description":"Whether the repository could actually be read."},"default_branch":{"type":"string","nullable":true,"example":"main"},"deploy_script_source":{"type":"string","nullable":true,"enum":["ai","default"],"description":"Present only when include_deploy_script was set."},"detection":{"type":"object","nullable":true,"description":"Null when `reachable` is false: nothing could be read, so nothing was detected \u2014 branch on `repository_access` instead.","properties":{"site_type":{"type":"string","example":"nodejs"},"serving_mode":{"type":"string","nullable":true,"enum":["static","ssr","hybrid"]},"framework":{"type":"string","nullable":true},"php_version":{"type":"string","nullable":true},"needs_redis":{"type":"boolean"},"has_node_build":{"type":"boolean"},"has_package_json":{"type":"boolean"},"node_build_script":{"type":"string","nullable":true},"has_dockerfile":{"type":"boolean","description":"Repo ships a root Dockerfile \u2014 deployable to a Docker server via the Docker Compose flow."},"has_compose":{"type":"boolean","description":"Repo ships a docker-compose\/compose file \u2014 deployable to a Docker server via the Docker Compose flow."},"build_command":{"type":"string","nullable":true},"start_command":{"type":"string","nullable":true},"web_root":{"type":"string","nullable":true},"api_proxy_path":{"type":"string","nullable":true},"deploy_script":{"type":"string","nullable":true},"supported":{"type":"boolean"},"required_node_version":{"type":"string","nullable":true,"example":">=18.0.0","description":"The Node version spec this repo declares, raw (package.json engines.node, or .nvmrc) \u2014 e.g. \">=18.0.0\", \"^18\", \"20.x\" \u2014 when it names any version at all."},"warnings":{"type":"array","items":{"type":"string"},"description":"Plain sentences, each starting with a stable bracketed code (e.g. \"[compound_start_command] ...\") so a caller can match on it."}}},"warnings":{"type":"array","description":"Present only when `reachable` is false, where `detection` (and the warnings it would have carried) is null: one `[repository_unreachable]` sentence plus anything the analysis recorded about what it could not check.","items":{"type":"string"}},"repository_access":{"type":"object","description":"Structured access verdict \u2014 branch on `code`, not on prose.","properties":{"status":{"type":"string","enum":["accessible","inaccessible"]},"mode":{"type":"string","enum":["public_https","connected_provider","deploy_key","unavailable"]},"code":{"allOf":[{"$ref":"#\/components\/schemas\/RepositoryAccessCode"}],"nullable":true,"description":"Present only when `status` is `inaccessible`."},"provider_uuid":{"type":"string","format":"uuid","nullable":true},"repository_full_name":{"type":"string","nullable":true},"capabilities":{"type":"object","nullable":true,"properties":{"can_pull":{"type":"boolean"},"can_add_deploy_key":{"type":"boolean"},"can_add_webhook":{"type":"boolean"}}},"next_actions":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Suggested remedies, e.g. connect_git_provider, prepare_deploy_key."}}},"dashboard_connect_url":{"type":"string","nullable":true,"description":"Present when the repository is inaccessible \u2014 the dashboard page to connect\/re-authorize a provider."},"compatibility":{"type":"object","nullable":true,"description":"Present only when `server_uuid` was supplied.","properties":{"compatible":{"type":"boolean"},"code":{"allOf":[{"$ref":"#\/components\/schemas\/RepositoryAccessCode"}],"nullable":true,"description":"Present when `compatible` is false (e.g. incompatible_server)."},"docker_deployable":{"type":"boolean","nullable":true,"description":"For a Docker server only. True when the repo ships a Dockerfile\/compose file and can therefore be deployed to this Docker server via the Docker Compose flow \u2014 even though `compatible` is false for the native Git endpoints. Not a dead end."},"deploy_via":{"type":"string","nullable":true,"enum":["docker_compose"],"description":"The deploy flow to use when `docker_deployable` is true."},"reason":{"type":"string","nullable":true},"recommended_stacks":{"type":"array","nullable":true,"items":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Git provider or server not found"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/git\/compose-scan":{"post":{"tags":["Servers"],"summary":"Scan a Git repository's docker-compose.yml (Docker deploy preview)","description":"Side-effect-free scan of a repository's `docker-compose.yml` (or an equivalent compose filename) \u2014 creating no site, no migration. Lists every service the file declares, the ports each publishes, and proposes a primary port. This is the Docker counterpart of `git.detect`'s preview step: use it before `POST \/servers\/{uuid}\/sites\/git\/docker` to learn what a repository's compose file declares, or let `POST \/servers\/{uuid}\/sites\/git\/auto` resolve it automatically on a Docker server.\n\nRepository addressing and the `repository_access` verdict are identical to `git.detect` \u2014 a private repo via a connected provider works the same way, and an inaccessible repository returns the same structured `code`\/`next_actions`, never a 422.\n\n`compose_file` may name a file OR a directory. A directory is searched for `compose.yaml`, `compose.yml`, `docker-compose.yml` and `docker-compose.yaml`, in that order; a filename that does not exist falls back to its siblings the same way. The file that was actually read comes back as `compose_file_resolved`, and a warning names it when it differs from what was asked for.\n\nWhen the repository ships no compose file (a standalone Dockerfile, or neither), `services` is empty and `warnings` explains why \u2014 the Dockerfile path is not scanned here; use `git.detect`'s `compatibility.docker_deployable` to learn a Dockerfile is present. Requires the `read:servers` scope.\n","operationId":"git.compose-scan","x-destructive":false,"x-required-scope":"read","x-mcp-description":"Call this before servers_sites_git_docker to learn the services a repository's docker-compose.yml declares and pick `port`. Creates nothing. Same repository_access verdict shape as git_detect \u2014 `status: inaccessible` is an ACCESS problem, follow its `code`\/`next_actions`. An empty `services` array with a `[...]` warning means no compose file was found (check git_detect's compatibility.docker_deployable for a Dockerfile instead). `suggested_primary_port.host_port` is the HOST port to pass as `port` to servers_sites_git_docker (compose mode binds a host port, not a container port) \u2014 when it is null the compose file publishes that service's port without pinning a host side, so either edit the compose file to add one or pass a free host port of your own.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"provider_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Connected git provider UUID. Required unless repository_url is given."},"repository_full_name":{"type":"string","nullable":true,"example":"dockersamples\/example-voting-app","description":"owner\/repo. Required with provider_uuid."},"repository_url":{"type":"string","nullable":true,"example":"https:\/\/github.com\/dockersamples\/example-voting-app","description":"Public repository URL. Required unless provider_uuid is given."},"branch":{"type":"string","nullable":true},"compose_file":{"type":"string","nullable":true,"default":"docker-compose.yml","description":"Compose filename\/path to scan, when the repository uses a non-default name. May also name a DIRECTORY (`flask`): the conventional names are then tried inside it, in order \u2014 `compose.yaml`, `compose.yml`, `docker-compose.yml`, `docker-compose.yaml`. A path that names a file is tried first and, when it is absent, its siblings are tried the same way, so guessing the wrong spelling of a file that IS there does not read as \"no compose file\". `compose_file_resolved` in the response says which one was read.\n"}}},"examples":{"public_url":{"summary":"Public URL","value":{"repository_url":"https:\/\/github.com\/dockersamples\/example-voting-app"}}}}}},"responses":{"200":{"description":"Scan result (also returned, with an empty services array, for an unreadable or compose-less repository)","content":{"application\/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Compose file scanned."},"data":{"type":"object","properties":{"compose_file":{"type":"string","example":"docker-compose.yml","description":"The path the caller asked for, echoed back."},"compose_file_resolved":{"type":"string","nullable":true,"description":"The compose file actually read. Equal to `compose_file` when that path existed; a sibling or a file inside the named directory when it did not; null when the repository ships no compose file at all. Pass it back as `docker.compose_file` when deploying.","example":"flask\/compose.yaml"},"services":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"vote"},"image":{"type":"string","nullable":true,"example":"dockersamples\/examplevotingapp_vote"},"build":{"type":"string","nullable":true,"description":"The build context path, when the service builds from a Dockerfile instead of a named image."},"published_ports":{"type":"array","items":{"type":"object","properties":{"host":{"type":"integer","nullable":true,"description":"Null for a bare container port (Docker assigns a random host port)."},"container":{"type":"integer","nullable":true},"protocol":{"type":"string","example":"tcp","enum":["tcp","udp"]}}}},"depends_on":{"type":"array","items":{"type":"string"}}}}},"suggested_primary_port":{"type":"object","nullable":true,"description":"`host_port` is the value to pass as `port` to servers.sites.git.docker (compose mode binds a HOST port, not a container port). Null overall when no service publishes a port; `host_port` alone is null when that service's port has no pinned host side (e.g. a bare \"8080\" entry) \u2014 the compose file needs a host mapping, or the caller must supply its own port.","properties":{"service":{"type":"string","example":"vote"},"container_port":{"type":"integer","nullable":true,"example":80},"host_port":{"type":"integer","nullable":true,"example":8080}}},"repository_access":{"type":"object","description":"Identical shape to git.detect's \u2014 branch on `code`, not on prose.","properties":{"status":{"type":"string","enum":["accessible","inaccessible"]},"mode":{"type":"string","enum":["public_https","connected_provider","deploy_key","unavailable"]},"code":{"allOf":[{"$ref":"#\/components\/schemas\/RepositoryAccessCode"}],"nullable":true,"description":"Present only when `status` is `inaccessible`."},"provider_uuid":{"type":"string","format":"uuid","nullable":true},"repository_full_name":{"type":"string","nullable":true},"capabilities":{"type":"object","nullable":true,"properties":{"can_pull":{"type":"boolean"},"can_add_deploy_key":{"type":"boolean"},"can_add_webhook":{"type":"boolean"}}},"next_actions":{"type":"array","nullable":true,"items":{"type":"string"}}}},"dashboard_connect_url":{"type":"string","nullable":true,"description":"Present when the repository is inaccessible \u2014 the dashboard page to connect\/re-authorize a provider."},"warnings":{"type":"array","items":{"type":"string"},"description":"Plain sentences explaining why services is empty (no compose file, a Dockerfile instead, unreadable repo) or noting compose env variables with no resolvable value."}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Git provider not found"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/integrations\/git":{"get":{"tags":["Integrations"],"summary":"List Git Integrations","description":"Returns the Git providers connected to the current team (GitHub today). Lets an agent resolve a `provider_uuid` for a private repository without ever handling an OAuth token. Never returns access\/refresh tokens or OAuth scopes. Requires the `read:servers` scope.\n","operationId":"integrations.git.index","x-destructive":false,"x-mcp-description":"Lists the team's connected Git providers (uuid, provider_type, account, status). Use it to find a provider_uuid to pass to git_detect or servers_sites_git_auto for a private repo. Read-only; returns no tokens.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Connected Git providers","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"provider_type":{"type":"string","example":"github"},"label":{"type":"string","example":"my-org"},"account":{"type":"string","nullable":true,"example":"octocat"},"avatar_url":{"type":"string","nullable":true},"status":{"type":"string","example":"connected"},"connected":{"type":"boolean"},"token_expires_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/integrations\/git\/{provider_uuid}\/repositories":{"get":{"tags":["Integrations"],"summary":"List Provider Repositories","description":"Lists\/searches the repositories reachable through a connected provider \u2014 the same cached crawl the dashboard uses. Returns only safe repository metadata (no owner blocks, ids, or tokens). GitHub only. Requires the `read:servers` scope.\n","operationId":"integrations.git.repositories","x-destructive":false,"x-mcp-description":"Lists\/searches repositories the connected provider can see (full_name, visibility, default_branch, url). Use the returned full_name as repository.full_name for a private-provider deploy. Read-only.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"provider_uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":30}},{"name":"sort","in":"query","schema":{"type":"string","enum":["name","updated","pushed","created"],"default":"pushed"}},{"name":"direction","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Repositories","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"full_name":{"type":"string","example":"octocat\/hello-world"},"name":{"type":"string","example":"hello-world"},"private":{"type":"boolean"},"visibility":{"type":"string","example":"public"},"default_branch":{"type":"string","nullable":true,"example":"main"},"url":{"type":"string","nullable":true}}}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Git provider not found"},"422":{"description":"The provider is not a GitHub provider."}}}},"\/servers\/{uuid}\/git\/deploy-keys":{"get":{"tags":["Servers"],"summary":"List Deploy Keys","description":"Lists the deploy keys already prepared for this team, newest first, with each key's PUBLIC half only. Use it to recover the `uuid` of a key prepared earlier \u2014 possibly in an earlier session \u2014 before calling **Verify Deploy Key**; **Prepare a Deploy Key** always mints a new one. `status` is `verified` once a clone check has proved the key against `repository`, and `pending` until then. Requires the `read:servers` scope.\n","operationId":"servers.git.deploy-keys.index","x-required-scope":"read","x-destructive":false,"x-mcp-description":"List the deploy keys already prepared for this team\/server, with each key's uuid, public key, whether it was verified against a repository and the site_uuid that already owns it \u2014 use it to find a key created earlier (for example in a previous conversation) before calling deploy-keys.verify. Reuse a key only while its site_uuid is null: one key backs one site, so a second site of the same repository needs a fresh key.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Deploy keys retrieved","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"public_key":{"type":"string","example":"ssh-rsa AAAA... deploy-xxxx@xcloud"},"status":{"type":"string","enum":["pending","verified"],"example":"pending"},"repository":{"type":"string","nullable":true,"description":"The repository this key was proved against, or null if it has not been verified yet.","example":"git@github.com:acme\/app.git"},"site_uuid":{"type":"string","format":"uuid","nullable":true,"description":"The site this key already backs, or null while it is free. One key backs one site, so a key with a site_uuid cannot be passed as repository.deploy_key_uuid for another site (errors.code = deploy_key_in_use); prepare a fresh key instead."},"read_only":{"type":"boolean","example":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Server is not provisioned yet."}}},"post":{"tags":["Servers"],"summary":"Prepare a Deploy Key","description":"Mints a standalone team deploy key and returns only its PUBLIC half. Add the public key to the repository as a read-only deploy key, then call **Verify Deploy Key**, then pass `repository.deploy_key_uuid` to a deploy endpoint. The private key never leaves xCloud. State-changing but non-billable. Requires the `write:servers` scope and `site:create`.\n","operationId":"servers.git.deploy-keys.store","x-destructive":false,"x-mcp-description":"Prepares a deploy key for a private SSH repo and returns the PUBLIC key only. Flow: prepare -> (human adds the public key to the repo) -> verify -> pass repository.deploy_key_uuid to servers_sites_git_auto\/create. Creates persistent key material on the team (non-billable, but not a read-only call) - delete it with the destroy endpoint if the deploy is abandoned.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"201":{"description":"Deploy key prepared","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"public_key":{"type":"string","example":"ssh-rsa AAAA... deploy-xxxx@xcloud"},"read_only":{"type":"boolean","example":true},"status":{"type":"string","example":"pending"},"instructions":{"type":"string"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"description":"Server is not provisioned yet."}}}},"\/servers\/{uuid}\/git\/deploy-keys\/{key_uuid}\/verify":{"post":{"tags":["Servers"],"summary":"Verify a Deploy Key","description":"Clone-probes the repository on the server with the prepared key to prove xCloud can read it (and optionally detect the framework). Non-destructive. Returns a stable `errors.code` of `deploy_key_not_verified` when the key can't reach the repo. Requires the `write:servers` scope and `site:create`.\n\nWith `detect: true` the response also carries the repository's compose scan (`compose.suggested_primary_port` is the `port` to pass to servers.sites.git.docker in compose mode) \u2014 this is the only way to scan a private SSH repository, because `git.compose-scan` cannot authenticate with a deploy key.\n","operationId":"servers.git.deploy-keys.verify","x-destructive":false,"x-mcp-description":"Proves a prepared deploy key can clone the repo (human adds it there first). Returns verified:true + default_branch, else errors.code=deploy_key_not_verified; deploy with repository.deploy_key_uuid. detect:true adds `compose`, the only compose scan a private SSH repo has: suggested_primary_port.host_port is servers_sites_git_docker's `port`.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"key_uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["repository_url"],"properties":{"repository_url":{"type":"string","example":"git@github.com:my-org\/private.git"},"branch":{"type":"string","nullable":true},"detect":{"type":"boolean","nullable":true,"default":false},"compose_file":{"type":"string","nullable":true,"description":"The compose filename you expect, echoed back as `compose.compose_file`. The probe reads the repository ROOT in Docker's own lookup order \u2014 `docker-compose.yml`, `docker-compose.yaml`, `compose.yaml`, `compose.yml` \u2014 and reports the one it found as `compose.compose_file_resolved`, so a repo using `compose.yaml` is scanned whether or not this is given. Only meaningful with `detect: true`.\n"}}}}}},"responses":{"200":{"description":"Verification result","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"verified":{"type":"boolean","example":true},"default_branch":{"type":"string","nullable":true,"example":"main"},"detection":{"type":"object","nullable":true},"compose":{"type":"object","nullable":true,"description":"The cloned repository's compose file, same shape as `git.compose-scan`'s data. Present only with `detect: true`; null when the repository ships no compose file (and none was named). This is the only compose scan available for a private SSH repository \u2014 `git.compose-scan` cannot use a deploy key.","properties":{"compose_file":{"type":"string","example":"compose.yaml","description":"The path asked for, echoed back; the resolved name when none was asked for."},"compose_file_resolved":{"type":"string","nullable":true,"example":"compose.yaml","description":"The compose file the probe actually found in the repository root; null when it ships none. Pass it back as `docker.compose_file` when deploying."},"services":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"web"},"image":{"type":"string","nullable":true},"build":{"type":"string","nullable":true},"published_ports":{"type":"array","items":{"type":"object","properties":{"host":{"type":"integer","nullable":true},"container":{"type":"integer","nullable":true},"protocol":{"type":"string","example":"tcp","enum":["tcp","udp"]}}}},"depends_on":{"type":"array","items":{"type":"string"}}}}},"suggested_primary_port":{"type":"object","nullable":true,"description":"`host_port` is the value to pass as `port` to servers.sites.git.docker (compose mode binds a HOST port). Null when no service publishes a port.","properties":{"service":{"type":"string","example":"web"},"container_port":{"type":"integer","nullable":true,"example":8080},"host_port":{"type":"integer","nullable":true,"example":8080}}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Deploy key not found"},"422":{"description":"Could not reach the repository with this deploy key (errors.code = deploy_key_not_verified).","content":{"application\/json":{"example":{"success":false,"message":"Could not reach the repository with this deploy key. Add the public key to the repository and check the URL\/branch.","errors":{"code":"deploy_key_not_verified"},"data":null}}}}}}},"\/servers\/{uuid}\/git\/deploy-keys\/{key_uuid}":{"delete":{"tags":["Servers"],"summary":"Delete a Deploy Key","description":"Drops an unadopted prepared deploy key. A key already linked to a site is protected (422). Requires the `write:servers` scope and `site:create`.\n","operationId":"servers.git.deploy-keys.destroy","x-destructive":true,"x-mcp-description":"Deletes an unadopted prepared deploy key. Refuses (422) if the key is already linked to a site. Destructive \u2014 confirm before calling.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"key_uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deploy key deleted"},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Deploy key not found"},"422":{"description":"The deploy key is linked to a site and cannot be deleted."}}}},"\/servers\/{uuid}\/staging-hostname":{"get":{"tags":["Servers"],"summary":"Suggest Staging Hostname","description":"The staging hostname the site-creation endpoints would mint for a label on this server: the label is normalised the way `POST \/servers\/{uuid}\/sites\/git\/auto` normalises a repository name (lowercase, DNS-safe, one level), joined to `suffix` (or the server's staging domain, or the platform default), and made unique with `-2`, `-3`, \u2026 when a site already answers to that exact name. `taken` reports whether the exact requested name is free, `domain` is the paste-ready `domain` object for the git create endpoints, and `available_suffixes` lists the staging domains this server may use (`domain.staging_domain` on git deploys, `demo_domain` on WordPress).\n\n**Read-only. Nothing is reserved.** The create call is the allocation; if another site takes the name in between, the create endpoints mint the next free label themselves (auto mode) or refuse with a domain-uniqueness 422 (an explicit `domain.name`). Requires the `read:servers` scope.\n","operationId":"servers.staging-hostname.suggest","x-required-scope":"read","x-destructive":false,"x-mcp-description":"Which staging hostname a site create would mint for a label on this server, whether that exact name is still free, and the suffixes the server may use. Read-only \u2014 nothing is reserved; the create call is the allocation. Use it to show the human the URL before a deploy, or to pass a known-free `domain` object to servers.sites.git.create\/docker.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"name":"label","in":"query","required":true,"description":"The label you would like (a repository or app name works). Normalised before use.","schema":{"type":"string","maxLength":63,"example":"My App"}},{"name":"suffix","in":"query","required":false,"description":"One of `available_suffixes`. Omit for the server's staging domain, falling back to the platform default.","schema":{"type":"string","example":"x-cloud.app"}}],"responses":{"200":{"description":"Hostname suggested \u2014 nothing reserved","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"hostname":{"type":"string","example":"my-app.x-cloud.app","description":"The unique hostname a create would mint for this label right now."},"label":{"type":"string","example":"my-app","description":"The unique, normalised label (-2, -3, \u2026 appended when the plain one is taken)."},"suffix":{"type":"string","example":"x-cloud.app"},"taken":{"type":"boolean","example":false,"description":"Whether a site already answers to the exact requested name (requested_hostname)."},"requested_hostname":{"type":"string","example":"my-app.x-cloud.app","description":"The normalised label joined to the suffix, before uniqueness."},"domain":{"type":"object","description":"Paste-ready `domain` for the git create endpoints.","properties":{"mode":{"type":"string","enum":["staging"]},"name":{"type":"string","example":"my-app"},"staging_domain":{"type":"string","example":"x-cloud.app"}}},"available_suffixes":{"type":"array","items":{"type":"string"},"example":["x-cloud.app"]},"reserved":{"type":"boolean","enum":[false],"description":"Always false \u2014 this endpoint never allocates."}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{uuid}\/sites\/git\/docker":{"post":{"tags":["Sites"],"summary":"Deploy a Git repo to a Docker server (Compose \/ Dockerfile)","description":"Deploy a git repository to a **Docker server** via Docker Compose or a Dockerfile \u2014 the Docker counterpart of `POST \/servers\/{uuid}\/sites\/git` (which targets nginx\/OpenLiteSpeed and refuses Docker servers). Use `git\/detect` first: a Docker server returns `compatibility.docker_deployable: true` and `deploy_via: docker_compose` when the repo ships a Dockerfile\/compose file.\n\nTwo modes via `docker.mode`:\n- `compose` (default) \u2014 runs the repository's own compose file (`docker.compose_file`);\n  `port` must be a host port that file publishes. xCloud never rewrites the file's\n  `ports:` mapping, so a port it does not publish has nothing listening on it: when the\n  compose file can be read, a `port` it does not publish is refused with 422 and\n  `errors.code: port_not_published`, and `errors.published_ports` lists the host ports\n  it does publish. When the file cannot be read (a private repo with no connected\n  provider, a rate-limited host) the port is accepted and the response `warnings` say it\n  could not be checked. NOTE: a compose file may publish on `0.0.0.0` (public, bypassing\n  the firewall) \u2014 the response `warnings` flags this too.\n- `dockerfile` \u2014 xCloud synthesizes the compose from `docker.dockerfile_path` +\n  `docker.container_port`; the host port is allocated server-side and bound to loopback.\n\nPrivate repositories need a connected provider (`repository.provider_uuid` + `full_name`) or a verified deploy key (`repository.deploy_key_uuid`). Provisions a real, billable site.\n**Dry run.** Send `dry_run: true` to run every check this call runs \u2014 field validation, repository\/branch reachability, deploy-key checks, domain uniqueness, port and database checks, the host-port check (compose) or allocation (dockerfile \u2014 reported in `would_create.port`, not reserved) \u2014 and stop at the persist step. A valid payload answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site, no credentials); an invalid one answers the exact 4xx the real call would (same `errors.code`, `next_actions` and dashboard links). A dry run writes nothing: no site, no DNS record, no deploy key, no job \u2014 the one trace is the live host-port probe, which records a read-only \"List bound host ports\" task in the server's task history (found by local QA, 2026-09-19) \u2014 and it does not consume the `Idempotency-Key`, so the same key can be sent again with the real call.\nRequires the `write:servers` scope.\n","operationId":"servers.sites.git.docker","x-mcp-dry-run":true,"x-mcp-description":"Deploys a repo to a DOCKER server with the container config pinned explicitly. Prefer servers_sites_git_auto, which resolves the same config from the repo itself; reach for this only to choose the compose file, host port, Dockerfile path or container port yourself. compose mode (default) runs the repo's own compose file \u2014 set `port` to a host port it publishes, and note that file may publish on 0.0.0.0 (see response warnings); dockerfile mode synthesizes the compose from docker.dockerfile_path + docker.container_port. The vhost denies every dot-prefixed URL path (\/.vite\/\u2026) and the app then hangs silently: if git_detect warned [dot_directory_assets], send docker.allowed_dot_paths with the entries it names. PROVISIONS A REAL, BILLABLE SITE \u2014 get the human's approval, then set confirm: true. Async: poll `poll_url`. Send dry_run: true to preview the resolved site without creating anything.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["repository","domain","docker"],"properties":{"repository":{"type":"object","required":["branch"],"description":"Supply ONE of: `provider_uuid`+`full_name` (private via a connected provider); `url` (public HTTPS); or a private SSH `url` + `deploy_key_uuid`.","properties":{"provider_uuid":{"type":"string","format":"uuid","nullable":true},"full_name":{"type":"string","nullable":true,"example":"bigBossBD\/mailwave"},"url":{"type":"string","nullable":true,"example":"https:\/\/github.com\/dockersamples\/example-voting-app","description":"Public HTTPS clone URL, or a private SSH URL (`git@host:owner\/repo.git` or `ssh:\/\/git@host\/owner\/repo.git`) with `deploy_key_uuid`."},"branch":{"type":"string","example":"main"},"deploy_key_uuid":{"type":"string","format":"uuid","nullable":true}}},"domain":{"type":"object","required":["mode","name"],"properties":{"mode":{"type":"string","enum":["staging","live"]},"name":{"type":"string","description":"Staging label, or the full live domain."},"staging_domain":{"type":"string","nullable":true},"title":{"type":"string","nullable":true}}},"docker":{"type":"object","properties":{"mode":{"type":"string","enum":["compose","dockerfile"],"default":"compose","description":"Optional \u2014 omitting it deploys in `compose` mode. `compose` runs the repository's own compose file \u2014 set `port` to a host port it publishes. `dockerfile` builds the repo's Dockerfile \u2014 set `docker.container_port`; the host port is allocated automatically."},"compose_file":{"type":"string","nullable":true,"example":"docker-compose.yml","description":"compose mode: path to the repo's compose file."},"dockerfile_path":{"type":"string","nullable":true,"example":"Dockerfile","description":"dockerfile mode: path to the Dockerfile."},"container_port":{"type":"integer","nullable":true,"example":3000,"description":"dockerfile mode: the port the app listens on inside the container."},"build_target":{"type":"string","nullable":true,"description":"dockerfile mode: optional build stage."},"allowed_dot_paths":{"$ref":"#\/components\/schemas\/DockerAllowedDotPaths"}}},"port":{"type":"integer","minimum":1024,"maximum":65535,"nullable":true,"description":"compose mode: a host port the repo's compose publishes (1024-65535; container ports may be lower). Checked against the file when it can be read \u2014 a port it does not publish is refused with errors.code port_not_published."},"cloudflare":{"type":"boolean","default":false,"description":"Let xCloud create the domain's DNS record instead of returning it for a human to add. On a live domain whose zone is on a connected Cloudflare account, pass `cloudflare: true` and do not create the A record yourself. `live` mode only, and only when a connected Cloudflare account holds the domain's zone \u2014 otherwise the deploy is refused with `cloudflare_zone_not_found`. The site's SSL provider becomes `cloudflare` (an Origin CA certificate, so the record is proxied from the start); the record itself is written during provisioning, as the certificate is issued, and `domain_setup` comes back with `cloudflare.managed: true` and `next_actions: [poll_site_status, verify_dns]`. The domain is created lower-cased.\n"},"env_file_content":{"type":"string","nullable":true},"enable_push_deploy":{"type":"boolean","nullable":true},"deploy_script":{"type":"string","nullable":true},"dry_run":{"type":"boolean","default":false,"description":"Run every check this call runs \u2014 including the host-port check or allocation \u2014 and stop before creating anything. Answers 200 with data.dry_run: true and data.would_create, or the same 4xx a real call would. Does not consume the Idempotency-Key."}}},"examples":{"compose":{"summary":"Compose mode, public repo, staging","value":{"repository":{"url":"https:\/\/github.com\/dockersamples\/example-voting-app","branch":"main"},"domain":{"mode":"staging","name":"voting"},"docker":{"mode":"compose","compose_file":"docker-compose.yml"},"port":8090}},"dockerfile":{"summary":"Dockerfile mode, private provider repo","value":{"repository":{"provider_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","full_name":"bigBossBD\/mailwave","branch":"main"},"domain":{"mode":"staging","name":"mailwave"},"docker":{"mode":"dockerfile","dockerfile_path":"Dockerfile","container_port":8000}}},"vite_dot_paths":{"summary":"A Vite app that serves pre-bundled deps from \/node_modules\/.vite\/","value":{"repository":{"url":"https:\/\/github.com\/the-teacher\/docker-vite","branch":"main"},"domain":{"mode":"staging","name":"vite-app"},"docker":{"mode":"compose","compose_file":"docker-compose.yml","allowed_dot_paths":["node_modules\/.vite"]},"port":4000}}}}}},"responses":{"200":{"$ref":"#\/components\/responses\/DryRun"},"202":{"description":"Docker git deployment initiated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"domain":{"type":"string"},"deploy_mode":{"type":"string","enum":["compose","dockerfile"]},"port":{"type":"integer","nullable":true},"server_uuid":{"type":"string","format":"uuid"},"poll_url":{"type":"string","format":"uri"},"warnings":{"type":"array","items":{"type":"string"}},"domain_setup":{"allOf":[{"$ref":"#\/components\/schemas\/DomainSetup"}],"description":"Present only for a live domain."}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Site limit reached \u2014 upgrade the plan."},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/servers\/{server}\/dns\/check":{"post":{"tags":["Servers"],"summary":"Check Domain DNS","description":"Does this domain resolve to this server yet? Answers the question the `domain_setup` block on a git deploy raises, without the deploy itself ever paying for a lookup.\n\nSide-effect-free and read-scoped. A single propagation attempt is made per call \u2014 poll this endpoint while waiting for a record to land rather than expecting one call to block until it does.\n\nA record sitting behind Cloudflare's proxy is reported distinctly (`cloudflare_proxy: true`): the record exists, but the server is unreachable for the HTTP-01 challenge, so the remedy is to turn the proxy off, not to add a record.\n\nThat remedy does NOT apply when `cloudflare_managed: true`. Such a site is served through Cloudflare deliberately, its certificate is a Cloudflare Origin CA certificate issued through the API, and the proxy is the finished state \u2014 `next_actions` comes back empty once that certificate is installed. The proxied record alone is not \"done\": while the certificate is still being issued, `next_actions` is `[poll_site_status]`, and when it failed or was revoked the proxy fronts a 526 and `next_actions` is `[reissue_ssl_certificate, poll_site_status]` \u2014 the same state `sites.status` reports as `ssl.serving_blocked`. While its record is still pending, `next_actions` is `[poll_site_status, verify_dns]`: provisioning owns that write, so a hand-added record would only be overwritten. The inverse is a fault too: a managed domain whose record resolves straight to the server (`resolves_to_server: true`, `cloudflare_proxy: false`) exposes that Origin CA certificate to browsers, so it is NOT reported as finished \u2014 `next_actions` is `[enable_cloudflare_proxy, verify_dns]`; when that domain's certificate is still being issued it is `[enable_cloudflare_proxy, verify_dns, poll_site_status]`, and when it failed or was revoked `[enable_cloudflare_proxy, reissue_ssl_certificate, verify_dns]` \u2014 the proxy alone would front a 526. Certificate state is read for the domain being checked, not the whole site: a healthy primary is not blocked by a failed additional domain.\n","operationId":"servers.dns.check","x-destructive":false,"x-required-scope":"read","x-mcp-description":"Checks whether a domain's A record points at an xCloud server yet \u2014 the follow-up to a live-domain deploy's `domain_setup`. Read-only, safe to poll. `cloudflare_proxy: true` means the record exists but is proxied (orange cloud), which blocks certificate issuance \u2014 turn the proxy off, do not add a record. Exception: `cloudflare_managed: true` means Cloudflare serves that site on purpose, the proxy is its finished state (`next_actions` empty once its certificate is installed; else relay them) \u2014 never say to turn it off; there, a record resolving straight to the server is the fault (`enable_cloudflare_proxy`). Relay `next_actions`.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"server","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of the xCloud server the domain should point at."}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"app.example.com"}}}}}},"responses":{"200":{"description":"DNS checked","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"domain":{"type":"string"},"server_ip":{"type":"string"},"resolves_to_server":{"type":"boolean"},"resolved_ips":{"type":"array","items":{"type":"string"}},"cloudflare_proxy":{"type":"boolean","description":"The record resolves to Cloudflare proxy IPs rather than the server."},"cloudflare_managed":{"type":"boolean","description":"Whether a site on this server serves this domain through Cloudflare (`ssl_provider` `cloudflare` or `cloudflare_multi_domain`) \u2014 as its primary name, an additional domain, or a child of a subdomain multisite's wildcard. For such a site a proxied record is the finished state once its certificate is installed, so `next_actions` is then empty and `disable_cloudflare_proxy` is never returned \u2014 turning the proxy off would expose an origin holding a Cloudflare Origin CA certificate, which browsers do not trust on its own. A proxied record over a certificate still being issued gives `[poll_site_status]`; over a failed or revoked one, `[reissue_ssl_certificate, poll_site_status]`. While such a domain does not resolve yet, `next_actions` is `[poll_site_status, verify_dns]`: provisioning owns that write, and a hand-added record would only be overwritten. When it resolves straight to the server with the proxy off, the site is not finished either \u2014 browsers do not trust its Origin CA certificate \u2014 and `next_actions` is `[enable_cloudflare_proxy, verify_dns]`, with `poll_site_status` appended while that domain's certificate is still being issued, or `reissue_ssl_certificate` (before `verify_dns`) when it failed or was revoked. Certificate state is that of the domain checked, not the whole site."},"next_actions":{"type":"array","items":{"$ref":"#\/components\/schemas\/NextAction"}},"checked_attempts":{"type":"integer","description":"Propagation attempts made in this call. Always 1 \u2014 poll for more."}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/blueprints":{"get":{"tags":["Blueprints"],"summary":"List Blueprints","description":"Returns blueprints accessible to the current team \u2014 both team-owned and public blueprints. Use the returned `uuid` as `blueprint_uuid` when creating a WordPress site. Requires the `read:servers` scope.\n","operationId":"blueprints.index","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Paginated list of blueprints","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Blueprint"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]},"example":{"success":true,"message":"","data":{"items":[{"uuid":"b1c2d3e4-f5a6-7890-bcde-f12345678901","name":"Default WordPress","is_default":true,"is_public":false,"created_at":"2025-06-01T12:00:00Z"},{"uuid":"c2d3e4f5-a6b7-8901-cdef-123456789012","name":"WooCommerce Starter","is_default":false,"is_public":true,"created_at":"2025-07-15T09:30:00Z"}],"pagination":{"total":2,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/sites":{"get":{"tags":["Sites"],"summary":"List Sites","description":"Returns a paginated list of all sites across all your servers. Requires the `read:sites` scope.\n","operationId":"sites.index","x-mcp-description":"Each site in the result includes a `dashboard_url` that opens it in the xCloud dashboard.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"status","in":"query","description":"Filter by site status","schema":{"type":"string","enum":["new","migration_init","migration_in_queue","clone_init","provisioning","migrating","cloning","deleting","provisioned","deleted","provisioning_failed","deletion_failed","migration_failed","migration_cancelled","clone_failed","update_failed","suspended"]}},{"name":"type","in":"query","description":"Filter by site type","schema":{"type":"string","enum":["wordpress","laravel","custom-php","oneclick","phpmyadmin","n8n","uptime-kuma","mautic","nextcloud","librechat","openwebui","ollama","nodejs","docker-compose","umami","lovable","site-pro","supabase","wireguard","openclaw","paperclip","hermes","deepseek_harness"]}},{"name":"server_uuid","in":"query","description":"Filter to sites on a specific server","schema":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},{"name":"search","in":"query","description":"Filter by domain or title","schema":{"type":"string","example":"example.com"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of sites","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PaginatedSites"}}}]},"example":{"success":true,"message":"Sites retrieved successfully.","data":{"items":[{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"example.com","domain_name":"example.com","type":"wordpress","status":"provisioned","deploy_state":"deployed","php_version":"8.2","server_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","created_at":"2024-06-10T14:30:00Z"}],"pagination":{"current_page":1,"last_page":8,"per_page":15,"total":120}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/sites\/{uuid}":{"get":{"tags":["Sites"],"summary":"Get Site","description":"Returns full details for a specific site. Requires the `read:sites` scope.\n","operationId":"sites.show","x-mcp-description":"The site includes `failed_steps` (latest unrecovered provisioning failures, max 10) and a `dashboard_url` that opens it in the xCloud dashboard.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Site detail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteDetail"}}}]},"example":{"success":true,"message":"Site retrieved successfully.","data":{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"example.com","domain_name":"example.com","type":"wordpress","status":"provisioned","deploy_state":"deployed","failed_steps":[],"php_version":"8.2","server_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","created_at":"2024-06-10T14:30:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"delete":{"tags":["Sites"],"summary":"Delete Site","description":"Deletes a site. Destructive and irreversible. Normally asynchronous: the site transitions to `deleting` and background jobs remove the site (and its staging sites); poll `GET \/sites\/{uuid}\/status` only when the response status is `deleting`. Deletion is refused with a 422 when another site on the same server has the same site name, because the two share their files, webserver config, cron entries and monitoring. A failed-provisioning record in that state is instead removed immediately without server cleanup; its response status is `deleted` and its UUID no longer resolves. When only the Linux user or the database identifiers are shared, the deletion still runs and those steps are skipped, so the other site keeps them. The 422 never names the other site. Requires the `write:sites` scope and the `site:delete` team permission. Sites tied to their server lifecycle (e.g. OpenClaw) cannot be deleted independently.\n","operationId":"sites.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["delete_files","delete_database","delete_user"],"properties":{"delete_files":{"type":"boolean","description":"Delete the site's files from the server."},"delete_database":{"type":"boolean","description":"Delete the site's database. Ignored when another site on the server still uses the database, or when the site uses a remote custom database connection; the response reports this in `database_kept`.\n"},"delete_user":{"type":"boolean","description":"Delete the site's system user."},"delete_local_backups":{"type":"boolean","default":false,"description":"Delete local backups of the site."},"delete_dns_record":{"type":"boolean","default":false,"description":"Delete the DNS record for the site's domain."}}},"example":{"delete_files":true,"delete_database":true,"delete_user":true,"delete_local_backups":true,"delete_dns_record":false}}}},"responses":{"200":{"description":"Site deletion initiated, or failed-provisioning record removed immediately","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["deleting","deleted"],"example":"deleting"},"database_kept":{"type":"boolean","description":"True when `delete_database` was requested but the database is kept because another site shares it or the site uses a remote custom connection.\n"},"database_kept_reason":{"type":"string","nullable":true,"enum":["shared","custom",null]}}}}}]},"example":{"success":true,"message":"Site deletion initiated","data":{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","status":"deleting","database_kept":false,"database_kept_reason":null}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A deletion of this site, or of one of its staging sites, is already in progress. Nothing was changed; wait for that attempt to reach a terminal state.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"This site is already being deleted. Wait for the current attempt to finish before trying again."}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/status":{"get":{"tags":["Sites"],"summary":"Get Site Status","description":"Lightweight endpoint for polling a site's current status during provisioning or after triggering async operations. Requires the `read:sites` scope.\n\n**How to poll.** Wait `poll_after_seconds` (10 while the deploy runs, null once `terminal` is true) between calls, and read `current_step` to see what is happening. `progress_percentage` can hold the same value for over a minute during a package install \u2014 that is a slow step, not a stall. Only reach for `GET \/sites\/{uuid}\/events` or `GET \/servers\/{uuid}\/tasks` once `terminal` is true, or when `progress_percentage` has not changed for five minutes.\n","operationId":"sites.status","x-mcp-description":"Poll this to track an async deploy\/provision. `deploy_state` (deployed\/failed\/cancelled\/in_progress) is the AUTHORITATIVE outcome \u2014 branch on it and stop polling once `terminal` is true. Wait `poll_after_seconds` between polls; `progress_percentage` can sit on one number for over a minute during a package install \u2014 slow, not stalled. Read sites_events or servers_tasks only once `terminal` is true or the percentage has not moved for 5 minutes. `failed_steps` lists unrecovered failed steps; non-empty on a `deployed` site means 'succeeded, but verify' \u2014 never report it as failed. `ssl.serving_blocked: true` means a Cloudflare-served site's certificate failed and every visitor gets 526 despite `deployed` \u2014 report it as broken. `error_message` gives the reason when `deploy_state` is failed. `status`\/`migration_status` are internal \u2014 do not branch on their spellings.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Site status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteStatus"}}}]},"example":{"success":true,"message":"Site status retrieved successfully.","data":{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","status":"migration_migrating","is_provisioned":false,"deploy_state":"in_progress","terminal":false,"poll_after_seconds":10,"current_step":"Install Composer dependencies","has_migration":true,"migration_status":"migrating","failed_steps":[],"ssl":{"provider":"cloudflare","certificate_status":"installed","serving_blocked":false},"progress_percentage":40,"error_message":null,"updated_at":"2024-06-10T14:30:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/deploy-diagnosis":{"get":{"tags":["Sites"],"summary":"Diagnose a Failed Deploy","description":"Explains why the site's latest git deploy failed and what can fix it: the step that failed, a redacted tail of its real output, a stable `classification`, the fields that could correct it, and the operation to call next. Deterministic \u2014 the same failure always produces the same answer. Returns 200 for every deploy state; when the last deploy did not fail, `classification`, `failed_step` and `next` are null and `explanation` says so.\n\nOne exception, and it is the point of reading this on a healthy-looking site: a deploy recorded as `deployed` whose build\/deploy step actually failed is still diagnosed \u2014 `deploy_state` stays `deployed` (that is what the platform recorded, and what `sites.status` reports), while `failed_step`, `classification` and `next: redeploy` describe the failure, and `explanation` opens by saying the deploy was recorded as finished. Sites provisioned before the deploy chain checked that step are exactly this case: the site serves a stale build, or 502. Requires the `read:sites` scope. The step's raw output is the same evidence `GET \/sites\/{uuid}\/events\/{task_uuid}` returns, so `output_tail`, `task_uuid`, `event_url` and `related.events_url` are null unless the caller also holds the `site:manage-events` team permission; the classification, correctable fields and next action are returned either way.\n","operationId":"sites.deploy-diagnosis","x-required-scope":"read","x-destructive":false,"x-mcp-description":"Call this after sites_status reports deploy_state failed, BEFORE retrying or changing anything. It names the failing step, returns a redacted tail of its output, and classifies it (deploy_script, repository_access, dependency_install, build_failed, web_root_missing, start_command, port_conflict, runtime_version, runtime_install, env_missing, docker_build, compose_invalid, service_unhealthy, provisioning_prerequisite, unknown). Act on `next` (retry \/ redeploy after correcting a `correctable_fields` field \/ rescue \/ recreate \/ support = stop and tell the human); `next_hint` names the exact operation. `classification: unknown` means read `failed_step.output_tail` yourself \u2014 do not guess a field. On a non-failed site everything is null \u2014 a normal answer, not an error. `output_tail`, `task_uuid` and the event links are null without the site:manage-events permission; the classification and `next` still apply.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Deploy diagnosis","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteDeployDiagnosis"}}}]},"example":{"success":true,"message":"Deploy diagnosis retrieved","data":{"uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","deploy_state":"failed","attempt":{"id":"7c9f2e18-5a4b-4d2e-9f31-0a1b2c3d4e5f","started_at":"2024-06-10T14:22:00Z"},"failed_step":{"name":"Deploying App on example.com","task_uuid":"9f1d2c3b-4a5e-6f70-8192-a3b4c5d6e7f8","exit_code":1,"status":"failed","output_tail":"npm ERR! code ERESOLVE\nnpm ERR! ERESOLVE could not resolve dependency\nERROR: the Node dependency install or build failed for example.com.\n","output_truncated":false,"event_url":"https:\/\/api.xcloud.host\/api\/v1\/sites\/b2c3d4e5-f6a7-8901-bcde-f12345678901\/events\/9f1d2c3b-4a5e-6f70-8192-a3b4c5d6e7f8"},"classification":"dependency_install","explanation":"The dependency install failed: the install command, the lockfile, or a declared dependency could not be satisfied.","correctable_fields":["install_command"],"next":"redeploy","next_hint":"Correct install_command with sites.git.update (for example `npm install --no-audit --no-fund` when the repository has no lockfile), then call sites.git.deploy.","related":{"events_url":"https:\/\/api.xcloud.host\/api\/v1\/sites\/b2c3d4e5-f6a7-8901-bcde-f12345678901\/events","status_url":"https:\/\/api.xcloud.host\/api\/v1\/sites\/b2c3d4e5-f6a7-8901-bcde-f12345678901\/status"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/ssl":{"get":{"tags":["Sites"],"summary":"Get SSL Certificate","description":"Returns SSL certificate information for the site. Requires the `read:sites` scope.\n","operationId":"sites.ssl","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"SSL certificate info. `data` is null when the site has no certificate row yet.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","nullable":true,"allOf":[{"$ref":"#\/components\/schemas\/SslInfo"}]}}}]},"example":{"success":true,"message":"Success","data":{"provider":"xcloud","status":"installed","expires_at":"2025-06-10T00:00:00Z","hostnames":["example.com","www.example.com"]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/staging-sites":{"get":{"tags":["Sites"],"summary":"List Staging Sites","description":"Returns every staging environment attached to a production site. Rejects with 422 when called on a non-production site \u2014 staging sites cannot have nested staging children. Requires the `read:sites` scope.\n","operationId":"sites.stagingSites","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of staging sites belonging to the production site","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/StagingSiteList"}}}]},"example":{"success":true,"message":"Staging sites retrieved successfully.","data":{"items":[{"uuid":"aa11bb22-cc33-4444-5555-66778899aabb","name":"staging.example.com","environment":"staging","status":"provisioned","created_at":"2026-01-15T00:00:00Z","updated_at":"2026-04-01T00:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Site is not a production site","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"This is not a production site. Staging sites cannot have nested staging sites."}}}}}},"post":{"tags":["Sites"],"summary":"Create Staging Environment","description":"Creates a Git-backed staging environment for a production site and queues provisioning (clone + deploy). The temporary staging URL is generated automatically. Only Git-backed sites (Laravel, Node.js, Custom PHP, Lovable) are supported \u2014 WordPress sites use the dashboard staging workflow and are rejected with 422. Manually-connected private repositories are rejected with 422 (they require an interactive deploy-key step a token cannot perform). Requires a paid plan, the `write:sites` scope, and the `site:deploy-staging` permission. Returns 202 when provisioning is queued.\n","operationId":"sites.stagingSites.create","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateStagingSiteRequest"},"examples":{"demo":{"summary":"Demo domain (auto-generated URL, xCloud SSL)","value":{"environment_name":"qa","branch":"develop","mode":"demo","env_init_mode":"copy_keys"}},"demoCustomSubdomain":{"summary":"Demo domain with a chosen subdomain","value":{"environment_name":"preview","branch":"feature\/checkout","mode":"demo","subdomain":"preview-checkout","demo_domain":"wp1.host"}},"liveXcloud":{"summary":"Live domain with Let's Encrypt (xCloud) SSL","value":{"environment_name":"staging","branch":"develop","mode":"live","domain":"staging.example.com","ssl":{"provider":"xcloud"}}},"liveCustomSsl":{"summary":"Live domain with an uploaded certificate","value":{"environment_name":"staging","branch":"develop","mode":"live","domain":"staging.example.com","ssl":{"provider":"custom","certificate":"-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----","private_key":"-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"}}},"liveCloudflare":{"summary":"Live domain with your connected Cloudflare integration","value":{"environment_name":"staging","branch":"develop","mode":"live","domain":"staging.example.com","ssl":{"provider":"cloudflare"}}},"manualPrivateRepo":{"summary":"Manual private repo (prepare + verify a deploy key first)","value":{"environment_name":"qa","branch":"develop","mode":"demo","deploy_key_uuid":"55ee66ff-aa11-4444-8888-99aabbccddee"}},"crossServer":{"summary":"Provision onto a different server","value":{"environment_name":"preview","branch":"feature\/checkout","mode":"demo","target_server_uuid":"77aa88bb-cc99-4444-5555-66778899aabb"}}}}}},"responses":{"202":{"description":"Staging environment creation queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CreateStagingSiteResponse"}}}]},"example":{"success":true,"message":"Staging environment creation queued","data":{"uuid":"aa11bb22-cc33-4444-5555-66778899aabb","name":"myapp-x7k2.x-cloud.app","environment":"staging","environment_name":"qa","mode":"demo","status":"queued"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Pre-flight failure. Possible reasons: not a production site, a WordPress site, a manually-connected private repository, a duplicate environment name, a branch that does not exist, or request validation errors.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"WordPress staging is not available via the API. This endpoint supports Git-backed sites (Laravel, Node.js, Custom PHP, Lovable)."}}}}}}},"\/sites\/{uuid}\/site-scripts":{"get":{"tags":["Sites"],"summary":"List Site Scripts","description":"Returns metadata for every site script (env, deployment, git-pull, staging hooks, docker-compose). The raw script body is intentionally NOT exposed because env scripts contain secrets (DB passwords, API keys). Use `has_content` and `size_bytes` to detect whether a script is configured. Requires the `read:sites` scope.\n","operationId":"sites.siteScripts","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of site scripts (metadata only)","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteScriptList"}}}]},"example":{"success":true,"message":"Site scripts retrieved successfully.","data":{"items":[{"uuid":"11223344-5566-7788-99aa-bbccddeeff00","script_type":"env","path":"\/var\/www\/example.com\/.env","has_content":true,"size_bytes":1024,"created_at":"2025-12-10T00:00:00Z","updated_at":"2026-05-15T00:00:00Z"},{"uuid":"22334455-6677-8899-aabb-ccddeeff0011","script_type":"deployment","path":null,"has_content":true,"size_bytes":256,"created_at":"2025-12-10T00:00:00Z","updated_at":"2025-12-10T00:00:00Z"}],"count":2}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/ip-access":{"get":{"tags":["Sites"],"summary":"List Site IP Access Rules","description":"Returns every IP access rule scoped to the site, split between whitelist and blacklist entries. Requires the `read:sites` scope.\n","operationId":"sites.ipAccess","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of IP access rules","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteIpAccessList"}}}]},"example":{"success":true,"message":"IP access rules retrieved successfully.","data":{"items":[{"uuid":"ab12cd34-ef56-7890-abcd-ef1234567890","ip_address":"203.0.113.42","type":"blacklist","created_at":"2025-12-10T00:00:00Z","updated_at":"2025-12-10T00:00:00Z"},{"uuid":"cd34ef56-7890-abcd-ef12-34567890abcd","ip_address":"198.51.100.7","type":"whitelist","created_at":"2025-12-11T00:00:00Z","updated_at":"2025-12-11T00:00:00Z"}],"counts":{"whitelist":1,"blacklist":1,"total":2}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/backup-settings":{"get":{"tags":["Sites"],"summary":"List Site Backup Settings","description":"Returns the backup settings configured for the site. A site can have multiple entries (typically one local and one remote). The storage provider's credentials are never exposed \u2014 only its UUID, provider name, and connection status. Requires the `read:sites` scope.\n","operationId":"sites.backupSettings","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of backup settings","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteBackupSettingList"}}}]},"example":{"success":true,"message":"Backup settings retrieved successfully.","data":{"items":[{"uuid":"0c1f3a89-2c4e-4a73-9d4c-8b1f2a3d4e5f","type":"full","version":"1","is_local":false,"status":"completed","database":true,"files":true,"exclude_paths":null,"auto_backup":true,"auto_backup_frequency":"daily","auto_incremental_backup":false,"auto_incremental_frequency":null,"auto_delete":true,"delete_after_days":30,"time":"02:00","incremental_time":null,"last_backup_at":"2026-05-15T02:00:00Z","storage_provider":{"uuid":"f8e7d6c5-b4a3-9281-7e6f-5d4c3b2a1f0e","provider":"s3","status":"connected"},"created_at":"2025-12-10T00:00:00Z","updated_at":"2026-05-15T02:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/backup-status":{"get":{"tags":["Sites"],"summary":"Get Site Backup Status","description":"Returns whether scheduled backups are active for the site, split into local and remote. A type is `active` when the site has its own backup setting of that type with auto backup enabled. `configured` indicates a site-owned setting exists; inherited server\/team defaults are not considered. The remote storage provider's credentials are never exposed \u2014 only its UUID, provider name, and connection status. Requires the `read:sites` scope.\n","operationId":"sites.backupStatus","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Backup status for local and remote","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteBackupStatus"}}}]},"example":{"success":true,"message":"Backup status retrieved successfully.","data":{"local":{"configured":true,"active":true,"status":"completed","last_backup_at":"2026-05-15T02:00:00Z"},"remote":{"configured":true,"active":false,"status":"completed","last_backup_at":"2026-05-14T02:00:00Z","storage_provider":{"uuid":"f8e7d6c5-b4a3-9281-7e6f-5d4c3b2a1f0e","provider":"s3","status":"connected"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/cron-jobs":{"get":{"tags":["Sites"],"summary":"List Site Cron Jobs","description":"Returns the cron jobs scoped to the site. Paginated. Requires the `read:sites` scope and the `site:cron-job` permission.\n","operationId":"sites.cronJobs","x-mcp-description":"List operation for the sites.cron-jobs.* family (sites.cron-jobs.create\/update\/destroy\/execute\/output), which each address one cron job by uuid.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of site cron jobs","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteCronJobList"}}}]},"example":{"success":true,"message":"Cron jobs retrieved successfully.","data":{"items":[{"uuid":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","command":"php \/var\/www\/example.com\/artisan schedule:run","user":"xcloud_example","frequency":"minutely","frequency_label":"Every Minute","pattern":"* * * * *","status":"active","created_at":"2025-12-10T00:00:00Z","updated_at":"2025-12-10T00:00:00Z"}],"pagination":{"total":1,"per_page":25,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Sites"],"summary":"Create Site Cron Job","description":"Creates a site-scoped cron job; the OS user is forced to the site's site_user. Installs the cron synchronously over SSH. Requires the `write:sites` scope and the `site:cron-job` permission. List with sites.cronJobs.\n","operationId":"sites.cron-jobs.create","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SiteCronJobInput"}}}},"responses":{"201":{"description":"Cron job created","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CronJobDetail"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/cron-jobs\/{cronJobUuid}":{"put":{"tags":["Sites"],"summary":"Update Site Cron Job","description":"Updates a site-scoped cron job by uuid. Requires the `write:sites` scope and the `site:cron-job` permission. List with sites.cronJobs.\n","operationId":"sites.cron-jobs.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SiteCronJobInput"}}}},"responses":{"200":{"description":"Cron job updated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CronJobDetail"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Sites"],"summary":"Delete Site Cron Job","description":"Deletes a site-scoped cron job by uuid. Requires the `write:sites` scope and the `site:cron-job` permission. List with sites.cronJobs.\n","operationId":"sites.cron-jobs.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cron job deleted","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"502":{"description":"Failed to remove cron job from the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/cron-jobs\/{cronJobUuid}\/execute":{"post":{"tags":["Sites"],"summary":"Execute Site Cron Job","description":"Triggers the cron command now. The command is launched in the background (detached over SSH) and returns 202 immediately, so long-running cron jobs do not time out the request. Retrieve the result via the cron-job output endpoint once it finishes. Requires the `write:sites` scope and the `site:cron-job` permission. List with sites.cronJobs.\n","operationId":"sites.cron-jobs.execute","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Cron job execution started","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"502":{"description":"Cron job failed to launch","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/cron-jobs\/{cronJobUuid}\/output":{"get":{"tags":["Sites"],"summary":"Get Site Cron Job Output","description":"Returns the last recorded output for a site-scoped cron job by uuid. Requires the `read:sites` scope and the `site:cron-job` permission. List with sites.cronJobs.\n","operationId":"sites.cron-jobs.output","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"cronJobUuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cron job output","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"output":{"type":"string"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/redirections":{"get":{"tags":["Sites"],"summary":"List Redirections","description":"Returns every URL redirection configured on the site. Each entry includes the source path, target URL, and HTTP status code (301 permanent or 302 temporary). Requires the `read:sites` scope.\n","operationId":"sites.redirections","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of redirections","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/RedirectionList"}}}]},"example":{"success":true,"message":"Redirections retrieved successfully.","data":{"items":[{"uuid":"8a7b6c5d-4e3f-2a1b-9c8d-7e6f5a4b3c2d","redirect_type":301,"redirect_label":"301 - Permanent","from":"\/old-page","to":"https:\/\/example.com\/new-page","created_at":"2025-12-10T00:00:00Z","updated_at":"2025-12-10T00:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/ssl-certificates":{"get":{"tags":["Sites"],"summary":"List SSL Certificates","description":"Returns all SSL certificates attached to the site, regardless of provider. Raw certificate body and private key are never included. Requires the `read:sites` scope.\n","operationId":"sites.sslCertificates","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of SSL certificates","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SslCertificateList"}}}]},"example":{"success":true,"message":"SSL certificates retrieved successfully.","data":{"items":[{"uuid":"5d5a3c5e-3c5e-4a5e-9d5a-3c5e3c5e3c5e","provider":"xcloud","status":"installed","obtained_from":"letsencrypt","expires_at":"2026-08-10T00:00:00Z","renewal_attempt_at":"2026-05-10T03:00:00Z","hostnames":["example.com","www.example.com"],"is_installed":true,"created_at":"2025-12-10T00:00:00Z","updated_at":"2026-05-10T03:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Sites"],"summary":"Create SSL Certificate","description":"Provisions an SSL certificate for the site. Three providers:\n  - **xcloud** \u2014 Let's Encrypt via xCloud. Requires DNS pointing\n    at the server, OR an active Cloudflare integration when\n    migrating from Cloudflare back to xCloud.\n  - **custom** \u2014 user-supplied certificate body + private key.\n  - **cloudflare** \u2014 uses the team's Cloudflare integration that\n    owns the domain's zone. Mapped server-side to the\n    multi-domain CF SSL architecture.\nSwitching providers on an already-SSL'd site requires `force: true`; the previous provider's row is retained as audit history. Returns 202 when the job is dispatched, 409 when generation is already in flight, 422 for pre-flight failures (invalid provider, missing custom cert, DNS not pointing at server, no Cloudflare integration owns the zone, strict-switch without force). Requires the `write:sites` scope.\n","operationId":"sites.sslCertificates.create","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateSslCertificateRequest"},"examples":{"xcloud":{"summary":"Generate an xCloud-managed (Let's Encrypt) certificate","value":{"provider":"xcloud"}},"custom":{"summary":"Upload a custom certificate","value":{"provider":"custom","certificate":"-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----","private_key":"-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"}},"cloudflare":{"summary":"Use the team's Cloudflare integration","value":{"provider":"cloudflare"}},"switch_with_force":{"summary":"Switch providers (e.g. xcloud \u2192 cloudflare)","value":{"provider":"cloudflare","force":true}},"wordpress_with_search_replace":{"summary":"WordPress site adopting HTTPS for the first time","value":{"provider":"xcloud","ssl_search_replace":true}}}}}},"responses":{"202":{"description":"SSL certificate generation queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CreateSslCertificateResponse"}}}]},"example":{"success":true,"message":"SSL certificate generation queued","data":{"site":{"uuid":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb","name":"example.com"},"provider":"xcloud","previous_provider":"cloudflare","status":"queued"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"SSL generation already in progress (an existing certificate is still in `new` or `obtained` status).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Pre-flight failure. Possible reasons:\n  - Invalid `provider` value.\n  - Custom provider with missing `certificate` \/ `private_key`.\n  - xcloud provider but DNS does not resolve to the server (and no Cloudflare migration in progress).\n  - cloudflare provider but no team Cloudflare integration owns the domain's zone.\n  - Different provider already configured and `force: true` not passed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/ssl\/renew":{"post":{"tags":["Sites"],"summary":"Renew SSL Certificate","description":"Queues a renewal of the site's xCloud-managed (Let's Encrypt) SSL certificate. Returns 202 immediately \u2014 completion is asynchronous. Custom certificates and Cloudflare SSL are not eligible (managed outside xCloud). By default the underlying job only renews certificates expiring within 7 days. Pass `force: true` to bypass that guard and force a renewal regardless of remaining lifetime. Requires the `write:sites` scope and the `site:manage-ssl` team permission.\n","operationId":"sites.ssl.renew","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","default":false,"description":"When true, skip the \"must expire within 7 days\" guard."}}}}}},"responses":{"202":{"description":"Renewal queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"certificate":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"provider":{"type":"string","example":"xcloud"},"status":{"type":"string","example":"installed"},"expires_at":{"type":"string","format":"date-time","nullable":true}}},"force":{"type":"boolean"},"renew_queued_at":{"type":"string","format":"date-time"}}}}}]},"example":{"success":true,"message":"SSL renewal queued.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"},"certificate":{"uuid":"9c8b7a6d-1111-2222-3333-444455556666","provider":"xcloud","status":"installed","expires_at":"2026-08-01T00:00:00Z"},"force":false,"renew_queued_at":"2026-05-14T10:30:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/domain":{"get":{"tags":["Sites"],"summary":"Get Domain Info","description":"Returns domain configuration for the site, including additional domains and parking method. Requires the `read:sites` scope.\n","operationId":"sites.domain","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Domain configuration","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DomainInfo"}}}]},"example":{"success":true,"message":"Domain info retrieved successfully.","data":{"primary":"example.com","additional_domains":["www.example.com","shop.example.com"],"parking_method":"redirect"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/domains":{"get":{"tags":["Sites"],"summary":"List Site Domains","description":"Returns the site's additional domains (aliases and redirects) plus the primary domain. Each entry includes whether the domain is a redirect and its port if a port-mapping is configured. Requires the `read:sites` scope.\n","operationId":"sites.domains","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Site domains","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteDomains"}}}]},"example":{"success":true,"message":"Domains retrieved successfully.","data":{"primary":"example.com","aliases":["www.example.com","shop.example.com"],"redirects":["old.example.com"],"counts":{"aliases":2,"redirects":1,"total":3}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/domain\/status":{"get":{"tags":["Sites"],"summary":"Get Domain Update Status","description":"Returns the most recent domain-update status from the site's `domainChangeInfo` metadata. Read-only \u2014 does not trigger a live SSH check. Returns `status=idle` when no domain update has been recorded. Requires the `read:sites` scope.\n","operationId":"sites.domainUpdateStatus","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Domain update status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DomainUpdateStatus"}}}]},"example":{"success":true,"message":"Domain update status retrieved successfully.","data":{"status":"updating","label":"Updating","is_updating":true,"old_domain":"old.example.com","new_domain":"new.example.com","message":null,"started_at":"2026-05-17T10:00:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/backups":{"get":{"tags":["Sites"],"summary":"List Backups","description":"Returns all backups for the specified site. Requires the `read:sites` scope.\n","operationId":"sites.backups","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of site backups","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Backup"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]},"example":{"success":true,"message":"Backups retrieved successfully.","data":{"items":[{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","file_name":"example.com-2025-03-14.tar.gz","file_size":"524288000","type":"full","status":"completed","is_remote":false,"date":"2025-03-14 02:00:00","created_at":"2025-03-14T02:00:00.000000Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/backup-count":{"get":{"tags":["Sites"],"summary":"Get Site Backup Count","description":"Returns the number of backup files stored for the site, split into local and remote, plus the combined total. Requires the `read:sites` scope.\n","operationId":"sites.backupCount","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Backup counts for local and remote","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteBackupCount"}}}]},"example":{"success":true,"message":"Backup count retrieved successfully.","data":{"local":12,"remote":8,"total":20}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/docker\/backups":{"get":{"tags":["Sites"],"summary":"List Docker Backups","description":"Lists backups for a Docker app (cold-stop + restic engine), newest first. Paginated. Only for Docker sites; other site types return 422. Requires the `read:sites` scope.\n","operationId":"sites.docker.backups","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of Docker backups","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/DockerBackup"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/docker\/backups\/{backupUuid}":{"get":{"tags":["Sites"],"summary":"Get Docker Backup","description":"A single Docker backup \u2014 poll this after triggering one to watch `running` \u2192 `completed`\/`failed`. Requires the `read:sites` scope.\n","operationId":"sites.docker.backup.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"backupUuid","in":"path","required":true,"description":"UUID of the Docker backup.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The Docker backup","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DockerBackup"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}},"delete":{"tags":["Sites"],"summary":"Delete Docker Backup","description":"Drops the snapshot from its restic repository and removes the record. Requires the `write:sites` scope.\n","operationId":"sites.docker.backup.destroy","x-destructive":true,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"backupUuid","in":"path","required":true,"description":"UUID of the Docker backup.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Backup deleted","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The snapshot could not be removed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/docker\/backups\/{backupUuid}\/note":{"put":{"tags":["Sites"],"summary":"Update Docker Backup Note","description":"Label a Docker backup so a restore candidate stays recognisable later (for example \"before 2.1 upgrade\"). Send `user_note: null` or an empty string to clear it. Requires the `write:sites` scope.\n","operationId":"sites.docker.backup.note.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"backupUuid","in":"path","required":true,"description":"UUID of the Docker backup.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["user_note"],"properties":{"user_note":{"type":"string","nullable":true,"maxLength":255,"description":"The label to store. Null or an empty string clears it.","example":"before 2.1 upgrade"}}}}}},"responses":{"200":{"description":"Note saved","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DockerBackup"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app, or the note failed validation.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/docker\/backup-count":{"get":{"tags":["Sites"],"summary":"Get Docker Backup Count","description":"Docker backup file count for the site, split into local and remote plus the combined total. Requires the `read:sites` scope.\n","operationId":"sites.docker.backupCount","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Backup counts for local and remote","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteBackupCount"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/docker\/backup":{"post":{"tags":["Sites"],"summary":"Trigger Docker Backup","description":"Triggers a backup of a Docker app (async). The app is briefly cold-stopped while its volumes are captured. Returns 202 with the `running` backup \u2014 poll `GET \/sites\/{uuid}\/docker\/backups\/{backupUuid}` for completion. `destination` is a storage provider uuid (omit for a local backup); Google Drive \/ pCloud are not supported. Requires the `write:sites` scope.\n","operationId":"sites.docker.backup","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"destination":{"type":"string","format":"uuid","nullable":true,"description":"Storage provider UUID (S3-compatible or SFTP). Omit for a local backup."}}}}}},"responses":{"202":{"description":"Backup started","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["running"]}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A backup could not be started \u2014 one may already be running for this site.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"The site is not a Docker app, or the destination is unsupported\/unreachable.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/docker\/backup-settings":{"get":{"tags":["Sites"],"summary":"List Docker Backup Settings","description":"Auto-backup schedule + retention per (site, destination) for a Docker app. Requires the `read:sites` scope.\n","operationId":"sites.docker.backupSettings","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Docker backup settings","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/DockerBackupSetting"}},"count":{"type":"integer"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}},"put":{"tags":["Sites"],"summary":"Update Docker Backup Settings","description":"Configures the auto-backup schedule + retention for a (site, destination). `destination` is a storage provider uuid (omit for local). Requires the `write:sites` scope.\n","operationId":"sites.docker.backupSettings.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["auto_backup","auto_backup_frequency"],"properties":{"destination":{"type":"string","format":"uuid","nullable":true,"description":"Storage provider UUID. Omit for local."},"auto_backup":{"type":"boolean"},"auto_backup_frequency":{"type":"string","enum":["daily","weekly","monthly"]},"delete_after_days":{"type":"integer","nullable":true,"minimum":1,"maximum":3650}}}}}},"responses":{"200":{"description":"Settings saved","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DockerBackupSetting"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site is not a Docker app, the destination is unsupported, or validation failed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/monitoring":{"get":{"tags":["Sites"],"summary":"Get Site Monitoring Stats","description":"Returns the last week of per-site CPU, RAM and disk samples, oldest first, or null when there are no matching samples. `sampled_at` is the recorded sample time, not the request time. Requires the `read:sites` scope and the `site:manage-monitoring` team permission.\n","operationId":"sites.monitoring","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Site monitoring stats","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","nullable":true,"items":{"$ref":"#\/components\/schemas\/SiteMonitoringSample"}}}}]},"example":{"success":true,"message":"Success","data":[{"cpu_usage":8.3,"ram_usage":12.5,"disk_usage":5,"time_at":"04:00 PM","sampled_at":"2026-09-09T16:00:00Z"}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/monitoring\/history":{"get":{"tags":["Sites"],"summary":"Monitoring History","description":"Returns a time-series of CPU, RAM, and disk usage for the site, sampled from the most recent server monitor snapshots. Pass `range=24h` for the last 24 hours, or `range=7d` (default) for the last 7 days. Requires the `read:sites` scope and the `site:manage-monitoring` team permission. Returns 403 on free plans \u2014 monitoring history is a paid feature.\n","operationId":"sites.monitoring.history","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"range","required":false,"schema":{"type":"string","enum":["24h","7d"],"default":"7d"}}],"responses":{"200":{"description":"Monitoring history","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"range":{"type":"string","enum":["24h","7d"]},"samples":{"type":"array","items":{"type":"object","properties":{"ram_usage":{"type":"number","format":"float","example":12.5},"cpu_usage":{"type":"number","format":"float","example":8.3},"disk_usage":{"type":"number","format":"float","example":25.1},"time_at":{"type":"string","example":"09:00 AM"},"sampled_at":{"type":"string","format":"date-time","nullable":true}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/access-logs":{"get":{"tags":["Sites"],"summary":"Site Access Logs","description":"Reads the live access log from the site's web server and returns parsed entries. The path is stack-aware \u2014 for nginx stacks it reads the nginx access log, for OpenLiteSpeed stacks it reads the LiteSpeed access log. The optional `type` parameter forces a specific source (`nginx`, `lsws`, or `access` which is stack-aware default). Note: this endpoint reads the log file over SSH on each call and can be slow on very large logs. Use `limit` to bound entry count. Requires the `read:sites` scope and the `site:manage-logs` team permission.\n","operationId":"sites.access-logs","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"type","required":false,"schema":{"type":"string","enum":["access","nginx","lsws"],"default":"access"}},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":200}}],"responses":{"200":{"description":"Parsed access log entries","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"type":{"type":"string","enum":["access","nginx","lsws"]},"limit":{"type":"integer"},"entry_count":{"type":"integer"},"entries":{"type":"array","items":{"type":"object","properties":{"log_type":{"type":"string","nullable":true,"example":"access"},"ip":{"type":"string","nullable":true,"example":"192.168.1.1"},"user":{"type":"string","nullable":true},"datetime":{"type":"string","nullable":true,"example":"13\/May\/2026:09:00:00 +0000"},"method":{"type":"string","nullable":true,"example":"GET"},"path":{"type":"string","nullable":true,"example":"\/index.php"},"protocol":{"type":"string","nullable":true,"example":"HTTP\/1.1"},"status_code":{"type":"integer","nullable":true,"example":200},"status_class":{"type":"string","nullable":true,"example":"2xx"},"bytes":{"type":"integer","nullable":true,"example":1024},"referer":{"type":"string","nullable":true},"user_agent":{"type":"string","nullable":true},"is_bot":{"type":"boolean","example":false},"bot_name":{"type":"string","nullable":true},"error_level":{"type":"string","nullable":true},"message":{"type":"string","nullable":true},"raw":{"type":"string"}}}}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/events":{"get":{"tags":["Sites"],"summary":"List Site Events","description":"Returns the recent task and event history for the site (e.g. SSL issuance, cache purge, plugin updates). Requires the `read:sites` scope.\n","operationId":"sites.events","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated site event log","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteEventList"}}}]},"example":{"success":true,"message":"Events retrieved successfully.","data":{"items":[{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","name":"Installing Custom Ssl Certificate","type":"ssl_issued","status":"finished","output":"SSL certificate issued for example.com","user":"xcloud_example","created_at":"2025-03-14T10:15:00Z"},{"uuid":"1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f","name":"Purging full-page cache","type":"cache_purge","status":"finished","output":"Full-page cache purged","user":null,"created_at":"2025-03-13T22:00:00Z"}],"pagination":{"total":2,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/events\/{task_uuid}":{"get":{"tags":["Sites"],"summary":"Get Site Event","description":"Returns one deploy step's output and exit code. `GET \/sites\/{uuid}\/events` caps output at 500 characters to keep a page small; when a step fails, the part that explains why is usually past that cap.\nOutput is returned in a window of at most 20,000 bytes, shrunk further when JSON escaping would push the encoded field over budget, so a response always stays under the MCP transport's 60,000-byte limit. With no `offset` the window is anchored to the END of the output \u2014 where a build failure states its cause \u2014 so diagnosing a failure normally takes one call. Use `offset`\/`next_offset` to read the rest. Credential-shaped values are redacted before the output is returned. Take `task_uuid` from an event's `uuid`. Requires the `read:sites` scope and the site's `site:manage-events` permission.\n","operationId":"sites.events.show","x-mcp-description":"Read a deploy step's real, credential-redacted output, after sites_events shows a failed step with output_truncated: true. Returns the END of the output by DEFAULT \u2014 where a build states its cause \u2014 so one call with no offset usually answers it, and that call returns next_offset: null because it already reaches the end. If you need the EARLIER output too, start again from offset=0 and follow next_offset until it is null.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"task_uuid","in":"path","required":true,"description":"The event's `uuid`, from `GET \/sites\/{uuid}\/events`.","schema":{"type":"string","format":"uuid"}},{"name":"offset","in":"query","required":false,"description":"Byte offset into the step's output. Omit it to get the LAST `limit` bytes, which is where a failure states its cause \u2014 that response reaches the end, so its `next_offset` is null. To read the whole log, start at `offset=0` and follow `next_offset` until it is null.\n","schema":{"type":"integer","minimum":0,"example":0}},{"name":"limit","in":"query","required":false,"description":"Bytes of output to return, clamped to 1000\u201320000, and reduced further when JSON escaping would push the encoded field over budget. Both caps keep the response under the MCP transport's 60,000-byte limit, above which a tool response is cut and only a leading fragment survives.\n","schema":{"type":"integer","minimum":1000,"maximum":20000,"default":20000}}],"responses":{"200":{"description":"One window of the event output","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteEventDetail"}}}]},"example":{"success":true,"message":"Event retrieved","data":{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","name":"Clone Git Repository","type":"site_migration","status":"failed","exit_code":128,"output":"Cloning private repository using SSH...\ngit@github.com: Permission denied (publickey).\nfatal: Could not read from remote repository.","created_at":"2025-03-14T10:15:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/deployment-logs":{"get":{"tags":["Sites"],"summary":"List Deployment Logs","description":"Returns paginated staging push\/pull history involving this site as source or destination, newest first. Entries carry a stable UUID, status, action, site names, actor and timestamps. This is not Git commit\/redeploy history. Requires the `read:sites` scope and `site:manage-logs` team permission.\n","operationId":"sites.deployment-logs","x-mcp-description":"Paginated staging push\/pull history involving this site, newest first. Includes UUID, status, action, source, destination, actor and timestamps. This is not Git commit\/redeploy history; use sites_status for initial Git deployment status.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number, clamped to at least 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","description":"Items per page, clamped to 1\u2013100; defaults to 10.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}}],"responses":{"200":{"description":"Deployment log history","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeploymentLog"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]},"example":{"success":true,"message":"Success","data":{"items":[{"uuid":"e9225db6-34b2-424a-84a1-b055342344bb","status":"success","action":"push","source":"staging.example.com","destination":"example.com","initiated_by":"Jane Smith","created_at":"2026-09-09T11:45:00Z","updated_at":"2026-09-09T11:46:00Z"}],"pagination":{"total":1,"per_page":10,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/git":{"get":{"tags":["Sites"],"summary":"Get Git Repository Info","description":"Returns the connected git repository configuration for the site. Private keys are never returned. Requires the `read:sites` scope.\n","operationId":"sites.git","x-mcp-description":"The site's connected git configuration: repository, tracked branch, provider and whether push-to-deploy is on. Read-only and safe \u2014 never returns keys or tokens. Call it before sites_git_update to see the current branch\/settings, or to report where a site deploys from.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Git repository configuration","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/GitInfo"}}}]},"example":{"success":true,"message":"Git info retrieved successfully.","data":{"repository":"github.com\/myorg\/my-wp-theme","branch":"main","provider":"github","push_deploy_enabled":true}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"tags":["Sites"],"summary":"Update Git Deployment Settings","description":"Updates the git deployment configuration for a git-deployed site: tracked `git_branch`, the push-to-deploy webhook toggle (`enable_push_deploy`), whether the deploy script runs after each pull (`run_after_deployment`), the post-pull `deploy_script`, a custom `git_pull_script`, an optional `restart_services` flag, and the site-relative `env_file_path`. Changing the branch is validated against the connected provider (or a live SSH check) before saving. Only `git_branch` is required \u2014 send it alone to switch branches and deploy without touching the deploy-script settings. Sensitive keys are never returned. Requires the `write:sites` scope, the `site:manage-update` permission and `site:manage-deploy-scripts`. The second one decides what the server runs on deploy, so it is never granted automatically: a team member needs it ticked by hand.\n","operationId":"sites.git.update","x-mcp-description":"Changes a git-deployed site's deploy settings: tracked branch, push-to-deploy toggle, post-pull deploy\/pull scripts, restart flag, env path. Only git_branch is required \u2014 send it alone to switch branch. Switching the branch DEPLOYS that branch onto a live site, so describe the effect, get the human's approval, then set confirm: true.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["git_branch"],"properties":{"git_branch":{"type":"string","maxLength":255,"example":"main"},"enable_push_deploy":{"type":"boolean","description":"Enable the push-to-deploy webhook.","example":true},"run_after_deployment":{"type":"boolean","nullable":true,"description":"Run the deploy script after each pull. Optional \u2014 omit to keep the current setting.","example":true},"deploy_script":{"type":"string","nullable":true,"description":"Optional post-pull deploy script.","example":"composer install --no-dev\nphp artisan migrate --force"},"git_pull_script":{"type":"string","nullable":true,"description":"Custom git-pull script (overrides the default).","example":"git pull origin main"},"restart_services":{"type":"boolean","nullable":true,"example":false},"env_file_path":{"type":"string","nullable":true,"maxLength":255,"description":"Site-relative directory for the .env file (no leading\/trailing slash).","example":"app"}}}}}},"responses":{"200":{"description":"Updated git deployment settings (sensitive keys stripped)","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/GitInfo"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Not a git-deployed site, validation failed, or the branch could not be validated against the repository.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/git\/deploy":{"post":{"tags":["Sites"],"summary":"Trigger Git Deployment","description":"Queues a manual pull-and-deploy for a git-deployed site. Returns 202 immediately. `data.task_uuid` is the deployment Task, created in this request so a reference exists before a worker picks the job up; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome, or poll `GET \/sites\/{uuid}\/status` until `terminal` and read `GET \/sites\/{uuid}\/events` for the deploy steps (the git build output is a step there \u2014 `\/sites\/{uuid}\/deployment-logs` is staging push\/pull history, not the git log). Returns 409 when a deployment for this site is already in flight \u2014 retry once it reaches a terminal state. Requires the `write:sites` scope and the `site:manage-update` permission.\n","operationId":"sites.git.deploy","x-mcp-description":"Triggers a redeploy of an existing git site \u2014 pulls the tracked branch and runs its deploy script on a LIVE site, which can change or break what is currently serving. Describe what will redeploy and get the human's approval, then set confirm: true. Async: it returns 202, then poll sites_status until terminal and check failed_steps. Refused with 409 while another deploy or a provision is still running, and with 422 until the site's deploy key is connected. Use this to ship the latest commit; use sites_git_update to deploy a DIFFERENT branch; use sites_provision_retry when the deploy FAILED and you are correcting what broke it.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Deployment queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["task_uuid"],"properties":{"task_uuid":{"type":"string","format":"uuid","description":"The deployment Task. Poll `GET \/sites\/{uuid}\/events\/{task_uuid}` for its outcome.\n"}}}}}]},"example":{"success":true,"message":"Site deployment queued","data":{"task_uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A deployment is already in progress for this site, or a provision\/retry chain still owns its working tree.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"A deployment is already in progress for this site."}}}},"422":{"description":"The site is not a git-deployed site, or its deploy key is not connected yet so it could not pull.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/deploy-config":{"get":{"tags":["Sites"],"summary":"Get Resolved Deploy Configuration","description":"Returns the deploy settings a deploy actually reads for a git-deployed site \u2014 the tracked branch and, for a native site, the serving mode, the install\/build\/start commands, the port, the web root and the env file path; for a container site the Docker block instead \u2014 together with `correctable_fields`: the settings that `PUT \/sites\/{uuid}\/deploy-config` and `POST \/sites\/{uuid}\/provision-retry` accept for THIS site type. A container site's env is set through `env_file_content` alone: `env_file_path` is a native-site setting and is not in a container site's `correctable_fields`.\n\nTwo bodies are never returned, by any endpoint: the env file and the deploy script. Both are written by the customer and routinely carry credentials, and this endpoint is reachable with a read-only token. `env_file_path`, `env_file_configured`, `deploy_script_configured` and `deploy_script_size_bytes` describe them instead. `deploy_script_fail_fast` says whether the deploy script aborts on its first failing command; sites created before this setting existed report `false` and keep running their script to the end whatever it does. Requires the `read:sites` scope.\n","operationId":"sites.deploy-config","x-mcp-description":"The deploy settings a deploy actually reads for a git site, plus `correctable_fields` \u2014 the settings you may change on this site type. Read-only and safe. Read this FIRST when a deploy failed: sites_status gives the failure, sites_deploy_diagnosis gives the cause, and this gives the values that produced it, so you can tell which one is wrong. Never returns the env file or deploy script bodies \u2014 only whether they are set and where.","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"The site's resolved deploy configuration","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeployConfig"}}}]},"example":{"success":true,"message":"Deploy configuration retrieved","data":{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","site_type":"nodejs","deploy_target":"native","git_branch":"main","port":3000,"env_file_path":".env","env_file_configured":true,"deploy_script_configured":false,"deploy_script_size_bytes":0,"run_after_deployment":false,"deploy_script_fail_fast":true,"docker":null,"serving_mode":"ssr","install_command":null,"build_command":"npm run build","start_command":"node server.js","web_root":null,"server_node_version":"20","correctable_fields":["git_branch","serving_mode","install_command","build_command","start_command","port","web_root","env_file_content","env_file_path","deploy_script"]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"The site was not deployed from a repository.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}},"put":{"tags":["Sites"],"summary":"Update Deploy Configuration","description":"Changes one or more deploy settings WITHOUT deploying. Most settings take effect on the next `POST \/sites\/{uuid}\/git\/deploy` or `POST \/sites\/{uuid}\/provision-retry`. Two apply immediately to a DEPLOYED site: `docker.allowed_dot_paths` regenerates the vhost (`regenerating_vhost: true`), and `env_file_content` is written to the site's env file and its process restarted (`env_pushed: true`) \u2014 neither touches the containers otherwise, and neither needs a redeploy. Send only the fields you are changing.\n\nWhich fields are accepted depends on the site \u2014 read `correctable_fields` from `GET \/sites\/{uuid}\/deploy-config` first. A native Node site takes the serving mode, the commands, the port and the web root; a container site takes the `docker` block instead. `site_type`, `repository`, `domain` and `database` are fixed for the life of a site and are refused with 422.\n\nA changed `port` is reserved before it is stored \u2014 against the other sites on the server and against the host itself \u2014 so a value something else already holds is refused here rather than failing the deploy later. On a compose-mode container site it is also checked against the repository's compose file when that file can be read: xCloud proxies to the host port the file publishes and never remaps it, so a port the file does not publish is refused with 422. `deploy_script_fail_fast` turns the deploy script's abort-on-first-failure preamble on or off and takes effect on the next deploy; it is a setting rather than a correction, so it is accepted here and not in `sites.provision-retry`'s `corrections`. Requires the `write:sites` scope and the `site:manage-update` permission.\n","operationId":"sites.deploy-config.update","x-destructive":false,"x-mcp-description":"Changes a git site's deploy settings (branch, serving mode, install\/build\/start commands, port, web root, env body\/path, deploy script, or the docker block) WITHOUT deploying. Read sites_deploy_config first for `correctable_fields` \u2014 the accepted set depends on the site type, and site_type\/repository\/domain\/database can never be changed. Most settings only take effect on the next deploy: this alone does not fix a broken site, so follow it with sites_provision_retry for a FAILED deploy, or sites_git_deploy for a live one. Two exceptions apply immediately on a deployed site, no redeploy needed: docker.allowed_dot_paths regenerates the vhost by itself (regenerating_vhost: true), and env_file_content is written to disk and the site's process restarted (env_pushed: true).","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DeploySettings"},"examples":{"node":{"summary":"Fix a Node site's build output and start command","value":{"build_command":"npm run build","start_command":"node .output\/server\/index.mjs","web_root":"dist","serving_mode":"ssr","port":3001}},"docker":{"summary":"Point a container site at the right compose file and port","value":{"docker":{"mode":"compose","compose_file":"deploy\/docker-compose.prod.yml"},"port":8080}},"dot_paths":{"summary":"Let a deployed Vite app serve \/node_modules\/.vite\/ (vhost regenerated, no redeploy)","value":{"docker":{"allowed_dot_paths":["node_modules\/.vite"]}}}}}}},"responses":{"200":{"description":"The settings that were applied, and the configuration as it now stands","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"applied":{"type":"array","description":"The fields that were persisted.","items":{"type":"string"},"example":["build_command","start_command"]},"regenerating_vhost":{"type":"boolean","description":"True whenever `docker.allowed_dot_paths` was submitted for a deployed Docker site \u2014 changed or not: the nginx vhost is being regenerated in the background (containers untouched), so no redeploy is needed. The stored value is committed before that single-attempt regeneration runs, so re-sending the same list is how a failed regeneration is retried. False otherwise \u2014 every other setting takes effect on the next deploy or retry.\n"},"env_pushed":{"type":"boolean","description":"True whenever `env_file_content` was submitted for a deployed site \u2014 cleared or not: the body was written to the site's env file and the process that reads it (Docker Compose, PM2) was restarted, so the running site already has it. False otherwise \u2014 a site that is not deployed sees the correction only on its next deploy or retry.\n"},"config":{"$ref":"#\/components\/schemas\/DeployConfig"}}}}}]},"example":{"success":true,"message":"Deploy configuration updated","data":{"applied":["build_command","start_command"],"config":{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","site_type":"nodejs","deploy_target":"native","git_branch":"main","port":3000,"serving_mode":"ssr","build_command":"npm run build","start_command":"node .output\/server\/index.mjs"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A deploy is in progress for this site; its settings cannot change while it runs.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"A deploy is in progress for this site. Wait for it to finish (poll the status endpoint) before changing its settings."}}}},"422":{"description":"Not a git-deployed site, a field that cannot be corrected on this site (`site_type`, `repository`, `domain`, `database` \u2014 recreate the site instead), a field that does not apply to this site type, a value that failed validation, or a port that is already taken on the server.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"These settings are fixed for the life of a site and cannot be corrected on a retry: repository. Create a new site with the values you want, then delete this one."}}}}}}},"\/sites\/{uuid}\/provision-retry":{"post":{"tags":["Sites"],"summary":"Retry a Failed Deploy","description":"Retries a FAILED deploy on the SAME site, optionally correcting the settings that broke it first. This is the recovery path for a failed provision: the site keeps its domain, its port and its database, so there is no need to delete it and create another one.\n\nA native (nginx\/OpenLiteSpeed) deploy RESUMES from the step that failed, skipping the steps that already succeeded \u2014 the staging DNS record, the PHP or Node runtime, the database, the clone. A correction pulls the restart point EARLIER when the step that reads it has already run: changing the branch or the env re-clones, changing a command, the serving mode, the web root or the port re-runs the build and process start and regenerates the web-server config. A container (Docker) deploy has a single install step, so it is re-run in full after the previous attempt's containers are stopped.\n\nRefused with 422 unless `deploy_state` is `failed` \u2014 starting a second chain over a live site is worse than the failure it would be fixing \u2014 and with 409 while another deploy or retry is in flight. Also refused with 422 when the failure was a REDEPLOY of an already-provisioned site (`POST \/sites\/{uuid}\/git\/deploy`, or a push-to-deploy hook): the provisioning chain finished long ago and there is nothing to resume, so the recovery there is `PUT \/sites\/{uuid}\/deploy-config` followed by `POST \/sites\/{uuid}\/git\/deploy` \u2014 which is what the deploy diagnosis says in `next_hint`. Corrections are validated and applied BEFORE the retry is admitted, so a rejected correction leaves the deploy exactly as it was.\n\nAsynchronous: returns 202, then poll `GET \/sites\/{uuid}\/status` until `terminal` is true. Requires the `write:sites` scope and the `site:manage-update` permission.\n","operationId":"sites.provision-retry","x-mcp-description":"Retries a FAILED git deploy on the SAME site, optionally correcting what broke it \u2014 this is the recovery path, so do NOT delete and recreate a site whose deploy failed. Order of work: sites_deploy_diagnosis for the cause, then sites_deploy_config to see the values that produced it, then call this with `corrections` for the settings that were wrong. A native deploy resumes from the failed step and moves earlier when a correction needs it; a Docker deploy re-runs its install. Refused (422) unless deploy_state is failed, and (409) while a deploy is running. It redeploys a real site, so describe what will change, get the human's approval, then set confirm: true. Async: poll sites_status until terminal. Pass an Idempotency-Key header so a retry of THIS call cannot start a second attempt.","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"$ref":"#\/components\/parameters\/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"corrections":{"$ref":"#\/components\/schemas\/DeployCorrections"}}},"examples":{"node":{"summary":"Node \u2014 the build command was wrong and the port was taken","value":{"corrections":{"build_command":"npm run build","start_command":"node .output\/server\/index.mjs","port":3005}}},"docker":{"summary":"Docker \u2014 the compose file was at another path","value":{"corrections":{"docker":{"mode":"compose","compose_file":"deploy\/docker-compose.prod.yml"},"port":8080}}},"noCorrections":{"summary":"Retry unchanged (a transient failure)","value":[]}}}}},"responses":{"202":{"description":"Retry queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"attempt_id":{"type":"string","format":"uuid","description":"Identifies THIS attempt. The steps it runs are stamped with it, so diagnostics can be scoped to the retry rather than re-reporting the attempt that failed.\n"},"resume_from":{"type":"integer","nullable":true,"description":"The internal step the chain restarts from, or null when it restarts from the beginning (and always null for a Docker site, whose install is one step).\n","example":13},"resume_from_step":{"type":"string","nullable":true,"description":"The name of that step.","enum":["configuring_ssl","checking_status","generating_ssh_key","installing_runtime","installing_database","installing_services","cloning_repository","updating_permissions","deploy_script","deploy_app","configuring_web_server","configuring_https","configuring_full_page_cache","configuring_redis_cache","finalizing","removing_debug_log","configuring_indexing","docker_install"]},"applied_corrections":{"type":"array","description":"The corrections that were persisted. May include `port` even when none was sent: a failed site stops reserving its port, so a retry reclaims it when something else has taken it.\n","items":{"type":"string"},"example":["build_command","port"]},"poll_url":{"type":"string","example":"https:\/\/xcloud.host\/api\/v1\/sites\/9b1f...\/status"}}}}}]},"example":{"success":true,"message":"Deploy retry queued","data":{"uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","attempt_id":"2f7c1a92-5b3e-4d81-9a6c-0e7f2b3a4d5e","resume_from":14,"resume_from_step":"deploy_app","applied_corrections":["build_command","start_command"],"poll_url":"https:\/\/xcloud.host\/api\/v1\/sites\/9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d\/status"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A deploy or another retry is already running for this site, or a request with the same Idempotency-Key is still in flight.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"A retry is already being started for this site. Poll the status endpoint before trying again."}}}},"422":{"description":"The site was not deployed from a repository, it has no deploy to retry, the deploy did not fail, a correction named a setting that cannot be changed, a correction failed validation, or a corrected port is already taken.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Only a failed deploy can be retried; this one is deployed."}}}},"503":{"description":"The retry could not be queued. The deploy is put back into its failed state with its original error, and any corrections sent with the call stay saved \u2014 call it again rather than re-sending them.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Could not queue the retry. The deploy is back in its failed state and any corrections you sent have been saved \u2014 call provision-retry again."}}}}}}},"\/sites\/{uuid}\/ssh-keys":{"get":{"tags":["Sites"],"summary":"List Site SSH Keys","description":"Returns every SSH key pair attached to the site, including the public key body. Private keys and sudo passwords are never exposed. Requires the `read:sites` scope.\n","operationId":"sites.sshKeys","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of SSH key pairs attached to the site","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SshKeyList"}}}]},"example":{"success":true,"message":"SSH keys retrieved successfully.","data":{"items":[{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","name":"deploy-key","type":"site_git","public_key":"ssh-rsa AAAAB3Nza...example deploy-key","fingerprint":"SHA256:AbCdEf1234567890exampleFingerprint","created_at":"2025-12-10T00:00:00Z","updated_at":"2025-12-10T00:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/ssh":{"get":{"tags":["Sites"],"summary":"Get SSH\/SFTP Config","description":"Returns the SSH\/SFTP configuration for the site, including the site user, authentication mode, and any associated SSH keypairs. Private keys are never returned. Requires the `read:sites` scope.\n","operationId":"sites.ssh.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"SSH\/SFTP configuration","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SshConfig"}}}]},"example":{"success":true,"message":"SSH config retrieved successfully.","data":{"site_user":"xcloud_example","authentication_mode":"public_key","ssh_keypairs":[{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","name":"deploy-key","fingerprint":"SHA256:AbCdEf1234567890exampleFingerprint"},{"uuid":"f6a7b8c9-d0e1-2345-fabc-678901234567","name":"ci-key","fingerprint":"SHA256:GhIjKl0987654321exampleFingerprint"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"tags":["Sites"],"summary":"Update SSH\/SFTP Config","description":"Updates the SSH\/SFTP authentication configuration for the site. When `authentication_mode` is `public_key`, provide `ssh_public_keys`. When `authentication_mode` is `password`, provide `password`. This is an asynchronous operation. Requires the `write:sites` scope.\n","operationId":"sites.ssh.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UpdateSshRequest"},"examples":{"public_key_auth":{"summary":"Switch to public-key authentication","value":{"authentication_mode":"public_key","ssh_public_keys":["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample deploy@workstation","ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQExample ci@pipeline"]}},"password_auth":{"summary":"Switch to password authentication","value":{"authentication_mode":"password","password":"Str0ngP@ssw0rd!"}}}}}},"responses":{"200":{"description":"SSH\/SFTP configuration updated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SshConfig"}}}]},"example":{"success":true,"message":"SSH config updated successfully.","data":{"site_user":"xcloud_example","authentication_mode":"public_key","ssh_keypairs":[{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","name":"deploy-key","fingerprint":"SHA256:AbCdEf1234567890exampleFingerprint"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/wordpress\/plugins":{"get":{"tags":["Sites - WordPress"],"summary":"List WordPress Plugins","description":"Returns the list of WordPress plugins installed on the site. Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.plugins","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"in":"query","name":"status","schema":{"type":"string","enum":["all","active","inactive"],"default":"all"}},{"in":"query","name":"update_status","schema":{"type":"string","enum":["all","available","none"],"default":"all"}},{"in":"query","name":"search","schema":{"type":"string","default":""}}],"responses":{"200":{"description":"List of WordPress plugins","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/WordPressItem"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"},"summary":{"$ref":"#\/components\/schemas\/WordPressItemSummary"}}}}}]},"example":{"success":true,"message":"Success","data":{"items":[{"type":"plugin","slug":"woocommerce","name":"WooCommerce","current_version":"8.5.2","available_version":"8.6.0","update_available":true,"status":"active","update_status":"available","is_must_use":false,"is_dropin":false,"last_checked_at":"2026-05-06T12:00:00Z"}],"pagination":{"total":23,"per_page":50,"current_page":1,"last_page":1},"summary":{"total":23,"active":18,"with_updates":5}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/wordpress\/themes":{"get":{"tags":["Sites - WordPress"],"summary":"List WordPress Themes","description":"Returns the list of WordPress themes installed on the site. Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.themes","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"in":"query","name":"status","schema":{"type":"string","enum":["all","active","inactive"],"default":"all"}},{"in":"query","name":"update_status","schema":{"type":"string","enum":["all","available","none"],"default":"all"}},{"in":"query","name":"search","schema":{"type":"string","default":""}}],"responses":{"200":{"description":"List of WordPress themes","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/WordPressItem"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"},"summary":{"$ref":"#\/components\/schemas\/WordPressItemSummary"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/wordpress\/updates":{"get":{"tags":["Sites - WordPress"],"summary":"Get WordPress Updates Summary","description":"Returns a summary of pending WordPress updates (core, plugins, themes) for the site, with a security cross-reference flag per item. Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.updates","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"include_security_only","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"WordPress updates summary","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WordPressUpdatesSummary"}}}]},"example":{"success":true,"message":"Success","data":{"site_uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","core":{"current_version":"6.4.2","available_version":"6.5.0","update_available":true,"is_security_update":false},"plugins":{"total":23,"active":18,"with_updates":5,"with_security_updates":1,"items":[{"slug":"woocommerce","name":"WooCommerce","current_version":"8.5.2","available_version":"8.6.0","is_security_update":true}]},"themes":{"total":3,"active":1,"with_updates":1,"with_security_updates":0,"items":[]},"summary":{"total_pending":7,"security_pending":1,"last_scanned_at":"2026-05-06T12:00:00Z"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/wordpress\/status":{"get":{"tags":["Sites - WordPress"],"summary":"Get WordPress Site Health Status","description":"Returns a single-call site health snapshot \u2014 WP version, PHP version, multisite\/debug\/cron flags, checksum status, item counts, pending update counts, SSL status. `wp_debug_enabled` reflects the last `\/wp-debug` toggle; if the flag was never toggled through the API, it is verified live against the server once and cached for subsequent calls. Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.status","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"WordPress site health snapshot","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WordPressStatus"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/backup":{"post":{"tags":["Sites"],"summary":"Trigger Backup","description":"Initiates an on-demand backup of the site. This is an asynchronous operation. `data.task_uuid` is the Task of the BACKUP itself \u2014 never the script install\/refresh step that may precede it \u2014 so it reaches a terminal status when the backup does; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome, or poll `GET \/sites\/{uuid}\/events` to track completion. `type` selects the destination: `local` (default) or `remote`. A remote backup requires a configured storage provider with a working connection; if it is missing or unreachable the request is rejected with 422 before anything is queued. Requires the `write:sites` scope.\n","operationId":"sites.backup","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["local","remote"],"default":"local","description":"Backup destination. Defaults to `local`.","example":"remote"}}}}}},"responses":{"200":{"description":"Backup started","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["task_uuid"],"properties":{"task_uuid":{"type":"string","format":"uuid","nullable":true,"description":"The backup Task. Poll `GET \/sites\/{uuid}\/events\/{task_uuid}` for its outcome. `null` only when the site has no server to back up.\n"}}}}}]},"example":{"success":true,"message":"Backup started successfully","data":{"task_uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Backups are not supported for this site type, the `type` value is invalid, or (for `remote`) no storage provider is configured or the provider connection failed.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/rescue":{"post":{"tags":["Sites"],"summary":"Rescue Site","description":"Queues a site rescue job with the selected repair options. Supported options depend on the site type and server stack. `data.task_uuid` is the Task covering the rescue as a whole \u2014 a rescue runs several independent repairs, each with its own event, so this one row is what settles when the rescue finishes; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome. It settles `failed` when at least one repair failed, naming them in its output. Queued repairs are tracked by their exact task IDs; the parent waits for their successful exit codes, with a two-hour maximum wait. Missing evidence never becomes success. Poll `GET \/sites\/{uuid}\/events` for each repair's own result. Requires the `write:sites` scope.\n","operationId":"sites.rescue","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"isolate_user":{"type":"boolean","description":"Repair the site user. Unsupported for OpenClaw sites."},"directory_permissions":{"type":"boolean","description":"Reset site directory permissions. Unsupported for OpenClaw sites."},"regenerate_nginx":{"type":"boolean","description":"Regenerate the site web server configuration."},"restart_nginx":{"type":"boolean","description":"Restart nginx after regenerating config. Requires regenerate_nginx and is unsupported for OpenLiteSpeed\/OpenClaw sites."},"reinstall_php":{"type":"boolean","description":"Repair the site's PHP runtime. Unsupported for Node sites and Docker nginx stacks."},"repair_node":{"type":"boolean","description":"Repair the site's Node runtime. Supported for Node-based sites, including OpenClaw."},"repair_pm2":{"type":"boolean","description":"Repair the PM2 process and ecosystem config. Unsupported for static Node sites and OpenClaw sites."},"restart_pm2":{"type":"boolean","description":"Restart the PM2 process. Unsupported for static Node sites and OpenClaw sites."},"repair_openclaw":{"type":"boolean","description":"Run the OpenClaw-specific repair flow. Supported only for OpenClaw sites."}}},"example":{"isolate_user":true,"directory_permissions":true,"reinstall_php":true,"regenerate_nginx":true,"restart_nginx":true}}}},"responses":{"202":{"description":"Rescue queued successfully","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site_uuid":{"type":"string","format":"uuid"},"requested_options":{"type":"object","additionalProperties":{"type":"boolean"}},"supported_options":{"type":"array","items":{"type":"string"}},"task_uuid":{"type":"string","format":"uuid","description":"The Task covering the whole rescue. Poll `GET \/sites\/{uuid}\/events\/{task_uuid}` for its outcome. A `failed` status means at least one repair failed \u2014 its output names which; the per-repair outcomes are separate events on `GET \/sites\/{uuid}\/events`.\n"}}}}}]},"example":{"success":true,"message":"Site rescue job has been queued.","data":{"site_uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","task_uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","requested_options":{"isolate_user":true,"directory_permissions":true,"regenerate_nginx":true,"restart_nginx":true,"reinstall_php":true,"repair_node":false,"repair_pm2":false,"restart_pm2":false,"repair_openclaw":false},"supported_options":["isolate_user","directory_permissions","regenerate_nginx","restart_nginx","reinstall_php"]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/custom-nginx":{"get":{"tags":["Sites"],"summary":"List Custom Nginx Configs","description":"Returns custom nginx configuration snippets attached to a site. Nginx-only \u2014 on non-nginx stacks (OpenLiteSpeed) the list is naturally empty since these rows are never created. Requires the `read:sites` scope.\n","operationId":"sites.customNginx","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of custom nginx configurations","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CustomNginxList"}}}]},"example":{"success":true,"message":"Custom nginx configs retrieved successfully.","data":{"items":[{"uuid":"44ee55ff-66aa-77bb-88cc-99dd00ee11ff","template":"Hide My WP","file":"hide-my-wp","type":"server","content":"# nginx snippet body","status":"applied","is_active":true,"created_at":"2026-02-01T00:00:00Z","updated_at":"2026-02-01T00:00:00Z"}],"count":1}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/web-rules":{"get":{"tags":["Sites"],"summary":"List Web Rules","description":"Returns the custom web-server rules attached to a site: HTTP header rules (set or unset response headers) and redirect rules (with optional conditions on host, URI, query arg, or device). Returned in render order (sort_order) then newest-first. Requires the `read:sites` scope.\n","operationId":"sites.webRules","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"List of web rules","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WebRuleList"}}}]},"example":{"success":true,"message":"Web rules retrieved successfully.","data":{"items":[{"uuid":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb","rule_type":"header","category":"headers","config":{"action":"set","header_name":"X-Frame-Options","header_value":"SAMEORIGIN","always_apply":true},"sort_order":0,"created_at":"2026-01-10T00:00:00Z","updated_at":"2026-01-10T00:00:00Z"},{"uuid":"22bb33cc-44dd-55ee-66ff-77aa88bb99cc","rule_type":"redirect","category":"redirects","config":{"source":"\/old","destination":"https:\/\/example.com\/new","redirect_type":301,"keep_query_string":true},"sort_order":1,"created_at":"2026-01-11T00:00:00Z","updated_at":"2026-01-11T00:00:00Z"}],"counts":{"headers":1,"redirects":1,"total":2}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/snapshots":{"get":{"tags":["Sites"],"summary":"List Site Snapshots","description":"Returns snapshots created from this site, paginated. SSH keypair references, raw meta, and logs are stripped. The `public_url_token` is included only for public snapshots \u2014 for private snapshots it is null. Requires the `read:sites` scope.\n","operationId":"sites.snapshots","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"minimum":1,"maximum":100,"example":10}}],"responses":{"200":{"description":"Paginated list of site snapshots","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteSnapshotList"}}}]},"example":{"success":true,"message":"Snapshots retrieved successfully.","data":{"items":[{"uuid":"9f8e7d6c-5b4a-3210-fedc-ba9876543210","name":"pre-deploy","description":"Snapshot before v2 release","type":"private","status":"ready","size_bytes":524288000,"formatted_size":"500 MB","public_url_token":null,"created_at":"2026-04-01T10:00:00Z","updated_at":"2026-04-01T10:15:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/cache\/settings":{"get":{"tags":["Sites"],"summary":"Get Cache Settings","description":"Returns which cache layers are active for the site: page cache (full-page or WP cache plugin), object cache (Redis and Object Cache Pro), and Cloudflare edge cache. Settings only \u2014 no cache contents are returned. Requires the `read:sites` scope.\n","operationId":"sites.cacheSettings","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Cache settings summary","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SiteCacheSettings"}}}]},"example":{"success":true,"message":"Cache settings retrieved successfully.","data":{"stack":"nginx","page_cache":{"enabled":true,"source":"fullpage","plugin":null},"object_cache":{"redis":true,"object_cache_pro":false},"cloudflare_edge_cache":{"enabled":false}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/cache\/purge":{"post":{"tags":["Sites"],"summary":"Purge Full-Page Cache","description":"Clears the full-page cache for the site. The purge Task is created synchronously in this request and only marked queued \u2014 `data.task_uuid` is its `uuid`; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome. When the site's Cloudflare edge cache integration is enabled, this also triggers a Cloudflare purge, whose Task uuid is `data.cloudflare_task_uuid` (`null` when the integration is not enabled). Requires the `write:sites` scope.\n","operationId":"sites.cache.purge","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Cache purge dispatched","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["task_uuid","cloudflare_task_uuid"],"properties":{"task_uuid":{"type":"string","format":"uuid"},"cloudflare_task_uuid":{"type":"string","format":"uuid","nullable":true}}}}}]},"example":{"success":true,"message":"Cache purge dispatched","data":{"task_uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","cloudflare_task_uuid":null}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/cache\/purge-all":{"post":{"tags":["Sites"],"summary":"Purge All Caches","description":"Purges every supported cache for this site in one call: object cache, Cloudflare edge cache (when enabled), Redis object cache, and Object Cache Pro (when the plugin integration is present). Dispatching one cache does not depend on another, so `data.caches` reports each entry as `queued` or `skipped` (the cache does not apply to this site). `data.cache_tasks` carries the Task `uuid` for each cache queued from this request; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome. A `cache_tasks` entry is `null` only when its `data.caches` counterpart is `skipped`. Requires the `write:sites` scope.\n","operationId":"sites.cache.purge-all","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Cache purge dispatched for all supported caches","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"caches":{"type":"object","properties":{"object_cache":{"type":"string","enum":["queued","skipped"]},"cloudflare_edge":{"type":"string","enum":["queued","skipped"]},"redis_object_cache":{"type":"string","enum":["queued","skipped"]},"object_cache_pro":{"type":"string","enum":["queued","skipped"]}}},"cache_tasks":{"$ref":"#\/components\/schemas\/CachePurgeTaskUuids"}}}}}]},"example":{"success":true,"message":"Cache purge dispatched for all supported caches.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"},"caches":{"object_cache":"queued","cloudflare_edge":"skipped","redis_object_cache":"queued","object_cache_pro":"skipped"},"cache_tasks":{"object_cache":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","cloudflare_edge":null,"redis_object_cache":"1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f","object_cache_pro":null}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/pagespeed\/scan":{"post":{"tags":["PageSpeed"],"summary":"Trigger PageSpeed Insights Scan","description":"Queues a new PageSpeed Insights run for both mobile and desktop strategies. Returns 202 with a `scan_uuid` that links the two strategy runs. Returns 409 if a scan is already running for this site (within the last one hour). After completion, poll `GET \/sites\/{uuid}\/pagespeed` for the latest results. Requires the `write:sites` scope and the `site:manage-update` team permission. Both strategy rows are created before queue dispatch under a per-site lock. Retries reuse those rows. Read \/sites\/{uuid}\/pagespeed\/scans\/{scan_uuid} for the exact invocation's pending, failed or completed outcome; completed history and an older latest result cannot prove this request finished.\n","operationId":"sites.pagespeed.scan","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Scan queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"scan_uuid":{"type":"string","format":"uuid"},"strategies":{"type":"array","items":{"type":"string","enum":["mobile","desktop"]}},"scan_queued_at":{"type":"string","format":"date-time"}}}}}]},"example":{"success":true,"message":"PageSpeed scan queued.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"},"scan_uuid":"f8a9c2d1-1234-4abc-9def-1234567890ab","strategies":["mobile","desktop"],"scan_queued_at":"2026-05-14T10:00:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A scan is already running","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"A PageSpeed scan is already running for this site. Please wait or retry in a few minutes.","data":null}}}}}}},"\/sites\/{uuid}\/vulnerabilities":{"get":{"tags":["Vulnerabilities"],"summary":"List Site Vulnerabilities","description":"Returns a merged inventory of known vulnerabilities affecting the specified site. Sources include Patchstack (when the addon is enabled) and Wordfence (always available). Ignored entries are excluded by default. Requires the `read:sites` scope.\n","operationId":"sites.vulnerabilities.list","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"severity","in":"query","required":false,"schema":{"type":"string","enum":["all","critical","high","medium","low","unknown"],"default":"all"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["all","patchstack","wordfence"],"default":"all"}},{"name":"include_ignored","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Vulnerability inventory for the site","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Vulnerability"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"},"summary":{"$ref":"#\/components\/schemas\/VulnerabilitiesSummary"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/vulnerabilities\/count":{"get":{"tags":["Vulnerabilities"],"summary":"Get Site Vulnerability Count by Severity","description":"Returns severity-grouped vulnerability counts (critical\/high\/medium\/low) for the site, plus a combined `total` and the `skipped` (ignored) count. Counts come from a single source \u2014 Patchstack is preferred over Wordfence (never both) \u2014 and only `insecure` detections are counted. Severity is derived from the vulnerability score, so these numbers match the xCloud dashboard's Vulnerability panel exactly. Requires the `read:sites` scope.\n","operationId":"sites.vulnerabilities.count","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Severity-grouped vulnerability counts for the site","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/VulnerabilityCount"}}}]},"example":{"success":true,"message":"Success","data":{"critical":0,"high":2,"medium":4,"low":0,"skipped":0,"total":6}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/ssl-certificates\/{uuid}":{"get":{"tags":["SSL Certificates"],"summary":"Get SSL Certificate","description":"Returns details for a single SSL certificate addressed by its own UUID. The raw certificate body and private key are never exposed. Requires the `read:sites` scope.\n","operationId":"ssl-certificates.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"uuid","in":"path","required":true,"description":"SSL certificate UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"SSL certificate","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SslCertificate"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"delete":{"tags":["SSL Certificates"],"summary":"Delete SSL Certificate","description":"Deletes an SSL certificate row by UUID. When the certificate is the parent site's currently active SSL provider, the site's `ssl_provider` is cleared and nginx is regenerated so HTTPS stops being served with that certificate. Historical rows (different provider than the active one) are removed without touching the served configuration. Requires the `write:sites` scope.\n","operationId":"ssl-certificates.destroy","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"uuid","in":"path","required":true,"description":"SSL certificate UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"SSL certificate deleted.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuccessEnvelope"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/ssl-certificates\/{uuid}\/status":{"get":{"tags":["SSL Certificates"],"summary":"Get SSL Certificate Status","description":"Returns the live status of a single SSL certificate. Lighter than the full `GET \/ssl-certificates\/{uuid}` shape, intended for clients polling after `POST \/sites\/{uuid}\/ssl-certificates`. Requires the `read:sites` scope.\n","operationId":"ssl-certificates.status","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"uuid","in":"path","required":true,"description":"SSL certificate UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"SSL certificate status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/SslCertificateStatus"}}}]},"example":{"success":true,"message":"SSL status retrieved successfully.","data":{"uuid":"5d5a3c5e-3c5e-4a5e-9d5a-3c5e3c5e3c5e","provider":"xcloud","status":"obtained","label":"Installed","is_installed":true,"is_in_progress":false,"expires_at":"2026-08-10T00:00:00Z","updated_at":"2026-05-10T03:00:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/vulnerabilities":{"get":{"tags":["Vulnerabilities"],"summary":"Team-Wide Vulnerability Rollup","description":"Aggregates vulnerabilities across every site in the current team. Each item includes a `site` context object. Ideal for fleet audits. Ignored entries are excluded by default. Requires the `read:sites` scope.\n","operationId":"vulnerabilities.index","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"severity","in":"query","required":false,"schema":{"type":"string","enum":["all","critical","high","medium","low","unknown"],"default":"all"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["all","patchstack","wordfence"],"default":"all"}},{"name":"include_ignored","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Team-wide vulnerability rollup","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"allOf":[{"$ref":"#\/components\/schemas\/Vulnerability"},{"type":"object","required":["site"],"properties":{"site":{"$ref":"#\/components\/schemas\/VulnerabilitySiteContext"}}}]}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"},"summary":{"allOf":[{"$ref":"#\/components\/schemas\/VulnerabilitiesSummary"},{"type":"object","properties":{"site_count_with_vulnerabilities":{"type":"integer","example":12}}}]}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/vulnerability-scan":{"post":{"tags":["Vulnerabilities"],"summary":"Trigger Vulnerability Rescan","description":"Queues an asynchronous WordPress item refresh and recomputes Wordfence vulnerability matches for the site. Returns 202 \u2014 completion is asynchronous. `data.task_uuid` covers both the shell refresh and result processing; completion is published only after vulnerability matches are saved. Only WordPress sites are supported (422 otherwise). Explicit scans recompute matches even when periodic scanning is disabled. The reference exists before a worker picks the job up; pass it to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for the outcome. After scanning, poll `GET \/sites\/{uuid}\/vulnerabilities` to see updated results. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.vulnerability-scan","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Scan queued","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"scan_queued_at":{"type":"string","format":"date-time"},"task_uuid":{"type":"string","format":"uuid","description":"The scan Task. Poll `GET \/sites\/{uuid}\/events\/{task_uuid}` for its outcome.\n"}}}}}]},"example":{"success":true,"message":"Vulnerability scan queued.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"},"scan_queued_at":"2026-05-13T10:30:00Z","task_uuid":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/vulnerabilities\/{vulnerabilityUuid}\/ignore":{"post":{"tags":["Vulnerabilities"],"summary":"Ignore Vulnerability","description":"Marks a single vulnerability (Wordfence or Patchstack source) as ignored for this site. Ignored entries are hidden from default listings. Idempotent \u2014 re-ignoring an already-ignored entry still returns 200. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.vulnerabilities.ignore","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"$ref":"#\/components\/parameters\/VulnerabilityUuid"}],"responses":{"200":{"description":"Vulnerability ignored","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/VulnerabilityIgnoreResult"}}}]},"example":{"success":true,"message":"Vulnerability ignored.","data":{"vulnerability":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","source":"wordfence","slug":"woocommerce","ignored":true},"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Vulnerabilities"],"summary":"Unignore Vulnerability","description":"Clears the `ignored` flag on a single vulnerability for this site. Idempotent. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.vulnerabilities.unignore","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"$ref":"#\/components\/parameters\/VulnerabilityUuid"}],"responses":{"200":{"description":"Vulnerability unignored","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/VulnerabilityIgnoreResult"}}}]},"example":{"success":true,"message":"Vulnerability unignored.","data":{"vulnerability":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","source":"wordfence","slug":"woocommerce","ignored":false},"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/pagespeed":{"get":{"tags":["PageSpeed"],"summary":"Latest PageSpeed Snapshot","description":"Returns the most recent COMPLETED PageSpeed Insights run for each strategy (mobile + desktop). Either or both may be `null` if the site has no completed runs. Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.pagespeed.latest","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Latest snapshot per strategy","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"mobile":{"$ref":"#\/components\/schemas\/NullablePagespeedRun"},"desktop":{"$ref":"#\/components\/schemas\/NullablePagespeedRun"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/pagespeed\/scans\/{scan_uuid}":{"get":{"tags":["Sites"],"summary":"Get PageSpeed Scan Status","description":"Returns this exact PageSpeed invocation, pending and failed strategy runs included. Completion needs both mobile and desktop results for this scan UUID.\n","operationId":"sites.pagespeed.scans.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"scan_uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Exact scan status. Requires read:sites and site:manage-update. Missing strategy evidence reads as unknown, an unrelated completed scan never satisfies this request, and abandoned active rows are marked failed after one hour.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PagespeedScanStatus"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/sites\/{uuid}\/pagespeed\/history":{"get":{"tags":["PageSpeed"],"summary":"PageSpeed History","description":"Paginated history of COMPLETED PageSpeed Insights runs for the site, ordered newest-first. Optional `strategy` filter (mobile|desktop). Requires the `read:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.pagespeed.history","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"in":"query","name":"strategy","required":false,"schema":{"type":"string","enum":["mobile","desktop"]}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Historical PageSpeed runs","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/PagespeedRun"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/broken-links":{"get":{"tags":["Broken Links"],"summary":"Broken Link Scan Status","description":"Current broken link scan status for a WordPress site, plus a paginated page of its open findings. Returns the `idle` status shape if a scan has never been run. Requires the `read:sites` scope and the `site:manage-broken-links` team permission.\n","operationId":"sites.broken-links.index","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"page","in":"query","description":"Page number for the findings list (default 1)","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Findings per page (default 25, max 100)","schema":{"type":"integer","default":25,"maximum":100,"example":25}}],"responses":{"200":{"description":"Broken link scan status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/BrokenLinkScanStatus"}}}]},"example":{"success":true,"message":"Success","data":{"status":"completed","enabled":true,"frequency":"weekly","last_scan_at":"2026-05-06T08:04:12Z","last_scan_failed_reason":null,"pages_scanned":42,"links_checked":318,"findings_count":1,"broken_links_count":1,"broken_images_count":0,"unverified_count":0,"ignored_count":0,"findings":[{"uuid":"9d6e8f9b-2345-4bcd-8e0f-2345678901bc","source_url":"https:\/\/example.com\/blog\/hello-world","source_title":"Hello World","destination_url":"https:\/\/example.com\/old-page","final_url":null,"occurrence_type":"link","finding_type":"broken_404","severity":"critical","http_status":404,"first_detected_at":"2026-05-01T10:00:00Z","last_seen_at":"2026-05-06T08:04:00Z","ignored":false}],"findings_pagination":{"total":1,"per_page":25,"current_page":1,"last_page":1},"run":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","status":"completed","failure_reason":null,"started_at":"2026-05-06T08:00:00Z","finished_at":"2026-05-06T08:04:12Z","last_heartbeat_at":"2026-05-06T08:04:12Z"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Site is not a WordPress site, or has no associated server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Broken link monitoring is not available for this site type.","data":null}}}}}}},"\/sites\/{uuid}\/broken-links\/{brokenLinkFindingUuid}":{"get":{"tags":["Broken Links"],"summary":"Get Broken Link Finding","description":"A single broken link finding by UUID. Requires the `read:sites` scope and the `site:manage-broken-links` team permission.\n","operationId":"sites.broken-links.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"$ref":"#\/components\/parameters\/BrokenLinkFindingUuid"}],"responses":{"200":{"description":"Broken link finding","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/BrokenLinkFinding"}}}]},"example":{"success":true,"message":"Success","data":{"uuid":"9d6e8f9b-2345-4bcd-8e0f-2345678901bc","source_url":"https:\/\/example.com\/blog\/hello-world","source_title":"Hello World","destination_url":"https:\/\/example.com\/old-page","final_url":null,"occurrence_type":"link","finding_type":"broken_404","severity":"critical","http_status":404,"first_detected_at":"2026-05-01T10:00:00Z","last_seen_at":"2026-05-06T08:04:00Z","last_checked_at":"2026-05-06T08:04:00Z","ignored":false}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Site not accessible to your team, or the finding does not exist \/ does not belong to this site.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Broken link finding not found.","data":null}}}},"422":{"description":"Site is not a WordPress site, or has no associated server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Broken link monitoring is not available for this site type.","data":null}}}}}}},"\/sites\/{uuid}\/broken-links\/scans\/{scan_uuid}":{"get":{"tags":["Broken Links"],"summary":"Broken Link Scan Status","description":"Returns the exact run for this UUID, even after a newer scan starts. Its counters and truncation flags describe this run, not current site-wide findings.\n","operationId":"sites.broken-links.scans.show","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"scan_uuid","in":"path","required":true,"description":"UUID returned in run.uuid by the scan submission.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Exact broken-link scan status and coverage. Requires read:sites and site:manage-broken-links; the existing plan and site-type gates apply. A completed run carrying truncation flags covered only part of the site, a lost heartbeat is marked failed after eight minutes, and callback credentials are never returned.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/BrokenLinkScanEvidence"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/broken-links\/scan":{"post":{"tags":["Broken Links"],"summary":"Trigger Broken Link Scan","description":"Starts an asynchronous broken link scan for the site. If broken link monitoring has never been configured for this site, settings are auto-created and enabled (frequency `manual`) \u2014 but only once the scan is actually accepted, so a 409\/422\/503 response never leaves monitoring enabled as a side effect. Returns the submitted run's status and exact `run.uuid` immediately. Poll `GET \/sites\/{uuid}\/broken-links\/scans\/{scan_uuid}` for that invocation's result and coverage; use `GET \/sites\/{uuid}\/broken-links` for current paginated findings. This response omits `findings` and `findings_pagination`. Requires the `write:sites` scope and the `site:manage-broken-links` team permission.\n","operationId":"sites.broken-links.scan","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Scan started","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/BrokenLinkScanStatus"}}}]},"example":{"success":true,"message":"Broken link scan initiated.","data":{"status":"queued","enabled":true,"frequency":"manual","last_scan_at":null,"last_scan_failed_reason":null,"pages_scanned":0,"links_checked":0,"findings_count":0,"broken_links_count":0,"broken_images_count":0,"unverified_count":0,"ignored_count":0,"run":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","status":"queued","failure_reason":null,"started_at":"2026-05-06T09:00:00Z","finished_at":null,"last_heartbeat_at":"2026-05-06T09:00:00Z"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A scan is already running for this site","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"A broken link scan is already in progress for this site.","data":null}}}},"422":{"description":"Site is not a WordPress site, or has no associated server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Broken link monitoring is not available for this site type.","data":null}}}},"503":{"description":"The scan was accepted but failed to launch on the server (e.g. script upload failure). The run is recorded as failed; retry the request.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Failed to launch the broken link scan on the server. Please try again.","data":null}}}}}}},"\/sites\/{uuid}\/wordpress\/update":{"post":{"tags":["WordPress Actions"],"summary":"Update WordPress Items","description":"Queues an update operation for one or more WordPress plugins, themes, or core on the specified site. Asynchronous \u2014 returns 202 with an operation UUID that can be polled for completion (operation tracking endpoints land in a later PR). Items already updating or with no update available are reported under `skipped_items`. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.update","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WordPressUpdateRequest"}}}},"responses":{"202":{"description":"Update queued for asynchronous processing","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WordPressUpdateOperation"}}}]},"example":{"success":true,"message":"WordPress update queued.","data":{"operation":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","status":"queued","operation_type":"update"},"queued_items":[{"slug":"woocommerce","title":"WooCommerce","type":"plugin","current_version":"8.3.1","available_version":"8.5.0"}],"skipped_items":[{"slug":"yoast-seo","reason":"no_update_available"}],"backup_before_update":false}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/wordpress\/activate":{"post":{"tags":["WordPress Actions"],"summary":"Activate WordPress Plugins or Themes","description":"Queues an activate operation for one or more WordPress plugins or themes on the specified site. Asynchronous \u2014 returns 202 with an operation UUID. Items that cannot be activated (already active, must-use, or dropin) are reported under `skipped_items`. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.activate","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WordPressActivateRequest"}}}},"responses":{"202":{"description":"Activate queued for asynchronous processing","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/WordPressToggleOperation"}}}]},"example":{"success":true,"message":"WordPress activation queued.","data":{"operation":{"uuid":"8c5d7e8a-1234-4abc-9def-1234567890ab","status":"queued","operation_type":"toggle","action":"activate"},"queued_items":[{"slug":"woocommerce","title":"WooCommerce","type":"plugin"}],"skipped_items":[{"slug":"akismet","reason":"already_active"}],"backup_before_action":false}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/wordpress\/refresh":{"post":{"tags":["WordPress Actions"],"summary":"Refresh WordPress Items","description":"Queues a background scan of the site that re-reads WordPress core, plugins, and themes from the server. Use after manual changes outside xCloud. Returns 202 \u2014 completion is asynchronous; poll `GET \/sites\/{uuid}\/wordpress\/updates` afterwards. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wordpress.refresh","x-destructive":false,"security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"202":{"description":"Refresh queued for asynchronous processing","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}}}]},"example":{"success":true,"message":"WordPress refresh queued.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/wp-debug":{"post":{"tags":["WordPress Actions"],"summary":"Toggle WP_DEBUG","description":"Updates the site's `wp-config.php` to enable or disable `WP_DEBUG`. Synchronous \u2014 the change is applied during the request and the response reflects the final state. Returns 200 on success, 422 when the server-side script fails. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"sites.wp-debug","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WpDebugRequest"}}}},"responses":{"200":{"description":"WP_DEBUG toggled successfully","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"wp_debug_enabled":{"type":"boolean"}}}}}]},"example":{"success":true,"message":"WP_DEBUG enabled.","data":{"site":{"uuid":"1a2b3c4d-5e6f-7890-abcd-1234567890ef","name":"my-wordpress-site"},"wp_debug_enabled":true}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/sites\/{uuid}\/magic-login":{"post":{"tags":["WordPress Actions"],"summary":"Generate Magic Login URL","description":"Returns a short-lived URL that logs the WordPress admin into the site's `\/wp-admin` dashboard without a password. Token TTL is 10 minutes. Synchronous \u2014 the first call uploads the xCloud magic-login WP plugin over SSH (subsequent calls skip when plugin status is healthy).\nRate-limited to 30 requests per minute per user. Requires the `write:sites` scope and the `site:magic-login` permission. Delegated logins (`login_as`) additionally require the `site:access-magic-login` team permission.\n","operationId":"sites.magic-login","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"login_as":{"type":"string","maxLength":64,"nullable":true,"description":"Optional WordPress username to log in as. When omitted, logs in as the site's admin user. Requires the caller's team membership to grant the `site:access-magic-login` permission.\n","example":"editor_user"}}}}}},"responses":{"200":{"description":"Magic login URL generated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MagicLoginResult"}}}]},"example":{"success":true,"message":"Magic login URL generated","data":{"url":"https:\/\/example.com\/wp-admin\/wp-login.php?xcloud_magic_login_token=eyJ...&auth_token=Xa3p9q&v=1742212211","expires_at":"2026-05-20T14:50:00Z","admin_user":"admin"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"description":"Forbidden \u2014 caller lacks site:magic-login or delegation permission","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Site is not WordPress, or magic login is disabled on the server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded (30\/min\/user)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"502":{"description":"Failed to provision magic login plugin on the site","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"503":{"description":"Server is not connected","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/oneclick-apps":{"get":{"tags":["OneClick Apps"],"summary":"List OneClick Apps","description":"Paginated catalog of installable OneClick apps with static facts (requirements, service class, versions). Per-server compatibility is a separate endpoint. Requires the `read:sites` scope.\n","operationId":"oneclickApps.index","x-destructive":false,"x-mcp-description":"Browse installable OneClick apps. Safe read \u2014 call freely to discover what can be installed before picking a slug.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"example":1}},{"name":"per_page","in":"query","description":"Items per page (default 10, max 100)","schema":{"type":"integer","default":10,"maximum":100,"example":10}},{"name":"search","in":"query","required":false,"description":"Filter by app name or slug (partial match)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated app catalog","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"items":[{"slug":"n8n","name":"n8n","description":"Workflow automation platform","category":"automation","service_class":"web_app","version":"1.2.0","app_version":"1.64.0","requirements":{"min_ram_mb":2048},"icon":"https:\/\/cdn.example.com\/oneclick-icons\/n8n.png"}],"pagination":{"total":24,"per_page":10,"current_page":1,"last_page":3}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/oneclick-apps\/{slug}":{"get":{"tags":["OneClick Apps"],"summary":"Get OneClick App Schema","description":"Machine-readable install form for one app. `fields` describes what the install endpoint accepts under `fields.*`; entries with `auto_generated: true` may be omitted (a value is generated server-side). `needs_domain` tells you whether the domain block (`domain_parking_method`, `name`, \u2026) is required. Requires the `read:sites` scope.\n","operationId":"oneclickApps.show","x-destructive":false,"x-mcp-description":"Read before installing: defines the accepted `fields.*` and whether the domain block is required (`needs_domain`).\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/OneClickSlug"}],"responses":{"200":{"description":"App field schema","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"slug":"postgresql","name":"PostgreSQL","description":"Relational database","category":"database","service_class":"data_service","needs_domain":false,"requirements":[],"field_groups":[],"fields":[{"key":"admin_user","label":"Admin User","type":"text","group":null,"required":true,"default":null,"auto_generated":false,"validation":"string|max:99","help":null}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/oneclick-apps\/{slug}\/compatibility":{"get":{"tags":["OneClick Apps"],"summary":"Check App\/Server Compatibility","description":"Advisory \"can this app install on this server?\" check \u2014 runtime, stack, service class, server state, billing, and RAM\/CPU\/disk requirements against the latest monitoring snapshot. The install endpoint re-runs every gate regardless of this result. When `monitor_available` is false, requirement issues cannot be evaluated and are omitted. Requires the `read:servers` scope.\n","operationId":"oneclickApps.compatibility","x-destructive":false,"x-mcp-description":"Safe preview of whether an app fits a server, and why not. Install re-validates everything.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/OneClickSlug"}],"responses":{"200":{"description":"Compatibility decision","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"compatible":false,"monitor_available":true,"issues":[{"type":"requirements","key":"memory","message":"Requires 4 GB RAM, server has 2 GB."}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/servers\/{uuid}\/sites\/oneclick\/{slug}":{"post":{"tags":["OneClick Apps"],"summary":"Install OneClick App","description":"Install an app on the server. Asynchronous \u2014 returns 202 and provisions in the background; poll the status endpoint until `is_terminal` is true, then fetch credentials. Manifest fields are validated as `fields.KEY` (422 errors are keyed the same way). Apps whose schema has `needs_domain: true` also require the domain block. Rate limited to 10 requests\/minute. Requires the `write:servers` scope and the `site:create` team permission.\n","operationId":"oneclickApps.install","x-mcp-description":"Read the app schema first to build `fields`, then poll `status_url` until `is_terminal` is true (5\u201310s interval).\n","security":[{"bearerAuth":[]},{"nativeOAuth":["write:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"},{"$ref":"#\/components\/parameters\/OneClickSlug"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Strongly recommended for automated callers. A unique key (e.g. a UUID) scoped to this team + server + app: retrying with the same key and payload replays the original 202 instead of creating a second billable site. Same key with a different payload \u2192 422; a concurrent request with the same key \u2192 409. Successful responses are replayable for 24 hours; failures are not stored, so retries after an error re-execute.\n","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":255,"description":"Display title for the site"},"fields":{"type":"object","description":"App-specific values keyed by the schema's field keys. Keys not declared in the app's schema are ignored.\n","additionalProperties":true},"domain_parking_method":{"type":"string","enum":["go_live","staging_env"],"description":"Required when the app's schema has `needs_domain: true`"},"name":{"type":"string","description":"Full domain, required for `go_live`. For `staging_env` it is an optional subdomain LABEL (letters, digits, hyphens \u2014 no dots); the platform joins it with `selected_staging_domain` to form the hostname, and generates a unique label from the title when omitted. For `data_service` apps the site name is always auto-generated.\n"},"selected_staging_domain":{"type":"string","description":"Staging TLD \u2014 required for `staging_env`; must be one of the platform's available staging domains.\n"},"ssl_provider":{"type":"string","enum":["xcloud"],"description":"Optional, `go_live` only \u2014 `xcloud` (Let's Encrypt) is the only supported value. Staging sites always use the staging certificate. Custom certificates are not supported for OneClick installs.\n"},"tags":{"type":"array","items":{"type":"string"}}}},"example":{"title":"My Redis","fields":[]}}}},"responses":{"202":{"description":"Installation initiated","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Redis installation initiated.","data":{"site_uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","installation_uuid":"c3d4e5f6-a7b8-9012-cdef-123456789012","domain":"redis-x7k2q1","title":"My Redis","app":{"slug":"redis","name":"Redis"},"server_uuid":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","site_status":"new","installation_status":"installing","status_url":"\/api\/v1\/sites\/b2c3d4e5-f6a7-8901-bcde-f12345678901\/oneclick\/status"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Site limit reached \u2014 plan upgrade required","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"A request with this Idempotency-Key is already in progress","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Two shapes share this status. Request validation failures return the standard Laravel error map (`errors` keyed by field, e.g. `fields.ADMIN_EMAIL`). Unmet RAM\/CPU\/disk requirements return `errors` as a list of `{key, message}` objects (`key` is `memory`, `cpu`, or `disk`). Also returned for incompatible runtime\/stack\/service class, non-operational servers (message only), and an Idempotency-Key reused with a different payload.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded (10 installs\/minute)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/oneclick\/status":{"get":{"tags":["OneClick Apps"],"summary":"Get OneClick Install Status","description":"Poll target for OneClick installs. Stop polling when `is_terminal` is true \u2014 success (site provisioned and the app operational), failure (`failed_phase` set: `pre_install`, `install`, `post_install`, or `provisioning` when a post-install provisioning step such as SSL failed even though the app installed), or a dead site (deleted, suspended, or otherwise removed from the install lifecycle \u2014 check `site_status`). Recommended poll interval: 5\u201310 seconds. Installs normally complete within a few minutes \u2014 if the status is still non-terminal and `installing_since` is more than 30 minutes old, treat the install as failed (the provisioning job was lost) rather than polling forever. Requires the `read:sites` scope.\n","operationId":"oneclickApps.status","x-destructive":false,"x-mcp-description":"Poll after installing. Stop when `is_terminal` is true; report `failed_phase` + `error` on failure. Do not poll more than once every 5 seconds.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Installation status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"site_uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","site_status":"provisioning","installation_status":"installing","percentage":62,"error":null,"failed_phase":null,"is_terminal":false,"installing_since":"2026-08-03T09:58:40+00:00","updated_at":"2026-08-03T10:00:00+00:00"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Installation record missing for this site (data inconsistency \u2014 stop polling)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/oneclick\/credentials":{"get":{"tags":["OneClick Apps"],"summary":"Get OneClick App Credentials","description":"Credentials and connection details for an installed app (contains secrets \u2014 handle accordingly). Only available once the installation is operational (`installed`, `running`, or `stopped`); returns 422 while installing or after a failed install. Requires the `read:sites` scope and the `site:manage-authentication` team permission.\n","operationId":"oneclickApps.credentials","x-destructive":false,"x-mcp-description":"Returns live secrets. Fetch only when asked or after an install completes; do not echo secrets into summaries.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["read:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"}],"responses":{"200":{"description":"Credentials payload","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"app_name":"Redis","service_class":"data_service","display":[{"label":"Host","value":"203.0.113.10","type":"text"},{"label":"Port","value":"18099","type":"text"},{"label":"Password","value":"s3cr3t","type":"password"}],"message":null}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"description":"Installation is not complete","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/sites\/{uuid}\/oneclick\/{action}":{"post":{"tags":["OneClick Apps"],"summary":"Run OneClick Lifecycle Action","description":"Run a docker-compose lifecycle action (`start`, `stop`, `restart`, `redeploy`) against an installed app. Synchronous \u2014 the installation status transitions on success (`stop` \u2192 `stopped`, others \u2192 `running`). Returns 422 while the install is in progress or after a failed install. Rate limited to 30 requests\/minute. Requires the `write:sites` scope and the `site:manage-update` team permission.\n","operationId":"oneclickApps.lifecycle","x-mcp-description":"`stop` takes the app offline; `redeploy` recreates containers.\n","security":[{"bearerAuth":[]},{"nativeOAuth":["write:sites"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/SiteUuid"},{"name":"action","in":"path","required":true,"description":"Lifecycle action","schema":{"type":"string","enum":["start","stop","restart","redeploy"]}}],"responses":{"200":{"description":"Action completed","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"}]},"example":{"success":true,"message":"Success","data":{"site_uuid":"b2c3d4e5-f6a7-8901-bcde-f12345678901","action":"stop","installation_status":"stopped"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Another lifecycle action is already running for this site \u2014 retry after it finishes","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Precondition failed \u2014 install not operational or non-docker server","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded (30 actions\/minute)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"502":{"description":"The action failed executing on the server \u2014 safe to retry with backoff","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/billing\/plan":{"get":{"tags":["Billing"],"summary":"Get Current Team Plan","description":"Returns the team's active billing plan, whether billing is active, and the billing status. Requires the `read:billing` scope.\n","operationId":"billing.plan","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Current plan","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"plan":{"nullable":true,"allOf":[{"$ref":"#\/components\/schemas\/BillingPlan"}]},"billing_active":{"type":"boolean"},"billing_status":{"type":"string","nullable":true}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/overview":{"get":{"tags":["Billing"],"summary":"Get Billing Overview","description":"One-call billing summary: plan name, billing-active flag, outstanding amount, and counts of unpaid\/failed invoices, active packages, and active subscriptions. Requires the `read:billing` scope.\n","operationId":"billing.overview","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Billing overview","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"plan_name":{"type":"string","nullable":true},"billing_active":{"type":"boolean"},"currency":{"type":"string","example":"usd","description":"Currency of all monetary totals below. xCloud bills a team in a single currency, so the totals are not cross-currency sums."},"outstanding_amount":{"type":"number","format":"float","description":"In `currency`."},"cost_this_month":{"type":"number","format":"float","description":"In `currency`."},"cost_next_month":{"type":"number","format":"float","description":"In `currency`."},"unpaid_invoices":{"type":"integer","description":"Invoices with status Unpaid only (disjoint from failed_invoices)."},"failed_invoices":{"type":"integer","description":"Invoices with status Failed or PaymentFailed."},"packages_count":{"type":"integer"},"products_count":{"type":"integer"},"active_subscriptions":{"type":"integer","description":"White-label subscriptions with active status that have not ended."},"has_active_payment_method":{"type":"boolean"},"payment_methods_count":{"type":"integer"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/bills":{"get":{"tags":["Billing"],"summary":"List Bills","description":"Paginated list of the team's bill line-items. Requires the `read:billing` scope. Filter with `status`, `service`, `renewal_period`, and `active`.\n","operationId":"billing.bills.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"service","in":"query","required":false,"schema":{"type":"string"}},{"name":"renewal_period","in":"query","required":false,"schema":{"type":"string"}},{"name":"active","in":"query","required":false,"schema":{"type":"boolean"},"description":"true = only active services, false = only inactive; omit for all."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Bill list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Bill"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/bills\/{uuid}":{"get":{"tags":["Billing"],"summary":"Get Bill","description":"Returns a single bill by UUID. Requires the `read:billing` scope.","operationId":"billing.bills.show","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bill detail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Bill"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/billing\/invoices":{"get":{"tags":["Billing"],"summary":"List Invoices","description":"Paginated list of the team's invoices. Requires the `read:billing` scope. Filter with `status` (use `failed` for failed + payment-failed) and `search` (matches the invoice number). Currently returns general invoices only.\n","operationId":"billing.invoices.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Invoice status; use `failed` for failed + payment_failed."},{"name":"search","in":"query","required":false,"schema":{"type":"string"},"description":"Substring match on invoice number."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Invoice list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Invoice"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/invoices\/{invoiceNumber}":{"get":{"tags":["Billing"],"summary":"Get Invoice","description":"Returns a single invoice by its invoice number (e.g. `XC-INV-20260729-482910`), with its bills embedded. Requires the `read:billing` scope.\n\nInvoices are addressed by their `invoice_number` \u2014 the canonical customer-facing identifier printed on the invoice \u2014 rather than a UUID, as an approved exception to the platform's UUID-in-URL convention. Access is team-scoped: an invoice belonging to another team returns 404.\n","operationId":"billing.invoices.show","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"invoiceNumber","in":"path","required":true,"description":"Canonical invoice number (e.g. XC-INV-20260729-482910).","schema":{"type":"string"}}],"responses":{"200":{"description":"Invoice detail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Invoice"}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/billing\/packages":{"get":{"tags":["Billing"],"summary":"List Purchased Packages","description":"Paginated list of packages currently attached to the team. Requires the `read:billing` scope.\n","operationId":"billing.packages.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Package list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Package"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/products":{"get":{"tags":["Billing"],"summary":"List Purchased Products","description":"Paginated list of products (add-ons) currently attached to the team. Requires the `read:billing` scope.\n","operationId":"billing.products.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Product list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Product"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/payment-methods":{"get":{"tags":["Billing"],"summary":"List Payment Methods","description":"Paginated list of the team's saved payment methods (masked card details only). Requires the `read:billing` scope.\n","operationId":"billing.payment-methods.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Payment method list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/PaymentMethod"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/billing\/subscriptions":{"get":{"tags":["Billing"],"summary":"List Subscriptions","description":"Paginated list of the team's subscriptions (white-label). Requires the `read:billing` scope.\n","operationId":"billing.subscriptions.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100}}],"responses":{"200":{"description":"Subscription list","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Subscription"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/servers\/plans":{"get":{"tags":["Servers"],"summary":"List Server Plans","description":"Returns the live xCloud-managed (Vultr) server plans purchasable by your team, each with its specs, per-renewal-period pricing, and the regions it can be deployed to. Pass a plan `slug` as `size` and a region `id` as `region` when creating a server via `POST \/servers`. Requires the `read:servers` scope.\n","operationId":"servers.plans","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Available server plans","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"plans":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerPlan"}}}}}}]},"example":{"success":true,"message":"Available server plans retrieved.","data":{"plans":[{"slug":"vc2-1c-1gb","name":"1 vCPU, 1 GB RAM","specs":{"vcpu":1,"memory_mb":1024,"disk_gb":25,"bandwidth_mb":2048},"pricing":[{"renewal_period":"monthly","price":5,"currency":"usd"},{"renewal_period":"yearly","price":54,"currency":"usd"}],"regions":[{"id":"ewr","city":"New Jersey","country":"US","continent":"North America"},{"id":"lhr","city":"London","country":"GB","continent":"Europe"}]}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/servers\/{uuid}\/provisioning-progress":{"get":{"tags":["Servers"],"summary":"Get Server Provisioning Progress","description":"Returns the step-by-step provisioning progress for a server \u2014 the same model the dashboard renders. Poll this after creating an xCloud-managed server to drive a checklist UI: `percent_complete` for a progress bar, and `stages[].tasks[].status` (`completed` \/ `in_progress` \/ `pending` \/ `failed`) for a per-step tick list. Labels are pre-filled (e.g. the server IP and provider). Once `is_provisioned` is `true` every task reads `completed` and `percent_complete` is `100`; if `is_failed` is `true` the current task reads `failed` and `error_message` explains why. Requires the `read:servers` scope.\n","operationId":"servers.provisioning-progress","security":[{"bearerAuth":[]},{"nativeOAuth":["read:servers"]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/ServerUuid"}],"responses":{"200":{"description":"Provisioning progress","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ServerProvisioningProgress"}}}]},"example":{"success":true,"message":"","data":{"status":"provisioning","status_readable":"Provisioning","is_provisioned":false,"is_failed":false,"percent_complete":71,"current_step":20,"total_steps":28,"current_stage":"Installing Software","current_task":"Installing Web Server","error_message":null,"stages":[{"stage":"Connecting to Server","tasks":[{"id":5,"label":"Connecting To SSH","status":"completed","completed":true},{"id":6,"label":"Connection Established","status":"completed","completed":true}]},{"stage":"Installing Software","tasks":[{"id":20,"label":"Installing Web Server","status":"in_progress","completed":false},{"id":22,"label":"Installing Redis","status":"pending","completed":false}]}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/addons\/mail-delivery\/plans":{"get":{"tags":["Addons - Mail Delivery"],"summary":"List Mail Delivery Plans","description":"Returns the paid Mail Delivery (\"xCloud Managed Email\") plans available for purchase. Each plan carries its monthly send allowance (`email_limit`), price, billing `window`, and `currency`. The `slug` is exactly what `POST \/addons\/mail-delivery\/purchase` accepts as `plan`. The free 100-email tier is excluded. Requires the `read:addons` scope.\n","operationId":"addons.mail-delivery.plans","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Available Mail Delivery plans","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"plans":{"type":"array","items":{"$ref":"#\/components\/schemas\/MailDeliveryPlan"}}}}}}]},"example":{"success":true,"message":"","data":{"plans":[{"slug":"xcloud_1000_emails","price":1,"email_limit":1000,"window":"month","currency":"usd"},{"slug":"xcloud_50000_emails","price":40,"email_limit":50000,"window":"month","currency":"usd"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/addons\/mail-delivery":{"get":{"tags":["Addons - Mail Delivery"],"summary":"List Mail Delivery Subscriptions","description":"Returns a paginated list of the current team's Mail Delivery subscriptions. Metadata only \u2014 sending credentials are never included in the list (fetch a single subscription to retrieve them). Requires the `read:addons` scope and the `addon:view` team permission.\n","operationId":"addons.mail-delivery.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":15}}],"responses":{"200":{"description":"Paginated list of Mail Delivery subscriptions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/MailDelivery"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]},"example":{"success":true,"message":"","data":{"items":[{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","label":"Transactional","plan":"xcloud_1000_emails","email_limit":1000,"price":1,"status":"active","provider":"xCloud Managed Email Service","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/addons\/mail-delivery\/purchase":{"post":{"tags":["Addons - Mail Delivery"],"summary":"Purchase Mail Delivery","description":"Purchases a Mail Delivery subscription on one of the paid plans and settles the generated invoice against the team's default payment method inline. Provisions (or tops up) the team's Elastic Email subaccount and returns the subscription **with its sending credentials**.\n**Notes:** - `plan` must be a paid public slug returned by `GET \/addons\/mail-delivery\/plans` (the free tier cannot be purchased) - `label` names the subscription so multiple subscriptions can be told apart - Purchase is instant-on-payment \u2014 a paid subscription is returned `active` with credentials; there is no DNS lifecycle - Multiple subscriptions per team are allowed; each purchase tops up the same subaccount's credits (no dedup) - If the payment requires additional authentication (e.g. 3-D Secure), a `402` is returned with an `authentication_url`; the subscription is left retryable via `POST \/payments\/{invoice}\/pay`\nRate limited to 10 requests per minute. Requires the `write:addons` scope and the `addon:create` team permission.\n","operationId":"addons.mail-delivery.purchase","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PurchaseMailDeliveryRequest"},"examples":{"purchase":{"summary":"Purchase a 1,000-email\/month plan","value":{"plan":"xcloud_1000_emails","label":"Transactional"}}}}}},"responses":{"201":{"description":"Mail Delivery purchased, invoice paid, and the subscription activated. Returns the subscription with its sending credentials (`api_key` + SMTP).\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MailDeliveryWithCredentials"}}}]},"example":{"success":true,"message":"Mail Delivery purchased successfully.","data":{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","label":"Transactional","plan":"xcloud_1000_emails","email_limit":1000,"price":1,"status":"active","provider":"xCloud Managed Email Service","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z","credentials":{"api_key":"ee-abc123def456...","smtp":{"host":"smtp.elasticemail.com","port":2525,"username":"team_42@xcloud.email","password":"s3cret-relay-p4ss"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Payment could not be completed. Either the payment requires additional authentication (`requires_action`), was declined (`failed`), or the team has no active payment method \/ is not in good billing standing (standard error envelope with `data: null`).\n","content":{"application\/json":{"examples":{"requires_action":{"summary":"Additional authentication required","value":{"success":false,"message":"Payment requires additional authentication.","data":{"payment_status":"requires_action","authentication_url":"https:\/\/app.xcloud.host\/billing\/authenticate\/pi_3Abc...","invoice_number":"XC-INV-20260712-1234"}}},"declined":{"summary":"Payment declined","value":{"success":false,"message":"Payment failed. You can retry paying the invoice.","data":{"payment_status":"failed","invoice_number":"XC-INV-20260712-1234"}}},"no_payment_method":{"summary":"No active payment method \/ billing not in good standing","value":{"success":false,"message":"Add a payment method to your team before purchasing add-ons.","data":null}}}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Mail Delivery is not available on this install (e.g. a white-label install that manages mail through its own surface).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}},"422":{"description":"Validation failed \u2014 e.g. missing\/invalid\/free `plan` or missing `label`.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/ErrorEnvelope"},{"type":"object","properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}]},"example":{"success":false,"message":"The given data was invalid.","data":null,"errors":{"plan":["The selected plan is invalid or not available for purchase."]}}}}},"429":{"description":"Rate limit exceeded (10\/min)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/addons\/mail-delivery\/{mailDelivery}":{"get":{"tags":["Addons - Mail Delivery"],"summary":"Get Mail Delivery Subscription","description":"Returns a single Mail Delivery subscription owned by the current team, **including its decrypted sending credentials** (`api_key` + SMTP secret), so it requires the elevated `write:addons` scope (NOT `read:addons`) and the `addon:view` team permission.\n","x-required-scope":"write","operationId":"addons.mail-delivery.show","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailDeliveryUuid"}],"responses":{"200":{"description":"Mail Delivery subscription details with sending credentials","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MailDeliveryWithCredentials"}}}]},"example":{"success":true,"message":"","data":{"uuid":"e5f6a7b8-c9d0-1234-efab-567890123456","label":"Transactional","plan":"xcloud_1000_emails","email_limit":1000,"price":1,"status":"active","provider":"xCloud Managed Email Service","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z","credentials":{"api_key":"ee-abc123def456...","smtp":{"host":"smtp.elasticemail.com","port":2525,"username":"team_42@xcloud.email","password":"s3cret-relay-p4ss"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/addons\/mailbox\/plans":{"get":{"tags":["Addons - Mailbox"],"summary":"List Mailbox Plans","description":"Returns the mailbox plans available for purchase. Plan slugs are provider-agnostic public identifiers (e.g. `mailbox_8gb`) \u2014 pass the chosen `slug` as the `plan` when purchasing a mailbox. The free tier is excluded. Requires the `read:addons` scope.\n","operationId":"addons.mailbox.plans","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"responses":{"200":{"description":"Available mailbox plans","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"plans":{"type":"array","items":{"$ref":"#\/components\/schemas\/MailboxPlan"}}}}}}]},"example":{"success":true,"message":"","data":{"plans":[{"slug":"mailbox_8gb","price":1,"storage":"8GB","currency":"usd"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/addons\/mailbox":{"get":{"tags":["Addons - Mailbox"],"summary":"List Mailboxes","description":"Returns a paginated list of the current team's purchased mailboxes. Requires the `read:addons` scope and the `addon:view` team permission.\n","operationId":"addons.mailbox.index","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"per_page","schema":{"type":"integer","minimum":1,"maximum":100,"default":15}}],"responses":{"200":{"description":"Paginated list of mailboxes","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Mailbox"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}}}}]},"example":{"success":true,"message":"","data":{"items":[{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"active","plan":"mailbox_8gb","domain":"example.com","dns_verified":true,"records":[{"type":"MX","name":"example.com","value":"mx001.cloudeu.xion.oxcs.net","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:spf.cloudeu.xion.oxcs.net ~all","verified":true}],"webmail_url":"https:\/\/webmail.example.com","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}],"pagination":{"total":1,"per_page":15,"current_page":1,"last_page":1}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/addons\/mailbox\/purchase":{"post":{"tags":["Addons - Mailbox"],"summary":"Purchase Mailbox","description":"Purchases a branded mailbox on one of the available plans and settles the generated invoice against the team's default payment method.\n**Notes:** - `plan` must be a public slug returned by `GET \/addons\/mailbox\/plans` (the free tier cannot be purchased) - `password` must be at least 8 characters and contain at least one digit - `postmaster@` is reserved and cannot be used as the mailbox address - When the domain has already been DNS-verified, the mailbox is created `active` immediately with a `webmail_url`; otherwise it is returned as `pending_verification` with the DNS `records` to add - If the payment requires additional authentication (e.g. 3-D Secure), a `402` is returned with an `authentication_url`\nRate limited to 10 requests per minute. Requires the `write:addons` scope and the `addon:create` team permission.\n","operationId":"addons.mailbox.purchase","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PurchaseMailboxRequest"},"examples":{"purchase":{"summary":"Purchase an 8GB mailbox","value":{"email":"hello@example.com","password":"S3curePass1","plan":"mailbox_8gb","site_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901"}}}}}},"responses":{"201":{"description":"Mailbox purchased and invoice paid. When the domain still needs DNS verification, `status` is `pending_verification` and `records` lists the DNS entries to add. When the domain is already verified, `status` is `active` and `webmail_url` is populated.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Mailbox"}}}]},"examples":{"pending_verification":{"summary":"Paid, DNS verification pending","value":{"success":true,"message":"Mailbox purchased successfully. Please verify your DNS records.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"pending_verification","plan":"mailbox_8gb","domain":"example.com","dns_verified":false,"records":[{"type":"MX","name":"example.com","value":"mx.xcloud.host","verified":false},{"type":"TXT","name":"example.com","value":"v=spf1 include:xcloud.host ~all","verified":false}],"webmail_url":null,"invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}},"active":{"summary":"Paid, domain already verified","value":{"success":true,"message":"Mailbox purchased successfully.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"active","plan":"mailbox_8gb","domain":"example.com","dns_verified":true,"records":[{"type":"MX","name":"example.com","value":"mx001.cloudeu.xion.oxcs.net","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:spf.cloudeu.xion.oxcs.net ~all","verified":true}],"webmail_url":"https:\/\/webmail.example.com","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Payment could not be completed. Either the payment requires additional authentication (`requires_action`), was declined (`failed`), or the team has no active payment method \/ is not in good billing standing (standard error envelope with `data: null`).\n","content":{"application\/json":{"examples":{"requires_action":{"summary":"Additional authentication required","value":{"success":false,"message":"This payment requires additional authentication.","data":{"payment_status":"requires_action","authentication_url":"https:\/\/app.xcloud.host\/billing\/authenticate\/pi_3Abc...","invoice_number":"XC-INV-20260712-1234"}}},"declined":{"summary":"Payment declined","value":{"success":false,"message":"Your payment was declined.","data":{"payment_status":"failed","invoice_number":"XC-INV-20260712-1234"}}},"no_payment_method":{"summary":"No active payment method \/ billing not in good standing","value":{"success":false,"message":"No active payment method found. Please add a payment method to continue.","data":null}}}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"409":{"description":"A mailbox with this email address already exists","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/ErrorEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string"},"invoice_number":{"type":"string","nullable":true,"description":"Present only when the existing mailbox is in a `pending_verification` or `payment_failed` state.\n"}}}}}]},"example":{"success":false,"message":"A mailbox with this email address already exists.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","status":"pending_verification","invoice_number":"XC-INV-20260712-1234"}}}}},"422":{"description":"Validation failed \u2014 e.g. invalid or free `plan`, weak `password`, reserved `postmaster@` address, or a `site_id` not owned by the team.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/ErrorEnvelope"},{"type":"object","properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}]},"example":{"success":false,"message":"The given data was invalid.","data":null,"errors":{"password":["The password must contain at least one number."]}}}}},"429":{"description":"Rate limit exceeded (10\/min)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/addons\/mailbox\/{mailbox}":{"get":{"tags":["Addons - Mailbox"],"summary":"Get Mailbox","description":"Returns a single mailbox owned by the current team. Requires the `read:addons` scope and the `addon:view` team permission.\n","operationId":"addons.mailbox.show","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"responses":{"200":{"description":"Mailbox details","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Mailbox"}}}]},"example":{"success":true,"message":"","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"active","plan":"mailbox_8gb","domain":"example.com","dns_verified":true,"records":[{"type":"MX","name":"example.com","value":"mx001.cloudeu.xion.oxcs.net","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:spf.cloudeu.xion.oxcs.net ~all","verified":true}],"webmail_url":"https:\/\/webmail.example.com","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"delete":{"tags":["Addons - Mailbox"],"summary":"Delete Mailbox","description":"Permanently removes a mailbox. Tears down the mailbox at the mail provider and removes its DNS, then hard-deletes it. When it is the last mailbox on the domain, the domain is removed too. Irreversible; the current paid period is not refunded. Requires the `write:addons` scope and the `addon:delete` team permission.\n","operationId":"addons.mailbox.destroy","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["acknowledged_data_loss"],"properties":{"acknowledged_data_loss":{"type":"boolean","description":"Must be true to confirm permanent, irreversible data loss.","example":true}}}}}},"responses":{"200":{"description":"Mailbox deleted","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"}}}}}]},"example":{"success":true,"message":"Mailbox deleted successfully.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/addons\/mailbox\/{mailbox}\/verify-dns":{"post":{"tags":["Addons - Mailbox"],"summary":"Verify Mailbox DNS","description":"Runs DNS verification for the mailbox's domain. This endpoint always returns `200` when verification runs \u2014 inspect the returned mailbox to see the result. Once every record verifies, `status` flips to `active` and `webmail_url` is populated; otherwise the mailbox stays `pending_verification` with per-record `verified` flags reflecting which records were found.\nRate limited to 30 requests per minute. Requires the `write:addons` scope and the `addon:create` team permission.\n","operationId":"addons.mailbox.verify-dns","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"responses":{"200":{"description":"Verification ran \u2014 the mailbox reflects the current state","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Mailbox"}}}]},"examples":{"verified":{"summary":"All records verified \u2014 mailbox activated","value":{"success":true,"message":"DNS records verified successfully. Your mailbox is now active.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"active","plan":"mailbox_8gb","domain":"example.com","dns_verified":true,"records":[{"type":"MX","name":"example.com","value":"mx.xcloud.host","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:xcloud.host ~all","verified":true}],"webmail_url":"https:\/\/webmail.example.com","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}},"pending":{"summary":"Some records still missing","value":{"success":true,"message":"Some DNS records could not be verified yet. Please try again later.","data":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"pending_verification","plan":"mailbox_8gb","domain":"example.com","dns_verified":false,"records":[{"type":"MX","name":"example.com","value":"mx.xcloud.host","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:xcloud.host ~all","verified":false}],"webmail_url":null,"invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"description":"Rate limit exceeded (30\/min)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}},"\/addons\/mailbox\/{mailbox}\/pop":{"get":{"tags":["Addons - Mailbox"],"summary":"Get POP Settings","description":"Returns the POP3 incoming-mail connection settings for the mailbox. The settings are provider-specific \u2014 Open-Xchange mailboxes use hosts under `*.eu.appsuite.cloud` while qbox mailboxes use `*.xcloud.email`; the ports and encryption are identical across providers. The login `username` is always the mailbox email. Available at any mailbox status. **Returns the decrypted mailbox password**, so it requires the elevated `write:addons` scope (NOT `read:addons`) and the `addon:view` team permission.\n","x-required-scope":"write","operationId":"addons.mailbox.pop","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"responses":{"200":{"description":"POP connection settings","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MailboxConnectionSettings"}}}]},"example":{"success":true,"message":"","data":{"host":"pop.eu.appsuite.cloud","port":995,"encryption":"TLS","username":"hello@example.com","password":"s3cret-p4ssw0rd"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/addons\/mailbox\/{mailbox}\/imap":{"get":{"tags":["Addons - Mailbox"],"summary":"Get IMAP Settings","description":"Returns the IMAP incoming-mail connection settings for the mailbox. The settings are provider-specific \u2014 Open-Xchange mailboxes use hosts under `*.eu.appsuite.cloud` while qbox mailboxes use `*.xcloud.email`; the ports and encryption are identical across providers. The login `username` is always the mailbox email. Available at any mailbox status. **Returns the decrypted mailbox password**, so it requires the elevated `write:addons` scope (NOT `read:addons`) and the `addon:view` team permission.\n","x-required-scope":"write","operationId":"addons.mailbox.imap","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"responses":{"200":{"description":"IMAP connection settings","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MailboxConnectionSettings"}}}]},"example":{"success":true,"message":"","data":{"host":"imap.eu.appsuite.cloud","port":993,"encryption":"TLS","username":"hello@example.com","password":"s3cret-p4ssw0rd"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/addons\/mailbox\/{mailbox}\/smtp":{"get":{"tags":["Addons - Mailbox"],"summary":"Get SMTP Settings","description":"Returns the SMTP outgoing-mail connection settings for the mailbox, including the send limit and an upsell to xCloud Managed Email for higher volumes. The settings are provider-specific \u2014 Open-Xchange mailboxes use hosts under `*.eu.appsuite.cloud` while qbox mailboxes use `*.xcloud.email`; the ports and encryption are identical across providers. The login `username` is always the mailbox email. Available at any mailbox status. **Returns the decrypted mailbox password**, so it requires the elevated `write:addons` scope (NOT `read:addons`) and the `addon:view` team permission.\n","x-required-scope":"write","operationId":"addons.mailbox.smtp","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/MailboxUuid"}],"responses":{"200":{"description":"SMTP connection settings","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MailboxSmtpConnectionSettings"}}}]},"example":{"success":true,"message":"","data":{"host":"smtp.eu.appsuite.cloud","port":465,"encryption":"SSL","starttls_port":587,"username":"hello@example.com","password":"s3cret-p4ssw0rd","send_limit":{"limit":250,"window":"day"},"message":"This SMTP configuration has a daily limit of sending 250 messages. To send higher volumes, use xCloud Managed Email (Mail Delivery).","managed_smtp":{"name":"xCloud Managed Email (Mail Delivery)","addon":"mail_delivery","message":"For higher sending volume, purchase the Mail Delivery addon (1,000-50,000 emails\/month)."}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/payments\/{invoice}\/pay":{"post":{"tags":["Payments"],"summary":"Pay Invoice","description":"Settles (or retries) an outstanding invoice against the team's default payment method. This is a generic settlement endpoint reused across purchase types \u2014 when the invoice is a mailbox invoice, the resulting mailbox is included in the response.\nRate limited to 10 requests per minute. Requires the `write:addons` scope and the `billing:create` team permission.\n","operationId":"payments.pay","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#\/components\/parameters\/XTeamId"},{"$ref":"#\/components\/parameters\/InvoiceReference"}],"responses":{"200":{"description":"Invoice paid","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"invoice_number":{"type":"string","example":"XC-INV-20260712-1234"},"payment_status":{"type":"string","example":"paid"},"mailbox":{"allOf":[{"$ref":"#\/components\/schemas\/Mailbox"}],"nullable":true,"description":"Present only when the invoice was a mailbox purchase.\n"}}}}}]},"example":{"success":true,"message":"Invoice paid successfully.","data":{"invoice_number":"XC-INV-20260712-1234","payment_status":"paid","mailbox":{"uuid":"d4e5f6a7-b8c9-0123-defa-456789012345","email":"hello@example.com","status":"active","plan":"mailbox_8gb","domain":"example.com","dns_verified":true,"records":[{"type":"MX","name":"example.com","value":"mx001.cloudeu.xion.oxcs.net","verified":true},{"type":"TXT","name":"example.com","value":"v=spf1 include:spf.cloudeu.xion.oxcs.net ~all","verified":true}],"webmail_url":"https:\/\/webmail.example.com","invoice_number":"XC-INV-20260712-1234","created_at":"2026-07-12T10:30:00Z"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"402":{"description":"Payment could not be completed. Either the payment requires additional authentication (`requires_action`), was declined (`failed`), or the team has no active payment method (standard error envelope with `data: null`).\n","content":{"application\/json":{"examples":{"requires_action":{"summary":"Additional authentication required","value":{"success":false,"message":"This payment requires additional authentication.","data":{"invoice_number":"XC-INV-20260712-1234","payment_status":"requires_action","authentication_url":"https:\/\/app.xcloud.host\/billing\/authenticate\/pi_3Abc..."}}},"declined":{"summary":"Payment declined","value":{"success":false,"message":"Your payment was declined.","data":{"invoice_number":"XC-INV-20260712-1234","payment_status":"failed"}}},"no_payment_method":{"summary":"No active payment method","value":{"success":false,"message":"No active payment method found. Please add a payment method to continue.","data":null}}}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"description":"Rate limit exceeded (10\/min)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Sanctum personal access token","description":"Sanctum personal access tokens only. Create PATs at `\/user\/api-tokens`. The server enforces the token's granted abilities, team access and resource policies; this HTTP bearer scheme does not express those abilities as OAuth scopes. Native Passport access tokens are represented exclusively by the nativeOAuth security scheme and must satisfy its operation-specific scopes. Both credential types use `Authorization: Bearer <token>`. MCP OAuth tokens and session cookies are not accepted directly by the Public API.\n"},"nativeOAuth":{"type":"oauth2","description":"Registered public first-party client only; S256 PKCE and exact HTTPS callback required. Authorize with an unpredictable state; validate it in the app. Authorization always requests fresh team consent. Access tokens last 15 days; refresh tokens last 30 days and rotate on every exchange. Reuse of a rotated refresh token revokes its entire session family. Teams remain bound to consent; each resource request rechecks current membership. Token endpoint uses standard application\/x-www-form-urlencoded authorization_code\/refresh_token grants.\n","flows":{"authorizationCode":{"authorizationUrl":"https:\/\/app.xcloud.host\/oauth\/authorize","tokenUrl":"https:\/\/app.xcloud.host\/api\/v1\/auth\/token","scopes":{"read:servers":"View servers and monitoring","write:servers":"Manage servers","read:sites":"View sites and monitoring","write:sites":"Manage sites"}}}}},"parameters":{"ServerUuid":{"name":"uuid","in":"path","required":true,"description":"UUID of the server","schema":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key that makes a create request safe to retry. Repeating a request with the same key returns the original response instead of creating a second resource; the key stays valid for 24 hours. Re-using a key with a different request body returns 422, and a second request that arrives while the first is still in flight returns 409.\n","schema":{"type":"string","maxLength":255,"example":"deploy-2026-07-16-abc123"}},"XTeamId":{"name":"X-Team-Id","in":"header","required":false,"description":"UUID of the team to run this request against. Must be one of the teams granted to the token (see `GET \/teams`). Omit to use the token's default team. An unknown or ungranted team returns 403.\n","schema":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},"SiteUuid":{"name":"uuid","in":"path","required":true,"description":"UUID of the site","schema":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"}},"OneClickSlug":{"name":"slug","in":"path","required":true,"description":"OneClick app slug from the catalog","schema":{"type":"string","pattern":"^[a-z0-9-]+$","example":"redis"}},"SudoUserUuid":{"name":"sudo_user_uuid","in":"path","required":true,"description":"UUID of the sudo user","schema":{"type":"string","format":"uuid","example":"c3d4e5f6-a7b8-9012-cdef-234567890123"}},"VulnerabilityUuid":{"name":"vulnerabilityUuid","in":"path","required":true,"description":"UUID of the vulnerability (either a Wordfence `vulnerability_sites` row or a Patchstack `patchstack_vulnerability_sites` row). The UUID is returned by `GET \/sites\/{uuid}\/vulnerabilities` as `item.uuid`.\n","schema":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"}},"BrokenLinkFindingUuid":{"name":"brokenLinkFindingUuid","in":"path","required":true,"description":"UUID of the broken link finding. Returned by `GET \/sites\/{uuid}\/broken-links` as `findings[].uuid`.\n","schema":{"type":"string","format":"uuid","example":"9d6e8f9b-2345-4bcd-8e0f-2345678901bc"}},"MailboxUuid":{"name":"mailbox","in":"path","required":true,"description":"UUID of the mailbox","schema":{"type":"string","format":"uuid","example":"d4e5f6a7-b8c9-0123-defa-456789012345"}},"MailDeliveryUuid":{"name":"mailDelivery","in":"path","required":true,"description":"UUID of the Mail Delivery subscription","schema":{"type":"string","format":"uuid","example":"e5f6a7b8-c9d0-1234-efab-567890123456"}},"InvoiceReference":{"name":"invoice","in":"path","required":true,"description":"Invoice reference \/ number (e.g. `XC-INV-20260712-1234`) \u2014 not a UUID. Returned as `invoice_number` by purchase endpoints.\n","schema":{"type":"string","example":"XC-INV-20260712-1234"}}},"responses":{"DryRun":{"description":"Dry run complete (`dry_run: true` was sent). The payload passed every check the real call runs; nothing was created and the `Idempotency-Key`, if any, was not consumed.\n","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"message":{"type":"string","example":"Dry run complete. The payload is valid and nothing was created."},"data":{"$ref":"#\/components\/schemas\/DryRunResult"}}}]},"example":{"success":true,"message":"Dry run complete. The payload is valid and nothing was created.","data":{"dry_run":true,"would_create":{"site_type":"laravel","domain":"my-app.x-cloud.app","domain_mode":"staging","staging_domain":"x-cloud.app","site_user":"my_app","repository":{"source":"public_url","url":"https:\/\/github.com\/acme\/my-app.git","branch":"main"},"php_version":"8.3","node_version":null,"serving_mode":null,"web_root":null,"port":null,"commands":{"install":null,"build":null,"start":null},"deploy_script":"$XCLOUD_COMPOSER install --no-dev","database":{"provider":"null"}},"warnings":["No domain provided \u2014 deploying to a staging hostname."]}}}}},"Unauthorized":{"description":"Missing or invalid authentication token","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Unauthenticated.","data":null}}}},"Forbidden":{"description":"Token lacks the required scope","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"This action is unauthorized. Your token is missing the required scope.","data":null}}}},"NotFound":{"description":"Resource not found or not accessible to your team","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorEnvelope"},"example":{"success":false,"message":"Not found.","data":null}}}},"ValidationError":{"description":"Request validation failed","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/ErrorEnvelope"},{"type":"object","properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}]},"example":{"success":false,"message":"The given data was invalid.","data":null,"errors":{"domain":["The domain field is required when mode is live."]}}}}}},"schemas":{"DryRunResult":{"type":"object","required":["dry_run","would_create","warnings"],"properties":{"dry_run":{"type":"boolean","enum":[true],"description":"Always true. Marks a response that created nothing."},"would_create":{"type":"object","additionalProperties":true,"description":"The site the same payload creates when sent without `dry_run` \u2014 resolved domain (a minted staging hostname included), site type, runtime versions, serving config, commands, database identities (no password), repository source and branch, `docker.*` and the host `port` for Docker deploys. Passwords and keys are never included.\n"},"warnings":{"type":"array","items":{"type":"string"},"description":"The same notes the real call's 202 would carry."},"domain_setup":{"type":"object","nullable":true,"description":"Present for a live domain only \u2014 the DNS record to add before SSL can be issued."}}},"NativePushConfiguration":{"type":"object","required":["registration_available","delivery_available","environment"],"properties":{"platform":{"type":"string","enum":["ios","android"],"description":"Returned by platform-aware servers. Optional for compatibility with older iOS-only servers; Android must require android before using this response."},"registration_available":{"type":"boolean"},"delivery_available":{"type":"boolean"},"environment":{"type":"string","enum":["sandbox","production"]}}},"NativePushQuietHours":{"type":"object","required":["enabled","start","end","timezone"],"properties":{"enabled":{"type":"boolean"},"start":{"type":"string","pattern":"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$","example":"22:00"},"end":{"type":"string","pattern":"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$","example":"07:00"},"timezone":{"type":"string","example":"Asia\/Dhaka"}},"description":"IANA timezone; start is inclusive, end exclusive. Overnight intervals are supported. Enabled intervals cannot have equal start and end. Events recorded during quiet hours are not replayed later."},"NativePushInstallation":{"type":"object","required":["uuid","revision","enabled","permission","environment","team_uuids","categories","include_recovery","quiet_hours","token_registered"],"properties":{"platform":{"type":"string","enum":["ios","android"],"description":"Returned by platform-aware servers. Optional for compatibility with older iOS-only servers; Android must require android before using this response."},"uuid":{"type":"string","format":"uuid"},"revision":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"},"permission":{"type":"string","enum":["authorized","provisional","denied","not_determined"]},"environment":{"type":"string","enum":["sandbox","production"]},"team_uuids":{"type":"array","maxItems":50,"uniqueItems":true,"items":{"type":"string","format":"uuid"}},"categories":{"type":"array","maxItems":6,"uniqueItems":true,"items":{"type":"string","enum":["availability","resources","deployments","backups","ssl","security"]}},"include_recovery":{"type":"boolean"},"quiet_hours":{"$ref":"#\/components\/schemas\/NativePushQuietHours"},"token_registered":{"type":"boolean"}}},"NativePushInstallationRequest":{"oneOf":[{"$ref":"#\/components\/schemas\/IOSNativePushInstallationRequest"},{"$ref":"#\/components\/schemas\/AndroidNativePushInstallationRequest"}]},"IOSNativePushInstallationRequest":{"type":"object","required":["platform","enabled","permission","environment","team_uuids","categories","include_recovery","quiet_hours","app_version","app_build"],"properties":{"platform":{"type":"string","enum":["ios"]},"enabled":{"type":"boolean"},"permission":{"type":"string","enum":["authorized","provisional","denied","not_determined"]},"environment":{"type":"string","enum":["sandbox","production"]},"team_uuids":{"type":"array","maxItems":50,"uniqueItems":true,"items":{"type":"string","format":"uuid"}},"categories":{"type":"array","maxItems":6,"uniqueItems":true,"items":{"type":"string","enum":["availability","resources","deployments","backups","ssl","security"]}},"include_recovery":{"type":"boolean"},"quiet_hours":{"$ref":"#\/components\/schemas\/NativePushQuietHours"},"revision":{"type":"string","format":"uuid","nullable":true,"description":"Omit for a new UUID. Existing registrations require the most recently returned revision."},"device_token":{"type":"string","nullable":true,"pattern":"^(?:[a-fA-F0-9]{2}){16,256}$","minLength":32,"maxLength":512,"writeOnly":true,"description":"Only sent over HTTPS and never returned. iOS requires 16-256 bytes encoded as hexadecimal; normalized to lowercase. Android requires a case-sensitive FCM registration token (1-4096 ASCII letters, digits, colon, underscore or hyphen). Enabled delivery requires a token and authorized permission; provisional is iOS-only."},"app_version":{"type":"string","maxLength":32},"app_build":{"type":"string","maxLength":32}}},"AndroidNativePushInstallationRequest":{"type":"object","required":["platform","enabled","permission","environment","team_uuids","categories","include_recovery","quiet_hours","app_version","app_build"],"properties":{"platform":{"type":"string","enum":["android"]},"enabled":{"type":"boolean"},"permission":{"type":"string","enum":["authorized","denied","not_determined"]},"environment":{"type":"string","enum":["sandbox","production"]},"team_uuids":{"type":"array","maxItems":50,"uniqueItems":true,"items":{"type":"string","format":"uuid"}},"categories":{"type":"array","maxItems":6,"uniqueItems":true,"items":{"type":"string","enum":["availability","resources","deployments","backups","ssl","security"]}},"include_recovery":{"type":"boolean"},"quiet_hours":{"$ref":"#\/components\/schemas\/NativePushQuietHours"},"revision":{"type":"string","format":"uuid","nullable":true,"description":"Omit for a new UUID. Existing registrations require the most recently returned revision."},"device_token":{"type":"string","nullable":true,"pattern":"^[A-Za-z0-9:_-]+$","minLength":1,"maxLength":4096,"writeOnly":true,"description":"Only sent over HTTPS and never returned. iOS requires 16-256 bytes encoded as hexadecimal; normalized to lowercase. Android requires a case-sensitive FCM registration token (1-4096 ASCII letters, digits, colon, underscore or hyphen). Enabled delivery requires a token and authorized permission; provisional is iOS-only."},"app_version":{"type":"string","maxLength":32},"app_build":{"type":"string","maxLength":32}}},"IncidentAlert":{"type":"object","required":["uuid","event_code","category","severity","title","is_recovery","recorded_at","is_read","read_at","team","resource"],"properties":{"uuid":{"type":"string","format":"uuid"},"event_code":{"type":"string","description":"Stable producer event code; clients must preserve unknown values.","example":"server.disconnected"},"category":{"type":"string","example":"availability"},"severity":{"type":"string","example":"error"},"title":{"type":"string","example":"Server disconnected"},"is_recovery":{"type":"boolean","description":"This record reports a recovery observation, not current incident status."},"recorded_at":{"type":"string","format":"date-time","description":"Absolute UTC time when the notification was stored, not the original monitor sample time."},"is_read":{"type":"boolean"},"read_at":{"type":"string","format":"date-time","nullable":true},"team":{"type":"object","required":["uuid","name"],"properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"resource":{"type":"object","required":["kind","uuid","name"],"properties":{"kind":{"type":"string","enum":["server","site"]},"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}},"IncidentAlertPage":{"type":"object","required":["items","unread_count","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/IncidentAlert"}},"unread_count":{"type":"integer","minimum":0},"pagination":{"type":"object","required":["total","per_page","current_page","last_page"],"properties":{"total":{"type":"integer","minimum":0},"per_page":{"type":"integer","minimum":1,"maximum":100},"current_page":{"type":"integer","minimum":1},"last_page":{"type":"integer","minimum":1}}}}},"ServerRebootOperationResponse":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ServerRebootOperation"}}}]},"ServerRebootOperation":{"type":"object","required":["uuid","status","is_terminal","reboot_verified","command_exit_code","result_code","created_at","verified_at","finished_at"],"properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","preparing","verifying","completed","failed","unconfirmed"]},"is_terminal":{"type":"boolean"},"reboot_verified":{"type":"boolean"},"command_exit_code":{"type":"integer","nullable":true,"description":"Original SSH exit code, including 255; never rewritten to manufacture success."},"result_code":{"type":"string","nullable":true,"description":"Stable outcome reason; no shell output or internal identifiers."},"created_at":{"type":"string","format":"date-time"},"verified_at":{"type":"string","format":"date-time","nullable":true},"finished_at":{"type":"string","format":"date-time","nullable":true}}},"NativeTokenRequest":{"type":"object","required":["grant_type","client_id"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"redirect_uri":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string"}}},"NativeTokenResponse":{"type":"object","required":["token_type","expires_in","access_token","refresh_token","scope"],"properties":{"token_type":{"type":"string"},"expires_in":{"type":"integer"},"access_token":{"type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string"}}},"NativeTokenError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"NativeAuthConfiguration":{"type":"object","required":["client_id","authorization_endpoint","token_endpoint","redirect_uri","scopes"],"properties":{"client_id":{"type":"string"},"authorization_endpoint":{"type":"string","format":"uri"},"token_endpoint":{"type":"string","format":"uri"},"redirect_uri":{"type":"string","format":"uri"},"scopes":{"type":"array","items":{"type":"string"}}}},"SuccessEnvelope":{"type":"object","required":["success","message","data"],"properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Operation completed successfully."},"data":{"nullable":true}}},"CatalogApp":{"type":"object","properties":{"slug":{"type":"string","example":"wordpress"},"name":{"type":"string","example":"WordPress"},"description":{"type":"string"},"icon":{"type":"string"},"group":{"type":"string","example":"one_click"},"category":{"type":"string","example":"cms"},"supported_stacks":{"type":"array","items":{"type":"string"},"example":["nginx","openlitespeed"]},"requirements":{"type":"object","description":"Minimum resources this app needs, raised to a 4GB RAM floor for every one-click app regardless of its own declared footprint so it always matches a purchasable managed plan. Zero means no requirement.\n","properties":{"min_ram_mb":{"type":"integer"},"min_cpu_cores":{"type":"integer"},"min_disk_gb":{"type":"integer"}}},"methods":{"type":"array","items":{"type":"object"},"description":"Creation paths offered once this app is chosen (populated for WordPress; empty for most apps)."},"entry_route_name":{"type":"string","nullable":true},"entry_route_params":{"type":"object"},"is_active":{"type":"boolean"},"coming_soon":{"type":"boolean"},"is_beta":{"type":"boolean"},"is_template":{"type":"boolean"},"requires_new_server":{"type":"boolean","description":"Agentic stacks (OpenClaw, Hermes, ...) are the server \u2014 they can never land on an existing one."},"keywords":{"type":"array","items":{"type":"string"}}}},"CatalogPricingPlan":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","description":"Stable public identifier for this plan. The raw internal `sku` is deliberately never published \u2014 for provider-sourced plans it can be the backing infrastructure provider's own public plan code, which would defeat this payload's exclusion of the provider.\n"},"title":{"type":"string","example":"xCloud Managed \u2014 4GB"},"service_type":{"type":"string","enum":["xcloud_managed_hosting","xcloud_provider_hosting"],"description":"Which billing family this plan belongs to (not its server family). Plans can share a title, currency, and renewal type across families at different prices; this field distinguishes them without exposing the backing infrastructure provider.\n"},"server_family":{"type":"string","nullable":true,"enum":["xcloud_managed","xcloud_vultr",null],"description":"Customer-facing tab shown in the app: xcloud_managed means \"xCloud Managed\", xcloud_vultr means \"Vultr\" with xCloud billing, not the customer's own Vultr account. This is not the backing infrastructure provider of xCloud Managed. Unmapped or missing providers return null with an empty regions list; new customer-facing families require an explicit mapping and schema update.\n"},"regions":{"type":"array","description":"Geographic locations from the create-server picker. xCloud Managed lists all mapped pool regions with warm inventory, not stock for this specific size. Vultr lists this plan's locations. Cached data is not a reservation or guarantee of capacity; an empty list means no known mapped locations. Unmapped locations are omitted rather than exposing internal region codes.\n","items":{"type":"object","required":["id","city","country","country_code"],"properties":{"id":{"type":"string","description":"Country-code plus city slug, not a provider datacentre code or a provisioning input. Informational catalog identifier only.\n","example":"nl-amsterdam"},"city":{"type":"string","example":"Amsterdam"},"country":{"type":"string","description":"Country label from the picker (name or ISO alpha-2 code).","example":"Netherlands"},"country_code":{"type":"string","pattern":"^[A-Z]{2}$","example":"NL"}}}},"price":{"type":"number","format":"float","example":48,"description":"The regular, ongoing price for this term \u2014 what every renewal after the first is charged. Equal to `first_purchase_price` when the plan has no introductory discount.\n"},"first_purchase_price":{"type":"number","format":"float","example":48,"description":"What checkout actually charges TODAY, on the first bill for this term only. Some plans (introductory-priced \"Cloud VPS\" AI-agent SKUs) discount just the first bill; every renewal after that is charged `price`, not this field \u2014 do not treat `first_purchase_price` as an ongoing\/current price. Equal to `price` when there is no introductory discount, so it is always safe to read without a null check.\n"},"currency":{"type":"string","example":"usd"},"renewal_type":{"type":"string","enum":["monthly","yearly","two_yearly","lifetime"]},"ram_gb":{"type":"integer","nullable":true},"disk_gb":{"type":"integer","nullable":true},"cpu_cores":{"type":"integer","nullable":true},"allowed_stacks":{"type":"array","items":{"type":"string"},"description":"Empty means any stack is allowed."}}},"ErrorEnvelope":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"An error occurred."},"errors":{"nullable":true,"description":"Error detail. On a refusal an API client can act on, this carries the guidance shape (`code`, `next_actions`, `dashboard_url`) \u2014 see `Guidance`. On a validation failure it is instead keyed by field name.","allOf":[{"$ref":"#\/components\/schemas\/Guidance"}]},"data":{"nullable":true}}},"NextAction":{"type":"string","description":"A stable token naming what the caller should do next. Branch on this rather than parsing the message. Values may be added over time; an unrecognised value should be surfaced verbatim, never treated as an error.","enum":["connect_git_provider","reconnect_git_provider","check_repository_full_name","choose_existing_branch","use_supported_host","retry_later","prepare_deploy_key","verify_deploy_key","add_dns_record","disable_cloudflare_proxy","enable_cloudflare_proxy","connect_cloudflare","verify_dns","poll_site_status","reissue_ssl_certificate","supply_site_type","supply_start_command","choose_database_engine","install_database_engine","choose_free_port","install_server_runtime","choose_compatible_server","add_docker_files","add_compose_port_mapping","add_dockerfile_expose","use_docker_endpoint","use_manual_endpoint","choose_ssl_provider"]},"Guidance":{"type":"object","description":"What to do about a refusal. Returned inside `errors` on 4xx responses from the git endpoints, so a machine consumer can branch on `code` and act on `next_actions` instead of interpreting the human message.","properties":{"code":{"allOf":[{"$ref":"#\/components\/schemas\/RepositoryAccessCode"}],"nullable":true},"next_actions":{"type":"array","nullable":true,"items":{"$ref":"#\/components\/schemas\/NextAction"}},"dashboard_url":{"type":"string","nullable":true,"description":"The dashboard page for the one remedy the API cannot perform itself (connecting a git provider, connecting Cloudflare). Always on the caller's own domain, including for a white-label team."}}},"DomainSetup":{"type":"object","description":"What still has to happen for a `mode: \"live\"` domain to serve. Returned on the 202 of every git deploy that targets a live domain; absent for a staging hostname, whose DNS xCloud owns.\n\nDescriptive, not blocking \u2014 the deploy proceeds and the record can be added afterwards. xCloud performs no DNS lookup while building this block.\n\nBy default xCloud does NOT create the record. It does when the deploy passed `cloudflare: true`: then `cloudflare.managed` is true and xCloud writes the record described here into the zone during provisioning, as it issues the certificate. It does not exist yet at the 202 \u2014 poll the site's status until it is terminal before checking DNS.","properties":{"mode":{"type":"string","enum":["live"]},"domain":{"type":"string","example":"app.example.com"},"server_ip":{"type":"string","example":"203.0.113.10"},"required_dns":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"A"},"name":{"type":"string","example":"app.example.com"},"value":{"type":"string","example":"203.0.113.10"},"ttl":{"type":"string","example":"auto"},"proxied":{"type":"boolean","example":false,"description":"False for a record the caller adds. Behind Cloudflare's proxy the HTTP-01 challenge never reaches the server, and xCloud's own DNS verification treats Cloudflare proxy IPs as a failure \u2014 keep the record DNS-only (grey cloud) until the certificate has been issued.\n\nTrue when `cloudflare.managed` is set: that certificate is a Cloudflare Origin CA certificate issued through the API, with no HTTP-01 challenge to answer, so the record is proxied from the start."}}}},"cloudflare":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether the team has a Cloudflare integration."},"managed":{"type":"boolean","description":"Whether xCloud owns this record (the deploy passed `cloudflare: true`). When true, xCloud writes it during provisioning and `required_dns` describes what it will write, rather than work for the caller."},"zone":{"type":"string","nullable":true,"description":"The Cloudflare zone the record will be written into.","example":"example.com"},"dashboard_url":{"type":"string"}}},"ssl":{"type":"object","properties":{"provider":{"type":"string","example":"xcloud"},"issues_after_dns_resolves":{"type":"boolean","example":true}}},"verify_url":{"type":"string","description":"The endpoint that answers \"has the record landed yet?\", given as `METHOD url`. Deliberately separate: the deploy performs no DNS lookup, because resolution is slowest exactly when the answer is no.","example":"POST https:\/\/app.xcloud.host\/api\/v1\/servers\/9f1c...\/dns\/check"},"next_actions":{"type":"array","items":{"$ref":"#\/components\/schemas\/NextAction"},"example":["connect_cloudflare","add_dns_record"]},"note":{"type":"string"}}},"PaginationMeta":{"type":"object","required":["current_page","last_page","per_page","total"],"properties":{"current_page":{"type":"integer","example":1},"last_page":{"type":"integer","example":5},"per_page":{"type":"integer","example":15},"total":{"type":"integer","example":72}}},"BillingPlan":{"type":"object","properties":{"name":{"type":"string","nullable":true,"example":"starter"},"name_readable":{"type":"string","nullable":true,"example":"Starter"},"title":{"type":"string","nullable":true,"example":"Starter Plan"},"requires_billing":{"type":"boolean","example":true},"supported_invoice_type":{"type":"string","nullable":true,"example":"general"}}},"Bill":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"service":{"type":"string","example":"self_managed_hosting"},"status":{"type":"string","example":"paid"},"type":{"type":"string","example":"prepaid"},"renewal_period":{"type":"string","example":"monthly"},"currency":{"type":"string","example":"usd"},"billing_amount":{"type":"number","format":"float","example":10},"amount_to_pay":{"type":"number","format":"float","example":10},"additional_usage_charge":{"type":"number","format":"float","example":0,"description":"Metered usage charge added on top of amount_to_pay."},"adjustable_amount":{"type":"number","format":"float","example":0},"is_lifetime":{"type":"boolean","example":false},"has_offer":{"type":"boolean","example":false},"service_is_active":{"type":"boolean","example":true},"title":{"type":"string","nullable":true},"short_description":{"type":"string","nullable":true},"bill_from":{"type":"string","format":"date-time","nullable":true},"next_billing_date":{"type":"string","format":"date-time","nullable":true},"due_on":{"type":"string","format":"date-time","nullable":true},"paid_on":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Invoice":{"type":"object","properties":{"number":{"type":"string","nullable":true,"example":"XC-INV-20260729-482910"},"reference_no":{"type":"string","example":"482910"},"status":{"type":"string","example":"unpaid"},"status_readable":{"type":"string","example":"Unpaid"},"type":{"type":"string","nullable":true,"example":"general"},"source":{"type":"string","example":"single_purchase"},"amount":{"type":"number","format":"float","example":49},"refunded_amount":{"type":"number","format":"float","nullable":true},"currency":{"type":"string","example":"usd"},"currency_symbol":{"type":"string","example":"$"},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"due_date":{"type":"string","format":"date-time","nullable":true},"paid_at":{"type":"string","format":"date-time","nullable":true},"refunded_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"bills":{"type":"array","items":{"$ref":"#\/components\/schemas\/Bill"}}}},"Package":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Test LTD Package"},"description":{"type":"string","nullable":true},"price":{"type":"number","format":"float","example":99},"currency":{"type":"string","example":"usd"},"renewal_type":{"type":"string","example":"lifetime"},"service_type":{"type":"string","example":"self_managed_hosting"},"is_renewable":{"type":"boolean","example":false},"start_from":{"type":"string","format":"date-time","nullable":true},"expire_at":{"type":"string","format":"date-time","nullable":true}}},"Product":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"title":{"type":"string","example":"Managed SMTP Add-on"},"slug":{"type":"string","nullable":true,"example":"managed-smtp-add-on"},"description":{"type":"string","nullable":true},"price":{"type":"number","format":"float","example":15},"currency":{"type":"string","example":"usd"},"renewal_type":{"type":"string","example":"monthly"},"service_type":{"type":"string","example":"email_provider"},"is_active":{"type":"boolean","example":true}}},"PaymentMethod":{"type":"object","description":"Masked card details only \u2014 never the raw card number or gateway identifiers.","properties":{"uuid":{"type":"string","format":"uuid"},"brand":{"type":"string","nullable":true,"example":"visa"},"last4":{"type":"string","nullable":true,"example":"4242"},"expiry_month":{"type":"integer","nullable":true,"example":12},"expiry_year":{"type":"integer","nullable":true,"example":2030},"is_default":{"type":"boolean","example":true},"status":{"type":"string","example":"active"}}},"Subscription":{"type":"object","description":"A team subscription (white-label). Gateway identifiers are never exposed.","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string","example":"White Label Monthly"},"description":{"type":"string","nullable":true},"price":{"type":"number","format":"float","example":99},"currency":{"type":"string","example":"usd"},"renewal_period":{"type":"string","example":"monthly"},"service_type":{"type":"string","example":"white_label_subscription"},"status":{"type":"string","nullable":true,"example":"active"},"trial_ends_at":{"type":"string","format":"date-time","nullable":true},"ends_at":{"type":"string","format":"date-time","nullable":true}}},"User":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"3f2c9a1e-7b64-4d2a-9c1f-5e8a2b7d4c10"},"name":{"type":"string","example":"Jane Smith"},"current_team":{"type":"object","nullable":true,"description":"The authorized team selected for this request; null when there is no team.","required":["uuid","name"],"properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Acme"},"team_photo_url":{"type":"string","format":"uri","nullable":true,"description":"HTTPS URL of the uploaded team logo; null when absent or unavailable. Use local initials as fallback."}}},"email":{"type":"string","format":"email","example":"jane@example.com"},"profile_photo_url":{"type":"string","format":"uri","nullable":true,"example":"https:\/\/app.xcloud.host\/storage\/profile-photos\/jane.jpg"},"current_team_uuid":{"type":"string","format":"uuid","nullable":true,"deprecated":true,"description":"Deprecated \u2014 use `current_team.uuid`. Kept for backwards compatibility; it carries the same value.\n","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"default_team_uuid":{"type":"string","format":"uuid","nullable":true,"description":"The team this token runs against when no `X-Team-Id` header is sent. Differs from `current_team` only when the request selected another granted team.\n","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"teams":{"type":"array","description":"Granted teams that remain available to this user.","items":{"type":"object","required":["uuid","name"],"properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Acme"},"team_photo_url":{"type":"string","format":"uri","nullable":true,"description":"HTTPS URL of the uploaded team logo; null when absent or unavailable. Use local initials as fallback."}}}},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"Laravel timestamps may include fractional seconds.","example":"2024-01-15T10:30:00.000000Z"}}},"GrantedTeam":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"name":{"type":"string","example":"Acme"},"team_photo_url":{"type":"string","format":"uri","nullable":true,"description":"HTTPS URL of the uploaded team logo; null when absent or unavailable. Use local initials as fallback."},"role":{"type":"string","nullable":true,"example":"admin"},"is_default":{"type":"boolean","description":"True for the team used when no X-Team-Id header is sent.","example":true}}},"ApiToken":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"8c1f3a89-2c4e-4a73-9d4c-8b1f2a3d4e5f"},"name":{"type":"string","example":"My CI Token"},"abilities":{"type":"array","items":{"type":"string"},"example":["read:servers","read:sites"]},"team_uuid":{"type":"string","format":"uuid","nullable":true,"example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"team_name":{"type":"string","nullable":true,"example":"Acme"},"last_used_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-03-10T08:22:00Z"},"created_at":{"type":"string","format":"date-time","example":"2025-01-20T14:00:00Z"}}},"CloudflareIntegration":{"type":"object","properties":{"id":{"type":"string","example":"cf_1"},"name":{"type":"string","example":"Jane's Cloudflare"},"email":{"type":"string","format":"email","example":"jane@example.com"},"zone_count":{"type":"integer","example":12}}},"ServerSummary":{"type":"object","required":["uuid","name","status","status_readable","ip_address","provider","stack","php_version","location","created_at","dashboard_url"],"properties":{"uuid":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"name":{"type":"string","example":"xcloud"},"status":{"type":"string","description":"Raw `App\\Enums\\ServerStatus` value, emitted verbatim by ServerResource. The same closed set the `status` query filter on `GET \/servers` accepts.\n","enum":["new","creating","modifying","deleting","provisioning","created","provisioned","modified","deleted","disconnected","creation_failed","error","provisioning_failed","payment_failed","deletion_failed","modification_failed","low_storage","suspended"],"example":"provisioned"},"status_readable":{"type":"string","nullable":true,"description":"Human-readable, title-cased status label.","example":"Provisioned"},"ip_address":{"type":"string","format":"ipv4","example":"203.0.113.42"},"provider":{"type":"string","example":"digitalocean"},"stack":{"type":"string","example":"lemp"},"php_version":{"type":"string","nullable":true,"description":"Null until the server finishes provisioning.","example":"8.2"},"location":{"type":"string","description":"Human-readable server location. The Public API ServerResource emits `location`; there is no `region` key on this surface. Server::getLocationAttribute() returns a non-null string.\n","example":"New York"},"created_at":{"type":"string","format":"date-time","example":"2024-06-01T09:00:00Z"},"dashboard_url":{"type":"string","format":"uri","description":"Browser-openable deep link to the xCloud server dashboard. Requires an active xCloud login session; redirects to login if not authenticated. Returns 404 if the caller's user does not have view access on the server. Honors white-label branded domains.\n","example":"https:\/\/app.xcloud.host\/server\/a1b2c3d4-e5f6-7890-abcd-ef1234567890\/dashboard"}}},"ServerDetail":{"allOf":[{"$ref":"#\/components\/schemas\/ServerSummary"},{"type":"object","required":["ubuntu_version","node_version","database_type"],"properties":{"ubuntu_version":{"type":"string","nullable":true,"description":"Null until the server finishes provisioning.","example":"24.04"},"node_version":{"type":"string","nullable":true,"example":"20"},"database_type":{"type":"string","nullable":true,"example":"mysql"}}}]},"PaginatedServers":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerSummary"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"ServerSnapshot":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"9f8e7d6c-5b4a-3210-fedc-ba9876543210"},"name":{"type":"string","example":"pre-deploy"},"description":{"type":"string","nullable":true,"example":"Snapshot before v2 release"},"type":{"type":"string","nullable":true,"enum":["public","private"],"example":"private"},"status":{"type":"string","nullable":true,"enum":["verifying","creating","ready","failed"],"example":"ready"},"size_bytes":{"type":"integer","example":524288000},"formatted_size":{"type":"string","example":"500 MB"},"public_url_token":{"type":"string","nullable":true,"description":"Share token for public snapshots; null for private snapshots.","example":null},"source_site_uuid":{"type":"string","format":"uuid","nullable":true,"description":"UUID of the site this snapshot was taken from.","example":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb"},"source_site_name":{"type":"string","nullable":true,"example":"example.com"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"ServerSnapshotList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerSnapshot"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"ServerMonitoringHistory":{"type":"object","properties":{"server":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"range":{"type":"string","enum":["24h","7d"],"example":"7d"},"samples":{"type":"array","items":{"type":"object","properties":{"cpu_usage":{"type":"number","format":"float","example":12.4},"ram_usage":{"type":"number","format":"float","example":68.1},"disk_usage":{"type":"number","format":"float","example":34.7},"time_at":{"type":"string","description":"Human-readable timestamp (hh:mm AM\/PM).","example":"10:00 AM"},"sampled_at":{"type":"string","format":"date-time","example":"2026-04-01T10:00:00Z"}}}}}},"ServerService":{"type":"object","properties":{"name":{"type":"string","enum":["mysql","mariadb","postgresql","nginx","redis","php","ssh","supervisor","docker","lsws","nodejs","openclaw","paperclip","hermes","deepseek_harness"],"example":"nginx"},"label":{"type":"string","description":"Human-readable service name (includes PHP version).","example":"NGINX"},"status":{"type":"string","enum":["active","inactive","failed","installing","starting","stopping","restarting","unknown"],"example":"active"},"version":{"type":"string","nullable":true,"description":"Version selector for versioned services (PHP, Node.js).","example":"8.2"},"is_required":{"type":"boolean","example":true},"auto_healing":{"type":"boolean","example":true},"can_restart":{"type":"boolean","description":"Whether this service can be restarted via the API. False for Node.js (a runtime) and for PHP on OpenLiteSpeed servers.\n","example":true},"can_start":{"type":"boolean","description":"Whether this service can be started via `POST \/services\/enable`. False for Node.js and for services that are already active.\n","example":false},"can_stop":{"type":"boolean","description":"Whether POST \/services\/disable is allowed: active services with supported stop actions, including required services. False for Node.js and PHP on OpenLiteSpeed.\n","example":true},"can_install":{"type":"boolean","description":"Whether this existing row can be repaired via install. Today this is true only for failed\/inactive PHP on OpenLiteSpeed. Use `data.installable` on the list response for first-time installs.\n","example":false}}},"SupervisorProcess":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"ab12cd34-5678-90ef-aabb-ccddeeff0011"},"command":{"type":"string","example":"php \/var\/www\/example.com\/artisan queue:work"},"directory":{"type":"string","nullable":true,"example":"\/var\/www\/example.com"},"user":{"type":"string","example":"xcloud_example"},"numprocs":{"type":"integer","example":2},"startsecs":{"type":"integer","nullable":true,"example":1},"stopsecs":{"type":"integer","nullable":true,"example":10},"stopsignal":{"type":"string","nullable":true,"example":"TERM"},"status":{"type":"string","nullable":true,"enum":["new","installing","restarting","inactive","running","stopped","fatal","backoff","starting","stopping","exited","unknown"],"example":"running"},"site_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Linked site uuid when the process is scoped to a site.","example":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb"},"site_name":{"type":"string","nullable":true,"example":"example.com"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SupervisorProcessList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SupervisorProcess"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"FirewallRule":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"55ff66aa-77bb-88cc-99dd-00ee11ff2233"},"name":{"type":"string","example":"ssh-admin"},"ip_address":{"type":"string","nullable":true,"example":"203.0.113.10"},"port":{"type":"string","nullable":true,"description":"Single port or comma\/range list as configured (e.g. \"22\", \"80,443\", \"8000-9000\").","example":"22"},"protocol":{"type":"string","nullable":true,"enum":["any","tcp","udp"],"example":"tcp"},"traffic":{"type":"string","nullable":true,"enum":["allow","deny"],"example":"allow"},"is_active":{"type":"boolean","example":true},"description":{"type":"string","nullable":true,"example":"Office static IP"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"FirewallRuleList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/FirewallRule"}},"counts":{"type":"object","properties":{"allow":{"type":"integer","example":1},"deny":{"type":"integer","example":0},"active":{"type":"integer","example":1},"total":{"type":"integer","example":1}}}}},"CreateFirewallRuleRequest":{"type":"object","required":["name","protocol","traffic"],"properties":{"name":{"type":"string","maxLength":255,"example":"ssh-office"},"port":{"type":"string","nullable":true,"maxLength":255,"description":"Empty for all ports, a single port (e.g. `22`), or a range (e.g. `8000:9000`). Port ranges require `protocol` to be `tcp` or `udp` (not `any`).","example":"22"},"ip_address":{"type":"string","nullable":true,"maxLength":255,"description":"Comma-separated IPv4 addresses with optional `\/CIDR` (e.g. `203.0.113.10,198.51.100.0\/24`). Empty matches all IPs.","example":"203.0.113.10"},"protocol":{"type":"string","enum":["any","tcp","udp"],"example":"tcp"},"traffic":{"type":"string","enum":["allow","deny"],"example":"allow"}}},"SshRestrictionStatus":{"type":"object","properties":{"caller_ip":{"type":"string","nullable":true,"description":"The IP address the API request came from.","example":"203.0.113.42"},"caller_ip_whitelisted":{"type":"boolean","description":"True when the caller IP is already whitelisted on this server's SSH firewall.","example":false},"xcloud_ips_status":{"type":"object","description":"Whitelist status for xCloud infrastructure IPs (jumpbox, API, backup, monitoring).","properties":{"all_whitelisted":{"type":"boolean","example":false},"missing_ips":{"type":"array","items":{"type":"string"},"example":["1.2.3.4","5.6.7.8"]},"not_configured":{"type":"boolean","description":"True when no xCloud SSH-allowed IPs are configured in the environment.","example":false}}},"jumpbox_ip":{"type":"string","nullable":true,"description":"The xCloud jumpbox IP, if configured.","example":"1.2.3.4"}}},"CronJob":{"type":"object","properties":{"id":{"type":"integer","example":101},"command":{"type":"string","example":"php \/home\/xcloud\/example.com\/artisan schedule:run"},"frequency":{"type":"string","example":"* * * * *"},"status":{"type":"string","enum":["active","inactive"],"example":"active"}}},"CronJobDetail":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"command":{"type":"string","example":"php artisan schedule:run"},"user":{"type":"string","example":"xcloud_example"},"frequency":{"type":"string","enum":["every_minute","every_five_minutes","every_ten_minutes","every_fifteen_minutes","every_thirty_minutes","hourly","weekly","monthly","on_reboot","custom"]},"frequency_label":{"type":"string","example":"Every Minute"},"pattern":{"type":"string","nullable":true,"example":"* * * * *"},"status":{"type":"string","enum":["processing","active","inactive"]},"site_uuid":{"type":"string","format":"uuid","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ServerCronJobInput":{"type":"object","required":["user","frequency","command"],"properties":{"user":{"type":"string","maxLength":64,"description":"Linux user that owns the cron entry. Validated against the server's actual users via SSH.","example":"root"},"frequency":{"type":"string","enum":["every_minute","every_five_minutes","every_ten_minutes","every_fifteen_minutes","every_thirty_minutes","hourly","weekly","monthly","on_reboot","custom"]},"pattern":{"type":"string","nullable":true,"maxLength":255,"description":"Required when frequency=custom. 5-field cron expression.","example":"*\/5 * * * *"},"command":{"type":"string","minLength":1,"maxLength":255,"example":"echo hello"}}},"SiteCronJobInput":{"type":"object","required":["frequency","command"],"properties":{"frequency":{"type":"string","enum":["every_minute","every_five_minutes","every_ten_minutes","every_fifteen_minutes","every_thirty_minutes","hourly","weekly","monthly","on_reboot","custom"]},"pattern":{"type":"string","nullable":true,"maxLength":255,"example":"*\/5 * * * *"},"command":{"type":"string","minLength":1,"maxLength":255,"example":"php artisan schedule:run"}}},"ServerMonitoring":{"type":"object","properties":{"cpu_usage":{"type":"number","format":"float","example":12.4},"memory_usage":{"type":"number","format":"float","example":68.1},"disk_usage":{"type":"number","format":"float","example":34.7},"uptime":{"type":"string","example":"15 days, 3:42:10"},"recorded_at":{"type":"string","format":"date-time","example":"2025-03-14T16:00:00Z"}}},"ServerTask":{"type":"object","required":["uuid","name","type","status","created_at"],"properties":{"uuid":{"type":"string","format":"uuid","example":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"name":{"type":"string","example":"Reboot Server"},"type":{"type":"string","example":"reboot"},"status":{"type":"string","enum":["pending","queued","running","finished","timeout","failed","killed"],"example":"finished"},"output":{"type":"string","nullable":true,"description":"Last captured task output, truncated to 500 characters.","example":"Server reboot completed"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-03-14T10:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-03-14T10:02:15Z"}}},"ServerTaskList":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerTask"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"PhpVersion":{"type":"object","properties":{"version":{"type":"string","example":"8.2"},"is_default":{"type":"boolean","example":true}}},"PhpVersionAvailable":{"type":"object","properties":{"version":{"type":"string","example":"8.3"},"status":{"type":"string","nullable":true,"enum":["installed","installing","uninstalling","failed",null]},"opcache_enabled":{"type":"boolean"},"is_default":{"type":"boolean"},"current_version":{"type":"string","nullable":true,"example":"8.3.7"},"patch_available":{"type":"string","nullable":true,"example":"8.3.9"},"installing":{"type":"boolean"}}},"PhpInstallStatus":{"type":"object","properties":{"version":{"type":"string","example":"8.3"},"status":{"type":"string","example":"installing"},"is_default":{"type":"boolean"},"started_at":{"type":"string","format":"date-time","nullable":true}}},"GitSiteCommon":{"type":"object","description":"Shared property bag for Git site deployment. The concrete request schemas (`GitSiteNodejs`, `GitSiteLaravel`, \u2026) and the auto-deploy request extend this; which fields are consumed depends on `site_type`.\n","properties":{"dry_run":{"type":"boolean","default":false,"description":"Run every check this call runs and stop at the persist step. `true` answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site) and creates nothing; an invalid payload answers the same 4xx the real call would. Does not consume the `Idempotency-Key`.\n"},"site_type":{"type":"string","enum":["laravel","nodejs","custom-php","wordpress","lovable"],"description":"The application type to provision for."},"repository":{"type":"object","description":"Supply ONE of: `provider_uuid` + `full_name` (a connected provider \u2014 required for private provider repositories); `url` (a public HTTPS clone URL); or a private SSH `url` together with `deploy_key_uuid` (a key prepared and verified via the deploy-key endpoints). A private SSH URL without a `deploy_key_uuid` is rejected.\n","properties":{"provider_uuid":{"type":"string","format":"uuid","description":"UUID of a connected git provider. Required unless `url` is given."},"full_name":{"type":"string","example":"my-org\/my-app","description":"`owner\/repo` on the provider. Required with `provider_uuid`."},"url":{"type":"string","example":"https:\/\/github.com\/laravel\/laravel.git","description":"Public HTTPS clone URL. Required unless `provider_uuid` is given. The `.git` suffix is added if omitted."},"branch":{"type":"string","nullable":true,"example":"main","description":"Defaults to the repository's default branch."},"deploy_key_uuid":{"type":"string","format":"uuid","nullable":true,"description":"UUID of a deploy key prepared and verified via `POST \/servers\/{server_uuid}\/git\/deploy-keys` (prepare \u2192 add the public key to the repo \u2192 verify \u2192 adopt). Required to deploy a private SSH `url` with no connected provider; the atomic deploy adopts this key instead of minting a new one.\n"}}},"domain":{"type":"object","required":["mode","name"],"description":"Where the site is served. Omit to default to a staging hostname derived from the repo.","properties":{"mode":{"type":"string","enum":["staging","live"],"description":"`staging` puts the site on a free xCloud hostname; `live` uses a domain you control."},"name":{"type":"string","description":"For `staging`, the hostname label. For `live`, the full domain.","example":"app.example.com"},"staging_domain":{"type":"string","nullable":true,"description":"Staging suffix to use (defaults to the server's)."},"title":{"type":"string","nullable":true,"description":"Site title (`live` only; defaults to the domain)."},"ssl_provider":{"type":"string","nullable":true,"enum":["xcloud","custom","cloudflare"],"description":"SSL issuer for a `live` domain (defaults to xcloud). With `cloudflare: true` the issuer becomes `cloudflare`; passing `custom` alongside it is refused with `cloudflare_ssl_provider_conflict`. `cloudflare` cannot be selected on its own: it is the certificate of the zone xCloud manages, so it needs `cloudflare: true` and is otherwise refused with the same code and `choose_ssl_provider`."}}},"cloudflare":{"type":"boolean","default":false,"description":"Let xCloud create the domain's DNS record instead of returning it for a human to add. On a live domain whose zone is on a connected Cloudflare account, pass `cloudflare: true` and do not create the A record yourself. `live` mode only, and only when a connected Cloudflare account holds the domain's zone \u2014 otherwise the deploy is refused with `cloudflare_zone_not_found`. The site's SSL provider becomes `cloudflare`; combining this with `ssl_provider: custom` is refused with `cloudflare_ssl_provider_conflict`, because a Cloudflare-served domain is served by a Cloudflare certificate. The record itself is written during provisioning, as the certificate is issued \u2014 `domain_setup` comes back with `cloudflare.managed: true` and no `add_dns_record` action, and the domain is created lower-cased.\n"},"php_version":{"type":"string","nullable":true,"example":"8.3","description":"Defaults to the server's PHP version."},"node_version":{"type":"string","nullable":true,"example":"20","description":"Node major version for Node apps. Defaults to the server's Node version."},"database":{"type":"object","nullable":true,"description":"Only WordPress provisions a database by default; other types default to none.","properties":{"provider":{"type":"string","nullable":true,"enum":["in_server","null"],"description":"`in_server` provisions a database on the server; `null` (JSON null or the string) or omitting `database` provisions none."},"engine":{"type":"string","nullable":true,"enum":["mysql8","mysql84","mariadb10","mariadb11"],"example":"mysql8","description":"Database engine to install when the server has no database yet. Defaults to `mysql8` when a database is requested (`provider: in_server`) on such a server."},"version":{"type":"string","nullable":true},"name":{"type":"string","nullable":true,"description":"Generated if omitted."},"user":{"type":"string","nullable":true,"description":"Generated if omitted."},"password":{"type":"string","nullable":true,"description":"Generated if omitted."}}},"deploy_script":{"type":"string","nullable":true,"maxLength":5000,"description":"Runs after every deployment."},"env_file_content":{"type":"string","nullable":true,"description":"Contents of the site's .env file."},"env_file_path":{"type":"string","nullable":true,"description":"Subdirectory for the .env file (Node only)."},"enable_push_deploy":{"type":"boolean","default":false,"description":"Deploy on push. Requires a connected provider."},"install_redis":{"type":"boolean","default":false},"web_root":{"type":"string","nullable":true,"example":"public","description":"Directory to serve, relative to the site root, with no leading or trailing slash (e.g. `dist`, `public`). Omit it for the site root itself."},"serving_mode":{"type":"string","nullable":true,"enum":["static","ssr","hybrid"],"description":"Node apps only. `ssr`\/`hybrid` require `start_command` and `port`."},"install_command":{"type":"string","nullable":true},"build_command":{"type":"string","nullable":true},"start_command":{"type":"string","nullable":true,"description":"Required for `ssr`\/`hybrid` Node apps."},"port":{"type":"integer","nullable":true,"minimum":1,"maximum":65535,"description":"Required for `ssr`\/`hybrid` Node apps."}}},"GitSiteNodejs":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["site_type","repository","domain"],"properties":{"site_type":{"type":"string","enum":["nodejs"]},"node_version":{"type":"string","example":"20"},"serving_mode":{"type":"string","enum":["static","ssr","hybrid"],"description":"`static` serves the build output; `ssr`\/`hybrid` run a long-lived process and require `start_command` + `port`."},"install_command":{"type":"string","nullable":true,"example":"npm ci"},"build_command":{"type":"string","nullable":true,"example":"npm run build"},"start_command":{"type":"string","nullable":true,"description":"Required for `ssr`\/`hybrid`."},"web_root":{"type":"string","nullable":true},"port":{"type":"integer","nullable":true,"minimum":1,"maximum":65535,"description":"Required for `ssr`\/`hybrid`."}}}],"example":{"site_type":"nodejs","repository":{"provider_uuid":"9f1c2f2e-3a5b-4c7d-8e9f-0a1b2c3d4e5f","full_name":"my-org\/my-node-app","branch":"main"},"domain":{"mode":"staging","name":"my-node-app"},"node_version":"20","serving_mode":"ssr","install_command":"npm ci","build_command":"npm run build","start_command":"npm run start","port":3000,"enable_push_deploy":true}},"GitSiteLaravel":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["site_type","repository","domain"],"properties":{"site_type":{"type":"string","enum":["laravel"]},"php_version":{"type":"string","example":"8.3","description":"Defaults to the server's PHP version."},"web_root":{"type":"string","nullable":true,"example":"public"},"install_redis":{"type":"boolean","default":false}}}],"example":{"site_type":"laravel","repository":{"url":"https:\/\/github.com\/laravel\/laravel.git","branch":"11.x"},"domain":{"mode":"live","name":"app.example.com","ssl_provider":"xcloud"},"php_version":"8.3","web_root":"public","database":{"provider":"in_server"},"install_redis":true,"env_file_content":"APP_ENV=production\nAPP_DEBUG=false\n","deploy_script":"$XCLOUD_COMPOSER install --no-dev --optimize-autoloader\n$XCLOUD_PHP artisan migrate --force\n"}},"GitSitePhp":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["site_type","repository","domain"],"properties":{"site_type":{"type":"string","enum":["custom-php"]},"php_version":{"type":"string","example":"8.3"},"web_root":{"type":"string","nullable":true}}}]},"GitSiteWordpress":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["site_type","repository","domain"],"properties":{"site_type":{"type":"string","enum":["wordpress"]}}}]},"GitSiteLovable":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["site_type","repository","domain"],"properties":{"site_type":{"type":"string","enum":["lovable"]}}}]},"CreateGitSiteRequest":{"oneOf":[{"$ref":"#\/components\/schemas\/GitSiteNodejs"},{"$ref":"#\/components\/schemas\/GitSiteLaravel"},{"$ref":"#\/components\/schemas\/GitSitePhp"},{"$ref":"#\/components\/schemas\/GitSiteWordpress"},{"$ref":"#\/components\/schemas\/GitSiteLovable"}],"discriminator":{"propertyName":"site_type","mapping":{"nodejs":"#\/components\/schemas\/GitSiteNodejs","laravel":"#\/components\/schemas\/GitSiteLaravel","custom-php":"#\/components\/schemas\/GitSitePhp","wordpress":"#\/components\/schemas\/GitSiteWordpress","lovable":"#\/components\/schemas\/GitSiteLovable"}}},"AutoGitSiteRequest":{"allOf":[{"$ref":"#\/components\/schemas\/GitSiteCommon"},{"type":"object","required":["repository"],"properties":{"generate_ai_script":{"type":"boolean","default":true,"description":"Generate the deploy script with AI when `deploy_script` is omitted. Set false to use the deterministic default script and skip the model round-trip.\n"},"docker":{"type":"object","description":"Docker servers only; ignored on nginx\/OpenLiteSpeed. The container config itself (compose file, port, Dockerfile) is resolved from the repository \u2014 this carries the one value no scan can know.\n","properties":{"allowed_dot_paths":{"$ref":"#\/components\/schemas\/DockerAllowedDotPaths"}}}}}]},"DockerAllowedDotPaths":{"type":"array","maxItems":10,"nullable":true,"description":"Dot-prefixed URL paths the Docker vhost lets through to the container. The vhost denies every URL path segment that starts with a dot except `.well-known\/` \u2014 that default stays, because it is what keeps `\/.ssh\/`, `\/.npmrc` and unapproved hidden endpoints from reaching a proxied app \u2014 but Vite, Nuxt, SvelteKit and Astro serve dev-server and pre-bundled assets from `\/node_modules\/.vite\/`, `\/.nuxt\/` or `\/.svelte-kit\/`, and a request the vhost denies makes the app hang on its splash screen with no error anywhere (xCloudDev\/xCloud#6933).\n\nTwo spellings. A bare name (`vite`) exempts that dot directory wherever it appears in a URL (`\/.vite\/\u2026`, `\/node_modules\/.vite\/\u2026`), which is how OneClick template exceptions match. A path (`node_modules\/.vite`) exempts it at that exact location under the site root only. Either way the match is exact on the right (`vite\/\u2026` or bare `vite`, never `vitefoo`). The leading `\/.` is implied; do not send a leading slash. Names that hold secrets (`env`, `env.*`, `git`, `svn`, `hg`, `ssh`, `gnupg`, `htaccess`, `htpasswd`, `netrc`, `npmrc`, `yarnrc`, `pgpass`, `docker`, `aws`, `kube`, `DS_Store`) and `well-known` (always allowed) are refused with 422 and a message that says why. Up to 10 entries of letters, digits, `.`, `_`, `-` and single slashes; no `..`.\n","items":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]{1,64}(\/[A-Za-z0-9._-]{1,64})*\/?$"},"example":["node_modules\/.vite"]},"RepositoryAccessCode":{"type":"string","enum":["repository_access_not_found","repository_not_found","repository_branch_not_found","repository_access_probe_unavailable","git_provider_not_found","git_provider_disconnected","git_provider_permission_insufficient","deploy_key_required","deploy_key_not_verified","deploy_key_in_use","unsupported_repository_host","unsupported_runtime","incompatible_server","no_available_port","port_unavailable","site_type_undetectable","start_command_required","compose_port_missing","dockerfile_port_missing","database_engine_required","cloudflare_zone_not_found","cloudflare_ssl_unsupported_domain","cloudflare_ssl_provider_conflict","cloudflare_zone_lookup_failed"]},"CreateWordPressSiteRequest":{"type":"object","required":["mode","title"],"properties":{"database":{"type":"object","description":"Only for a server that has no database server yet (servers.index reports `database_type: none`): which engine to install before the site. Omit it and the Ubuntu-aware default is installed (MySQL 8.0, or 8.4 on Ubuntu 26.04+); sending it on a server that already runs a database is refused with 422. The 202 and the dry run both name the engine chosen, in `warnings` and in `would_create.database.engine`.\n","properties":{"engine":{"type":"string","enum":["mysql8","mysql84","mariadb10","mariadb11"],"example":"mysql84"}}},"dry_run":{"type":"boolean","default":false,"description":"Run every check this call runs and stop at the persist step. `true` answers `200` with `data.dry_run: true` and `data.would_create` (the resolved site) and creates nothing; an invalid payload answers the same 4xx the real call would. Does not consume the `Idempotency-Key`.\n"},"mode":{"type":"string","enum":["live","demo"],"description":"`live` creates a real production site with a custom domain. `demo` creates a temporary site with an auto-generated xcloud.host subdomain.\n","example":"live"},"domain":{"type":"string","description":"Primary domain name. Required when `mode` is `live`.","example":"example.com"},"title":{"type":"string","maxLength":256,"description":"WordPress site title.","example":"My Awesome Site"},"php_version":{"type":"string","nullable":true,"description":"PHP version to use. If the version is not already installed on the server, it will be installed automatically. Available versions depend on the server type. When omitted, defaults to the server's current PHP version.\n","example":"8.2"},"ssl":{"type":"object","description":"SSL configuration. Required when `mode` is `live`.","properties":{"provider":{"type":"string","enum":["xcloud","custom","cloudflare"],"description":"SSL provider. `xcloud` issues and renews a free Let's Encrypt certificate for the domain. `custom` installs the `certificate` \/ `private_key` you supply (both are then required) and cannot be combined with `cloudflare: true` (refused with `cloudflare_ssl_provider_conflict`, nothing created). `cloudflare` proxies through Cloudflare and needs `cloudflare: true`; on its own it is refused with the same code.\n","example":"xcloud"}}},"wordpress_version":{"type":"string","description":"WordPress version to install. Defaults to latest stable.","example":"6.7"},"multisite":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"type":{"type":"string","enum":["subdirectory","subdomain"],"default":"subdirectory"}}},"blueprint_uuid":{"type":"string","format":"uuid","description":"UUID of a blueprint to apply after installation. Get available blueprints from `GET \/blueprints`. Mutually exclusive with `snapshot_uuid`.\n","example":"b1c2d3e4-f5a6-7890-bcde-f12345678901"},"snapshot_uuid":{"type":"string","format":"uuid","description":"UUID of a site snapshot to restore from.","example":"d4e5f6a7-b8c9-0123-defa-234567890123"},"cache":{"type":"object","description":"Cache configuration. On Nginx servers, full-page cache and Redis object cache are separate plugins. On OpenLiteSpeed servers, enabling full-page cache installs LiteSpeed Cache plugin which includes built-in object caching.","properties":{"full_page":{"type":"boolean","default":false,"description":"Enable full-page caching. Nginx: installs FastCGI cache. OpenLiteSpeed: installs LiteSpeed Cache plugin."},"object_cache":{"type":"boolean","default":false,"description":"Enable object caching. Nginx: installs Redis Object Cache plugin. OpenLiteSpeed: included with LiteSpeed Cache."},"xspeed":{"type":"boolean","default":false,"description":"Enable xSpeed Cache as the page cache. Installs and activates the xSpeed Cache plugin and turns page caching on. On Nginx it additionally generates and applies the matching server-block, so cached HTML is served without starting PHP; on OpenLiteSpeed the plugin manages its own rules and no server config is generated. Mutually exclusive with `full_page`: when both are true, xSpeed wins and full-page caching is left off.\n"}}},"cloudflare":{"type":"boolean","default":false,"description":"Auto-discover and configure Cloudflare for the domain (requires a connected Cloudflare integration with the domain's zone). Same contract as the git deploys: on a `live` domain xCloud resolves the zone, stores it and raises `ssl.provider` to `cloudflare`; no zone on any connected account is refused with `cloudflare_zone_not_found`, a domain deeper than Cloudflare can certify with `cloudflare_ssl_unsupported_domain`, `ssl.provider: custom` alongside it with `cloudflare_ssl_provider_conflict`, and an account that could not list its zones with `cloudflare_zone_lookup_failed` (retry) \u2014 all before anything is created.\n"},"additional_domains":{"type":"array","items":{"type":"string"},"description":"Additional domains to point to this site.","example":["www.example.com","shop.example.com"]},"deploy_script":{"type":"string","description":"Custom shell script to run after WordPress installation.","example":"cd $SITE_PATH && wp plugin install woocommerce --activate"},"tags":{"type":"array","items":{"type":"string"},"description":"Tags to apply to the site for organization.","example":["production","client-site"]}}},"CreatedWordPressSite":{"type":"object","properties":{"dashboard_url":{"type":"string","format":"uri","description":"The page to give the human: the dashboard's progress view, which redirects into the site once the install has finished. Do not hand out the domain before `GET \/sites\/{uuid}\/status` reports `terminal: true` \u2014 a browser that opens it first caches the empty DNS answer and keeps showing an error after the site is up.\n","example":"https:\/\/app.xcloud.host\/site\/9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d\/dashboard"},"status_url":{"type":"string","format":"uri","description":"The status endpoint to poll until `terminal` is true.","example":"https:\/\/app.xcloud.host\/api\/v1\/sites\/9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d\/status"},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal notes about what provisioning will do first \u2014 today, that the server had no database server and which engine is being installed.\n"},"uuid":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"},"domain":{"type":"string","example":"example.com"},"title":{"type":"string","example":"My Awesome Site"},"type":{"type":"string","example":"wordpress"},"mode":{"type":"string","enum":["live","demo"],"example":"live"},"status":{"type":"string","example":"provisioning"},"server_uuid":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"auto_generated":{"type":"object","description":"Auto-generated credentials \u2014 shown only once, store securely.","properties":{"admin_password":{"type":"string","nullable":true,"example":"Xc!9pMnK2v#rT"},"database_password":{"type":"string","nullable":true,"example":"Db#7qWnZ4xA!p"}}}}},"SiteSummary":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"},"name":{"type":"string","example":"example.com"},"domain_name":{"type":"string","description":"The site's primary domain (same as `name`).","example":"example.com"},"type":{"type":"string","enum":["wordpress","laravel","custom-php","nodejs","oneclick","static","lovable"],"example":"wordpress"},"status":{"type":"string","description":"The site's coarse INTERNAL status (raw SiteStatus value, e.g. `provisioned`, `migrating`, `provisioning_failed`). Diagnostic only \u2014 branch on `deploy_state`.","example":"provisioned"},"status_readable":{"type":"string","nullable":true,"description":"Human-readable, title-cased status label.","example":"Provisioned"},"php_version":{"type":"string","example":"8.2"},"server_uuid":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"is_git":{"type":"boolean","description":"Whether this site is deployed from a git repository.","example":true},"serving_mode":{"type":"string","nullable":true,"enum":["static","ssr","hybrid"],"description":"Serving mode for Node apps; null for non-Node sites.","example":"ssr"},"has_migration":{"type":"boolean","description":"Whether a git deploy \/ migration is (or was) attached to this site.","example":true},"migration_status":{"type":"string","nullable":true,"enum":["filling","init","migrating","finished","failed","canceled"],"description":"Live status of the attached deploy\/migration, or null when there is none.","example":"migrating"},"deploy_state":{"type":"string","enum":["in_progress","deployed","failed","cancelled"],"description":"Stable, machine-readable deploy outcome \u2014 identical to the value from `GET \/sites\/{uuid}\/status`. Branch on this, not on `status`\/`migration_status`. `in_progress` means work is genuinely still running \u2014 a deploy in flight, or a deletion whose background jobs have not finished. Every other site state, including a suspended one, reports a terminal value so a poller stops.","example":"deployed"},"terminal":{"type":"boolean","description":"True once `deploy_state` is final (`deployed`, `failed` or `cancelled`).","example":true},"progress_percentage":{"type":"integer","minimum":0,"maximum":100,"description":"Deploy progress, clamped to 0\u2013100 (100 once deployed) \u2014 the same clamped value the status endpoint returns, so the list and status never disagree.","example":100},"error_message":{"type":"string","nullable":true,"description":"Failure reason when a git deploy failed, otherwise null.","example":null},"is_backup_supported":{"type":"boolean","description":"Whether backups are supported for this site (false for Docker+Nginx and Paperclip sites, or types without database\/file backup support).\n","example":true},"created_at":{"type":"string","format":"date-time","example":"2024-06-10T14:30:00Z"},"dashboard_url":{"type":"string","format":"uri","description":"Browser-openable deep link to the xCloud site dashboard. Requires an active xCloud login session; redirects to login if not authenticated. Returns 404 if the caller's user does not have view access on the site. Honors white-label branded domains.\n","example":"https:\/\/app.xcloud.host\/site\/b2c3d4e5-f6a7-8901-bcde-f12345678901\/dashboard"}}},"SiteDetail":{"allOf":[{"$ref":"#\/components\/schemas\/SiteSummary"},{"type":"object","properties":{"failed_steps":{"type":"array","items":{"type":"string"},"maxItems":10,"description":"Diagnostic provisioning steps whose latest attempt failed and was not recovered, newest first. Omitted from list responses to keep them query-stable.","example":[]}}}]},"PaginatedSites":{"type":"object","required":["items","pagination"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteSummary"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"SiteStatus":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"},"status":{"type":"string","description":"The site's coarse INTERNAL status. Diagnostic only \u2014 branch on `deploy_state`, which is the stable contract.","example":"provisioning"},"is_provisioned":{"type":"boolean","example":false},"deploy_state":{"type":"string","enum":["in_progress","deployed","failed","cancelled"],"description":"Stable, machine-readable outcome of the deploy, collapsed from the internal site and migration statuses. This is the field to branch on; the raw statuses below are diagnostics and may gain cases. `in_progress` is reserved for work that is genuinely still running: a deploy in flight, or a deletion still being processed. A suspended site reports a terminal value, so polling always ends. Read `status` to see why a site is not serving traffic, and which way a deletion settled.","example":"in_progress"},"terminal":{"type":"boolean","description":"True once `deploy_state` is final (`deployed`, `failed` or `cancelled`) \u2014 stop polling. Nothing expires a deployment, so a non-terminal deploy is still running.","example":false},"poll_after_seconds":{"type":"integer","nullable":true,"description":"How many seconds to wait before polling this endpoint again, or null once `terminal` is true. Honour it: deploy steps take minutes, and polling faster only returns the same numbers.","example":10},"current_step":{"type":"string","nullable":true,"description":"The provisioning\/deploy step running right now, or null when the attempt is over (`terminal`) or no step is in flight. Read this together with `progress_percentage`: the percentage can hold one value for well over a minute during a package install, and the step name is what tells a slow step from a stalled deploy. Same task window as `failed_steps`.","example":"Install Composer dependencies"},"has_migration":{"type":"boolean","description":"Whether a git deploy \/ migration is (or was) attached to this site.","example":true},"migration_status":{"type":"string","nullable":true,"enum":["filling","init","migrating","finished","failed","canceled"],"description":"Live status of the attached deploy\/migration, or null when there is none.","example":"migrating"},"failed_steps":{"type":"array","items":{"type":"string"},"description":"Diagnostic: provisioning steps whose LATEST attempt failed and was not recovered, newest first (max 10). Retry-aware \u2014 a step that failed then succeeded on retry is not listed. `deploy_state` is the authoritative outcome; a non-empty list on a `deployed` site means \"succeeded, but a non-fatal step reported an error worth verifying\", NOT a failure. Empty on a clean deploy.","example":[]},"ssl":{"type":"object","description":"The site's certificate outcome. A certificate failure is NOT a failed provisioning step, so it never appears in `failed_steps` and the deploy still reports `deployed`. Read `serving_blocked` to tell the two consequences apart.","properties":{"provider":{"type":"string","nullable":true,"example":"cloudflare"},"certificate_status":{"type":"string","description":"The worst status among the site's CURRENT certificates for its own provider, or `none` when nothing was attempted. A Cloudflare multi-domain site holds one certificate per hostname group, so one failed domain makes this `failed` even while the others are `installed`. `new` and `obtained` are on their way to `installed`; `renewal_required` is installed but due for renewal (still serving); `failed` and `revoked` are terminal until the certificate is issued again (`sites.sslCertificates.create`).","enum":["none","new","obtained","installed","failed","revoked","renewal_required"],"example":"installed"},"serving_blocked":{"type":"boolean","description":"True when the site cannot answer at all because of this. A Cloudflare-served site whose Origin CA certificate is missing, failed or revoked \u2014 for ANY of its current certificates \u2014 returns 526 to every visitor on the domains that certificate covers; an xCloud or custom site simply stays on HTTP until its certificate is fixed.","example":false},"failed_domains":{"type":"array","items":{"type":"string"},"description":"The hostnames covered by the current certificates that failed or were revoked (the site's own name for a single-certificate provider). Empty when none did.","example":[]}}},"progress_percentage":{"type":"integer","description":"Deploy\/migration progress (falls back to provisioning progress) as a percentage.","example":40},"error_message":{"type":"string","nullable":true,"description":"The failure reason when the deploy\/migration failed, otherwise null. On a failure this is the stored reason followed by the name of the failing step and the redacted tail of that step's output, so one poll answers \"why\".","example":null},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteDeployDiagnosis":{"type":"object","description":"A deterministic explanation of the site's latest git deploy failure, built by App\\Services\\Site\\DeployDiagnosis from the same failing-step derivation `sites.status` uses. No language model is involved: the pattern table in App\\Services\\Site\\DeployFailurePatterns maps the failing step's output and name to one classification, so the same failure always yields the same answer.\n","properties":{"uuid":{"type":"string","format":"uuid"},"deploy_state":{"type":"string","enum":["in_progress","deployed","failed","cancelled"],"description":"Identical to `deploy_state` on sites.status. Everything below is null unless this is `failed` \u2014 with one exception: a deploy recorded as `deployed` whose build\/deploy step failed is still diagnosed, and reports `next: redeploy`."},"attempt":{"type":"object","description":"The deploy attempt being diagnosed.","properties":{"id":{"type":"string","nullable":true,"description":"The attempt identifier. Stamped at the start of every deploy \u2014 the provisioning chain, a provision retry, and a redeploy \u2014 so a caller can tell one attempt's steps from another's. Null only for a site whose last deploy predates attempt stamping; the attempt is then bounded by time instead (the migration's window, or the site's most recent unbroken run of deploy tasks)."},"started_at":{"type":"string","format":"date-time","nullable":true}}},"failed_step":{"type":"object","nullable":true,"description":"The newest step of this attempt that failed, timed out or was killed. Null when the deploy did not fail, or when no task recorded the failure.","properties":{"name":{"type":"string","nullable":true,"example":"Deploying App on example.com"},"task_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Pass to sites.events.show for the step's complete output. Null when the caller lacks the `site:manage-events` permission."},"exit_code":{"type":"integer","nullable":true,"example":1},"status":{"type":"string","nullable":true,"enum":["failed","timeout","killed"],"example":"failed"},"output_tail":{"type":"string","nullable":true,"description":"The last 4,000 bytes of the step's output, redacted with the same service that guards sites.events.show. The TAIL, because a build states its cause at the end. Null when the step recorded no output, and null when the caller lacks the `site:manage-events` permission that gates the step output everywhere else."},"output_truncated":{"type":"boolean","description":"True when the output was longer than the tail returned; fetch sites.events.show for the rest."},"event_url":{"type":"string","nullable":true,"description":"Absolute URL of the sites.events.show call that returns this step's full output."}}},"classification":{"type":"string","nullable":true,"enum":["deploy_script","repository_access","dependency_install","build_failed","web_root_missing","start_command","port_conflict","runtime_version","runtime_install","env_missing","docker_build","compose_invalid","service_unhealthy","provisioning_prerequisite","unknown"],"description":"Stable, machine-readable cause. `unknown` means no pattern matched \u2014 read `failed_step.output_tail` rather than guessing a field. Null when the deploy did not fail."},"explanation":{"type":"string","description":"One plain sentence derived from the matched pattern, safe to show a human verbatim."},"correctable_fields":{"type":"array","description":"Site fields that could plausibly fix this failure, narrowed to the ones `PUT \/sites\/{uuid}\/deploy-config` and `POST \/sites\/{uuid}\/provision-retry` actually accept for THIS site type \u2014 the same set `GET \/sites\/{uuid}\/deploy-config` reports. Every name here can therefore be sent back; nothing in this list is refused with a 422. A container site is corrected through the `docker` block and `env_file_content` and has no `env_file_path`, `web_root` or build\/start commands. Empty when nothing in the site configuration is at fault; the full set for the site type when `classification` is `unknown`.\n\nSettings that are NOT per-site \u2014 the server's Node version, above all \u2014 are named in `explanation` and `next_hint` instead, because there is no operation that changes them.","items":{"type":"string","enum":["git_branch","install_command","build_command","start_command","serving_mode","port","web_root","env_file_content","env_file_path","deploy_script","docker.mode","docker.compose_file","docker.dockerfile_path","docker.container_port","docker.build_target"]}},"next":{"type":"string","nullable":true,"enum":["retry","redeploy","rescue","recreate","support"],"description":"What to do next. `retry` re-runs the same attempt unchanged; `redeploy` means correct a field in `correctable_fields` first; `rescue` repairs the server side before redeploying; `recreate` means the site has to be deleted and created again; `support` means stop and involve a human. Null when the deploy did not fail."},"next_hint":{"type":"string","nullable":true,"description":"One sentence naming the exact operation to call next."},"related":{"type":"object","properties":{"events_url":{"type":"string","nullable":true,"description":"Absolute URL of sites.events for this site, or null when the caller lacks the site:manage-events permission."},"status_url":{"type":"string","description":"Absolute URL of sites.status for this site."}}}}},"SslInfo":{"type":"object","description":"The site's most recent certificate, projected by SiteController::ssl() straight off the `ssl_certificates` row.\n","required":["provider","status","expires_at","hostnames"],"properties":{"provider":{"type":"string","description":"Raw `SslCertificate::PROVIDER_*` value \u2014 the same set as SslCertificate.provider. NOT NULL column.","enum":["xcloud","custom","staging","cloudflare","cloudflare_multi_domain"],"example":"xcloud"},"status":{"type":"string","description":"Raw `SslCertificate::STATUS_*` value \u2014 the same set as SslCertificate.status. NOT NULL column.","enum":["new","obtained","installed","failed","revoked","renewal_required"],"example":"installed"},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-06-10T00:00:00Z"},"hostnames":{"type":"array","nullable":true,"description":"Hostnames the certificate covers. The controller returns this key as `hostnames`.","items":{"type":"string"},"example":["example.com","www.example.com"]}}},"CustomNginx":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"44ee55ff-66aa-77bb-88cc-99dd00ee11ff"},"template":{"type":"string","description":"Template label (e.g. \"Use My Own Config\", \"Hide My WP\").","example":"Hide My WP"},"file":{"type":"string","nullable":true,"description":"File slug under the site's custom nginx directory.","example":"hide-my-wp"},"type":{"type":"string","nullable":true,"enum":["before","after","server","php","proxy","7g","8g"],"description":"Where in the nginx config the snippet is inserted.","example":"server"},"content":{"type":"string","description":"Raw nginx directive body.","example":"# nginx snippet body"},"status":{"type":"string","nullable":true,"example":"applied"},"is_active":{"type":"boolean","description":"Whether this saved snippet is currently written into nginx includes.","example":true},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"CustomNginxList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/CustomNginx"}},"count":{"type":"integer","example":1}}},"WebRule":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb"},"rule_type":{"type":"string","enum":["header","redirect"],"example":"header"},"category":{"type":"string","enum":["headers","redirects"],"description":"Grouping for UI \/ config-file routing.","example":"headers"},"config":{"type":"object","description":"Rule body \u2014 shape depends on rule_type. Header rules carry action\/header_name\/header_value\/always_apply; redirect rules carry source\/destination\/redirect_type\/keep_query_string and an optional condition.","additionalProperties":true},"sort_order":{"type":"integer","example":0},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"WebRuleList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/WebRule"}},"counts":{"type":"object","properties":{"headers":{"type":"integer","example":1},"redirects":{"type":"integer","example":1},"total":{"type":"integer","example":2}}}}},"SiteSnapshot":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"9f8e7d6c-5b4a-3210-fedc-ba9876543210"},"name":{"type":"string","example":"pre-deploy"},"description":{"type":"string","nullable":true,"example":"Snapshot before v2 release"},"type":{"type":"string","nullable":true,"enum":["public","private"],"example":"private"},"status":{"type":"string","nullable":true,"enum":["verifying","creating","ready","failed"],"example":"ready"},"size_bytes":{"type":"integer","example":524288000},"formatted_size":{"type":"string","example":"500 MB"},"public_url_token":{"type":"string","nullable":true,"description":"Share token for public snapshots; null for private snapshots.","example":null},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteSnapshotList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteSnapshot"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"SiteCacheSettings":{"type":"object","properties":{"stack":{"type":"string","nullable":true,"description":"Server stack key (e.g. nginx, openlitespeed).","example":"nginx"},"page_cache":{"type":"object","properties":{"enabled":{"type":"boolean","example":true},"source":{"type":"string","nullable":true,"enum":["fullpage","plugin"],"description":"How page caching is provided when enabled.","example":"fullpage"},"plugin":{"type":"string","nullable":true,"description":"Active WP cache plugin slug when source=plugin.","example":null}}},"object_cache":{"type":"object","properties":{"redis":{"type":"boolean","example":true},"object_cache_pro":{"type":"boolean","description":"True when an Object Cache Pro plugin integration exists for the site.","example":false}}},"cloudflare_edge_cache":{"type":"object","properties":{"enabled":{"type":"boolean","example":false}}}}},"StagingSite":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"aa11bb22-cc33-4444-5555-66778899aabb"},"name":{"type":"string","example":"staging.example.com"},"environment":{"type":"string","enum":["staging","staging_with_own_domain"],"example":"staging"},"status":{"type":"string","nullable":true,"example":"provisioned"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"StagingSiteList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/StagingSite"}},"count":{"type":"integer","example":1}}},"CreateStagingSiteRequest":{"type":"object","required":["environment_name","branch","mode"],"description":"Mirrors the WordPress site-create shape. `mode: demo` uses an auto-generated (or caller-supplied) test hostname; `mode: live` uses a custom domain with an SSL provider.\n","properties":{"environment_name":{"type":"string","maxLength":63,"pattern":"^[a-z0-9-]+$","description":"Human label for the environment (lowercase letters, numbers, hyphens).","example":"qa"},"branch":{"type":"string","maxLength":255,"description":"The Git branch the environment tracks.","example":"develop"},"mode":{"type":"string","enum":["demo","live"],"description":"`demo` provisions a test-domain environment (xCloud-managed SSL); `live` provisions on a custom domain with the SSL provider you choose.\n","example":"demo"},"env_init_mode":{"type":"string","enum":["copy_values","copy_keys","empty"],"default":"copy_keys","description":"How the environment's `.env` is seeded from production. `copy_keys` copies keys but blanks secrets; `copy_values` copies values; `empty` starts blank. Database and Redis credentials are always the environment's own regardless of mode.\n","example":"copy_keys"},"target_server_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Optional server to provision the environment onto. Defaults to the production site's server. Must be a provisioned server the team can access, else 404.\n","example":"77aa88bb-cc99-4444-5555-66778899aabb"},"deploy_key_uuid":{"type":"string","format":"uuid","nullable":true,"description":"Required only for a manually-connected **private** repository. The UUID of a deploy key you prepared and verified via `POST \/servers\/{uuid}\/git\/deploy-keys` (then `...\/verify`) \u2014 add the returned public key to the repo first. Provider-connected and public repos need no key. Omitting it for a manual private repo returns 422.\n","example":"55ee66ff-aa11-4444-8888-99aabbccddee"},"subdomain":{"type":"string","maxLength":63,"description":"demo mode only. The subdomain for the test hostname. Auto-generated from the production site name when omitted.\n","example":"preview-checkout"},"demo_domain":{"type":"string","enum":["wp1.host","wp1.sh","1wp.site"],"description":"demo mode only. The platform test-domain suffix. Defaults to the platform default when omitted.\n","example":"wp1.host"},"domain":{"type":"string","maxLength":255,"description":"live mode only (required). The custom domain for the environment. May not use a demo TLD.","example":"staging.example.com"},"ssl":{"type":"object","description":"live mode only. SSL configuration for the custom domain.","properties":{"provider":{"type":"string","enum":["xcloud","custom","cloudflare"],"description":"`xcloud` issues a Let's Encrypt certificate (point DNS at the server first); `custom` uses the certificate and key you supply; `cloudflare` uses your team's already-connected Cloudflare integration (the domain must be active on it \u2014 rejected with 422 otherwise).\n","example":"xcloud"},"certificate":{"type":"string","description":"PEM certificate. Required when provider is `custom`."},"private_key":{"type":"string","description":"PEM private key. Required when provider is `custom`."}}},"additional_domains":{"type":"array","description":"live mode only. Extra domains to attach to the environment.","items":{"type":"string","example":"www.staging.example.com"}}}},"CreateStagingSiteResponse":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"aa11bb22-cc33-4444-5555-66778899aabb"},"name":{"type":"string","description":"The environment hostname (generated for demo, your domain for live).","example":"myapp-x7k2.x-cloud.app"},"environment":{"type":"string","enum":["staging","staging_with_own_domain"],"example":"staging"},"environment_name":{"type":"string","example":"qa"},"mode":{"type":"string","enum":["demo","live"],"example":"demo"},"server_uuid":{"type":"string","format":"uuid","description":"The server the environment is provisioning on.","example":"77aa88bb-cc99-4444-5555-66778899aabb"},"poll_url":{"type":"string","description":"Poll this endpoint for the environment's provisioning status.","example":"\/api\/v1\/sites\/aa11bb22-cc33-4444-5555-66778899aabb\/status"},"status":{"type":"string","enum":["queued"],"example":"queued"}}},"SiteScript":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"11223344-5566-7788-99aa-bbccddeeff00"},"script_type":{"type":"string","enum":["docker-compose","deployment","git-pull","env","staging-post-pull","staging-post-push"],"example":"env"},"path":{"type":"string","nullable":true,"description":"Absolute file path for path-backed scripts (e.g. .env).","example":"\/var\/www\/example.com\/.env"},"has_content":{"type":"boolean","description":"True when the script body is non-empty.","example":true},"size_bytes":{"type":"integer","description":"Size of the script body in bytes (body itself is not exposed).","example":1024},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteScriptList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteScript"}},"count":{"type":"integer","example":2}}},"SiteIpAccessRule":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"ab12cd34-ef56-7890-abcd-ef1234567890"},"ip_address":{"type":"string","example":"203.0.113.42"},"type":{"type":"string","enum":["whitelist","blacklist"],"example":"blacklist"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteIpAccessList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteIpAccessRule"}},"counts":{"type":"object","properties":{"whitelist":{"type":"integer","example":1},"blacklist":{"type":"integer","example":1},"total":{"type":"integer","example":2}}}}},"SiteBackupSetting":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"0c1f3a89-2c4e-4a73-9d4c-8b1f2a3d4e5f"},"type":{"type":"string","nullable":true,"enum":["full","incremental"],"example":"full"},"version":{"type":"string","nullable":true,"enum":["1","2"],"description":"Backup engine version.","example":"1"},"is_local":{"type":"boolean","example":false},"status":{"type":"string","nullable":true,"enum":["pending","running","completed","failed"],"example":"completed"},"database":{"type":"boolean","example":true},"files":{"type":"boolean","example":true},"exclude_paths":{"type":"string","nullable":true},"auto_backup":{"type":"boolean","example":true},"auto_backup_frequency":{"type":"string","nullable":true,"enum":["twelve_hours","daily","weekly","monthly"],"example":"daily"},"auto_incremental_backup":{"type":"boolean","example":false},"auto_incremental_frequency":{"type":"string","nullable":true},"auto_delete":{"type":"boolean","example":true},"delete_after_days":{"type":"integer","nullable":true,"example":30},"time":{"type":"string","nullable":true,"example":"02:00"},"incremental_time":{"type":"string","nullable":true},"last_backup_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-15T02:00:00Z"},"storage_provider":{"type":"object","nullable":true,"description":"Null for local backups. Credentials are never exposed.","properties":{"uuid":{"type":"string","format":"uuid","example":"f8e7d6c5-b4a3-9281-7e6f-5d4c3b2a1f0e"},"provider":{"type":"string","example":"s3"},"status":{"type":"string","example":"connected"}}},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteBackupSettingList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteBackupSetting"}},"count":{"type":"integer","example":1}}},"SiteBackupCount":{"type":"object","properties":{"local":{"type":"integer","description":"Number of local backup files.","example":12},"remote":{"type":"integer","description":"Number of remote backup files.","example":8},"total":{"type":"integer","description":"Combined local + remote backup count.","example":20}}},"SiteBackupStatusEntry":{"type":"object","properties":{"configured":{"type":"boolean","description":"Whether the site has its own backup setting of this type.","example":true},"active":{"type":"boolean","description":"Whether scheduled (auto) backup is enabled for this type.","example":true},"status":{"type":"string","nullable":true,"enum":["pending","running","completed","failed"],"description":"Last run state of the most recent setting of this type.","example":"completed"},"last_backup_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-15T02:00:00Z"}}},"SiteBackupStatus":{"type":"object","properties":{"local":{"$ref":"#\/components\/schemas\/SiteBackupStatusEntry"},"remote":{"allOf":[{"$ref":"#\/components\/schemas\/SiteBackupStatusEntry"},{"type":"object","properties":{"storage_provider":{"type":"object","nullable":true,"description":"Remote destination. Credentials are never exposed.","properties":{"uuid":{"type":"string","format":"uuid","example":"f8e7d6c5-b4a3-9281-7e6f-5d4c3b2a1f0e"},"provider":{"type":"string","example":"s3"},"status":{"type":"string","example":"connected"}}}}}]}}},"SiteCronJob":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"},"command":{"type":"string","example":"php \/var\/www\/example.com\/artisan schedule:run"},"user":{"type":"string","example":"xcloud_example"},"frequency":{"type":"string","nullable":true,"description":"Frequency key (see CronJobFrequency enum).","example":"minutely"},"frequency_label":{"type":"string","nullable":true,"description":"Human-readable frequency.","example":"Every Minute"},"pattern":{"type":"string","nullable":true,"description":"Crontab pattern.","example":"* * * * *"},"status":{"type":"string","nullable":true,"enum":["processing","active","inactive"],"example":"active"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"}}},"SiteCronJobList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteCronJob"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"Redirection":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"8a7b6c5d-4e3f-2a1b-9c8d-7e6f5a4b3c2d"},"redirect_type":{"type":"integer","enum":[301,302],"description":"HTTP status code (301 permanent, 302 temporary).","example":301},"redirect_label":{"type":"string","nullable":true,"enum":["301 - Permanent","302 - Temporarily"],"example":"301 - Permanent"},"from":{"type":"string","description":"Source path or URL.","example":"\/old-page"},"to":{"type":"string","description":"Target URL.","example":"https:\/\/example.com\/new-page"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"}}},"RedirectionList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Redirection"}},"count":{"type":"integer","example":1}}},"SslCertificate":{"type":"object","required":["uuid","provider","status","obtained_from","expires_at","renewal_attempt_at","hostnames","is_installed","created_at","updated_at"],"properties":{"uuid":{"type":"string","format":"uuid","example":"5d5a3c5e-3c5e-4a5e-9d5a-3c5e3c5e3c5e"},"provider":{"type":"string","enum":["xcloud","custom","staging","cloudflare","cloudflare_multi_domain"],"example":"xcloud"},"status":{"type":"string","enum":["new","obtained","installed","failed","revoked","renewal_required"],"example":"installed"},"obtained_from":{"type":"string","nullable":true,"description":"Issuing authority where the certificate was obtained (e.g. letsencrypt).","example":"letsencrypt"},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-08-10T00:00:00Z"},"renewal_attempt_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of the most recent renewal attempt.","example":"2026-05-10T03:00:00Z"},"hostnames":{"type":"array","items":{"type":"string"},"example":["example.com","www.example.com"]},"is_installed":{"type":"boolean","description":"True when the certificate is currently installed on the server.","example":true},"site":{"type":"object","description":"Present on `GET \/ssl-certificates\/{uuid}` (returns the parent site); omitted from `GET \/sites\/{uuid}\/ssl-certificates` list responses since they are already site-scoped.","properties":{"uuid":{"type":"string","format":"uuid","example":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb"},"name":{"type":"string","example":"example.com"}}},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-10T03:00:00Z"}}},"SslCertificateList":{"type":"object","required":["items","count"],"properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SslCertificate"}},"count":{"type":"integer","example":1}}},"SslCertificateStatus":{"type":"object","required":["uuid","provider","status","label","is_installed","is_in_progress","expires_at","updated_at"],"properties":{"uuid":{"type":"string","format":"uuid","example":"5d5a3c5e-3c5e-4a5e-9d5a-3c5e3c5e3c5e"},"provider":{"type":"string","nullable":true,"enum":["xcloud","custom","cloudflare","cloudflare_multi_domain","staging"],"example":"xcloud"},"status":{"type":"string","nullable":true,"enum":["new","obtained","installed","failed","revoked","renewal_required"],"example":"obtained"},"label":{"type":"string","example":"Installed"},"is_installed":{"type":"boolean","description":"True when the certificate is installed and serving HTTPS (`status` is `obtained` or `installed`).","example":true},"is_in_progress":{"type":"boolean","description":"True when the certificate is still being generated (`status=new`).","example":false},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-08-10T00:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-10T03:00:00Z"}}},"CreateSslCertificateRequest":{"type":"object","required":["provider"],"properties":{"provider":{"type":"string","enum":["xcloud","custom","cloudflare"],"description":"SSL provider. `cloudflare` maps server-side to the multi-domain CF architecture.","example":"xcloud"},"certificate":{"type":"string","description":"PEM-encoded certificate body. Required when `provider=custom`.","example":"-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----"},"private_key":{"type":"string","description":"PEM-encoded private key. Required when `provider=custom`.","example":"-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"},"force":{"type":"boolean","default":false,"description":"When the site already has an SSL provider configured, `force: true` is required to switch to a different provider. Ignored when no existing provider is set or the requested provider matches.","example":false},"ssl_search_replace":{"type":"boolean","default":false,"description":"WordPress only \u2014 when true, the site's HTTPS rollout will include a DB search-replace from `http:\/\/` to `https:\/\/`. Silently ignored on non-WordPress sites.","example":false}}},"CreateSslCertificateResponse":{"type":"object","properties":{"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"11aa22bb-33cc-44dd-55ee-66ff77aa88bb"},"name":{"type":"string","example":"example.com"}}},"certificate_uuid":{"type":"string","format":"uuid","nullable":true,"description":"UUID of the new certificate row. Populated for `xcloud` and `custom` providers (row created synchronously). Null for `cloudflare`, where the row is created by the background job after the Cloudflare API call returns; clients can fetch the new uuid via `GET \/sites\/{uuid}\/ssl-certificates` after a short poll.","example":"5d5a3c5e-3c5e-4a5e-9d5a-3c5e3c5e3c5e"},"provider":{"type":"string","enum":["xcloud","custom","cloudflare"],"description":"Echoes the provider requested.","example":"xcloud"},"previous_provider":{"type":"string","nullable":true,"description":"The site's previously configured SSL provider, or null if none. Useful when switching providers with `force: true`.","example":"cloudflare"},"status":{"type":"string","enum":["queued"],"example":"queued"}}},"DomainInfo":{"type":"object","properties":{"primary":{"type":"string","example":"example.com"},"additional_domains":{"type":"array","items":{"type":"string"},"example":["www.example.com","shop.example.com"]},"parking_method":{"type":"string","enum":["redirect","alias"],"nullable":true,"example":"redirect"}}},"SiteDomains":{"type":"object","properties":{"primary":{"type":"string","example":"example.com"},"aliases":{"type":"array","description":"Non-redirect additional domains served by this site.","items":{"type":"string"},"example":["www.example.com","shop.example.com"]},"redirects":{"type":"array","description":"Additional domains configured to redirect to the primary domain.","items":{"type":"string"},"example":["old.example.com"]},"counts":{"type":"object","properties":{"aliases":{"type":"integer","example":2},"redirects":{"type":"integer","example":1},"total":{"type":"integer","example":3}}}}},"DomainUpdateStatus":{"type":"object","properties":{"status":{"type":"string","enum":["idle","updating","success","failed"],"description":"Current state of the most recent domain change.","example":"updating"},"label":{"type":"string","description":"Human-readable status label.","example":"Updating"},"is_updating":{"type":"boolean","description":"True when a domain change is currently in progress.","example":true},"old_domain":{"type":"string","nullable":true,"example":"old.example.com"},"new_domain":{"type":"string","nullable":true,"example":"new.example.com"},"message":{"type":"string","nullable":true,"description":"Outcome message set when the update completed or failed.","example":null},"started_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-17T10:00:00Z"}}},"Backup":{"type":"object","description":"One row from `backup_files`, projected by App\\Http\\Resources\\PublicAPI\\V1\\BackupResource. This is the complete set of keys the endpoint returns \u2014 no internal column reaches the wire.\n","additionalProperties":false,"required":["uuid","file_name","file_size","type","status","is_remote","date","created_at"],"properties":{"uuid":{"type":"string","format":"uuid","nullable":true,"description":"Stable public identity. Null on legacy rows created before UUIDs.","example":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"file_name":{"type":"string","nullable":true,"example":"example.com-2025-03-14.tar.gz"},"file_size":{"type":"string","nullable":true,"description":"Backup size in bytes, stored and returned as a string.","example":"524288000"},"type":{"type":"string","description":"Backup engine kind \u2014 the `BackupFile::*_BACKUP` constants.","enum":["full","incremental","incremental_full","docker"],"example":"full"},"status":{"type":"string","nullable":true,"description":"Raw `App\\Enums\\BackupStatus` value.","enum":["pending","running","processed","completed","failed","deleting"],"example":"completed"},"is_remote":{"type":"boolean","description":"True when the file lives on a remote storage provider rather than the server. BackupResource casts it, so it is never null.","example":false},"date":{"type":"string","nullable":true,"description":"Server-local backup timestamp as `Y-m-d H:i:s`. Not RFC 3339 \u2014 this column carries no timezone and is not date-cast, so it is documented as a plain string rather than `format: date-time`.\n","example":"2025-03-14 02:00:00"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-03-14T02:00:00.000000Z"}}},"DockerBackup":{"type":"object","description":"A Docker-app backup \u2014 a restic snapshot captured by the cold-stop + docker-inspect engine, not a downloadable file. There is no download endpoint in v1.\n","properties":{"uuid":{"type":"string","format":"uuid","example":"9b1c0e5a-7d3f-4a2b-8c1e-2f4a6b8c0d2e"},"snapshot_id":{"type":"string","nullable":true,"description":"Short restic snapshot handle. Null until the backup completes.","example":"cccc2222"},"status":{"type":"string","enum":["running","completed","failed"],"example":"completed"},"is_remote":{"type":"boolean","example":false},"storage_provider":{"type":"object","nullable":true,"properties":{"uuid":{"type":"string","format":"uuid"},"provider":{"type":"string","example":"digital_ocean"}}},"note":{"type":"string","nullable":true,"description":"Engine-set note, present on a failure (for example `spawn_failed`).","example":null},"user_note":{"type":"string","nullable":true,"description":"Free-text label set by a user to make a restore candidate recognisable.","example":"before 2.1 upgrade"},"size_kb":{"type":"number","nullable":true,"description":"Restore size of the snapshot in kilobytes. Null until the backup completes.","example":20480},"date":{"type":"string","format":"date-time","nullable":true,"example":"2026-03-14T02:00:00Z"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-03-14T02:00:05Z"}}},"DockerBackupSetting":{"type":"object","properties":{"is_local":{"type":"boolean","example":true},"auto_backup":{"type":"boolean","example":true},"auto_backup_frequency":{"type":"string","enum":["daily","weekly","monthly"],"example":"weekly"},"delete_after_days":{"type":"integer","nullable":true,"example":14},"storage_provider":{"type":"object","nullable":true,"properties":{"uuid":{"type":"string","format":"uuid"},"provider":{"type":"string"},"status":{"type":"string"}}}}},"PagespeedScanStatus":{"type":"object","required":["scan_uuid","status","is_terminal","strategies"],"properties":{"scan_uuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","running","completed","failed","unknown"]},"is_terminal":{"type":"boolean"},"strategies":{"type":"object","required":["mobile","desktop"],"properties":{"mobile":{"$ref":"#\/components\/schemas\/PagespeedScanStrategyStatus"},"desktop":{"$ref":"#\/components\/schemas\/PagespeedScanStrategyStatus"}}}}},"PagespeedScanStrategyStatus":{"type":"object","required":["status","updated_at"],"properties":{"status":{"type":"string","enum":["pending","scanning","completed","failed","unknown"]},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"SiteEvent":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"name":{"type":"string","description":"The deploy step this event belongs to. Use it to attribute a failure to its own output; pair it with `failed_steps` from the site or status endpoint.\n","example":"Clone Git Repository"},"type":{"type":"string","example":"ssl_issued"},"status":{"type":"string","enum":["pending","queued","running","finished","timeout","failed","killed"],"example":"finished"},"output":{"type":"string","nullable":true,"description":"Last captured task output, truncated to 500 characters. When `output_truncated` is true, fetch the whole thing from `GET \/sites\/{uuid}\/events\/{task_uuid}`.\n","example":"SSL certificate issued for example.com"},"output_truncated":{"type":"boolean","description":"True when `output` was cut short and the full text is available from the single-event endpoint.","example":false},"user":{"type":"string","nullable":true,"description":"OS user the task ran as on the server (null for system tasks).","example":"xcloud_example"},"created_at":{"type":"string","format":"date-time","example":"2025-03-14T10:15:00Z"}}},"SiteEventDetail":{"type":"object","description":"One deploy step, with a bounded window of its output.","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The deploy step this event belongs to.","example":"Clone Git Repository"},"type":{"type":"string","example":"site_migration"},"status":{"type":"string","enum":["pending","queued","running","finished","timeout","failed","killed"],"example":"failed"},"exit_code":{"type":"integer","nullable":true,"example":128},"output":{"type":"string","nullable":true,"description":"The requested window of the step's captured output, stdout and stderr, with credential-shaped values redacted. Defaults to the END of the output \u2014 see `offset`. Use the `output_*` fields to tell whether more remains.\n"},"output_total_bytes":{"type":"integer","description":"Total size of the step's output, in bytes, before windowing.","example":184320},"output_offset":{"type":"integer","description":"Byte offset this window starts at.","example":164320},"output_returned_bytes":{"type":"integer","description":"Size of the window in `output`, in bytes.","example":20000},"output_complete":{"type":"boolean","description":"True when `output` holds the entire output and no paging is needed.","example":false},"next_offset":{"type":"integer","nullable":true,"description":"Pass as `offset` to fetch the next window, or null when this window reaches the end of the output.\n","example":184320},"created_at":{"type":"string","format":"date-time"}}},"SiteEventList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SiteEvent"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"CachePurgeTaskUuids":{"type":"object","description":"The Task `uuid` queued for each cache from this request; pass one to `GET \/sites\/{uuid}\/events\/{task_uuid}` to poll for its outcome. `null` only for a cache that was `skipped` in `data.caches`.\n","properties":{"object_cache":{"type":"string","format":"uuid","nullable":true,"example":"9b1f2a3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"cloudflare_edge":{"type":"string","format":"uuid","nullable":true,"example":null},"redis_object_cache":{"type":"string","format":"uuid","nullable":true,"example":"1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f"},"object_cache_pro":{"type":"string","format":"uuid","nullable":true,"example":null}}},"SiteMonitoringSample":{"type":"object","required":["cpu_usage","ram_usage","disk_usage","time_at","sampled_at"],"properties":{"cpu_usage":{"type":"number"},"ram_usage":{"type":"number"},"disk_usage":{"type":"number"},"time_at":{"type":"string","description":"Display clock time only; use sampled_at for freshness and ordering."},"sampled_at":{"type":"string","format":"date-time","nullable":true,"description":"Original monitor creation timestamp, never the response time."}}},"DeploymentLog":{"type":"object","required":["uuid","status","action","source","destination","initiated_by","created_at","updated_at"],"properties":{"uuid":{"type":"string","format":"uuid","nullable":true,"description":"Stable public identity. Legacy rows may be null during a rolling deployment."},"status":{"type":"string","enum":["pending","processing","pull","pulled","push","pushed","success","failed"],"example":"success"},"action":{"type":"string","nullable":true,"example":"push"},"source":{"type":"string","nullable":true,"example":"staging.example.com"},"destination":{"type":"string","nullable":true,"example":"example.com"},"initiated_by":{"type":"string","nullable":true,"example":"Jane Smith"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"DeployConfig":{"type":"object","description":"A git site's deploy settings as they are stored. The env file BODY has no representation here on purpose \u2014 it holds the site's secrets.\n","properties":{"uuid":{"type":"string","format":"uuid"},"site_type":{"type":"string","example":"nodejs"},"deploy_target":{"type":"string","description":"Which pipeline deploys this site.","enum":["native","docker"],"example":"native"},"git_branch":{"type":"string","nullable":true,"example":"main"},"port":{"type":"integer","nullable":true,"description":"Host port the site's process (native) or container (docker) is published on.","example":3000},"env_file_path":{"type":"string","nullable":true,"description":"Where the env file lives, relative to the site root \u2014 the stored override when there is one (a DIRECTORY, which is what `env_file_path` sets), otherwise the path the deploy actually writes for this site type. Null only for a site type whose deploy writes no env file.\n","example":"app"},"env_file_configured":{"type":"boolean","description":"Whether this site has an environment file \u2014 one whose body was supplied, or one the deploy writes for this site type (Laravel and Node sites always get a `.env`: seeded from the repository's `.env.example` when it ships one, then given the platform-managed variables). The body itself is never returned.\n"},"deploy_script_configured":{"type":"boolean","description":"Whether a post-pull deploy script is stored. The script BODY is never returned by any endpoint \u2014 it is arbitrary shell that routinely exports credentials.\n"},"deploy_script_size_bytes":{"type":"integer"},"run_after_deployment":{"type":"boolean","description":"Whether the deploy script runs after each pull."},"deploy_script_fail_fast":{"type":"boolean","description":"Whether the deploy script aborts on its first failing command (bash `set -e` semantics; failures inside shell control flow do not abort). False for every site created before this setting existed \u2014 their scripts were written against a shell that ran to the end whatever happened.\n"},"serving_mode":{"type":"string","nullable":true,"description":"Node sites only; null for every other type.","enum":["static","ssr","hybrid"]},"install_command":{"type":"string","nullable":true,"description":"Node sites only. Null means the platform default."},"build_command":{"type":"string","nullable":true},"start_command":{"type":"string","nullable":true},"web_root":{"type":"string","nullable":true,"description":"Build output directory served by the web server (static\/hybrid)."},"docker":{"type":"object","nullable":true,"description":"Container sites only; null for a native site.","properties":{"mode":{"type":"string","enum":["compose","dockerfile"]},"compose_file":{"type":"string","nullable":true},"dockerfile_path":{"type":"string","nullable":true},"container_port":{"type":"integer","nullable":true},"build_target":{"type":"string","nullable":true},"port_mappings":{"type":"object","nullable":true,"additionalProperties":{"type":"integer"}},"allowed_dot_paths":{"type":"array","description":"The dot-prefixed URL paths the vhost lets through (see `DockerAllowedDotPaths`); empty when none are allowed.","items":{"type":"string"}}}},"server_node_version":{"type":"string","nullable":true},"correctable_fields":{"type":"array","description":"The settings this site type accepts on deploy-config.update and provision-retry.","items":{"type":"string","enum":["git_branch","serving_mode","install_command","build_command","start_command","port","web_root","env_file_content","env_file_path","deploy_script","docker.mode","docker.compose_file","docker.dockerfile_path","docker.container_port","docker.build_target","docker.allowed_dot_paths"]}}}},"DeploySettings":{"description":"Everything `PUT \/sites\/{uuid}\/deploy-config` accepts: the correctable settings, plus the settings that are not corrections. A correction answers \"this is what broke the deploy\", so a switch that only decides how a failure is REPORTED does not belong in `corrections` \u2014 an agent offered it there clears a failed deploy by turning the safety off. It is a normal setting here.\n","allOf":[{"$ref":"#\/components\/schemas\/DeployCorrections"},{"type":"object","properties":{"deploy_script_fail_fast":{"type":"boolean","description":"Abort the deploy script at its first failing command (bash `set -e` semantics: a failure inside an `if` condition or a non-final `&&`\/`||` operand does not abort) instead of running on to the end and reporting the status of its last line. A script whose own first line is a `set` keeps its own options. Takes effect on the next deploy.\n","example":true}}}]},"DeployCorrections":{"type":"object","description":"Deploy settings to change. Send only the fields you are changing; the accepted set depends on the site type \u2014 read `correctable_fields` from `GET \/sites\/{uuid}\/deploy-config`. Naming a field this site does not accept, or one of the permanently fixed settings (`site_type`, `repository`, `domain`, `database`), is refused with 422 rather than silently ignored. The properties below are the whole accepted set; anything else is refused.\n\nNo `additionalProperties: false` here: this schema is also an `allOf` branch of `DeploySettings`, and each branch of an `allOf` is validated against the WHOLE object, so closing it made a conforming validator reject `DeploySettings`' own `deploy_script_fail_fast` \u2014 a field the endpoint accepts.\n","properties":{"git_branch":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._\/-]+$","description":"Tracked branch. Changing it re-clones the repository.","example":"main"},"serving_mode":{"type":"string","description":"Node sites only. static serves the build output; ssr proxies everything to the process; hybrid does both.","enum":["static","ssr","hybrid"],"example":"ssr"},"install_command":{"type":"string","maxLength":255,"nullable":true,"description":"Node sites only. Null or empty restores the platform default.","example":"npm ci"},"build_command":{"type":"string","maxLength":255,"nullable":true,"example":"npm run build"},"start_command":{"type":"string","maxLength":255,"nullable":true,"description":"Node ssr\/hybrid sites only.","example":"node .output\/server\/index.mjs"},"port":{"type":"integer","minimum":1,"maximum":65535,"description":"Host port. Reserved against the other sites on the server and probed on the host before it is stored. Container sites take a minimum of 1024.\n","example":3001},"web_root":{"type":"string","maxLength":255,"nullable":true,"description":"Node static\/hybrid sites only \u2014 the build output directory, relative to the site root, with no leading or trailing slash.","example":"dist"},"env_file_content":{"type":"string","nullable":true,"description":"The complete environment file body. Write-only \u2014 it is never returned by any endpoint. Replaces the stored body in full. On a DEPLOYED site this is written to disk and the site's process restarted immediately (`env_pushed: true` in the response) \u2014 unlike every other field here, it does not wait for a redeploy.\n","example":"NODE_ENV=production\nDATABASE_URL=postgres:\/\/\u2026"},"env_file_path":{"type":"string","maxLength":255,"nullable":true,"description":"Native sites only \u2014 the site-relative DIRECTORY for the .env file, with no leading or trailing slash. A container site's env is written from `env_file_content` into the compose project, so sending this for one is refused with 422.\n","example":"app"},"deploy_script":{"type":"string","maxLength":5000,"nullable":true,"description":"Post-pull deploy script. Setting one turns on run-after-deployment; clearing it turns it off."},"docker":{"type":"object","description":"Container sites only.","additionalProperties":false,"properties":{"mode":{"type":"string","description":"compose runs the repository's own compose file; dockerfile builds the image and lets xCloud synthesize the compose.","enum":["compose","dockerfile"]},"compose_file":{"type":"string","maxLength":255,"description":"Path to the compose file, relative to the repository root (compose mode).","example":"deploy\/docker-compose.prod.yml"},"dockerfile_path":{"type":"string","maxLength":255,"description":"Path to the Dockerfile, relative to the repository root (dockerfile mode).","example":"Dockerfile"},"container_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port the container listens on (dockerfile mode).","example":3000},"build_target":{"type":"string","maxLength":128,"description":"Multi-stage Dockerfile target to build.","example":"production"},"allowed_dot_paths":{"type":"array","maxItems":10,"nullable":true,"description":"Replaces the stored list in full; an empty list clears it. Read by the nginx vhost only, so on a DEPLOYED site `PUT \/deploy-config` regenerates the vhost (`regenerating_vhost: true`) without touching the containers \u2014 no redeploy needed; sending it again, unchanged, regenerates again (the retry when a regeneration failed). On a failed site the retry's install writes the vhost itself. Entry rules and the refused secret-bearing names: see `DockerAllowedDotPaths`.\n","items":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]{1,64}(\/[A-Za-z0-9._-]{1,64})*\/?$"},"example":["node_modules\/.vite"]}}}}},"GitInfo":{"type":"object","properties":{"repository":{"type":"string","example":"github.com\/myorg\/my-wp-theme"},"branch":{"type":"string","example":"main"},"provider":{"type":"string","enum":["github","gitlab","bitbucket","custom"],"example":"github"},"push_deploy_enabled":{"type":"boolean","example":true}}},"PaginatedSudoUsers":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SudoUser"}},"pagination":{"$ref":"#\/components\/schemas\/PaginationMeta"}}},"SudoUser":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"c3d4e5f6-a7b8-9012-cdef-234567890123"},"username":{"type":"string","example":"deploy"},"status":{"type":"string","enum":["active","updating","deleting"],"example":"active"},"is_temporary":{"type":"boolean","example":false},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":null},"created_at":{"type":"string","format":"date-time","example":"2025-06-01T12:00:00Z"}}},"CreateSudoUserRequest":{"type":"object","required":["username","ssh_public_keys"],"properties":{"username":{"type":"string","maxLength":64,"description":"Username for the sudo user. Cannot be \"xcloud\".\n","example":"deploy"},"password":{"type":"string","minLength":6,"maxLength":64,"description":"Password for the sudo user. Required for non-root users.\n","example":"S3cur3P@ss!"},"ssh_public_keys":{"type":"array","minItems":1,"items":{"type":"string"},"description":"One or more SSH public keys to authorize for this user.\n","example":["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample deploy@workstation"]},"is_temporary":{"type":"boolean","default":false,"description":"Whether the sudo user is temporary. Temporary users may be automatically removed after expiry.\n","example":false}}},"SshKey":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"e5f6a7b8-c9d0-1234-efab-567890123456"},"name":{"type":"string","example":"deploy-key"},"type":{"type":"string","enum":["system_default","team_key","server_default","site_git"],"example":"site_git"},"public_key":{"type":"string","example":"ssh-rsa AAAAB3Nza...example deploy-key"},"fingerprint":{"type":"string","example":"SHA256:AbCdEf1234567890exampleFingerprint"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-12-10T00:00:00Z"}}},"SshKeyList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/SshKey"}},"count":{"type":"integer","example":1}}},"SshConfig":{"type":"object","properties":{"site_user":{"type":"string","example":"xcloud_example"},"authentication_mode":{"type":"string","enum":["public_key","password"],"example":"public_key"},"ssh_keypairs":{"type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"e5f6a7b8-c9d0-1234-efab-567890123456"},"name":{"type":"string","example":"deploy-key"},"fingerprint":{"type":"string","example":"SHA256:AbCdEf1234567890exampleFingerprint"}}}}}},"UpdateSshRequest":{"type":"object","required":["authentication_mode"],"properties":{"authentication_mode":{"type":"string","enum":["public_key","password"],"description":"The authentication mode to use for SSH\/SFTP access.\n","example":"public_key"},"ssh_public_keys":{"type":"array","items":{"type":"string"},"description":"SSH public keys to authorize. Required when `authentication_mode` is `public_key`.\n","example":["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample deploy@workstation"]},"password":{"type":"string","description":"Password for SSH\/SFTP access. Required when `authentication_mode` is `password`.\n","example":"Str0ngP@ssw0rd!"}}},"Blueprint":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","description":"Unique identifier for the blueprint. Use as `blueprint_uuid` when creating sites.","example":"b1c2d3e4-f5a6-7890-bcde-f12345678901"},"name":{"type":"string","description":"Blueprint name.","example":"Default WordPress"},"is_default":{"type":"boolean","description":"Whether this is the team's default blueprint.","example":true},"is_public":{"type":"boolean","description":"Whether this blueprint is publicly available to all teams.","example":false},"created_at":{"type":"string","format":"date-time","example":"2025-06-01T12:00:00Z"}}},"WordPressItem":{"type":"object","properties":{"type":{"type":"string","enum":["plugin","theme","core"],"example":"plugin"},"slug":{"type":"string","description":"Stable identifier for the item within a site.","example":"woocommerce"},"name":{"type":"string","example":"WooCommerce"},"current_version":{"type":"string","nullable":true,"example":"8.5.2"},"available_version":{"type":"string","nullable":true,"example":"8.6.0"},"update_available":{"type":"boolean","example":true},"status":{"type":"string","enum":["active","inactive","not_applicable","unknown","must-use","dropin"],"example":"active"},"update_status":{"type":"string","enum":["available","updating","updated","failed","ignored","none"],"example":"available"},"is_must_use":{"type":"boolean","example":false},"is_dropin":{"type":"boolean","example":false},"last_checked_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T12:00:00Z"}}},"WordPressItemSummary":{"type":"object","properties":{"total":{"type":"integer","example":23},"active":{"type":"integer","example":18},"with_updates":{"type":"integer","example":5}}},"WordPressUpdatesSummary":{"type":"object","properties":{"site_uuid":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"},"core":{"type":"object","properties":{"current_version":{"type":"string","nullable":true,"example":"6.4.2"},"available_version":{"type":"string","nullable":true,"example":"6.5.0"},"update_available":{"type":"boolean","example":true},"is_security_update":{"type":"boolean","example":false}}},"plugins":{"$ref":"#\/components\/schemas\/WordPressUpdatesGroup"},"themes":{"$ref":"#\/components\/schemas\/WordPressUpdatesGroup"},"summary":{"type":"object","properties":{"total_pending":{"type":"integer","example":7},"security_pending":{"type":"integer","example":1},"last_scanned_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T12:00:00Z"}}}}},"WordPressUpdatesGroup":{"type":"object","properties":{"total":{"type":"integer","example":23},"active":{"type":"integer","example":18},"with_updates":{"type":"integer","example":5},"with_security_updates":{"type":"integer","example":1},"items":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string","example":"woocommerce"},"name":{"type":"string","example":"WooCommerce"},"current_version":{"type":"string","nullable":true,"example":"8.5.2"},"available_version":{"type":"string","nullable":true,"example":"8.6.0"},"is_security_update":{"type":"boolean","example":true}}}}}},"WordPressStatus":{"type":"object","properties":{"wordpress_version":{"type":"string","nullable":true,"example":"6.4.2"},"php_version":{"type":"string","nullable":true,"example":"8.2"},"multisite_enabled":{"type":"boolean","example":false},"wp_debug_enabled":{"type":"boolean","example":false},"wp_cron_enabled":{"type":"boolean","example":true},"checksum_status":{"type":"string","nullable":true,"example":"passed"},"last_checksum_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:00:00Z"},"items_count":{"type":"object","properties":{"plugins":{"type":"integer","example":23},"themes":{"type":"integer","example":3},"core":{"type":"integer","example":1}}},"updates_pending":{"type":"object","properties":{"core":{"type":"boolean","example":false},"plugins":{"type":"integer","example":5},"themes":{"type":"integer","example":1}}},"ssl":{"type":"object","properties":{"enabled":{"type":"boolean","example":true},"provider":{"type":"string","nullable":true,"example":"xcloud"},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-08-12T00:00:00Z"}}}}},"Vulnerability":{"type":"object","required":["uuid","slug","title","type","severity","source","ignored"],"properties":{"uuid":{"type":"string","description":"Stable identifier. For Wordfence entries this is the Wordfence advisory UUID. For Patchstack entries this is a synthetic xCloud-generated identifier (`patchstack-<slug>-<id>`).\n","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"slug":{"type":"string","example":"woocommerce"},"title":{"type":"string","example":"WooCommerce <= 8.4 Reflected XSS"},"type":{"type":"string","enum":["plugin","theme","core","unknown"],"example":"plugin"},"severity":{"type":"string","enum":["critical","high","medium","low","unknown"],"example":"high"},"cvss":{"type":"number","format":"float","nullable":true,"example":7.5},"current_version":{"type":"string","nullable":true,"example":"8.3.1"},"affected_versions":{"type":"array","nullable":true,"items":{"type":"string"},"example":["<8.5.0"]},"patched_versions":{"type":"array","nullable":true,"items":{"type":"string"},"example":[">=8.5.0"]},"remediation":{"type":"string","nullable":true,"example":"Update WooCommerce to 8.5.0 or later."},"cwe":{"type":"string","nullable":true,"example":"CWE-79"},"source":{"type":"string","enum":["patchstack","wordfence"],"example":"wordfence"},"ignored":{"type":"boolean","example":false},"detected_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-04T10:32:11Z"}}},"VulnerabilitySiteContext":{"type":"object","required":["uuid","name"],"properties":{"uuid":{"type":"string","format":"uuid","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"},"name":{"type":"string","example":"example-com"}}},"VulnerabilitiesSummary":{"type":"object","required":["total","by_severity","by_source"],"properties":{"total":{"type":"integer","example":12},"by_severity":{"type":"object","properties":{"critical":{"type":"integer","example":2},"high":{"type":"integer","example":4},"medium":{"type":"integer","example":5},"low":{"type":"integer","example":1},"unknown":{"type":"integer","example":0}}},"by_source":{"type":"object","properties":{"patchstack":{"type":"integer","example":3},"wordfence":{"type":"integer","example":9}}}}},"VulnerabilityCount":{"type":"object","description":"Flat severity counts for the site from a single source (Patchstack preferred over Wordfence, only `insecure` detections), with severity derived from the vulnerability score. `total` is the sum of critical\/high\/medium\/low; `skipped` is the count of ignored detections and is reported separately (not included in `total`).\n","required":["critical","high","medium","low","skipped","total"],"properties":{"critical":{"type":"integer","example":0},"high":{"type":"integer","example":2},"medium":{"type":"integer","example":4},"low":{"type":"integer","example":0},"skipped":{"type":"integer","example":0},"total":{"type":"integer","example":6}}},"VulnerabilityIgnoreResult":{"type":"object","required":["vulnerability","site"],"properties":{"vulnerability":{"type":"object","required":["uuid","source","ignored"],"properties":{"uuid":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"source":{"type":"string","enum":["wordfence","patchstack"],"example":"wordfence"},"slug":{"type":"string","nullable":true,"example":"woocommerce"},"ignored":{"type":"boolean","example":true}}},"site":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}},"NullablePagespeedRun":{"type":"object","nullable":true,"description":"A completed PageSpeed run, or null when this strategy has no completed run.","allOf":[{"$ref":"#\/components\/schemas\/PagespeedRunData"}]},"PagespeedRun":{"type":"object","allOf":[{"$ref":"#\/components\/schemas\/PagespeedRunData"}]},"PagespeedRunData":{"required":["uuid","strategy","status"],"properties":{"uuid":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"strategy":{"type":"string","enum":["mobile","desktop"],"example":"mobile"},"status":{"type":"string","enum":["pending","scanning","completed","failed"],"example":"completed"},"scores":{"type":"object","description":"Lighthouse category scores (0\u2013100).","properties":{"performance":{"type":"integer","example":92},"accessibility":{"type":"integer","example":88},"best_practices":{"type":"integer","example":95},"seo":{"type":"integer","example":100}}},"core_web_vitals":{"type":"object","description":"Core Web Vitals \u2014 LCP, CLS, INP, FCP.","properties":{"lcp":{"type":"object","properties":{"value":{"type":"number","example":2.1},"unit":{"type":"string","example":"s"},"rating":{"type":"string","enum":["good","needs-improvement","poor"],"example":"good"}}},"cls":{"type":"object","properties":{"value":{"type":"number","example":0.05},"unit":{"type":"string","example":""},"rating":{"type":"string","enum":["good","needs-improvement","poor"],"example":"good"}}},"inp":{"type":"object","properties":{"value":{"type":"number","example":150},"unit":{"type":"string","example":"ms"},"rating":{"type":"string","enum":["good","needs-improvement","poor"],"example":"good"}}},"fcp":{"type":"object","properties":{"value":{"type":"number","example":1.2},"unit":{"type":"string","example":"s"},"rating":{"type":"string","enum":["good","needs-improvement","poor"],"example":"good"}}}}},"insights":{"type":"array","items":{"type":"object"}},"diagnostics":{"type":"array","items":{"type":"object"}},"analyzed_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:00:00Z"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:00:30Z"}}},"BrokenLinkScanEvidence":{"type":"object","description":"Exact invocation status and bounded scan coverage, without callback credentials.","required":["uuid","status","is_terminal","failure_reason","started_at","finished_at","last_heartbeat_at","pages_scanned","links_checked","findings_count","truncated_findings","truncated_queue","truncated_memory"],"properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","completed","failed","cancelled"]},"is_terminal":{"type":"boolean"},"failure_reason":{"type":"string","nullable":true},"started_at":{"type":"string","format":"date-time","nullable":true},"finished_at":{"type":"string","format":"date-time","nullable":true},"last_heartbeat_at":{"type":"string","format":"date-time","nullable":true},"pages_scanned":{"type":"integer","minimum":0},"links_checked":{"type":"integer","minimum":0},"findings_count":{"type":"integer","minimum":0},"truncated_findings":{"type":"boolean"},"truncated_queue":{"type":"boolean"},"truncated_memory":{"type":"boolean"}}},"BrokenLinkScanRun":{"type":"object","nullable":true,"description":"The most recent scan run, or `null` if a scan has never been started.","properties":{"uuid":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"status":{"type":"string","enum":["queued","running","completed","failed","cancelled"],"example":"completed"},"failure_reason":{"type":"string","nullable":true,"example":null},"started_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:00:00Z"},"finished_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:12Z"},"last_heartbeat_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:12Z"}}},"BrokenLinkFindingSummary":{"type":"object","description":"A broken link finding as returned inside `findings[]` on the scan status endpoints (`GET \/sites\/{uuid}\/broken-links` and `POST \/sites\/{uuid}\/broken-links\/scan`). Omits `last_checked_at` \u2014 see `BrokenLinkFinding` for the full single-finding shape.\n","properties":{"uuid":{"type":"string","format":"uuid","example":"9d6e8f9b-2345-4bcd-8e0f-2345678901bc"},"source_url":{"type":"string","example":"https:\/\/example.com\/blog\/hello-world"},"source_title":{"type":"string","nullable":true,"example":"Hello World"},"destination_url":{"type":"string","nullable":true,"example":"https:\/\/example.com\/old-page"},"final_url":{"type":"string","nullable":true,"example":null},"occurrence_type":{"type":"string","enum":["link","image","stylesheet","script","source","page"],"example":"link"},"finding_type":{"type":"string","enum":["broken_404","broken_410","redirect","redirect_chain","timeout","dns_error","ssl_error","blocked_403","blocked_429","blocked_waf","server_error_5xx","soft_404"],"example":"broken_404"},"severity":{"type":"string","enum":["critical","warning","info"],"example":"critical"},"http_status":{"type":"integer","nullable":true,"example":404},"first_detected_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:00:00Z"},"last_seen_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:00Z"},"ignored":{"type":"boolean","example":false}}},"BrokenLinkFinding":{"type":"object","description":"A single broken link finding, as returned by `GET \/sites\/{uuid}\/broken-links\/{brokenLinkFindingUuid}`. Includes `last_checked_at`, which is omitted from the summary shape used in `findings[]`.\n","properties":{"uuid":{"type":"string","format":"uuid","example":"9d6e8f9b-2345-4bcd-8e0f-2345678901bc"},"source_url":{"type":"string","example":"https:\/\/example.com\/blog\/hello-world"},"source_title":{"type":"string","nullable":true,"example":"Hello World"},"destination_url":{"type":"string","nullable":true,"example":"https:\/\/example.com\/old-page"},"final_url":{"type":"string","nullable":true,"example":null},"occurrence_type":{"type":"string","enum":["link","image","stylesheet","script","source","page"],"example":"link"},"finding_type":{"type":"string","enum":["broken_404","broken_410","redirect","redirect_chain","timeout","dns_error","ssl_error","blocked_403","blocked_429","blocked_waf","server_error_5xx","soft_404"],"example":"broken_404"},"severity":{"type":"string","enum":["critical","warning","info"],"example":"critical"},"http_status":{"type":"integer","nullable":true,"example":404},"first_detected_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:00:00Z"},"last_seen_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:00Z"},"last_checked_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:00Z"},"ignored":{"type":"boolean","example":false}}},"BrokenLinkScanStatus":{"type":"object","required":["status","enabled"],"description":"Aggregate broken link scan status for a site, returned by both `GET \/sites\/{uuid}\/broken-links` and `POST \/sites\/{uuid}\/broken-links\/scan`. Count fields (`findings_count`, `broken_links_count`, `broken_images_count`, `unverified_count`, `ignored_count`) are always computed across ALL open findings for the site, never just the current page. `findings` and `findings_pagination` are present only on the GET response \u2014 the POST response omits them since nothing has been found by the newly-triggered scan yet.\n","properties":{"status":{"type":"string","enum":["idle","queued","running","completed","failed","cancelled"],"example":"completed"},"enabled":{"type":"boolean","example":true},"frequency":{"type":"string","enum":["daily","weekly","manual"],"nullable":true,"example":"weekly"},"last_scan_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-06T08:04:12Z"},"last_scan_failed_reason":{"type":"string","nullable":true,"example":null},"pages_scanned":{"type":"integer","example":42},"links_checked":{"type":"integer","example":318},"findings_count":{"type":"integer","example":1},"broken_links_count":{"type":"integer","example":1},"broken_images_count":{"type":"integer","example":0},"unverified_count":{"type":"integer","example":0},"ignored_count":{"type":"integer","example":0},"findings":{"type":"array","description":"Present only on the GET response.","items":{"$ref":"#\/components\/schemas\/BrokenLinkFindingSummary"}},"findings_pagination":{"type":"object","description":"Present only on the GET response.","properties":{"total":{"type":"integer","example":1},"per_page":{"type":"integer","example":25},"current_page":{"type":"integer","example":1},"last_page":{"type":"integer","example":1}}},"run":{"$ref":"#\/components\/schemas\/BrokenLinkScanRun"}}},"WordPressUpdateRequest":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["plugin","theme","core"],"example":"plugin"},"slugs":{"type":"array","description":"Optional list of `item_name` values to update. If omitted, every updatable item of the requested type is queued.\n","items":{"type":"string"},"example":["woocommerce","yoast-seo"]},"backup_before_update":{"type":"boolean","default":false,"description":"When true and the site has remote (cloud) backup settings configured, a pre-update backup is taken before the update runs.\n"}}},"WordPressUpdateOperation":{"type":"object","required":["operation","queued_items","skipped_items","backup_before_update"],"properties":{"operation":{"type":"object","required":["uuid","status","operation_type"],"properties":{"uuid":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"status":{"type":"string","enum":["queued","running","completed","failed"],"example":"queued"},"operation_type":{"type":"string","example":"update"}}},"queued_items":{"type":"array","items":{"type":"object","required":["slug","type"],"properties":{"slug":{"type":"string","example":"woocommerce"},"title":{"type":"string","example":"WooCommerce"},"type":{"type":"string","enum":["plugin","theme","core"],"example":"plugin"},"current_version":{"type":"string","nullable":true,"example":"8.3.1"},"available_version":{"type":"string","nullable":true,"example":"8.5.0"}}}},"skipped_items":{"type":"array","items":{"type":"object","required":["slug","reason"],"properties":{"slug":{"type":"string","example":"yoast-seo"},"reason":{"type":"string","enum":["no_update_available","already_updating"],"example":"no_update_available"}}}},"backup_before_update":{"type":"boolean","example":false}}},"WordPressActivateRequest":{"type":"object","required":["type","slugs"],"properties":{"type":{"type":"string","enum":["plugin","theme"],"example":"plugin"},"slugs":{"type":"array","minItems":1,"description":"List of `item_name` values to activate (required).","items":{"type":"string"},"example":["woocommerce","akismet"]},"backup_before_action":{"type":"boolean","default":false,"description":"When true and the site has backup settings configured, a pre-action backup is taken before the activation runs.\n"}}},"WordPressToggleOperation":{"type":"object","required":["operation","queued_items","skipped_items","backup_before_action"],"properties":{"operation":{"type":"object","required":["uuid","status","operation_type","action"],"properties":{"uuid":{"type":"string","format":"uuid","example":"8c5d7e8a-1234-4abc-9def-1234567890ab"},"status":{"type":"string","enum":["queued","running","completed","failed"],"example":"queued"},"operation_type":{"type":"string","example":"toggle"},"action":{"type":"string","enum":["activate","deactivate"],"example":"activate"}}},"queued_items":{"type":"array","items":{"type":"object","required":["slug","type"],"properties":{"slug":{"type":"string","example":"woocommerce"},"title":{"type":"string","example":"WooCommerce"},"type":{"type":"string","enum":["plugin","theme"],"example":"plugin"}}}},"skipped_items":{"type":"array","items":{"type":"object","required":["slug","reason"],"properties":{"slug":{"type":"string","example":"akismet"},"reason":{"type":"string","enum":["already_active","must_use_cannot_activate","dropin_cannot_activate","core_not_toggleable","cannot_activate"],"example":"already_active"}}}},"backup_before_action":{"type":"boolean","example":false}}},"WpDebugRequest":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Set to true to enable WP_DEBUG, false to disable.","example":true}}},"MagicLoginResult":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Short-lived URL that logs the WordPress admin in without a password. Open in a browser to land on \/wp-admin. Valid for 10 minutes from creation.\n","example":"https:\/\/example.com\/wp-admin\/wp-login.php?xcloud_magic_login_token=eyJ...&auth_token=Xa3p9q&v=1742212211"},"expires_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp at which the URL stops being valid.","example":"2026-05-20T14:50:00Z"},"admin_user":{"type":"string","description":"WordPress username that the URL will log in as. Equals the site's admin user unless `login_as` was passed.\n","example":"admin"}}},"ServerProvisioningProgressTask":{"type":"object","properties":{"id":{"type":"integer","description":"Ordinal step id within the provisioning sequence.","example":20},"label":{"type":"string","example":"Installing Web Server"},"status":{"type":"string","enum":["completed","in_progress","pending","failed"],"example":"in_progress"},"completed":{"type":"boolean","description":"Convenience flag; true only when status is `completed`.","example":false}}},"ServerProvisioningProgressStage":{"type":"object","properties":{"stage":{"type":"string","example":"Installing Software"},"tasks":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerProvisioningProgressTask"}}}},"ServerProvisioningProgress":{"type":"object","properties":{"status":{"type":"string","description":"The server's ServerStatus value.","example":"provisioning"},"status_readable":{"type":"string","example":"Provisioning"},"is_provisioned":{"type":"boolean","example":false},"is_failed":{"type":"boolean","example":false},"percent_complete":{"type":"integer","minimum":0,"maximum":100,"example":71},"current_step":{"type":"integer","example":20},"total_steps":{"type":"integer","example":28},"current_stage":{"type":"string","nullable":true,"example":"Installing Software"},"current_task":{"type":"string","nullable":true,"example":"Installing Web Server"},"error_message":{"type":"string","nullable":true,"description":"Present only when `is_failed` is true.","example":null},"stages":{"type":"array","items":{"$ref":"#\/components\/schemas\/ServerProvisioningProgressStage"}}}},"ServerPlan":{"type":"object","description":"A purchasable xCloud-managed (Vultr) server plan for the team, projected into an id-free public shape. Use `slug` as `size` and a region `id` as `region` when creating a server via `POST \/servers`.\n","properties":{"slug":{"type":"string","description":"Plan identifier. Pass as `size` when creating a server.","example":"vc2-1c-1gb"},"name":{"type":"string","example":"1 vCPU, 1 GB RAM"},"specs":{"type":"object","properties":{"vcpu":{"type":"integer","nullable":true,"example":1},"memory_mb":{"type":"integer","nullable":true,"example":1024},"disk_gb":{"type":"integer","nullable":true,"example":25},"bandwidth_mb":{"type":"integer","nullable":true,"example":2048}}},"pricing":{"type":"array","items":{"type":"object","properties":{"renewal_period":{"type":"string","enum":["monthly","yearly","two_yearly"],"example":"monthly"},"price":{"type":"number","format":"float","example":5},"currency":{"type":"string","example":"usd"}}}},"regions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","nullable":true,"description":"Region identifier. Pass as `region` when creating a server.","example":"ewr"},"city":{"type":"string","nullable":true,"example":"New Jersey"},"country":{"type":"string","nullable":true,"example":"US"},"continent":{"type":"string","nullable":true,"example":"North America"}}}}}},"PurchaseServerRequest":{"type":"object","required":["name","size","region"],"properties":{"name":{"type":"string","description":"Name for the new server.","example":"my-app-server"},"size":{"type":"string","description":"Plan slug from `GET \/servers\/plans`.","example":"vc2-1c-1gb"},"region":{"type":"string","description":"Region `id` offered by the selected plan (see `GET \/servers\/plans`).","example":"ewr"},"renewal_period":{"type":"string","enum":["monthly","yearly","two_yearly"],"default":"monthly","description":"Billing renewal period. Defaults to `monthly`.","example":"monthly"},"stack":{"type":"string","enum":["nginx","openlitespeed"],"default":"nginx","description":"Web server stack. Defaults to `nginx`.","example":"nginx"},"database_type":{"type":"string","nullable":true,"enum":["none","mysql","mariadb","mysql8","mysql84","mariadb10","mariadb11"],"description":"Database engine to install (case-insensitive). The friendly aliases `mysql` \u2192 MySQL 8.0 (`mysql8`) and `mariadb` \u2192 MariaDB 11 (`mariadb11`); or pass an explicit version. Omit or `none` for no database server.\n","example":"mysql"},"ubuntu_version":{"type":"string","nullable":true,"description":"Ubuntu OS version to provision.","example":"24.04"},"backups":{"type":"boolean","default":false,"description":"Whether to enable automated backups. Defaults to `false`.","example":false},"tags":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Optional list of tags to attach to the server.","example":["production","api"]}}},"MailboxPlan":{"type":"object","properties":{"slug":{"type":"string","description":"Provider-agnostic public plan identifier. Pass as `plan` when purchasing.","example":"mailbox_8gb"},"price":{"type":"number","format":"float","example":1},"storage":{"type":"string","example":"8GB"},"currency":{"type":"string","example":"usd"}}},"Mailbox":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","example":"d4e5f6a7-b8c9-0123-defa-456789012345"},"email":{"type":"string","format":"email","example":"hello@example.com"},"status":{"type":"string","enum":["new","pending_verification","verifying","verification_failed","payment_failed","active","inactive","failed"],"example":"active"},"plan":{"type":"string","description":"Public plan slug the mailbox was purchased on.","example":"mailbox_8gb"},"domain":{"type":"string","example":"example.com"},"dns_verified":{"type":"boolean","example":true},"records":{"type":"array","description":"DNS records for the domain, each with its current `verified` state. Returned at every status (not only while pending) so a client can display the required setup and which records have verified.\n","items":{"type":"object","properties":{"type":{"type":"string","example":"MX"},"name":{"type":"string","example":"example.com"},"value":{"type":"string","example":"mx.xcloud.host"},"verified":{"type":"boolean","example":true}}}},"webmail_url":{"type":"string","format":"uri","nullable":true,"description":"Webmail login URL. Populated once the mailbox is active.","example":"https:\/\/webmail.example.com"},"invoice_number":{"type":"string","nullable":true,"example":"XC-INV-20260712-1234"},"created_at":{"type":"string","format":"date-time","example":"2026-07-12T10:30:00Z"}}},"MailboxConnectionSettings":{"type":"object","description":"POP3\/IMAP incoming-mail connection settings. Provider-specific hosts (Open-Xchange `*.eu.appsuite.cloud`, qbox `*.xcloud.email`) with identical ports and encryption. Log in with the mailbox email.\n","properties":{"host":{"type":"string","example":"imap.eu.appsuite.cloud"},"port":{"type":"integer","example":993},"encryption":{"type":"string","example":"TLS"},"username":{"type":"string","format":"email","description":"Login username \u2014 always the mailbox email.","example":"hello@example.com"},"password":{"type":"string","description":"The mailbox login password (set at purchase), returned to the authorized owner.","example":"s3cret-p4ssw0rd"}}},"MailboxSmtpConnectionSettings":{"type":"object","description":"SMTP outgoing-mail connection settings. Provider-specific hosts (Open-Xchange `*.eu.appsuite.cloud`, qbox `*.xcloud.email`) with identical ports and encryption. Log in with the mailbox email. Includes the send limit and an upsell to xCloud Managed Email for higher volume.\n","properties":{"host":{"type":"string","example":"smtp.eu.appsuite.cloud"},"port":{"type":"integer","example":465},"encryption":{"type":"string","example":"SSL"},"starttls_port":{"type":"integer","description":"Alternative submission port using STARTTLS.","example":587},"username":{"type":"string","format":"email","description":"Login username \u2014 always the mailbox email.","example":"hello@example.com"},"password":{"type":"string","description":"The mailbox login password (set at purchase), returned to the authorized owner.","example":"s3cret-p4ssw0rd"},"send_limit":{"type":"object","properties":{"limit":{"type":"integer","example":250},"window":{"type":"string","example":"day"}}},"message":{"type":"string","example":"This SMTP configuration has a daily limit of sending 250 messages. To send higher volumes, use xCloud Managed Email (Mail Delivery)."},"managed_smtp":{"type":"object","description":"Upsell to xCloud Managed Email for higher sending volume.","properties":{"name":{"type":"string","example":"xCloud Managed Email (Mail Delivery)"},"addon":{"type":"string","example":"mail_delivery"},"message":{"type":"string","example":"For higher sending volume, purchase the Mail Delivery addon (1,000-50,000 emails\/month)."}}}}},"MailDeliveryPlan":{"type":"object","properties":{"slug":{"type":"string","description":"Public plan slug. Pass as `plan` when purchasing.","example":"xcloud_1000_emails"},"price":{"type":"number","format":"float","example":1},"email_limit":{"type":"integer","description":"Monthly send allowance for this plan.","example":1000},"window":{"type":"string","description":"Billing\/allowance window.","example":"month"},"currency":{"type":"string","example":"usd"}}},"MailDelivery":{"type":"object","description":"A Mail Delivery (\"xCloud Managed Email\") subscription \u2014 metadata only. Returned by the list endpoint; never carries sending credentials.\n","properties":{"uuid":{"type":"string","format":"uuid","example":"e5f6a7b8-c9d0-1234-efab-567890123456"},"label":{"type":"string","description":"Caller-supplied name for the subscription.","example":"Transactional"},"plan":{"type":"string","description":"Public plan slug the subscription was purchased on.","example":"xcloud_1000_emails"},"email_limit":{"type":"integer","example":1000},"price":{"type":"number","format":"float","example":1},"status":{"type":"string","enum":["new","pending","requires_action","payment_failed","active","inactive","failed"],"example":"active"},"provider":{"type":"string","description":"Human-readable sending provider.","example":"xCloud Managed Email Service"},"invoice_number":{"type":"string","nullable":true,"example":"XC-INV-20260712-1234"},"created_at":{"type":"string","format":"date-time","example":"2026-07-12T10:30:00Z"}}},"MailDeliveryWithCredentials":{"allOf":[{"$ref":"#\/components\/schemas\/MailDelivery"},{"type":"object","description":"A subscription plus its sending credentials. Returned only on show\/purchase to the authorized owner over HTTPS \u2014 never in the list.\n","properties":{"credentials":{"type":"object","properties":{"api_key":{"type":"string","nullable":true,"description":"Elastic Email HTTP API key for the team's subaccount (decrypted).","example":"ee-abc123def456..."},"smtp":{"type":"object","properties":{"host":{"type":"string","example":"smtp.elasticemail.com"},"port":{"type":"integer","example":2525},"username":{"type":"string","nullable":true,"description":"The team's Elastic Email subaccount email.","example":"team_42@xcloud.email"},"password":{"type":"string","nullable":true,"description":"The team's Elastic Email subaccount password (decrypted).","example":"s3cret-relay-p4ss"}}}}}}}]},"PurchaseMailDeliveryRequest":{"type":"object","required":["plan","label"],"properties":{"plan":{"type":"string","description":"Paid public plan slug from `GET \/addons\/mail-delivery\/plans`. The free tier cannot be purchased.","example":"xcloud_1000_emails"},"label":{"type":"string","maxLength":255,"description":"A name for the subscription so multiple subscriptions can be told apart.","example":"Transactional"}}},"PurchaseMailboxRequest":{"type":"object","required":["email","password","plan"],"properties":{"email":{"type":"string","format":"email","description":"Mailbox address to create. `postmaster@` is reserved.","example":"hello@example.com"},"password":{"type":"string","format":"password","minLength":8,"description":"Mailbox password \u2014 at least 8 characters and must contain at least one digit.","example":"S3curePass1"},"plan":{"type":"string","description":"Public plan slug from `GET \/addons\/mailbox\/plans`. The free tier cannot be purchased.","example":"mailbox_8gb"},"site_id":{"type":"string","format":"uuid","nullable":true,"description":"Optional UUID of a site (owned by the team) to associate the mailbox with.","example":"b2c3d4e5-f6a7-8901-bcde-f12345678901"}}}}}}