Generate a Go SDK from your OpenAPI spec

Octri reads your OpenAPI document and writes a Go client on the standard library, pushes it to your repository with a semver tag, and rebuilds it every time the spec changes.

Install
go get github.com/acme/acme-go

The client they actually get

What your users write once the Go package is installed.

Two call-site shapes come out of the same spec. class-namespaced is the default: a client object whose operations are grouped by resource, which is the surface every widely used SDK ships and the one your own reference docs already assume. functions is the override: no client object, one configure call, and each operation called directly. It is a single setting, per language, and nothing else about the client changes with it.

Context first and errors last either way, on net/http. Arguments travel in a params struct, so an optional field added to your spec later leaves every existing call compiling.

Go modules resolve from a repository, so there is no registry account to connect. Octri pushes the generated code and a semver tag, and go get resolves it from there.

Client object (default)
import acme "github.com/acme/acme-go"

client := acme.NewAcme(acme.ClientConfig{
	BaseURL: "https://api.acme.com/v1",
	Auth:    &acme.ClientAuthConfig{Bearer: os.Getenv("ACME_TOKEN")},
})

// The Paginated companion takes a yield callback. Return false to stop early.
limit := int64(100)
err := client.Users.ListPaginated(ctx, acme.ListUsersParams{Limit: &limit},
	func(user *acme.User) bool {
		fmt.Println(user.Id)
		return true
	})
Free functions
import acme "github.com/acme/acme-go"

acme.Configure(&acme.ClientConfig{
	BaseURL: "https://api.acme.com/v1",
	Auth:    &acme.ClientAuthConfig{Bearer: os.Getenv("ACME_TOKEN")},
})

limit := int64(100)
err := acme.ListUsersPaginated(ctx, acme.ListUsersParams{Limit: &limit},
	func(user *acme.User) bool {
		fmt.Println(user.Id)
		return true
	})

The package at a glance

What a developer checks before adding the Go package to a project.

Runs on
Go 1.21+
Transport
net/http
Runtime dependencies
None outside the standard library
Models
Structs with json tags, optional fields as pointers
Pagination
Yield callback that returns false to stop
Ships to
Your repository, as a semver git tag

When a call fails

Common statuses are sentinels for errors.Is, and the full response is one errors.As away, so failures compose with the wrapping your users already do.

Typed errors
invoice, err := client.Invoices.Create(ctx, acme.CreateInvoiceParams{
	CustomerId:  "cus_123",
	AmountCents: 4900,
})
if errors.Is(err, acme.ErrRateLimit) {
	// 429 after the built-in retries. Back off at the caller.
}
var httpErr *acme.SdkHttpError
if errors.As(err, &httpErr) {
	log.Printf("acme: status %d, request %s", httpErr.StatusCode, httpErr.RequestID)
}

What you decide for Go

Go exposes no HTTP engine setting: the generated client uses net/http, which is what a Go consumer expects.

  • Client style

    class-namespaced (default), class, namespaced, functions

    A client struct with a field per resource is the default. Package-level functions are the override.

  • Argument style

    object (default), positional

    A params struct per operation, which is how large Go SDKs keep call sites stable as an API grows. Positional passes plain parameters, still promoting a wide operation to a struct on its own.

  • Doc comments

    full (default), minimal

    Full carries your spec’s prose into the comments godoc and pkg.go.dev render.

Method naming and namespacing work the same in every language: operations are grouped by your spec’s tags or by path, and the namespace word is dropped from the method name unless you ask to keep the operationId whole.

The repository is the import path

Go derives the module path from the repository URL and writes it into go.mod, so github.com/acme/acme-go is the line every consumer types. Rename or move that repository and every import breaks, with no deprecation path and no redirect. Set the Go repository deliberately before the first tag, and expect it to differ from the repository your other languages use.

Go module path, in every import
github.com/acme/acme-go
  • Not possible: Move or rename the repository
  • Not possible: Re-tag a published version
  • Still possible: Retract a version in go.mod

The same in every generated client

The parts nobody wants to hand-write, and the parts a hand-written client usually skips.

  • Retries with exponential backoff and jitter, on the statuses that mean "try again", with a per-attempt timeout.
  • Idempotency keys on the methods that need them, so a retried write does not become two.
  • Auth wired in from your spec’s security scheme, read from wherever your users keep secrets.
  • Typed errors split by cause: a failed constraint, an HTTP status, a network failure and a timeout are four different things.
  • Pagination that follows whichever contract your spec declares, cursor, offset, page number or next URL, without prefetching the collection.
  • Server-sent events as a native stream, for the operations that stream.
  • Error reporting to Octri’s monitoring, pre-wired and switched off until a consumer opts in.
  1. Point it at your spec

    Upload an OpenAPI or Swagger document, paste a URL, or connect the repository it lives in so a merge to main updates it.

  2. Read the audit

    The spec is scored out of 10 on what a generator can do with it, with every missing schema and colliding operationId named. 8 of the fourteen rules carry a button that writes the fix.

  3. Shape the client

    Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.

  4. Generate and read it

    The package is generated, compiled and handed to you as source you can read before anyone installs it.

  5. Publish, then keep publishing

    Release to the registry under your own account. Every later spec change regenerates the client, and the version number comes from your own document.

Questions

None. Go modules resolve from a repository and a tag, so Octri pushes the generated code and the semver tag, and go get takes it from there. That is why Go needs the GitHub App connected and does not need a registry token.