Skip to main content

Go SDK quickstart

The github.com/driftstackdev/driftstack-go module wraps the same REST surface as the curl quickstart. Idiomatic Go: every call takes a context.Context, returns a typed struct + error, and streaming progress arrives through callbacks (MessageOptions.OnStep / OnEvent).

SDK automation needs a ds_live_… customer API key. Every plan includes the API, the free trial too.

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.mod and go.sum for reproducible deployments. Go ≥ 1.22 is supported. The module has zero non-stdlib runtime dependencies (it speaks raw JSON over net/http); no transitive bloat.

2. Construct the client

package main

import (
    "os"

    "github.com/driftstackdev/driftstack-go"
)

func main() {
    // BaseURL defaults to https://api.driftstack.dev; pass
    // driftstack.WithBaseURL(...) to override for staging or
    // self-hosted deployments. New does not return an error.
    client := driftstack.New(os.Getenv("DRIFTSTACK_API_KEY"))
    _ = client
}

The constructor does not make any network calls. Reuse one client across your process — it is internally pooled via http.Client with sensible defaults (no reconfiguration needed for production traffic).

3. Run your first agent session

An agent session is an iPhone Safari browser in the cloud that an AI drives for you: your code sends 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 (ProxyID). You will need:

agent, err := client.AgentSessions.Create(ctx, &driftstack.CreateAgentSessionRequest{
    Mode:    "ai",
    ProxyID: os.Getenv("DRIFTSTACK_PROXY_ID"),
}, nil)
if err != nil {
    return 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 { return err }
    if s.Status == "closed" {
        reason := "unknown"
        if s.ClosedReason != nil { reason = *s.ClosedReason }
        return errors.New("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 { return err }
log.Println(reply.Kind, reply.Answer)

4. What happened

Create started an agent session through the proxy you named; without a ProxyID it is refused with 422 proxy-required, and nothing is created. A new session reads Status active at once; the loop waits for IsReady(), which comes when its iPhone Safari browser has started. Message gave the agent the task: Kind is plan-executed when it ran, with the Answer it read back, and the idempotency key makes a retried message the same turn rather than a second one. Close, run by defer, ends the session and releases its slot.

Every method takes a context.Context — parent cancellations / deadlines propagate cleanly through the HTTP layer. 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.

5. Paginate over your agent sessions

page, err := client.AgentSessions.List(ctx, &driftstack.ListAgentSessionsQuery{Limit: 50})
if err != nil { return err }
for _, s := range page.Data {
    if s.Status == "closed" {
        log.Println(s.ID)
    }
}
// page.NextCursor is nil when there are no more pages; otherwise
// pass *page.NextCursor as query.Cursor on the next call, or use
// client.AgentSessions.Iterate to walk the full history.
// From v0.8.0 (not published yet),
// set query.Status (for example "open") to list only the sessions
// with that status; Iterate keeps it on every page.

Error handling

Every SDK method returns a typed error wrapper on non-2xx responses — one per problem class (ValidationError, RateLimitError, ConcurrencyLimitError, NotFoundError, etc.). Use errors.As to recover the typed shape (lifted extension fields, RetryAfterSeconds, etc.); use errors.Is against the sentinel (ErrRateLimit, ErrValidation, …) when you just need category matching:

var pr *driftstack.ProxyRequiredError
if errors.As(err, &pr) {
    // No ProxyID, so nothing was created.
}

var rl *driftstack.RateLimitError
if errors.As(err, &rl) {
    time.Sleep(time.Duration(rl.RetryAfterSeconds) * time.Second)
    // retry
}

var ve *driftstack.ValidationError
if errors.As(err, &ve) {
    // ve.Problem carries the per-field issues
}

if errors.Is(err, driftstack.ErrConflict) {
    // any 409, regardless of subclass
}

Every typed error embeds an apiError shape with Status, ProblemType (the RFC 9457 URI), Message, and Problem (the full parsed problem map). Read additional extension fields off err.Problem.

Context propagation

Every method takes a context.Context as its first argument. Cancellation cascades through the underlying HTTP requests, so a parent deadline (or an HTTP request cancellation in a Gin / chi / standard http.Server handler) cleanly aborts the underlying in-flight HTTP call.

Driving each step yourself

client.Sessions is the direct session API: your code drives each step itself with Navigate, Interact, Wait and Capture instead of giving an AI a task. On hosted Driftstack only agent sessions load pages, so use client.AgentSessions for real browsing. See the Sessions reference.

Where to go next

Need help?

[email protected]. Reply times follow your plan's support on the pricing page: a 48-hour reply target on Starter and Pro.