SDKs / Go
Go SDK
Install pantech-go, create a client, make a machine and wait for it, and handle errors, pages and retries.
c, _ := pantech.NewClient()
me, _ := c.Me.Get(ctx)
fmt.Println(me.OrganizationID, me.APIKey.Scopes)pantech-go is the official Go library. It needs Go 1.23 or later and has no dependencies outside the standard library.
Install
$ go get github.com/Pantech-Dynamics/pantech-goimport pantech "github.com/Pantech-Dynamics/pantech-go"The package is called pantech.
Authenticate
Make an API key and put it in PANTECH_API_KEY. NewClient reads it.
$ export PANTECH_API_KEY=PAN_…Or pass options:
c, err := pantech.NewClient(
pantech.WithAPIKey(os.Getenv("MY_PANTECH_KEY")),
pantech.WithBaseURL("https://api.pantechdynamics.com/public/v1"), // or PANTECH_API_URL
pantech.WithMaxRetries(5), // default 3; 0 turns retries off
)The other options are WithHTTPClient(h) (its Timeout bounds each attempt; the default is 90 seconds), WithUserAgent(ua) and WithHeader(key, value).
c.Me.Get(ctx) describes the key, and c.Me.Revoke(ctx) revokes it (DELETE /me).
Create a machine and wait for it
package main
import (
"context"
"fmt"
"log"
pantech "github.com/Pantech-Dynamics/pantech-go"
)
func main() {
ctx := context.Background()
c, err := pantech.NewClient()
if err != nil {
log.Fatal(err)
}
order, err := c.Instances.Create(ctx, pantech.CreateInstanceRequest{
Name: "web-01",
PlanSlug: "individual",
ImageSlug: "ubuntu-24-04",
SSHKeyID: "sshk_…",
})
if err != nil {
log.Fatal(err)
}
// Creating a machine places an order: payment, then provisioning.
// WaitForOrder returns when the order is provisioned, or an
// *pantech.OrderError if it ends payment_failed or failed.
provisioned, err := c.Instances.WaitForOrder(ctx, order.OrderID, nil)
if err != nil {
log.Fatal(err)
}
if provisioned.OperationID != nil {
if _, err := c.Operations.Wait(ctx, *provisioned.OperationID, nil); err != nil {
log.Fatal(err)
}
}
instance, err := c.Instances.Get(ctx, order.InstanceID)
if err != nil {
log.Fatal(err)
}
fmt.Println(instance.Name, instance.ObservedState)
if instance.PublicIPv4 != nil {
fmt.Println(*instance.PublicIPv4)
}
}Optional request fields are pointers, and a nil field is not sent. pantech.Ptr sets one inline, for example Tags: pantech.Ptr(map[string]string{"env": "prod"}). c.Databases.WaitForOrder does the same for a new database.
Read real slugs from the catalogue first: c.Catalog lists regions, plans, images, disk offerings, database engines and Kubernetes versions, and estimates prices.
Services
Each part of the API is a field on the client:
c.Instances, c.Networks, c.Subnets, c.FirewallRules, c.PublicIPs, c.PortForwardingRules, c.SecurityGroups, c.LoadBalancers, c.Volumes, c.Snapshots, c.Databases, c.Kubernetes, c.SSHKeys, c.Webhooks, c.Operations, c.Catalog, c.Billing and c.Me.
Errors
Every API error is a *pantech.Error, with Status, Code, Title, Detail, RequestID, Errors (the invalid fields of a 422) and RetryAfter. Check the code with pantech.IsCode, and get the error with pantech.AsError (or errors.As):
_, err := c.Instances.Get(ctx, "vm_…")
if pantech.IsCode(err, "RESOURCE_NOT_FOUND") {
fmt.Println("no such machine")
}
if apiErr, ok := pantech.AsError(err); ok {
log.Printf("%s: %s (request %s)", apiErr.Code, apiErr.Detail, apiErr.RequestID)
}Act on the code, never the wording. Every code.
Pages
A paginated list has two methods. List returns one *pantech.Page[T] (Data, NextCursor, HasMore()). All iterates over every item, following next_cursor for you, as a Go iter.Seq2:
page, err := c.Instances.List(ctx, &pantech.ListParams{Limit: 50, Q: "web"})
if err != nil {
log.Fatal(err)
}
fmt.Println(len(page.Data), page.HasMore())
for instance, err := range c.Instances.All(ctx, nil) {
if err != nil {
log.Fatal(err)
}
fmt.Println(instance.Name)
}Lists the API returns in one page, such as regions, plans, images and SSH keys, return a plain slice.
Operations
Most changes return an operation. Wait for it before you rely on the result:
accepted, err := c.Instances.Stop(ctx, "vm_…")
if err != nil {
log.Fatal(err)
}
op, err := c.Operations.Wait(ctx, accepted.OperationID, &pantech.WaitOptions{Timeout: 10 * time.Minute})
var failed *pantech.OperationError
if errors.As(err, &failed) {
fmt.Println(failed.Operation.Failure.Code, failed.Operation.Failure.Reason)
} else if err == nil {
fmt.Println(op.Status)
}Wait polls GET /operations/{id} (every 2 seconds by default; pass nil for the defaults) until the status is succeeded or failed, and returns a *pantech.OperationError when it failed. c.Operations.Stream(ctx, id) follows it over Server-Sent Events instead.
Idempotency and retries
Every POST, PUT, PATCH and DELETE gets an Idempotency-Key automatically (a random UUID). The same key is sent on every retry, so the change happens once. To make a change safe to repeat across runs, choose the key yourself:
order, err := c.Instances.Create(ctx, req, pantech.WithIdempotencyKey("deploy-42-web-1"))The client retries network errors, 429, 500, 502, 503 and 504 up to 3 times, with a growing, random wait (0.5 s, 1 s, 2 s… up to 8 s), and honours Retry-After (up to 60 seconds). Calls the API does not replay are retried only on 429, which the API refuses before acting: creating or deleting an SSH key, opening an instance console, and testing, deleting, rotating the secret of or redelivering a webhook. The list. Cancelling the context stops a call and its retries at once.
Calling any endpoint
Do calls a path directly, with the same auth, retries and errors:
var out map[string]any
err := c.Do(ctx, "GET", "/instances", url.Values{"q": {"web"}}, nil, &out)Every typed method, and Do, takes per-request options: pantech.WithIdempotencyKey(key) to choose the key yourself, pantech.WithRequestHeader(key, value), and pantech.WithNoRetry() to send a call once.
Webhooks
pantech.VerifyWebhook(secret, header, body, tolerance) checks a delivery's signature and returns an error if it fails. pantech.ParseWebhook checks it and decodes the event in one call. Verify webhooks with the SDKs.