xCloud Public API Skill — Test Report
Date: 2026-06-17
Tester: mrx-arafat
Skill version: 1.2.0
Test site: arafat-skill-test.1wp.site (UUID: 9f267a2a-e046-4ecf-85cf-7784bce187f6)
Server: arafat-dev (UUID: 016c6bbb-e74e-4132-9210-7d840e9c9566)
SSH key tested: ssh-rsa AAAA...easinarafat@Easins-MacBook-Air.local (fingerprint: d2:27:c1:82:17:9d:9e:58:f6:38:c1:a3:74:b9:18:10)
Scope: ALL skill files — SKILL.md, SETUP.md, DEPLOY.md, CONFIGURE.md, OPERATE.md, TROUBLESHOOT.md, ANALYZE.md, REQUEST.md, OPERATIONS.md, xcloud-api.sh
Verdict: NOT PRODUCTION READY — 24 bugs confirmed (BUG-01 through BUG-24, no gaps)
Executive Summary
Every endpoint, every jq expression, every bash function, and every SDK method was tested against the live API. 90+ assertions. 50 pass, 40 fail.
Three categories of problems:
- Missing endpoints — 5 endpoints documented do not exist in live API. Entire recovery and configuration workflows are broken.
- Wrong field names — 6 response shapes differ from what API actually returns. Every jq expression using those fields silently produces
null.
- Broken tool code — xcloud-api.sh has 4 broken functions. The async poller always times out. The CLI crashes on all non-health commands.
1. READ Endpoints — 26/26 PASS
| Endpoint |
Result |
Evidence |
GET /health |
PASS |
{"status":"ok","version":"v1"} |
GET /user |
PASS |
{"uuid":"...","name":"Support_390","email":"arafat@wpdeveloper.com"} |
GET /servers?per_page=100 |
PASS |
5 servers |
GET /servers?search=arafat-dev |
PASS |
filter works |
GET /servers/{uuid} |
PASS |
full server object |
GET /servers/{uuid}/php-versions |
PASS |
returns ["5.6"] (see BUG-24 on shape) |
GET /servers/{uuid}/monitoring |
PASS |
{"cpu":11,"memory":58.4,"disk":61} |
GET /servers/{uuid}/tasks |
PASS |
2312 tasks, pagination works |
GET /servers/{uuid}/sudo-users |
PASS |
4 users listed |
GET /servers/{uuid}/sites |
PASS |
5 sites |
GET /sites?per_page=100 |
PASS |
32 sites |
GET /sites?server_uuid= |
PASS |
filter works |
GET /sites?type=wordpress |
PASS |
5 results |
GET /sites/{uuid} |
PASS |
|
GET /sites/{uuid}/status |
PASS |
{uuid, status, is_provisioned, updated_at} |
GET /sites/{uuid}/events |
PASS |
10 events |
GET /sites/{uuid}/backups |
PASS |
|
GET /sites/{uuid}/ssl |
PASS |
{provider, status, expires_at, hostnames} (see BUG-18) |
GET /sites/{uuid}/ssh |
PASS |
{site_user, authentication_mode, ssh_keypairs} |
GET /sites/{uuid}/domain |
PASS |
{primary_domain, environment} |
GET /sites/{uuid}/deployment-logs |
PASS |
empty list on new site |
GET /sites/{uuid}/git |
PASS |
null when no repo connected |
GET /sites/{uuid}/monitoring |
PASS |
[{ram_usage, cpu_usage, disk_usage, time_at}] (see BUG-17) |
GET /blueprints |
PASS |
3 blueprints |
GET /user/tokens |
PASS |
token list (see BUG-16 on shape) |
GET /integrations/cloudflare |
PASS |
returns Cloudflare account info |
2. SSH Key Management — 5/5 PASS
Tested with real RSA 4096 key.
| TC |
Operation |
Result |
| SSH-01 |
PUT /sites/{uuid}/ssh public_key |
PASS — fingerprint d2:27:c1:82... stored as "ArafatMAC" |
| SSH-02 |
GET /sites/{uuid}/ssh verify stored |
PASS —keypairs: 1, fingerprint confirmed |
| SSH-03 |
POST /servers/{uuid}/sudo-users with RSA key |
PASS — UUID 979bfb66, status provisioned |
| SSH-04 |
GET /servers/{uuid}/sudo-users list |
PASS — 4 users visible |
| SSH-05 |
PUT /sites/{uuid}/ssh password mode |
PASS |
3. Write Operations — 6/9 PASS
| TC |
Operation |
Result |
| TC-18 |
Create site — live mode +ssl.provider: letsencrypt |
FAIL — BUG-01 |
| TC-18c |
Create site — demo mode |
PASS — provisioned in ~2.5 min |
| TC-19 |
Poll GET /sites/{uuid}/status manually |
PASS —is_provisioned: true |
| TC-19b |
AsyncPoller.poll_until_ready() |
FAIL — BUG-02 |
| TC-20 |
POST /sites/{uuid}/backup |
PASS |
| TC-21 |
POST /sites/{uuid}/cache/purge |
PASS |
| TC-22 |
POST /sites/{uuid}/restart |
FAIL — BUG-07 (404) |
| TC-23 |
PATCH /sites/{uuid} all fields |
FAIL — BUG-06 (Server Error) |
| TC-24 |
POST /servers/{uuid}/reboot |
NOT TESTED (destructive) |
4. Confirmed Bugs — 24 Total
CRITICAL
BUG-02 — AsyncPoller always times out — wrong field name
File: src/xcloud_async.py:122, src/xcloud_sdk.py:468
# CODE (WRONG):
ready_check = lambda r: r.get("provisioned") == True
# API actually returns:
{"uuid":"9f267a2a-...","status":"provisioned","is_provisioned":true,"updated_at":"..."}
# Evidence:
r.get('is_provisioned') -> True (correct field)
r.get('provisioned') -> None (what code checks — always None)
create_site_with_poll() always raises TimeoutError. Primary high-level deployment method is broken.
Fix:
xcloud_async.py:122: r.get("provisioned") == True → r.get("is_provisioned") == True
xcloud_sdk.py:468: status.get("provisioned") → status.get("is_provisioned")
BUG-06 — PATCH /sites/ returns Server Error for every documented field
File: CONFIGURE.md:106,157,197,210,250, OPERATIONS.md:44
CONFIGURE.md documents PATCH /sites/{uuid} for domain, PHP version, cache, monitoring. All return:
{"success":false,"message":"Server Error"}
Evidence — all 4 use cases:
PATCH /sites/{uuid} {"php_version": "8.1"} -> Server Error
PATCH /sites/{uuid} {"cache":{"full_page":true}} -> Server Error
PATCH /sites/{uuid} {"monitoring":{"enabled":true}} -> Server Error
PATCH /sites/{uuid} {"domain":"test.com"} -> Server Error
Entire CONFIGURE.md is non-functional. No site settings can be changed via API.
HIGH
BUG-01 — SSL provider letsencrypt rejected — live site creation broken
File: SKILL.md:312, DEPLOY.md:44, DEPLOY.md:220
{"mode":"live","domain":"example.com","ssl":{"provider":"letsencrypt"}}
Every variant tested:
"letsencrypt" -> "The selected ssl.provider is invalid."
"lets_encrypt" -> "The selected ssl.provider is invalid."
"lets-encrypt" -> "The selected ssl.provider is invalid."
"none" -> "The selected ssl.provider is invalid."
Demo mode works. Test site created via demo mode workaround.
BUG-03 — CLI crashes on every command except health
File: src/xcloud-cli.sh:5, src/xcloud-cli.sh:63
set -euo pipefail at line 5 enables -u (nounset). Line 63:
"${extra_args[@]}" # crashes: extra_args[@]: unbound variable
Evidence:
$ xcloud-cli.sh whoami
xcloud-cli.sh: line 59: extra_args[@]: unbound variable
$ xcloud-cli.sh server list
xcloud-cli.sh: line 59: extra_args[@]: unbound variable
Only health works. All 24+ documented commands are broken.
Fix: "${extra_args[@]}" → "${extra_args[@]+"${extra_args[@]}"}"
BUG-07 — POST /sites//restart endpoint does not exist
File: OPERATE.md:231, TROUBLESHOOT.md:97
POST /sites/9f267a2a-.../restart
-> {"success":false,"message":"Resource not found."}
OPERATE.md workflow 5 (Auto-Recovery) and TROUBLESHOOT.md section 1 (502 recovery) both depend on this endpoint. Neither workflow can complete.
BUG-08 — POST /sites//restore endpoint does not exist
File: TROUBLESHOOT.md:409
POST /sites/9f267a2a-.../restore {"backup_id":"fake-id"}
-> {"success":false,"message":"Resource not found."}
Disaster recovery workflow in TROUBLESHOOT.md is non-functional. Backups can be listed but not restored via API.
BUG-09 — GET /sites//databases endpoint does not exist
File: TROUBLESHOOT.md:301
GET /sites/9f267a2a-.../databases
-> {"success":false,"message":"Resource not found."}
Database diagnosis workflow in TROUBLESHOOT.md cannot run.
BUG-10 — POST /servers//restart-database endpoint does not exist
File: TROUBLESHOOT.md:328
POST /servers/016c6bbb-.../restart-database
-> {"success":false,"message":"Resource not found."}
BUG-11 — xcloud_test_connection always reports Authentication Failed
File: references/xcloud-api.sh:365
user=$(xcloud_whoami | jq -r '.name // empty')
API response has user data inside .data, not at root:
{"success":true,"data":{"uuid":"...","name":"Support_390","email":"arafat@..."}}
.name at root → null → // empty → empty string → always fails.
Live evidence:
$ xcloud_test_connection
Testing xCloud API connection...
Token format valid
Authentication failed <- token IS valid, API IS up
Fix: jq -r '.name // empty' → jq -r '.data.name // empty'
BUG-12 — xcloud_wait_provisioned never succeeds — wrong status string
File: references/xcloud-api.sh:302
if [ "$status" = "active" ]; then # WRONG
API returns "provisioned" not "active". Confirmed:
GET /sites/9f267a2a-... -> .data.status = "provisioned"
Polls for 600 seconds then always fails with "Timeout waiting for site provisioning" even when site is fully ready.
Fix: "active" → "provisioned"
BUG-19 — openssl rand -base64 32 password always rejected by API
File: SETUP.md:156, CONFIGURE.md:43
Both documents recommend:
PASSWORD=$(openssl rand -base64 32)
base64 output includes +, /, =. API rejects these:
Generated: hAdudpDQPLUEilG1lvUDHm88+TWielUDsoidQUOoizI=
-> {"success":false,"message":"Password contains invalid characters"}
Allowed special chars (verified): ! only.
Rejected: @, #, -, _, +, /, =.
Password command in docs NEVER works.
Fix: Replace openssl rand -base64 32 with openssl rand -hex 16.
MEDIUM
BUG-04 — SDK create_sudo_user(ssh_keys=None) always 422
File: src/xcloud_sdk.py:338
API always requires ssh_public_keys:
{"success":false,"message":"The ssh public keys field is required."}
SDK allows ssh_keys=None (optional). Silently fails with 422.
Note: SKILL.md and SETUP.md workflow 4 correctly show ssh_public_keys in examples. Bug is in SDK code only.
Fix: Remove ssh_keys=None default; make parameter required.
BUG-05 — Top-level import backoff blocks entire async module
File: src/xcloud_async.py:28
import backoff # top-level
StateManager, RateLimitManager, DeploymentTracker, OperationBatcher all fail to import if backoff not installed — even though only AsyncPoller.retry_with_backoff() uses it.
On macOS Python 3.14 (Homebrew), PEP 668 blocks pip3 install --user. Requires undocumented: python3 -m pip install backoff --break-system-packages.
Fix: Move import backoff inside retry_with_backoff() method.
BUG-13 — xcloud_update_ssh produces malformed JSON when called without credentials
File: references/xcloud-api.sh:164
Default payload is set to:
payload=$(cat <<EOF
{
"authentication_mode": "$auth_mode"
EOF
)
Missing closing }. Neither if/elif branch matches when called without 3rd/4th argument. Broken payload sent to curl silently — no error to caller.
BUG-14 — xcloud_add_sudo_user always 422 — never sends ssh_public_keys
File: references/xcloud-api.sh:252
Wrapper sends only username + password. API requires ssh_public_keys always:
{"success":false,"message":"The ssh public keys field is required."}
Fix: Add ssh_key parameter and include ssh_public_keys in payload.
BUG-15 — GET /servers//tasks field names wrong in OPERATIONS.md
File: OPERATIONS.md:255
Doc says: {id, task_type, status, created_at, completed_at}
API returns: {uuid, type, status, output, created_at, updated_at}
Evidence:
Has id: False Has uuid: True
Has task_type: False Has type: True
Has completed_at: False Has updated_at: True
Has output: True (present but undocumented)
jq '{id, task_type, completed_at}' silently returns nulls.
BUG-16 — GET /user/tokens response shape wrong in CONFIGURE.md
File: CONFIGURE.md:282
# Doc says:
jq '.data.items[] | {id, name, last_used_at, created_at}'
# Actual response:
{"data":[{"uuid":"81886973-...","name":"xCloud-test","abilities":["*"],"team_uuid":"..."}]}
Three issues:
.data is a list, not {items:[...]} — .data.items[] returns null
- Field is
uuid not id
abilities, team_uuid, team_name present but undocumented
Correct: .data[] | {uuid, name, last_used_at, created_at}
BUG-17 — GET /sites//monitoring fields wrong in ANALYZE.md
File: ANALYZE.md:163
# Doc says:
jq '.data | {cache_hit_rate, page_load_time, request_count}'
# Actual response:
{"data":[{"ram_usage":8.35,"cpu_usage":0,"disk_usage":0.37,"time_at":"08:10 AM"}]}
Three issues:
.data is an array, not an object
cache_hit_rate, page_load_time, request_count do not exist
- Actual fields:
ram_usage, cpu_usage, disk_usage, time_at
BUG-18 — GET /sites//ssl missing issuer and valid_from fields
File: TROUBLESHOOT.md:179, REQUEST.md:282
# Doc says:
jq '.data | {provider, status, expires_at, issuer, valid_from}'
# Actual response:
{"data":{"provider":"staging","status":"installed","expires_at":null,"hostnames":null}}
issuer and valid_from do not exist. Scripts checking certificate issuer get null silently.
BUG-20 — GET /sites?status= filter not supported
File: OPERATE.md:37, REQUEST.md:37,91, SKILL.md:389,394
curl ".../sites?per_page=100&status=provisioned"
-> {"success":false}
?status= filter rejected. ?type=wordpress filter works; ?status= does not.
LOW
BUG-21 — _api_call missing Accept: application/json header
File: references/xcloud-api.sh:43,50
SKILL.md canonical requires:
Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json
_api_call sends only Authorization + Content-Type. Omits Accept.
Fix: Add -H "Accept: application/json" to both curl calls in _api_call.
BUG-22 — POST /sites//domains returns Server Error
File: references/xcloud-api.sh:215, CONFIGURE.md workflow 2
POST /sites/9f267a2a-.../domains {"domain":"test.com","primary":false}
-> {"success":false,"message":"Server Error"}
xcloud_add_domain and CONFIGURE.md domain workflow both broken.
BUG-23 — get_fleet_health() SDK example KeyError
File: OPERATE.md:56
# Doc says:
print(f"({health['sites']['provisioned']} provisioned)")
# Actual structure:
{
'sites': {
'total': 32,
'by_status': {'provisioned': 21, ...}, # <- correct path
'by_type': {'wordpress': 15, ...}
}
}
health['sites']['provisioned'] → KeyError.
Fix: ['sites']['provisioned'] → ['sites']['by_status']['provisioned']
BUG-24 — PHP versions response shape wrong in SETUP.md and CONFIGURE.md
File: SETUP.md:103, CONFIGURE.md:148
# Doc says:
jq '.data.items[] | {version, status}'
# Actual response:
{"data":["5.6"]}
data is a list of plain strings, not objects. .data.items[] returns null.
Fix: jq '.data.items[] | {version, status}' → jq '.data[]'
5. Observations
OBS-01 — SSH password allowed character set undocumented
| Password |
Result |
SkillTest2026 |
OK |
SkillTest2026! |
OK |
SkillTest2026@ |
Invalid characters |
SkillTest2026# |
Invalid characters |
Skill-Test2026 |
Invalid characters |
Skill_Test2026 |
Invalid characters |
base64+/=chars |
Invalid characters |
Allowed: alphanumeric + !. SETUP.md only says "8+ chars, mixed case, numbers" — never mentions valid special chars. Combined with BUG-19, creates silent failure trap.
OBS-02 — POST /sites//ssl/renew requires Let's Encrypt (undocumented)
-> "SSL renewal is only supported for xCloud-managed (Let's Encrypt) certificates."
Demo sites use provider: "staging". TROUBLESHOOT.md does not mention this constraint.
OBS-03 — POST /sites//rescue exists but options undocumented
-> {"success":false,"message":"Please select at least one rescue option."}
OPERATIONS.md lists it as CAUTION but required body fields are not documented.
OBS-04 — backup_all_sites() swallows errors silently
{'status': 'backup_failed', 'error': 'Request failed: 422 Client Error'}
Caller cannot distinguish "not configured" from "rate limited" from "wrong state".
OBS-05 — GET /sites/ .data.domain is null
Actual domain is at GET /sites/{uuid}/domain. OPERATE.md step 2 uses .data | {domain, status} — domain would be null.
OBS-06 — Demo site returns no WordPress credentials
Demo create returns only data.uuid and data.domain. No wordpress_user, wordpress_password, or admin_url.
6. Summary Tables
Endpoint Existence
| Endpoint |
Claimed In |
Result |
Evidence |
PATCH /sites/{uuid} |
CONFIGURE.md:106,157,197,210,250 |
FAIL — Server Error |
all field payloads fail |
POST /sites/{uuid}/restart |
OPERATE.md:231, TROUBLESHOOT.md:97 |
FAIL — 404 |
Resource not found |
POST /sites/{uuid}/restore |
TROUBLESHOOT.md:409 |
FAIL — 404 |
Resource not found |
GET /sites/{uuid}/databases |
TROUBLESHOOT.md:301 |
FAIL — 404 |
Resource not found |
POST /servers/{uuid}/restart-database |
TROUBLESHOOT.md:328 |
FAIL — 404 |
Resource not found |
POST /sites/{uuid}/domains |
xcloud-api.sh:215 |
FAIL — 500 |
Server Error |
POST /sites/{uuid}/ssl/renew |
TROUBLESHOOT.md |
PARTIAL |
Let's Encrypt only |
POST /sites/{uuid}/rescue |
OPERATIONS.md |
PARTIAL |
options undocumented |
GET /integrations/cloudflare |
SETUP.md |
PASS |
works |
Response Field Mismatches
| Endpoint |
Doc Claims |
Actual |
GET /servers/{uuid}/tasks |
{id, task_type, completed_at} |
{uuid, type, updated_at} |
GET /user/tokens |
.data.items[]|{id} |
.data[]|{uuid} (list not object) |
GET /sites/{uuid}/monitoring |
.data|{cache_hit_rate, page_load_time} |
.data[]|{ram_usage, cpu_usage, disk_usage} |
GET /sites/{uuid}/ssl |
{issuer, valid_from} |
fields absent; has hostnames instead |
GET /servers/{uuid}/php-versions |
.data.items[]|{version,status} |
.data = ["5.6"] (string list) |
GET /sites/{uuid}/status (SDK) |
{provisioned} |
{is_provisioned} |
Tool Code Status
| Component |
Status |
Root Cause |
| xcloud-api.sh — all read functions |
PASS |
— |
xcloud-api.sh xcloud_test_connection |
FAIL |
BUG-11 —.name vs .data.name |
xcloud-api.sh xcloud_wait_provisioned |
FAIL |
BUG-12 —"active" vs "provisioned" |
xcloud-api.sh xcloud_update_ssh (no creds) |
FAIL |
BUG-13 — malformed JSON |
xcloud-api.sh xcloud_add_sudo_user |
FAIL |
BUG-14 — missing ssh_public_keys |
xcloud-api.sh _api_call |
WARN |
BUG-21 — missing Accept header |
xcloud-cli.sh health |
PASS |
— |
| xcloud-cli.sh all other commands |
FAIL |
BUG-03 —extra_args[@] unbound |
| xcloud_sdk.py most methods |
PASS |
— |
xcloud_sdk.py create_site_with_poll |
FAIL |
BUG-02 — wrong status field |
xcloud_sdk.py create_sudo_user(None) |
FAIL |
BUG-04 — API needs ssh_public_keys |
xcloud_async.py AsyncPoller |
FAIL |
BUG-02 — wrong status field |
xcloud_async.py import (fresh env) |
FAIL |
BUG-05 — top-level backoff import |
| xcloud_async.py other classes |
PASS |
— |
7. Complete Bug Priority Matrix
| # |
Severity |
Description |
File:Line |
Fix |
| BUG-01 |
HIGH |
SSL provider letsencrypt rejected — live site creation impossible |
SKILL.md:312, DEPLOY.md:44,220 |
Find valid ssl.provider value |
| BUG-02 |
CRITICAL |
AsyncPoller/create_site_with_poll wrong status field — always times out |
xcloud_async.py:122, xcloud_sdk.py:468 |
"provisioned" → "is_provisioned" |
| BUG-03 |
HIGH |
CLI crashes all 24 commands — extra_args[@] unbound variable |
xcloud-cli.sh:5,63 |
add + guard to array expansion |
| BUG-04 |
MEDIUM |
SDK create_sudo_user(ssh_keys=None) always 422 |
xcloud_sdk.py:338 |
make ssh_keys required |
| BUG-05 |
MEDIUM |
import backoff at module level blocks all async classes |
xcloud_async.py:28 |
lazy import inside method |
| BUG-06 |
CRITICAL |
PATCH /sites/{uuid} Server Error for all fields |
CONFIGURE.md:106,157,197,210,250 |
API investigation needed |
| BUG-07 |
HIGH |
POST /sites/{uuid}/restart 404 |
OPERATE.md:231, TROUBLESHOOT.md:97 |
remove endpoint from docs |
| BUG-08 |
HIGH |
POST /sites/{uuid}/restore 404 |
TROUBLESHOOT.md:409 |
remove endpoint from docs |
| BUG-09 |
HIGH |
GET /sites/{uuid}/databases 404 |
TROUBLESHOOT.md:301 |
remove endpoint from docs |
| BUG-10 |
HIGH |
POST /servers/{uuid}/restart-database 404 |
TROUBLESHOOT.md:328 |
remove endpoint from docs |
| BUG-11 |
HIGH |
xcloud_test_connection always reports auth failed |
xcloud-api.sh:365 |
.name → .data.name |
| BUG-12 |
HIGH |
xcloud_wait_provisioned always times out — checks "active" |
xcloud-api.sh:302 |
"active" → "provisioned" |
| BUG-13 |
MEDIUM |
xcloud_update_ssh malformed JSON when called without credentials |
xcloud-api.sh:164 |
fix default payload closing brace |
| BUG-14 |
MEDIUM |
xcloud_add_sudo_user always 422 — never sends ssh_public_keys |
xcloud-api.sh:252 |
add ssh_key param to function |
| BUG-15 |
MEDIUM |
Task fields wrong — id/task_type/completed_at don't exist |
OPERATIONS.md:255 |
update jq to uuid/type/updated_at |
| BUG-16 |
MEDIUM |
Token response wrong — .data.items[]|{id} vs .data[]|{uuid} |
CONFIGURE.md:282 |
update jq expression |
| BUG-17 |
MEDIUM |
Monitoring fields wrong — cache_hit_rate/page_load_time don't exist |
ANALYZE.md:163 |
update to ram_usage/cpu_usage/disk_usage |
| BUG-18 |
MEDIUM |
SSL response missing issuer and valid_from |
TROUBLESHOOT.md:179, REQUEST.md:282 |
remove from jq expressions |
| BUG-19 |
HIGH |
openssl rand -base64 32 always rejected — bad advice in docs |
SETUP.md:156, CONFIGURE.md:43 |
replace with openssl rand -hex 16 |
| BUG-20 |
MEDIUM |
?status= filter not supported — returns failure |
OPERATE.md:37, REQUEST.md:37,91, SKILL.md:389,394 |
remove from examples |
| BUG-21 |
LOW |
_api_call missing Accept: application/json header |
xcloud-api.sh:43,50 |
add header to curl calls |
| BUG-22 |
LOW |
POST /sites/{uuid}/domains 500 |
xcloud-api.sh:215, CONFIGURE.md |
remove or fix endpoint |
| BUG-23 |
LOW |
get_fleet_health() example KeyError on health['sites']['provisioned'] |
OPERATE.md:56 |
add ['by_status'] to path |
| BUG-24 |
LOW |
PHP versions jq '.data.items[]' wrong — data is string list |
SETUP.md:103, CONFIGURE.md:148 |
update to jq '.data[]' |
8. What Works
- All 26 read endpoints
- SSH key management (when ssh_public_keys provided)
POST /sites/{uuid}/backup (with ~2 min cooldown on fresh sites)
POST /sites/{uuid}/cache/purge
- Python SDK read methods
- xcloud-api.sh read functions (xcloud_servers, xcloud_server, xcloud_sites, xcloud_site, xcloud_backups, xcloud_ssh_config, xcloud_sudo_users, xcloud_domains)
- Async helpers (StateManager, RateLimitManager, DeploymentTracker, OperationBatcher)
- xcloud-cli.sh
health command only
- Authentication — all methods work
9. Conclusion
Ready for production use: NO.
| Category |
Status |
| Read operations |
PASS — all work |
| SSH / key management |
PASS — works with real RSA key |
| Site configuration (PATCH) |
FAIL — BUG-06 Server Error |
| Automated recovery (restart, restore) |
FAIL — BUG-07/08 404 |
| Database operations |
FAIL — BUG-09/10 404 |
| Live WordPress site creation |
FAIL — BUG-01 SSL provider rejected |
| CLI tool |
FAIL — BUG-03 24/25 commands crash |
| Async poller |
FAIL — BUG-02 wrong field |
| Bash wrapper reliability |
FAIL — BUG-11/12/13/14 4 broken functions |
| Documentation field accuracy |
FAIL — BUG-15 to BUG-20 6 mismatches |
Fix BUG-02 and BUG-06 first (CRITICAL). Then BUG-01, BUG-03, BUG-07 through BUG-12, BUG-19 (HIGH). Remaining in a documentation pass.
10. Quick Fix Reference
| Bug |
File:Line |
Change |
| BUG-02 |
src/xcloud_async.py:122 |
r.get("provisioned") == True → r.get("is_provisioned") == True |
| BUG-02 |
src/xcloud_sdk.py:468 |
status.get("provisioned") → status.get("is_provisioned") |
| BUG-03 |
src/xcloud-cli.sh:63 |
"${extra_args[@]}" → "${extra_args[@]+"${extra_args[@]}"}" |
| BUG-04 |
src/xcloud_sdk.py:338 |
remove ssh_keys=None default |
| BUG-05 |
src/xcloud_async.py:28 |
move import backoff inside retry_with_backoff() |
| BUG-11 |
references/xcloud-api.sh:365 |
jq -r '.name // empty' → jq -r '.data.name // empty' |
| BUG-12 |
references/xcloud-api.sh:302 |
"active" → "provisioned" |
| BUG-13 |
references/xcloud-api.sh:164 |
add closing } to default payload |
| BUG-14 |
references/xcloud-api.sh:252 |
add ssh_public_keys param |
| BUG-19 |
SETUP.md:156, CONFIGURE.md:43 |
openssl rand -base64 32 → openssl rand -hex 16 |
| BUG-21 |
references/xcloud-api.sh:43,50 |
add -H "Accept: application/json" |
| BUG-23 |
OPERATE.md:56 |
['sites']['provisioned'] → ['sites']['by_status']['provisioned'] |
| BUG-06,07,08,09,10 |
multiple docs |
remove non-existent endpoint references pending API fix |
| BUG-15,16,17,18,20,24 |
multiple docs |
update jq expressions to match actual API fields |
Final report — mrx-arafat · xcloud-agent-skills v1.2.0 · 2026-06-17
90+ assertions tested · 50 pass · 40 fail · 24 confirmed bugs (BUG-01 through BUG-24, sequential)
SSH key: RSA 4096 fingerprint d2:27:c1:82:17:9d:9e:58:f6:38:c1:a3:74:b9:18:10 (ArafatMAC)
xCloud Public API Skill — Test Report
Date: 2026-06-17
Tester: mrx-arafat
Skill version: 1.2.0
Test site:
arafat-skill-test.1wp.site(UUID:9f267a2a-e046-4ecf-85cf-7784bce187f6)Server:
arafat-dev(UUID:016c6bbb-e74e-4132-9210-7d840e9c9566)SSH key tested:
ssh-rsa AAAA...easinarafat@Easins-MacBook-Air.local(fingerprint:d2:27:c1:82:17:9d:9e:58:f6:38:c1:a3:74:b9:18:10)Scope: ALL skill files — SKILL.md, SETUP.md, DEPLOY.md, CONFIGURE.md, OPERATE.md, TROUBLESHOOT.md, ANALYZE.md, REQUEST.md, OPERATIONS.md, xcloud-api.sh
Verdict: NOT PRODUCTION READY — 24 bugs confirmed (BUG-01 through BUG-24, no gaps)
Executive Summary
Every endpoint, every jq expression, every bash function, and every SDK method was tested against the live API. 90+ assertions. 50 pass, 40 fail.
Three categories of problems:
null.1. READ Endpoints — 26/26 PASS
GET /health{"status":"ok","version":"v1"}GET /user{"uuid":"...","name":"Support_390","email":"arafat@wpdeveloper.com"}GET /servers?per_page=100GET /servers?search=arafat-devGET /servers/{uuid}GET /servers/{uuid}/php-versions["5.6"](see BUG-24 on shape)GET /servers/{uuid}/monitoring{"cpu":11,"memory":58.4,"disk":61}GET /servers/{uuid}/tasksGET /servers/{uuid}/sudo-usersGET /servers/{uuid}/sitesGET /sites?per_page=100GET /sites?server_uuid=GET /sites?type=wordpressGET /sites/{uuid}GET /sites/{uuid}/status{uuid, status, is_provisioned, updated_at}GET /sites/{uuid}/eventsGET /sites/{uuid}/backupsGET /sites/{uuid}/ssl{provider, status, expires_at, hostnames}(see BUG-18)GET /sites/{uuid}/ssh{site_user, authentication_mode, ssh_keypairs}GET /sites/{uuid}/domain{primary_domain, environment}GET /sites/{uuid}/deployment-logsGET /sites/{uuid}/gitnullwhen no repo connectedGET /sites/{uuid}/monitoring[{ram_usage, cpu_usage, disk_usage, time_at}](see BUG-17)GET /blueprintsGET /user/tokensGET /integrations/cloudflare2. SSH Key Management — 5/5 PASS
Tested with real RSA 4096 key.
PUT /sites/{uuid}/sshpublic_keyd2:27:c1:82...stored as "ArafatMAC"GET /sites/{uuid}/sshverify storedkeypairs: 1, fingerprint confirmedPOST /servers/{uuid}/sudo-userswith RSA key979bfb66, status provisionedGET /servers/{uuid}/sudo-userslistPUT /sites/{uuid}/sshpassword mode3. Write Operations — 6/9 PASS
ssl.provider: letsencryptGET /sites/{uuid}/statusmanuallyis_provisioned: trueAsyncPoller.poll_until_ready()POST /sites/{uuid}/backupPOST /sites/{uuid}/cache/purgePOST /sites/{uuid}/restartPATCH /sites/{uuid}all fieldsPOST /servers/{uuid}/reboot4. Confirmed Bugs — 24 Total
CRITICAL
BUG-02 — AsyncPoller always times out — wrong field name
File:
src/xcloud_async.py:122,src/xcloud_sdk.py:468create_site_with_poll()always raisesTimeoutError. Primary high-level deployment method is broken.Fix:
xcloud_async.py:122:r.get("provisioned") == True→r.get("is_provisioned") == Truexcloud_sdk.py:468:status.get("provisioned")→status.get("is_provisioned")BUG-06 — PATCH /sites/ returns Server Error for every documented field
File:
CONFIGURE.md:106,157,197,210,250,OPERATIONS.md:44CONFIGURE.md documents
PATCH /sites/{uuid}for domain, PHP version, cache, monitoring. All return:{"success":false,"message":"Server Error"}Evidence — all 4 use cases:
Entire CONFIGURE.md is non-functional. No site settings can be changed via API.
HIGH
BUG-01 — SSL provider letsencrypt rejected — live site creation broken
File:
SKILL.md:312,DEPLOY.md:44,DEPLOY.md:220{"mode":"live","domain":"example.com","ssl":{"provider":"letsencrypt"}}Every variant tested:
Demo mode works. Test site created via demo mode workaround.
BUG-03 — CLI crashes on every command except health
File:
src/xcloud-cli.sh:5,src/xcloud-cli.sh:63set -euo pipefailat line 5 enables-u(nounset). Line 63:Evidence:
Only
healthworks. All 24+ documented commands are broken.Fix:
"${extra_args[@]}"→"${extra_args[@]+"${extra_args[@]}"}"BUG-07 — POST /sites//restart endpoint does not exist
File:
OPERATE.md:231,TROUBLESHOOT.md:97OPERATE.md workflow 5 (Auto-Recovery) and TROUBLESHOOT.md section 1 (502 recovery) both depend on this endpoint. Neither workflow can complete.
BUG-08 — POST /sites//restore endpoint does not exist
File:
TROUBLESHOOT.md:409POST /sites/9f267a2a-.../restore {"backup_id":"fake-id"} -> {"success":false,"message":"Resource not found."}Disaster recovery workflow in TROUBLESHOOT.md is non-functional. Backups can be listed but not restored via API.
BUG-09 — GET /sites//databases endpoint does not exist
File:
TROUBLESHOOT.md:301Database diagnosis workflow in TROUBLESHOOT.md cannot run.
BUG-10 — POST /servers//restart-database endpoint does not exist
File:
TROUBLESHOOT.md:328BUG-11 — xcloud_test_connection always reports Authentication Failed
File:
references/xcloud-api.sh:365user=$(xcloud_whoami | jq -r '.name // empty')API response has user data inside
.data, not at root:{"success":true,"data":{"uuid":"...","name":"Support_390","email":"arafat@..."}}.nameat root → null →// empty→ empty string → always fails.Live evidence:
Fix:
jq -r '.name // empty'→jq -r '.data.name // empty'BUG-12 — xcloud_wait_provisioned never succeeds — wrong status string
File:
references/xcloud-api.sh:302API returns
"provisioned"not"active". Confirmed:Polls for 600 seconds then always fails with "Timeout waiting for site provisioning" even when site is fully ready.
Fix:
"active"→"provisioned"BUG-19 — openssl rand -base64 32 password always rejected by API
File:
SETUP.md:156,CONFIGURE.md:43Both documents recommend:
PASSWORD=$(openssl rand -base64 32)base64 output includes
+,/,=. API rejects these:Allowed special chars (verified):
!only.Rejected:
@,#,-,_,+,/,=.Password command in docs NEVER works.
Fix: Replace
openssl rand -base64 32withopenssl rand -hex 16.MEDIUM
BUG-04 — SDK create_sudo_user(ssh_keys=None) always 422
File:
src/xcloud_sdk.py:338API always requires
ssh_public_keys:{"success":false,"message":"The ssh public keys field is required."}SDK allows
ssh_keys=None(optional). Silently fails with 422.Note: SKILL.md and SETUP.md workflow 4 correctly show
ssh_public_keysin examples. Bug is in SDK code only.Fix: Remove
ssh_keys=Nonedefault; make parameter required.BUG-05 — Top-level import backoff blocks entire async module
File:
src/xcloud_async.py:28StateManager,RateLimitManager,DeploymentTracker,OperationBatcherall fail to import ifbackoffnot installed — even though onlyAsyncPoller.retry_with_backoff()uses it.On macOS Python 3.14 (Homebrew), PEP 668 blocks
pip3 install --user. Requires undocumented:python3 -m pip install backoff --break-system-packages.Fix: Move
import backoffinsideretry_with_backoff()method.BUG-13 — xcloud_update_ssh produces malformed JSON when called without credentials
File:
references/xcloud-api.sh:164Default payload is set to:
Missing closing
}. Neitherif/elifbranch matches when called without 3rd/4th argument. Broken payload sent to curl silently — no error to caller.BUG-14 — xcloud_add_sudo_user always 422 — never sends ssh_public_keys
File:
references/xcloud-api.sh:252Wrapper sends only
username+password. API requiresssh_public_keysalways:{"success":false,"message":"The ssh public keys field is required."}Fix: Add
ssh_keyparameter and includessh_public_keysin payload.BUG-15 — GET /servers//tasks field names wrong in OPERATIONS.md
File:
OPERATIONS.md:255Evidence:
jq '{id, task_type, completed_at}'silently returns nulls.BUG-16 — GET /user/tokens response shape wrong in CONFIGURE.md
File:
CONFIGURE.md:282Three issues:
.datais a list, not{items:[...]}—.data.items[]returns nulluuidnotidabilities,team_uuid,team_namepresent but undocumentedCorrect:
.data[] | {uuid, name, last_used_at, created_at}BUG-17 — GET /sites//monitoring fields wrong in ANALYZE.md
File:
ANALYZE.md:163Three issues:
.datais an array, not an objectcache_hit_rate,page_load_time,request_countdo not existram_usage,cpu_usage,disk_usage,time_atBUG-18 — GET /sites//ssl missing issuer and valid_from fields
File:
TROUBLESHOOT.md:179,REQUEST.md:282issuerandvalid_fromdo not exist. Scripts checking certificate issuer get null silently.BUG-20 — GET /sites?status= filter not supported
File:
OPERATE.md:37,REQUEST.md:37,91,SKILL.md:389,394?status=filter rejected.?type=wordpressfilter works;?status=does not.LOW
BUG-21 — _api_call missing Accept: application/json header
File:
references/xcloud-api.sh:43,50SKILL.md canonical requires:
_api_callsends onlyAuthorization+Content-Type. OmitsAccept.Fix: Add
-H "Accept: application/json"to both curl calls in_api_call.BUG-22 — POST /sites//domains returns Server Error
File:
references/xcloud-api.sh:215,CONFIGURE.mdworkflow 2POST /sites/9f267a2a-.../domains {"domain":"test.com","primary":false} -> {"success":false,"message":"Server Error"}xcloud_add_domainand CONFIGURE.md domain workflow both broken.BUG-23 — get_fleet_health() SDK example KeyError
File:
OPERATE.md:56health['sites']['provisioned']→ KeyError.Fix:
['sites']['provisioned']→['sites']['by_status']['provisioned']BUG-24 — PHP versions response shape wrong in SETUP.md and CONFIGURE.md
File:
SETUP.md:103,CONFIGURE.md:148datais a list of plain strings, not objects..data.items[]returns null.Fix:
jq '.data.items[] | {version, status}'→jq '.data[]'5. Observations
OBS-01 — SSH password allowed character set undocumented
SkillTest2026SkillTest2026!SkillTest2026@SkillTest2026#Skill-Test2026Skill_Test2026base64+/=charsAllowed: alphanumeric +
!. SETUP.md only says "8+ chars, mixed case, numbers" — never mentions valid special chars. Combined with BUG-19, creates silent failure trap.OBS-02 — POST /sites//ssl/renew requires Let's Encrypt (undocumented)
Demo sites use
provider: "staging". TROUBLESHOOT.md does not mention this constraint.OBS-03 — POST /sites//rescue exists but options undocumented
OPERATIONS.md lists it as CAUTION but required body fields are not documented.
OBS-04 — backup_all_sites() swallows errors silently
{'status': 'backup_failed', 'error': 'Request failed: 422 Client Error'}Caller cannot distinguish "not configured" from "rate limited" from "wrong state".
OBS-05 — GET /sites/ .data.domain is null
Actual domain is at
GET /sites/{uuid}/domain. OPERATE.md step 2 uses.data | {domain, status}— domain would be null.OBS-06 — Demo site returns no WordPress credentials
Demo create returns only
data.uuidanddata.domain. Nowordpress_user,wordpress_password, oradmin_url.6. Summary Tables
Endpoint Existence
PATCH /sites/{uuid}POST /sites/{uuid}/restartPOST /sites/{uuid}/restoreGET /sites/{uuid}/databasesPOST /servers/{uuid}/restart-databasePOST /sites/{uuid}/domainsPOST /sites/{uuid}/ssl/renewPOST /sites/{uuid}/rescueGET /integrations/cloudflareResponse Field Mismatches
GET /servers/{uuid}/tasks{id, task_type, completed_at}{uuid, type, updated_at}GET /user/tokens.data.items[]|{id}.data[]|{uuid}(list not object)GET /sites/{uuid}/monitoring.data|{cache_hit_rate, page_load_time}.data[]|{ram_usage, cpu_usage, disk_usage}GET /sites/{uuid}/ssl{issuer, valid_from}hostnamesinsteadGET /servers/{uuid}/php-versions.data.items[]|{version,status}.data=["5.6"](string list)GET /sites/{uuid}/status(SDK){provisioned}{is_provisioned}Tool Code Status
xcloud_test_connection.namevs.data.namexcloud_wait_provisioned"active"vs"provisioned"xcloud_update_ssh(no creds)xcloud_add_sudo_userssh_public_keys_api_callhealthextra_args[@]unboundcreate_site_with_pollcreate_sudo_user(None)AsyncPollerimport(fresh env)7. Complete Bug Priority Matrix
letsencryptrejected — live site creation impossibleSKILL.md:312,DEPLOY.md:44,220xcloud_async.py:122,xcloud_sdk.py:468"provisioned"→"is_provisioned"xcloud-cli.sh:5,63+guard to array expansionxcloud_sdk.py:338xcloud_async.py:28CONFIGURE.md:106,157,197,210,250OPERATE.md:231,TROUBLESHOOT.md:97TROUBLESHOOT.md:409TROUBLESHOOT.md:301TROUBLESHOOT.md:328xcloud-api.sh:365.name→.data.namexcloud-api.sh:302"active"→"provisioned"xcloud-api.sh:164xcloud-api.sh:252OPERATIONS.md:255CONFIGURE.md:282ANALYZE.md:163TROUBLESHOOT.md:179,REQUEST.md:282SETUP.md:156,CONFIGURE.md:43OPERATE.md:37,REQUEST.md:37,91,SKILL.md:389,394xcloud-api.sh:43,50xcloud-api.sh:215,CONFIGURE.mdOPERATE.md:56SETUP.md:103,CONFIGURE.md:1488. What Works
POST /sites/{uuid}/backup(with ~2 min cooldown on fresh sites)POST /sites/{uuid}/cache/purgehealthcommand only9. Conclusion
Ready for production use: NO.
Fix BUG-02 and BUG-06 first (CRITICAL). Then BUG-01, BUG-03, BUG-07 through BUG-12, BUG-19 (HIGH). Remaining in a documentation pass.
10. Quick Fix Reference
src/xcloud_async.py:122r.get("provisioned") == True→r.get("is_provisioned") == Truesrc/xcloud_sdk.py:468status.get("provisioned")→status.get("is_provisioned")src/xcloud-cli.sh:63"${extra_args[@]}"→"${extra_args[@]+"${extra_args[@]}"}"src/xcloud_sdk.py:338ssh_keys=Nonedefaultsrc/xcloud_async.py:28import backoffinsideretry_with_backoff()references/xcloud-api.sh:365jq -r '.name // empty'→jq -r '.data.name // empty'references/xcloud-api.sh:302"active"→"provisioned"references/xcloud-api.sh:164}to default payloadreferences/xcloud-api.sh:252ssh_public_keysparamSETUP.md:156,CONFIGURE.md:43openssl rand -base64 32→openssl rand -hex 16references/xcloud-api.sh:43,50-H "Accept: application/json"OPERATE.md:56['sites']['provisioned']→['sites']['by_status']['provisioned']Final report — mrx-arafat · xcloud-agent-skills v1.2.0 · 2026-06-17
90+ assertions tested · 50 pass · 40 fail · 24 confirmed bugs (BUG-01 through BUG-24, sequential)
SSH key: RSA 4096 fingerprint d2:27:c1:82:17:9d:9e:58:f6:38:c1:a3:74:b9:18:10 (ArafatMAC)