Go quickstart
A five-minute path to a working Go Driftstack session. For the multi-language overview see the combined quickstart.
Prerequisites
- Go 1.22+ (the SDK uses generic constraints +
slicespackage). - The Go module at
v0.7.0or newer. The examples on this page are written against it. - A paid plan: these steps use the AI agent, which the free trial does not include.
- A
ds_live_…customer API key. Mint one at app.driftstack.io/api-keys. - The id of one of your saved proxies: every agent session goes out through a proxy you chose. See Find a proxy’s id.
- A way to pay for the AI: your plan’s monthly AI credits, or your own Anthropic key. See Who pays for the AI.
1. Install
go get github.com/driftstackdev/driftstack-go@latest
The Go SDK is published as a tagged pre-1.0 module. Commit the resulting
go.modandgo.sumso production builds remain reproducible.
Moving from the old import path? A program that imports
github.com/driftstackdev/driftstack-api/packages/sdk-gokeeps building: the module proxy serves the versions it already has of that path (v0.4.0 and earlier). New versions land only ongithub.com/driftstackdev/driftstack-go. To move, change the import path, rungo get github.com/driftstackdev/driftstack-go@latestandgo mod tidy. The package name is stilldriftstack, so an import line changes only its path. The API does not carry over unchanged, though: the move also takes you from v0.4.0 to v0.5.0, a breaking release. Nine resources left the module for the dashboard —APIKeys,Auth,Mfa,Billing,CryptoOrders,Team,Legal,AuditLogandEmailPreferences— along withProfiles.Transferand the desktop-app controls onAgentSessions(SetMode,Takeover,Handback,SendInputEvent); the compiler flags every call to those. It cannot flag the one behaviour change:AgentSessions.Createnow requiresProxyID, and a create without one is refused at run time. Read Upgrading from v0.4.0 in the module’s CHANGELOG before you move.
2. Configure the client
package main
import (
"context"
"os"
driftstack "github.com/driftstackdev/driftstack-go"
)
func main() {
client := driftstack.New(os.Getenv("DRIFTSTACK_API_KEY"))
defer client.Close()
ctx := context.Background()
_ = ctx
_ = client
}
driftstack.New returns *Client. The constructor accepts options:
client := driftstack.New(
os.Getenv("DRIFTSTACK_API_KEY"),
driftstack.WithBaseURL("https://api.driftstack.dev"), // the default; set it only to pin the host
driftstack.WithHTTPClient(myInstrumentedHTTPClient), // OpenTelemetry, retries, etc.
)
Close() releases the underlying http.Transport connection pool.
Call once at process shutdown; idiomatic Go is defer client.Close()
in main.
3. Run your first session
An agent session is an iPhone Safari browser in the cloud that an AI drives for you: you
send a task in plain words, and the agent plans the taps and page loads, runs
them, and answers. Every agent session goes out through one of your saved
proxies, so the create names its id (proxy_id).
package main
import (
"context"
"fmt"
"log"
"os"
"time"
driftstack "github.com/driftstackdev/driftstack-go"
)
func main() {
client := driftstack.New(os.Getenv("DRIFTSTACK_API_KEY"))
defer client.Close()
ctx := context.Background()
agent, err := client.AgentSessions.Create(ctx, &driftstack.CreateAgentSessionRequest{
Mode: "ai",
ProxyID: os.Getenv("DRIFTSTACK_PROXY_ID"),
}, nil)
if err != nil {
log.Fatal(err)
}
// Always close it: an open session counts against your plan's
// concurrent-session limit.
defer func() {
if err := client.AgentSessions.Close(context.Background(), agent.ID); err != nil {
log.Printf("close failed: %v", err)
}
}()
// Wait up to two minutes for its browser: Status is "active" from the
// start, so read IsReady.
for i := 0; i < 60; i++ {
s, err := client.AgentSessions.Get(ctx, agent.ID)
if err != nil {
log.Fatal(err)
}
if s.Status == "closed" {
reason := "unknown"
if s.ClosedReason != nil {
reason = *s.ClosedReason
}
log.Fatal("The session did not start: " + reason)
}
if s.IsReady() {
break
}
time.Sleep(2 * time.Second)
}
reply, err := client.AgentSessions.Message(ctx, agent.ID,
"Open https://example.com and tell me the page title.",
&driftstack.MessageOptions{IdempotencyKey: "task-" + time.Now().Format(time.RFC3339Nano)})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.Kind, reply.Answer)
}
The guide Run AI tasks from your code covers the rest a program needs: answering the agent’s questions, approving a payment it held, stopping a task that runs too long, safe retries, and limits.
4. Error handling
Each problem-type maps to a typed error class. Match with
errors.As for the granular case, or errors.Is against a sentinel
for category-only matching:
import (
"errors"
"log"
"time"
)
if _, err := client.AgentSessions.Create(ctx, req, nil); err != nil {
var pr *driftstack.ProxyRequiredError
var rl *driftstack.RateLimitError
var cl *driftstack.ConcurrencyLimitError
var qe *driftstack.QuotaExceededError
var forbidden *driftstack.ForbiddenError
var other *driftstack.UnknownError
switch {
case errors.As(err, &pr):
// No ProxyID: nothing was created. Pass the id of one of your saved
// proxies.
log.Print(pr.Message)
case errors.As(err, &rl):
time.Sleep(time.Duration(rl.RetryAfterSeconds) * time.Second)
// … retry
case errors.As(err, &cl):
log.Printf("concurrent cap reached (%d/%d)", cl.CurrentSessions, cl.Limit)
case errors.As(err, &qe):
log.Printf("tier limit reached (current=%d/limit=%d)", qe.Current, qe.Limit)
case errors.As(err, &other) && other.Status == 402:
// The free trial has ended, or a subscription lapsed: the account keeps
// its data, and sessions start again once it chooses a plan.
log.Print(other.Message)
case errors.As(err, &forbidden):
log.Printf("forbidden: %s", forbidden.Message)
case errors.Is(err, driftstack.ErrAuth):
log.Print("bad API key")
default:
log.Fatal(err) // Network / parse / unrecoverable.
}
}
The full mapping from problem-type to Go error class lives at
/reference/errors. For low-level cases the
errors also satisfy errors.As to a shared payload carrying
.Status, .ProblemType, and a .Problem map[string]any with the
unmapped extension fields.
5. Webhooks (optional)
import driftstack "github.com/driftstackdev/driftstack-go"
ok := driftstack.VerifyWebhookSignature(
rawBody,
r.Header.Get("X-Driftstack-Signature"),
os.Getenv("DRIFTSTACK_WEBHOOK_SECRET"),
driftstack.VerifyWebhookOptions{},
)
if !ok {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
During the 24h signing-secret rotation grace window the single
X-Driftstack-Signature header carries both the new and old HMACs
as two v1= entries (t=…,v1=<new>,v1=<old>) — see
/webhooks/endpoints for the rotate-secret
endpoint. VerifyWebhookSignature already checks every v1= entry
in that header, so the call above keeps verifying through a rotation
without setting any option while you roll the new secret across your
verifier infra.
Next steps
- Run AI tasks from your code — give the AI agent a task, read its answer, and handle questions, approvals, stops, errors and retries.
- Session lifecycle reference — states, reconnect semantics.
- Profile management — persistent profiles that survive across sessions.
- Agent sessions — let an AI agent drive a
session from plain-language instructions, with a live transcript
stream and live video (the
LiveKitfield on the create response). - Who pays for the AI — your plan’s monthly AI credits, or your own Anthropic key.
- Idempotency keys —
Idempotency-Keyis honoured on agent sessions, and NOT onPOST /v1/sessions; retrying that one mints a second session. - Webhook event catalog — every event the platform can push.
- Error catalogue — every problem-type you might see and how to react.
- API versioning — what’s additive vs. major.
Stuck? Email
[email protected] with your
account id (acc_…, from client.Account.Whoami(ctx)) and the failing
x-request-id.