# the REST control plane for the CS4 Cloud backup & DR platform
No session or bearer token — every request is a POST whose body carries
the credentials it needs. The data API (/Connection, /WebQuery)
authenticates the way the legacy Apx clients do; the VM power API (/vms/*)
authenticates with an API key — apiKeyName + apiKeySecret, created in the UserConsole
"API Keys" screen — which scopes every operation to its tenant. HTTPS only; the caller IP is forwarded upstream.
Routes carry no /api prefix — they match the legacy Apx surface exactly, so existing
clients keep working.
/Connection — core database (and downstream) connectivity; returns "OK" or an error./WebQuery — one data operation, selected by the envelope's single populated member (GetVirtualMachinesListRqst, GetImageInfoRqst, CreateTaskRqst, …)./status — core database and Veeam (VBR) connectivity as JSON./vms/change-state — change a VM's power: start · stop (hard off) · shutdown (graceful). Returns a task id./vms/task-status — query a queued VM task by the GUID returned from change-state.This isn't CRUD. You dispatch an action; CS4.CloudCore.Engine drains the queue and
drives the hypervisor; you watch the task to a terminal state:
# set "wait": true to block for the terminal state, or poll /vms/task-status by GUID.
Data-API operations return the same envelope at HTTP 200 — check the payload, not the
status code. VM power operations return a small result object:
taskId (the GUID identifying the action)status — pending · running · ok · failedmessage · powerState (when known)Dispatch a power action and read the result inline with wait:
curl -X POST https://api.cloud.example.com/vms/change-state \
-H "Content-Type: application/json" \
-d '{
"apiKeyName": "reporting-bot",
"apiKeySecret": "<shown once>",
"vmName": "web-prod-01",
"action": "shutdown",
"wait": true
}'
# → { "taskId":"3f1c8a20-…", "status":"ok", "powerState":"poweredOff" }
Plain HttpClient + System.Text.Json on .NET 8 — no extra packages. Records
model the request and result; branch on Status, not the HTTP code:
using System.Net.Http.Json;
record ChangeVmState(string ApiKeyName, string ApiKeySecret,
string VmName, string Action, bool Wait = true);
record VmStateResult(string? TaskId, string Status, string Message, string? PowerState);
using var http = new HttpClient { BaseAddress = new("https://api.cloud.example.com") };
var rqst = new ChangeVmState(
ApiKeyName: "reporting-bot",
ApiKeySecret: Environment.GetEnvironmentVariable("CS4_API_SECRET")!,
VmName: "web-prod-01",
Action: "shutdown");
var res = await http.PostAsJsonAsync("/vms/change-state", rqst);
var body = await res.Content.ReadFromJsonAsync<VmStateResult>();
Console.WriteLine(body switch
{
{ Status: "ok" } => $"[ok] {body.Message} — now {body.PowerState}",
{ Status: "failed" } => $"[err] {body.Message}",
null => "[err] empty response",
_ => $"[..] {body.Status} (task {body.TaskId})",
});
Non-.NET callers POST the same JSON bodies directly — see /swagger.
/vms/change-state enqueues the work; the engine runs it and updates the task.status, never on the HTTP status — data-API operations return 200 with a JSON envelope.