Examples
Copy-paste recipes for common Metalhost tasks — the same operation in CLI, Go SDK, and curl. New to the platform? Read Concepts first. Set these env vars once:
export METALHOST_ENDPOINT=https://api.metalhost.net
export METALHOST_API_KEY=mh_live_...
export METALHOST_PROJECT=projects/my-project Discover projects and datacenters
CLI
metalhost auth whoami -o json
metalhost project list
metalhost catalog datacenter list
metalhost catalog capacity --datacenter datacenters/us-dal-1 Go
projectv1connect.NewProjectServiceClient(httpClient, baseURL).ListProjects(ctx, ...)
catalogv1connect.NewCatalogServiceClient(httpClient, baseURL).ListDatacenters(ctx, ...) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.project.v1.ProjectService/ListProjects" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" -d '{}' Who am I?
CLI
metalhost auth whoami -o json Go
import (
iamv1connect "github.com/AES-Services/metalhost-sdk/gen/go/aes/iam/v1/iamv1connect"
iamv1 "github.com/AES-Services/metalhost-sdk/gen/go/aes/iam/v1"
)
resp, err := iamv1connect.NewIamServiceClient(httpClient, baseURL).GetCallerIdentity(
ctx, connect.NewRequest(&iamv1.GetCallerIdentityRequest{}),
) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.iam.v1.IamService/GetCallerIdentity" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{}' Register an SSH key
CLI
metalhost vm ssh-key create \
--id laptop \
--public-key "$(cat ~/.ssh/id_ed25519.pub)" Go
keys := computev1connect.NewSSHKeysServiceClient(httpClient, baseURL)
_, err := keys.CreateSSHKey(ctx, connect.NewRequest(&computev1.CreateSSHKeyRequest{
ProjectName: os.Getenv("METALHOST_PROJECT"),
SshKeyId: "laptop",
PublicKey: string(mustRead("~/.ssh/id_ed25519.pub")),
})) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.SSHKeysService/CreateSSHKey" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg p "$METALHOST_PROJECT" --arg k "$(cat ~/.ssh/id_ed25519.pub)" \
'{projectName:$p, sshKeyId:"laptop", publicKey:$k}')" Create a VM (flags)
CLI
metalhost vm create \
--hostname web-1 \
--vcpus 2 --ram-gib 8 --cpu-class cascadelake \
--image ubuntu-24-04 --disk-size-gib 80 \
--assign-public-ipv4 \
--ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
--wait Go
compute := computev1connect.NewComputeServiceClient(httpClient, baseURL)
resp, err := compute.CreateVirtualMachine(ctx, connect.NewRequest(
&computev1.CreateVirtualMachineRequest{
Manifest: &computev1.VirtualMachineManifest{
Metadata: &computev1.VirtualMachineMetadata{
Name: "web-1", Project: os.Getenv("METALHOST_PROJECT"),
},
Spec: &computev1.VirtualMachineSpec{
Region: "datacenters/us-dal-1",
Compute: &computev1.VMComputeSpec{
CpuClass: "cascadelake", Vcpus: 2, RamGib: 8,
},
Boot: &computev1.VMBootSpec{Image: "ubuntu-24-04", DiskGib: 80},
Network: &computev1.VMNetworkSpec{PublicIpv4: true},
Users: []*computev1.UserSpec{{
Name: "ubuntu",
SshKeys: []string{os.Getenv("METALHOST_PROJECT") + "/ssh-keys/laptop"},
}},
},
},
},
))
opName := resp.Msg.GetOperation().GetName() curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/CreateVirtualMachine" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"manifest": {
"metadata": { "name": "web-1", "project": "projects/my-project" },
"spec": {
"region": "datacenters/us-dal-1",
"compute": { "cpuClass": "cascadelake", "vcpus": 2, "ramGib": 8 },
"boot": { "image": "ubuntu-24-04", "diskGib": 80 },
"network": { "publicIpv4": true },
"users": [{ "name": "ubuntu", "sshKeys": ["projects/my-project/ssh-keys/laptop"] }]
}
}
}
EOF Create a GPU VM
Whole-GPU passthrough — check capacity first with
metalhost catalog capacity. Full manifest fields:
CLI → VMs (YAML).
CLI
metalhost vm create \
--hostname gpu-1 \
--vcpus 8 --ram-gib 32 --cpu-class cascadelake \
--gpu-model rtx4090 --gpu-count 1 \
--image ubuntu-24-04 --disk-size-gib 100 \
--assign-public-ipv4 \
--ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
--wait Go
compute.CreateVirtualMachine(ctx, connect.NewRequest(
&computev1.CreateVirtualMachineRequest{
Manifest: &computev1.VirtualMachineManifest{
Metadata: &computev1.VirtualMachineMetadata{Name: "gpu-1", Project: project},
Spec: &computev1.VirtualMachineSpec{
Region: "datacenters/us-dal-1",
Compute: &computev1.VMComputeSpec{
CpuClass: "cascadelake", Vcpus: 8, RamGib: 32,
Gpu: &computev1.GPUSpec{Model: "rtx4090", Count: 1},
},
Boot: &computev1.VMBootSpec{Image: "ubuntu-24-04", DiskGib: 100},
Network: &computev1.VMNetworkSpec{PublicIpv4: true},
},
},
},
)) Create a VM (YAML manifest)
Full guide with GPU, billing, cloud-init, and multi-user examples: CLI → VMs (YAML manifest). Minimal example:
apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
name: web-1
project: projects/my-project
spec:
region: datacenters/us-dal-1
compute:
cpuClass: cascadelake
vcpus: 2
ramGib: 8
boot:
image: ubuntu-24-04
diskGib: 80
network:
publicIpv4: true
users:
- name: ubuntu
sshKeys:
- projects/my-project/ssh-keys/laptop CLI
metalhost vm apply -f vm.yaml --wait Go
Unmarshal YAML into computev1.VirtualMachineManifest (e.g.
with google.golang.org/protobuf/encoding/protojson after a
YAML→JSON hop), then pass to
CreateVirtualMachineRequest with a
Manifest field.
curl
Convert the manifest to the JSON shape in the previous section and POST to
CreateVirtualMachine.
Poll an operation
CLI
metalhost ops wait operations/op_abc123
# or block on the mutating command directly:
metalhost vm create ... --wait Go
ops := opsv1connect.NewOperationsServiceClient(httpClient, baseURL)
for {
r, _ := ops.GetOperation(ctx, connect.NewRequest(
&opsv1.GetOperationRequest{Name: opName},
))
st := r.Msg.GetOperation().GetState()
if st == opsv1.State_STATE_SUCCEEDED || st == opsv1.State_STATE_FAILED {
break
}
time.Sleep(2 * time.Second)
} curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.ops.v1.OperationsService/GetOperation" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"operations/op_abc123"}' | jq '.operation.state' List and get VMs
CLI
metalhost vm list
metalhost vm get web-1 -o json | jq '.virtualMachine.publicIpv4' Go
list, _ := compute.ListVirtualMachines(ctx, connect.NewRequest(
&computev1.ListVirtualMachinesRequest{ProjectName: project, PageSize: 100},
))
one, _ := compute.GetVirtualMachine(ctx, connect.NewRequest(
&computev1.GetVirtualMachineRequest{Name: "projects/my-project/virtual-machines/web-1"},
)) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/ListVirtualMachines" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"projectName\":\"$METALHOST_PROJECT\"}" VM lifecycle
Start, stop, resize, snapshot, and clone — see CLI → VMs.
CLI
metalhost vm stop web-1
metalhost vm start web-1
metalhost vm resize web-1 --vcpus 4 --ram-gib 16 --cpu-class cascadelake
metalhost vm snapshot create web-1 --display-name pre-upgrade
metalhost vm clone --source projects/my-project/virtual-machines/web-1 --display-name web-1-clone Go
compute.StopVirtualMachine(ctx, connect.NewRequest(&computev1.StopVirtualMachineRequest{Name: vm}))
compute.StartVirtualMachine(ctx, connect.NewRequest(&computev1.StartVirtualMachineRequest{Name: vm}))
compute.ResizeVirtualMachine(ctx, connect.NewRequest(&computev1.ResizeVirtualMachineRequest{
Name: vm, Vcpus: 4, RamGib: 16, CpuClass: "cascadelake",
}))
compute.CreateVmSnapshot(ctx, connect.NewRequest(&computev1.CreateVmSnapshotRequest{
VmName: vm, DisplayName: "pre-upgrade",
}))
compute.CloneVirtualMachine(ctx, connect.NewRequest(&computev1.CloneVirtualMachineRequest{
SourceVmName: vm, TargetDisplayName: "web-1-clone",
})) Delete a VM
CLI
metalhost vm delete web-1 --yes --wait Go
_, err := compute.DeleteVirtualMachine(ctx, connect.NewRequest(
&computev1.DeleteVirtualMachineRequest{
Name: "projects/my-project/virtual-machines/web-1",
},
)) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/DeleteVirtualMachine" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"projects/my-project/virtual-machines/web-1"}' Quote pricing
CLI
metalhost catalog pricing quote \
--vcpus 2 --ram-gib 8 --cpu-class cascadelake --boot-disk-gib 80 Go
catalog := catalogv1connect.NewCatalogServiceClient(httpClient, baseURL)
_, err := catalog.QuoteVirtualMachine(ctx, connect.NewRequest(
&catalogv1.QuoteVirtualMachineRequest{
Vcpus: 2, RamGib: 8, CpuClass: "cascadelake", BootDiskGib: 80,
},
)) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.catalog.v1.CatalogService/QuoteVirtualMachine" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vcpus":2,"ramGib":8,"cpuClass":"cascadelake","bootDiskGib":80}' Lease bare metal
CLI
metalhost bare-metal available list
metalhost bare-metal quote hosts/dal-r740-01 --billing-mode monthly-1
metalhost bare-metal create \
--name my-box --host hosts/dal-r740-01 \
--image ubuntu-24-04 --ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
--billing-mode monthly-1 --public-ipv4-prefix-len 32 Go
bm := baremetalv1connect.NewBareMetalServiceClient(httpClient, baseURL)
bm.ListAvailableBareMetal(ctx, connect.NewRequest(&baremetalv1.ListAvailableBareMetalRequest{}))
bm.CreateBareMetalInstance(ctx, connect.NewRequest(&baremetalv1.CreateBareMetalInstanceRequest{
Name: "bare-metal-instances/my-box", ProjectName: project,
HostName: "hosts/dal-r740-01", OsImage: "ubuntu-24-04",
SshKeyNames: []string{project + "/ssh-keys/laptop"},
})) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.baremetal.v1.BareMetalService/ListAvailableBareMetal" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" -d '{}' Bare-metal leases include unlimited bandwidth — no transfer metering. Full lifecycle (power, ISO, release): Go SDK — Bare metal.
Create a file share
CLI
metalhost storage file-share create \
--size-gib 100 \
--datacenter datacenters/us-dal-1 \
--display-name shared-data Go
storage.CreateFileShare(ctx, connect.NewRequest(&storagev1.CreateFileShareRequest{
ProjectName: project, DatacenterName: "datacenters/us-dal-1",
SizeGib: 100, DisplayName: "shared-data",
})) Mount from any VM on the same tenant network — CLI → Storage.
Create and attach a disk
CLI
metalhost storage disk create --size-gib 100 --region datacenters/us-dal-1
metalhost storage disk attach projects/my-project/disks/data-1 web-1
metalhost storage disk resize projects/my-project/disks/data-1 --size-gib 200 Go
storage := storagev1connect.NewStorageServiceClient(httpClient, baseURL)
storage.CreateDisk(ctx, connect.NewRequest(&storagev1.CreateDiskRequest{
Parent: project,
Disk: &storagev1.Disk{
DatacenterName: "datacenters/us-dal-1",
SizeGib: 100, StorageClass: "nvme",
},
}))
storage.AttachDisk(ctx, connect.NewRequest(&storagev1.AttachDiskRequest{
DiskName: "projects/my-project/disks/data-1",
VmName: "projects/my-project/virtual-machines/web-1",
})) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.storage.v1.StorageService/CreateDisk" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
-d '{"parent":"projects/my-project","disk":{"datacenterName":"datacenters/us-dal-1","sizeGib":100,"storageClass":"nvme"}}' Create a tenant network
Only needed for extra isolation — default network is auto-created per datacenter.
CLI
metalhost network create \
--id staging-net \
--region datacenters/us-dal-1 \
--display-name "Staging" Go
net.CreateNetwork(ctx, connect.NewRequest(&networkv1.CreateNetworkRequest{
ProjectName: project, NetworkId: "staging-net",
DatacenterName: "datacenters/us-dal-1", DisplayName: "Staging",
})) Open a firewall port
CLI
metalhost network firewall create \
--vm web-1 --datacenter datacenters/us-dal-1 \
--port tcp:22 --source 203.0.113.10/32 \
--display-name "ssh from office" Go
net := networkv1connect.NewNetworkServiceClient(httpClient, baseURL)
net.CreateFirewallRule(ctx, connect.NewRequest(&networkv1.CreateFirewallRuleRequest{
ProjectName: project, DatacenterName: "datacenters/us-dal-1",
TargetVm: project + "/virtual-machines/web-1",
Sources: []string{"203.0.113.10/32"},
Ports: []*networkv1.PortMapping{{Protocol: "tcp", Port: 22}},
})) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.network.v1.NetworkService/CreateFirewallRule" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
-d '{"projectName":"projects/my-project","datacenterName":"datacenters/us-dal-1","targetVm":"projects/my-project/virtual-machines/web-1","sources":["203.0.113.10/32"],"ports":[{"protocol":"tcp","port":22}]}' Register a webhook
CLI support is limited — use the SDK or curl. Verify deliveries with HMAC-SHA256:
Go
hooks := webhooksv1connect.NewWebhooksServiceClient(httpClient, baseURL)
resp, _ := hooks.CreateSubscription(ctx, connect.NewRequest(
&webhooksv1.CreateSubscriptionRequest{
Name: "webhook-subscriptions/deploy-hook",
ProjectName: project,
EndpointUrl: "https://hooks.example.com/metalhost",
EventTypes: []string{"compute.vm.*", "ops.operation.completed"},
},
))
secret := resp.Msg.GetSecret() // store for HMAC verification curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.webhooks.v1.WebhooksService/CreateSubscription" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"webhook-subscriptions/deploy-hook","projectName":"projects/my-project","endpointUrl":"https://hooks.example.com/metalhost","eventTypes":["compute.vm.*"]}' See Go SDK — Webhooks for signature verification.
Mint an API key
CLI
metalhost iam keys create \
--display-name ci-bot \
--project projects/my-app \
--project-scoped \
-o json | jq -r '.secret' Go
iam.CreateApiKey(ctx, connect.NewRequest(&iamv1.CreateApiKeyRequest{
DisplayName: "ci-bot", DefaultProject: project, ProjectScoped: true,
}))
// resp.Msg.Secret — shown once; store immediately Dashboard walkthrough: API keys.
Open a support ticket
CLI
metalhost support ticket create \
--org organizations/my-org \
--subject "Quota increase" \
--body "Need 50 VMs in projects/prod for load test." \
--category BILLING Go
support.CreateTicket(ctx, connect.NewRequest(&supportv1.CreateTicketRequest{
OrganizationName: org, Subject: "Quota increase",
Body: "Need 50 VMs in projects/prod for load test.",
Category: supportv1.TicketCategory_TICKET_CATEGORY_BILLING,
})) Check quota usage
CLI
metalhost quota --project projects/my-app -o json Request increases via support — CLI → Support.
Check wallet balance
CLI
metalhost wallet account list --org organizations/my-org
metalhost wallet balance organizations/my-org/wallets/default Go
wallet := walletv1connect.NewWalletServiceClient(httpClient, baseURL)
_, err := wallet.GetWalletBalance(ctx, connect.NewRequest(
&walletv1.GetWalletBalanceRequest{
Name: "organizations/my-org/wallets/default",
},
)) curl
curl -sS -X POST "$METALHOST_ENDPOINT/aes.wallet.v1.WalletService/GetWalletBalance" \
-H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"organizations/my-org/wallets/default"}' Top up with crypto (Coinbase)
Returns a checkout URL — settled 1:1 in USDC. Minimum $5 (500 minor units).
CLI
metalhost wallet top-up coinbase \
--wallet organizations/my-org/wallets/default \
--amount-minor 5000 Card top-ups and payment methods: CLI → Wallet.
CI script pattern
A minimal bash deploy script using only the CLI and jq:
#!/usr/bin/env bash
set -euo pipefail
: "${METALHOST_API_KEY:?}"
: "${METALHOST_PROJECT:?}"
NAME="ci-$(date +%s)"
metalhost vm create \
--hostname "$NAME" \
--vcpus 2 --ram-gib 4 --cpu-class cascadelake \
--image ubuntu-24-04 --disk-size-gib 40 \
--ssh-key-name "$METALHOST_PROJECT/ssh-keys/ci" \
--wait -o json > create.json
IP=$(jq -r '.operation.metadata.virtual_machine_name // empty' create.json)
VM=$(metalhost vm get "$NAME" -o json)
IP=$(echo "$VM" | jq -r '.virtualMachine.publicIpv4')
echo "VM $NAME at $IP"
# ... run tests over SSH ...
metalhost vm delete "$NAME" --yes --wait What's next
- Concepts — platform mental model.
- CLI quickstart — guided first run.
- Go SDK — Getting started — complete program and service map.
- Go SDK — VMs — manifest reference.
- CLI reference — every command.
- API reference — schema browser.