# godo, the Go client where each DigitalOcean resource is its own file

> godo is the Go client library for the DigitalOcean V2 API, and its shape follows the API: one file per resource, one service tree per area, and a client that now also carries inference and Action Gateway calls. The trade is a client that tracks the platform closely and a token model with two credential classes you have to understand.

**digitalocean/godo** — DigitalOcean Go API client

- Repository: https://github.com/digitalocean/godo
- Website: https://pkg.go.dev/github.com/digitalocean/godo
- Stars: 1,548 · Forks: 408
- Language: Go
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/digitalocean-godo

## The directory listing is the API surface

The most honest description of godo is its own file list. Every DigitalOcean resource has a file of its own, and nearly every one of those files has a matching test file next to it. There are droplets.go and droplet_actions.go and droplet_autoscale.go, databases.go, cdn.go, domains.go, certificates.go, account.go, balance.go, billing_history.go, byoip_prefixes.go, apps.go with a generated companion apps.gen.go, action.go, agentplatform.go, and a family of action_gateway files split by concern into tools, toolbelts, resources, runtime, sessions, connections and users. The result is that a developer can look up where the Kubernetes client lives, or the databases client, by reading the file list instead of reading documentation. The consequence of that layout is a library with a very wide surface and a very small amount of shared abstraction, which is why the rest of the design matters more than the file count.

## One client object, one service tree per area

A single client carries every service, and you reach the API through the tree hanging off it. The construction is deliberately minimal.

```go
package main

import (
    "github.com/digitalocean/godo"
)

func main() {
    client := godo.NewFromToken("my-digitalocean-api-token")
}
```

From there, client.Droplets.Create, client.Kubernetes, client.Volumes, client.Models, client.Chat and client.ActionGateway are the entry points, and the services are typed rather than generic, which is what makes the library worth having over a hand rolled HTTP client. One documented detail is easy to miss. If you need to pass a context.Context into the client itself, you construct it manually with godo.NewClient instead of the token helper, so the context is available to the HTTP layer rather than being created later per call. The client also carries retry behaviour, since hashicorp/go-retryablehttp is a direct dependency, and query parameters are encoded with google/go-querystring, which is how the list options work.

## Two credential classes, and a 401 that explains itself

The authentication section is unusually explicit about a trap, and it is worth reading before you write the code. The credential you pass to godo.NewFromToken matters more than which API you call. Infrastructure calls, the ones covering Droplets, Kubernetes and Volumes among others, need a DigitalOcean API token, a personal access token, managed from the Applications page of the control panel. Inference calls, covering Chat, Models, Embeddings, ImageGenerations, Messages, Responses and BatchInference, need either a PAT created with full access scope or a model access key.

```go
client := godo.NewFromToken(os.Getenv("DIGITALOCEAN_TOKEN"))
client := godo.NewFromToken(os.Getenv("MODEL_ACCESS_KEY"))
```

Both lines construct the same client. The consequence of getting it wrong is precise: with a limited scope PAT, infrastructure calls work and inference calls fail with a 401. The remedy is stated too, create a PAT with full access or use a model access key. The Action Gateway carries the same pattern, since it needs a PAT with the matching action_gateway scopes. A library where one constructor accepts two credential classes and the failure mode is a 401 on a subset of endpoints is a good argument for keeping the client behind your own thin interface in application code.

## Installing it and creating your first Droplet

Installation is a single go get, and the pinned form is the one to use in an application.

```bash
go get github.com/digitalocean/godo@vX.Y.Z
```

The unversioned form is documented for non module usage or for taking the latest version.

```bash
go get github.com/digitalocean/godo
```

The first real call is a Droplet creation, and the request struct shows the level of detail the library exposes.

```go
dropletName := "super-cool-droplet"

createRequest := &godo.DropletCreateRequest{
    Name:   dropletName,
    Region: "nyc3",
    Size:   "s-1vcpu-1gb",
    Image: godo.DropletCreateImage{
        Slug: "ubuntu-20-04-x64",
    },
}

ctx := context.TODO()

newDroplet, _, err := client.Droplets.Create(ctx, createRequest)
```

Note the return signature. Every call returns the value, a response object and an error, and the response object is where pagination links and rate limit headers live, which is the reason the error check in the example is a three part return rather than a plain error. The image is given as a struct with a Slug field, and the region and size are string identifiers, so the library does not wrap the platform's catalog of regions and sizes in typed enums.

## Pagination is your loop, not the library's

List endpoints are paginated, and the documented pattern is to request pages yourself using a list options struct. The example walks the Droplets list until the last page.

```go
func DropletList(ctx context.Context, client *godo.Client) ([]godo.Droplet, error) {
    list := []godo.Droplet{}

    opt := &godo.ListOptions{}
    for {
        droplets, resp, err := client.Droplets.List(ctx, opt)
        if err != nil {
            return nil, err
        }

        list = append(list, droplets...)
```

The response returned alongside the items carries the information needed to advance to the next page, and the loop terminates when that information says there is no further page. This is the design you would expect from a library that mirrors the API rather than one that hides it, and it has one practical consequence worth planning for. A list of every resource on an account is an unbounded amount of work, so a job that enumerates droplets should page deliberately and handle the case where the account grows between pages, rather than assuming a single request returns everything.

## Action Gateway sessions, tool discovery and a policy default

The newest area of the client is the Action Gateway, a control plane for tools, toolbelts, output views, MCP servers, connections, sessions, users and end-user limits, and it is where the library's most interesting behaviour sits. A session is created for an actor, and the flow is to create a runtime session, ask it for its tools, call a chat completion with those tools attached, and hand the tool calls back to the session for handling.

```go
toolbelts, _, err := client.ActionGateway.Toolbelts.List(ctx, &godo.ActionGatewayToolbeltsListOptions{Status: "active"})

session, _, err := client.ActionGateway.Sessions.CreateRuntime(ctx, &godo.ActionGatewaySessionCreateRequest{
    ActorID: "alice",
})
tools, _, err := session.ChatTools(ctx, nil)
```

Three details in the documentation change how you should write this. The session exposes direct tool discovery and invocation through session.Tools, while session.Code runs sandboxed code, and the other inference formats have their own MessageTools and ResponseTools equivalents. CreateRuntime defaults to an ask-before-executing policy, so an agent will not silently run a tool unless you pass an explicit policy. And the Tools field is nil-sensitive: omitting it enables all tools, while an empty non-nil slice enables none, which is a sharp edge worth a comment in your own code. MCP calls are restricted to HTTPS session URLs on actions.do-ai.run, with WithActionGatewayMCPBaseURL available to override the origin for development or tests.

## Serverless inference and the model catalog

The inference side of the client talks to the Serverless Inference API at inference.do-ai.run, and the same client object serves it. A chat completion takes a model identifier and a message list, with a helper for the user role.

```go
completion, _, err := client.Chat.Completions.New(ctx, &godo.ChatCompletionNewParams{
    Model: "llama3.3-70b-instruct",
    Messages: []godo.ChatCompletionMessage{
        godo.UserMessage("Write me a haiku"),
    },
})
```

Listing models follows the same shape as any other list, returning a page with a data slice.

```go
page, _, err := client.Models.List(ctx)
for _, m := range page.Data {
    fmt.Println(m.ID)
}
```

Image generation is a separate service with its own parameter struct, taking a model, a prompt and a count.

```go
image, _, err := client.ImageGenerations.Generate(ctx, &godo.ImageGenerateParams{
    Model:  "stable-diffusion-3.5-large",
    Prompt: "A friendly cartoon shark typing on a laptop at a sunny beach",
    N:      1,
})
```

The repository also carries example directories for serverless inference, agent inference, agent platform, hosted agents and insights, so the streaming, embedding, message, response, async and batch variants are demonstrated somewhere even where the main documentation only shows the simplest calls.

## Release mechanics, retracted versions and the licence question

godo is a client for a platform that ships new products often, and the release process is automated in the Makefile. The version string is read out of the source rather than duplicated, the bump type is a make variable, and a changes target runs github-changelog-generator over the merged pull requests since the last release.

```bash
make BUMP=(bugfix|feature|breaking) bump_version
```

The go.mod declares go 1.23.0 and pins a small dependency set, and it also carries a retract block, which lists v1.165.0 and v1.164.0 as versions that should not be used, with a comment that it contains a retagging issue. That is a signal worth noticing: a module that gets published often enough to hit a tagging mistake will occasionally publish a bad version, and the retract directive is how the Go toolchain tells users to move past it. The tags themselves show the cadence, with v1.223.0, v1.224.0 and v1.225.0 published within days of each other in October 2026, and the last push on 2026-10-19, so the library moves at the speed of the API it wraps. The licence is the one item that needs attention rather than trust: the repository carries a LICENSE.txt that GitHub could not classify, so its terms are in a custom document. Nothing here is legal advice, but a company that runs a licence scanner over its dependencies will not get an answer for this package until somebody reads that file.

## Conclusion

godo fits a Go team that automates DigitalOcean from code, whether that means creating droplets, running Kubernetes clusters, or wiring an agent into the Action Gateway, because the library gives typed request and response structs for each area instead of hand written HTTP calls. It does not fit a team whose only need is a one-off script against the API from another language, where the extra dependency is more than the problem, and it does not fit a caller that intends to use the inference endpoints with a limited scope token, because those calls will fail with a 401. Verify three things before you build on it: that the credential you hold is the right class for the endpoints you plan to call, that the version you pin contains the service tree you need, since the library ships releases almost daily, and what the custom LICENSE.txt permits, because GitHub could not classify it.

## FAQ

### How do I install the godo Go client?

Use go get github.com/digitalocean/godo@vX.Y.Z with the version you want, or the unversioned form for the latest version or for non module usage. The module path is github.com/digitalocean/godo and it declares go 1.23.0.

### Which credentials does godo need for inference endpoints?

Infrastructure calls need a DigitalOcean API token, while inference calls such as Chat, Models, Embeddings and ImageGenerations need a PAT with full access scope or a model access key. With a limited scope PAT, infrastructure calls work and inference calls fail with a 401.

### Does godo handle pagination for me?

No. The documented pattern is to request pages individually with a ListOptions struct and loop until the response indicates the last page. The examples directory shows the pattern for listing Droplets.

### What is the Action Gateway in godo?

It is a control plane exposed as client.ActionGateway, covering tools, toolbelts, output views, MCP servers, connections, sessions, users and end-user limits, and it needs a PAT with the matching action_gateway scopes. Sessions created with CreateRuntime default to an ask-before-executing policy.

## Sources

- [digitalocean/godo on GitHub](https://github.com/digitalocean/godo)
- [Issues](https://github.com/digitalocean/godo/issues)
- [Project website](https://pkg.go.dev/github.com/digitalocean/godo)
- [README](https://github.com/digitalocean/godo/blob/main/README.md)
- [Releases](https://github.com/digitalocean/godo/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/digitalocean-godo
