---
title: "Go SDK"
description: "Build on Metalhost with the Go SDK — auth, Connect clients, VM manifests, operations, pagination, and a complete working program."
url: "https://metalhost.net/docs/developers/sdk"
---

# 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](https://metalhost.net/docs/developers/concepts.md) first if you're new to the platform model.

> **other languages**
>
> Generate clients from the [OpenAPI spec](https://metalhost.net/docs/developers/api.md) (`/openapi.yaml`) for Python, TypeScript, Java, etc. The HTTP path and JSON body shapes are identical — see [Examples](https://metalhost.net/docs/developers/examples.md) for curl copies of every common task.

## Install

```
go get github.com/AES-Services/metalhost-sdk@v1.1.2
```

[SDK v1.1.2](https://github.com/AES-Services/metalhost-sdk/releases/tag/v1.1.2) adds monitoring, alerts, service accounts, and GitHub Actions on top of the [v1.1.0](https://github.com/AES-Services/metalhost-sdk/releases/tag/v1.1.0) backup contracts. See [Monitoring & automation](https://metalhost.net/docs/developers/observability.md) and the [backup API guide](https://metalhost.net/docs/developers/guides/sdk-storage.md).

Module: `github.com/AES-Services/metalhost-sdk` · Repo: [github.com/AES-Services/metalhost-sdk](https://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](https://metalhost.net/docs/developers/quickstart.md).

```
// 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=aes_...
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](https://metalhost.net/docs/developers/api.md).

## Resource names

Pass fully-qualified names or bare slugs (CLI expands slugs; in SDK you should use full names). See [Concepts → Resource names](https://metalhost.net/docs/developers/concepts.md#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](https://metalhost.net/docs/developers/guides/sdk-vms.md) — manifest reference, create, quote, SSH keys, lifecycle, snapshots
- [Bare metal](https://metalhost.net/docs/developers/guides/sdk-bare-metal.md) — browse, quote, lease, power, ISO library, release
- [Storage](https://metalhost.net/docs/developers/guides/sdk-storage.md) — block disks and NFS file shares
- [Network & firewall](https://metalhost.net/docs/developers/guides/sdk-network.md) — tenant networks and per-VM rules
- [Wallet & billing](https://metalhost.net/docs/developers/guides/sdk-wallet.md) — balance, usage, top-ups, invoices
- [Monitoring & automation](https://metalhost.net/docs/developers/observability.md) — metrics, alerts, service accounts, GitHub Actions
- [IAM & API keys](https://metalhost.net/docs/developers/guides/sdk-iam.md) — identity, keys, org members
- [Webhooks](https://metalhost.net/docs/developers/guides/sdk-webhooks.md) — subscriptions, HMAC verification
- [Support](https://metalhost.net/docs/developers/guides/sdk-support.md) — tickets and quota requests
- [Catalog & quotas](https://metalhost.net/docs/developers/guides/sdk-catalog.md) — 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 term
- `InvalidArgument` — manifest validation failed (missing cpuClass, bad ratio, etc.)
- `NotFound` — resource name doesn't exist or wrong project scope
- `PermissionDenied` — 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](https://metalhost.net/docs/developers/api.md).

| 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](https://metalhost.net/docs/developers/guides/sdk-vms.md) — manifest reference and lifecycle.
- [Examples cookbook](https://metalhost.net/docs/developers/examples.md) — CLI, Go, and curl side by side.
- [CLI quickstart](https://metalhost.net/docs/developers/quickstart.md) — fastest path to a running VM.
- [API reference](https://metalhost.net/docs/developers/api.md) — every RPC and schema.
- [Markdown pages](https://metalhost.net/docs/developers/markdown.md) — curl this page as `.md` for a local agent.
