Go SDK
The metalhost-sdk module is the public API contract:
protobuf types, Connect-RPC clients for every customer-facing service, and
a thin metalhost.Config auth helper. The CLI is built on this
module. Read Concepts first if you're new to
the platform model.
Install
go get github.com/AES-Services/metalhost-sdk@latest
Module: github.com/AES-Services/metalhost-sdk ·
Repo: github.com/AES-Services/metalhost-sdk
Dependencies you'll also need in your go.mod:
go get connectrpc.com/connect@latest Environment variables
| Variable | Required | Purpose |
|---|---|---|
METALHOST_API_KEY | Yes | Bearer token for every RPC |
METALHOST_ENDPOINT | No | API base URL (default https://api.metalhost.net) |
METALHOST_PROJECT | For VM ops | Default project, e.g. projects/my-app |
METALHOST_REGION | For VM ops | Default datacenter, e.g. datacenters/us-dal-1 |
Mint an API key in the dashboard: Developers → API keys → Create. Fund the wallet before creating VMs.
Complete program
This program creates a VM, waits for provisioning, and prints an SSH command. It is the SDK equivalent of the CLI quickstart.
// cmd/metalhost-demo/main.go — end-to-end: create VM, wait, print SSH target.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"time"
"connectrpc.com/connect"
"github.com/AES-Services/metalhost-sdk/metalhost"
computev1 "github.com/AES-Services/metalhost-sdk/gen/go/aes/compute/v1"
computev1connect "github.com/AES-Services/metalhost-sdk/gen/go/aes/compute/v1/computev1connect"
opsv1 "github.com/AES-Services/metalhost-sdk/gen/go/aes/ops/v1"
opsv1connect "github.com/AES-Services/metalhost-sdk/gen/go/aes/ops/v1/opsv1connect"
)
func main() {
cfg := metalhost.Config{
Endpoint: env("METALHOST_ENDPOINT", "https://api.metalhost.net"),
APIKey: os.Getenv("METALHOST_API_KEY"),
}
if cfg.APIKey == "" {
log.Fatal("METALHOST_API_KEY is required")
}
httpClient := &http.Client{
Transport: cfg.RoundTripper(http.DefaultTransport),
Timeout: 60 * time.Second,
}
base := cfg.BaseURL()
compute := computev1connect.NewComputeServiceClient(httpClient, base)
ops := opsv1connect.NewOperationsServiceClient(httpClient, base)
ctx := context.Background()
project := env("METALHOST_PROJECT", "")
region := env("METALHOST_REGION", "datacenters/us-dal-1")
sshKey := env("METALHOST_SSH_KEY", project+"/ssh-keys/laptop")
hostname := env("METALHOST_VM_NAME", "web-1")
createResp, err := compute.CreateVirtualMachine(ctx, connect.NewRequest(
&computev1.CreateVirtualMachineRequest{
Manifest: &computev1.VirtualMachineManifest{
ApiVersion: "compute.metalhost.io/v1",
Kind: "VirtualMachine",
Metadata: &computev1.VirtualMachineMetadata{
Name: hostname, Project: project,
},
Spec: &computev1.VirtualMachineSpec{
Region: region,
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{sshKey},
}},
},
},
},
))
if err != nil {
log.Fatal(err)
}
op := createResp.Msg.GetOperation()
if op == nil {
log.Fatal("CreateVirtualMachine returned no operation")
}
final, err := waitOperation(ctx, ops, op.GetName(), 10*time.Minute)
if err != nil {
log.Fatal(err)
}
if final.GetState() != opsv1.State_STATE_SUCCEEDED {
log.Fatalf("provision failed: %s", final.GetErrorMessage())
}
vmName := final.GetMetadata()["virtual_machine_name"]
if vmName == "" {
vmName = project + "/virtual-machines/" + hostname
}
vmResp, err := compute.GetVirtualMachine(ctx, connect.NewRequest(
&computev1.GetVirtualMachineRequest{Name: vmName},
))
if err != nil {
log.Fatal(err)
}
vm := vmResp.Msg.GetVirtualMachine()
fmt.Printf("ssh %s@%s\n", vm.GetLinuxUsername(), vm.GetPublicIpv4())
}
func waitOperation(ctx context.Context, ops opsv1connect.OperationsServiceClient, name string, timeout time.Duration) (*opsv1.Operation, error) {
deadline := time.Now().Add(timeout)
for {
resp, err := ops.GetOperation(ctx, connect.NewRequest(&opsv1.GetOperationRequest{Name: name}))
if err != nil {
return nil, err
}
op := resp.Msg.GetOperation()
switch op.GetState() {
case opsv1.State_STATE_SUCCEEDED, opsv1.State_STATE_FAILED, opsv1.State_STATE_CANCELLED:
return op, nil
}
if time.Now().After(deadline) {
return nil, fmt.Errorf("timeout waiting for %s", name)
}
time.Sleep(2 * time.Second)
}
}
func env(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
} export METALHOST_API_KEY=mh_live_...
export METALHOST_PROJECT=projects/my-app
export METALHOST_SSH_KEY=projects/my-app/ssh-keys/laptop
go run ./cmd/metalhost-demo Configure clients
Every Connect service client takes an *http.Client and base
URL. Wrap the transport with cfg.RoundTripper so the API key
is sent on every request:
cfg := metalhost.Config{
Endpoint: os.Getenv("METALHOST_ENDPOINT"), // https://api.metalhost.net
APIKey: os.Getenv("METALHOST_API_KEY"),
}
httpClient := &http.Client{
Transport: cfg.RoundTripper(http.DefaultTransport),
}
compute := computev1connect.NewComputeServiceClient(httpClient, cfg.BaseURL())
Create one *http.Client and reuse it across all service
clients (compute, ops, storage, network, etc.).
HTTP protocol
Every RPC is:
- Method:
POST - Path:
/aes.<domain>.v1.<Service>/<Method> - Header:
Authorization: Bearer <API_KEY> - Header:
Content-Type: application/json - Body: proto-JSON request message (camelCase fields)
Wrap requests with connect.NewRequest and a pointer to the
request struct.
Read responses from resp.Msg. Full schema for every method is
in the OpenAPI viewer.
Resource names
Pass fully-qualified names or bare slugs (CLI expands slugs; in SDK you should use full names). See Concepts → Resource names.
Topic guides
The getting-started sections below cover install, auth, protocol, and errors. Per-resource guides have field tables, constraints, and full code samples:
- VMs — manifest reference, create, quote, SSH keys, lifecycle, snapshots
- Bare metal — browse, quote, lease, power, ISO library, release
- Storage — block disks and NFS file shares
- Network & firewall — tenant networks and per-VM rules
- Wallet & billing — balance, usage, top-ups, invoices
- IAM & API keys — identity, keys, org members
- Webhooks — subscriptions, HMAC verification
- Support — tickets and quota requests
- Catalog & quotas — datacenters, capacity, audit
Long-running operations
VM create, delete, resize, clone, and reimage return an
operation field. Poll OperationsService/GetOperation
until state is SUCCEEDED,
FAILED, or CANCELLED.
Important metadata keys on success:
virtual_machine_name— full VM resource name after create/clone
The complete program above includes a waitOperation helper.
The SDK does not ship a built-in wait function — copy that loop or use the
CLI's ops wait for debugging.
Pagination
List RPCs accept pageSize and pageToken. Pass
the nextPageToken from the prior response to fetch the next
page. The CLI's --all flag loops this for you.
Error handling
Failed RPCs return a connect.Error with a gRPC-style code
(NotFound, InvalidArgument,
FailedPrecondition, PermissionDenied, etc.):
resp, err := compute.GetVirtualMachine(ctx, req)
if err != nil {
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
// connectErr.Code() is connect.Code (NotFound, InvalidArgument, etc.)
// connectErr.Message() is the server error string
}
return err
} Common causes:
FailedPrecondition— wallet balance too low for create or prepaid termInvalidArgument— manifest validation failed (missing cpuClass, bad ratio, etc.)NotFound— resource name doesn't exist or wrong project scopePermissionDenied— API key lacks access to the project
Services and key RPCs
Import clients from gen/go/aes/<domain>/v1/*v1connect.
Every RPC path is listed in the OpenAPI spec.
| Client | Key RPCs |
|---|---|
ComputeServiceClient | Create/Get/List/Delete/Start/Stop/Restart/Resize/Reimage/Clone VirtualMachine; Snapshot; GetVMMetrics; OpenConsole |
SSHKeysServiceClient | Create/List/Delete SSHKey |
OperationsServiceClient | Get/List Operation |
StorageServiceClient | Create/Get/List/Delete Disk; File shares |
NetworkServiceClient | Create/Get/List Network; Firewall rules |
CatalogServiceClient | ListDatacenters; QuoteVirtualMachine; GetVMCapacity |
ProjectServiceClient | Create/Get/List Project; ListOrganizations |
WalletServiceClient | GetBalance; CreateTopUp; ListUsage; Invoices |
IamServiceClient | GetCallerIdentity; Create/Revoke ApiKey; Login; Org members |
BareMetalServiceClient | ListAvailable; Create/Get/Release instance; Power; ISO |
WebhooksServiceClient | Create/List/Delete subscriptions; List deliveries |
QuotaServiceClient | Get org/project quotas |
AuditServiceClient | SearchEvents |
SupportServiceClient | Create/List tickets |
HealthServiceClient | Check |
HTTP/JSON without Go
Same request shape as the SDK — useful for quick tests and non-Go integrations:
curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/ListVirtualMachines" \
-H "Authorization: Bearer $METALHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"projectName":"projects/my-app"}' What's next
- Go SDK — VMs — manifest reference and lifecycle.
- Examples cookbook — CLI, Go, and curl side by side.
- CLI quickstart — fastest path to a running VM.
- API reference — every RPC and schema.