If you’ve ever had to manually write an SDK for your Go API, you know the feeling. It’s like being handed a massive spreadsheet and asked to transcribe it by hand while someone taps their pencil on the desk. Sure, you can do it, but why would you want to? This is exactly where SDK generation automation comes in—a lifeline for developers tired of maintaining boilerplate code. In this article, we’re going to dive deep into creating a tool that automates Go SDK generation from your REST APIs. We’ll explore the landscape of available generators, build practical solutions, and set up workflows that actually make your life easier.

The Problem We’re Solving

Before we get excited about solutions, let’s acknowledge the chaos of manual SDK creation:

  • Consistency nightmare: Different team members implement things differently. One person uses contexts everywhere, another thinks they’re optional. Soon you have an SDK that feels like it was designed by committee.
  • Maintenance burden: When your API changes, you manually update the SDK. Change the endpoint path? Update the SDK. Modify a response model? Update the SDK again. It’s a never-ending dance.
  • Time drain: Writing SDKs takes actual developer hours—hours that could be spent on features, bugs, or, let’s be honest, coffee breaks.
  • Type safety gaps: Manual implementations often have runtime surprises. “Oh, I didn’t realize this field could be null” is not a fun discovery in production. Enter automated SDK generation: the developer’s way of saying “I have better things to do than this.”

The Modern Approach: OpenAPI as Your Single Source of Truth

The magic ingredient here is OpenAPI (formerly Swagger). OpenAPI specifications describe your API in a machine-readable format. The tooling ecosystem that’s built around it is genuinely impressive—it’s like the UN of API documentation, except everyone actually gets along. When your API has an OpenAPI spec, you can feed it to various generators that output SDKs in Go, Python, TypeScript, Java, and about 50 other languages. This means your clients can work with type-safe, automatically generated code that mirrors your API exactly.

The Major Players: Which Generator Should You Choose?

Let’s talk about the tools available for Go SDK generation. Think of this as picking the right chef for your API’s kitchen: OpenAPI Generator is the tried-and-true veteran. It’s been around forever, actively maintained, and it supports more target languages than you can remember. Installation is straightforward—if you have Homebrew, you’re literally one command away from having it:

brew install openapi-generator
openapi-generator version

The flexibility is there, but so is the complexity. Configuration files can get dense, and you need to understand quite a few parameters to get the output exactly how you want it. oapi-codegen is the Go community’s darling for this job. It’s purpose-built for Go, which means it understands Go idioms and conventions. It can generate not just clients, but also server implementations that work with popular frameworks like Gin, Fiber, Echo, or the standard library. The configuration is YAML-based and tends to be more approachable:

go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest

Speakeasy takes a modern approach, emphasizing minimal dependencies, type safety, and debugging ease. Their Go SDKs feel natural to use, with support for custom HTTP clients and clean configuration patterns. Kiota (GitHub’s generator) is relatively newer but built with enterprise thinking. It generates SDKs from OpenAPI specs and provides first-class support for authentication patterns like GitHub App authentication. For this guide, we’re going to focus on oapi-codegen because it strikes the sweet spot between power, flexibility, and Go-native thinking.

Building Your SDK Generation Tool: The Architecture

Let me show you how to build a practical automation layer on top of oapi-codegen. The goal is to create something that your team can use consistently across multiple APIs.

graph TD A[OpenAPI Spec] -->|Input| B[SDK Generator Tool] B -->|Config| C[oapi-codegen] C -->|Generates| D[Go Client Code] D -->|Produces| E[Generated SDK Package] B -->|Validates| F{Spec Valid?} F -->|No| G[Error Report] F -->|Yes| C B -->|Packages| H[Versioned Release] E --> I[Developer Usage]

Here’s a practical generator tool written in Go. This is the kind of thing you’d commit to your infrastructure repository and teams would use via a simple CLI:

package main
import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)
type SDKGeneratorConfig struct {
	SpecURL      string // Path to OpenAPI spec
	OutputDir    string // Where to place generated SDK
	PackageName  string // Go package name
	ModuleName   string // Go module name (e.g., github.com/myorg/my-sdk)
	ClientName   string // Custom HTTP client type (optional)
}
// GenerateSDK orchestrates the entire generation process
func GenerateSDK(config SDKGeneratorConfig) error {
	// Validate configuration
	if err := validateConfig(config); err != nil {
		return fmt.Errorf("invalid configuration: %w", err)
	}
	// Create output directory
	if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
		return fmt.Errorf("failed to create output directory: %w", err)
	}
	// Generate oapi-codegen configuration file
	configYAML := generateOAPIConfig(config)
	configPath := filepath.Join(config.OutputDir, ".oapi-codegen.yaml")
	if err := os.WriteFile(configPath, []byte(configYAML), 0644); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}
	// Run oapi-codegen
	cmd := exec.Command("oapi-codegen", 
		"--config", configPath,
		config.SpecURL,
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("oapi-codegen failed: %w", err)
	}
	// Initialize Go module if needed
	if err := initializeGoModule(config); err != nil {
		return fmt.Errorf("failed to initialize Go module: %w", err)
	}
	fmt.Printf("✓ SDK successfully generated in %s\n", config.OutputDir)
	return nil
}
func validateConfig(config SDKGeneratorConfig) error {
	if config.SpecURL == "" {
		return fmt.Errorf("SpecURL is required")
	}
	if config.OutputDir == "" {
		return fmt.Errorf("OutputDir is required")
	}
	if config.PackageName == "" {
		return fmt.Errorf("PackageName is required")
	}
	if config.ModuleName == "" {
		return fmt.Errorf("ModuleName is required")
	}
	return nil
}
func generateOAPIConfig(config SDKGeneratorConfig) string {
	// This generates a YAML configuration for oapi-codegen
	return fmt.Sprintf(`output: client.gen.go
package: %s
generate:
  client: true
  models: true
  embedded-spec: true
`, config.PackageName)
}
func initializeGoModule(config SDKGeneratorConfig) error {
	// Check if go.mod exists
	modPath := filepath.Join(config.OutputDir, "go.mod")
	if _, err := os.Stat(modPath); err == nil {
		return nil // Already exists
	}
	cmd := exec.Command("go", "mod", "init", config.ModuleName)
	cmd.Dir = config.OutputDir
	if err := cmd.Run(); err != nil {
		// Module might already exist or other issue
		// This is often non-fatal
		return nil
	}
	return nil
}
func main() {
	config := SDKGeneratorConfig{
		SpecURL:     "https://api.example.com/openapi.json",
		OutputDir:   "./generated-sdk",
		PackageName: "exampleapi",
		ModuleName:  "github.com/myorg/example-sdk-go",
	}
	if err := GenerateSDK(config); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}

This tool handles the orchestration—it validates your configuration, sets up the environment, runs oapi-codegen with appropriate settings, and initializes your Go module.

The Generated SDK in Action

Once your SDK is generated, using it is refreshingly straightforward. Here’s what working with a generated API client actually looks like in practice:

package main
import (
	"context"
	"log"
	"net/http"
	"time"
	"github.com/myorg/example-sdk-go/exampleapi"
)
func main() {
	// Create a custom HTTP client with sensible defaults
	httpClient := &http.Client{
		Timeout: 30 * time.Second,
		Transport: &http.Transport{
			MaxIdleConns:        100,
			MaxIdleConnsPerHost: 10,
		},
	}
	// Initialize the API client
	client := exampleapi.NewClient(
		"https://api.example.com",
		exampleapi.WithHTTPClient(httpClient),
	)
	ctx := context.Background()
	// Making requests is type-safe and intuitive
	// The generated code handles all the boring parts
	users, err := client.GetUsers(ctx, &exampleapi.GetUsersParams{
		Limit:  10,
		Offset: 0,
	})
	if err != nil {
		log.Fatalf("Failed to fetch users: %v", err)
	}
	for _, user := range users.Items {
		log.Printf("User: %s (%s)", user.Name, user.Email)
	}
	// Creating resources
	newUser, err := client.CreateUser(ctx, &exampleapi.User{
		Name:  "Alice",
		Email: "[email protected]",
	})
	if err != nil {
		log.Fatalf("Failed to create user: %v", err)
	}
	log.Printf("Created user: %s (ID: %d)", newUser.Name, newUser.ID)
}

Notice how clean this is? No manual JSON marshaling, no string-based route building, no guessing about what fields are required. The generated code gives you all this for free.

Advanced: Building a CI/CD Integration

Here’s where automation becomes genuinely powerful. You want this to run automatically whenever your OpenAPI spec changes. Here’s a GitHub Actions workflow that generates and publishes your SDK:

name: Generate and Release SDK
on:
  push:
    paths:
      - 'openapi.yaml'
      - '.github/workflows/generate-sdk.yml'
    branches:
      - main
jobs:
  generate-sdk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      - name: Install oapi-codegen
        run: go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
      - name: Validate OpenAPI spec
        run: |
          curl -X POST \
            -H "Content-Type: application/yaml" \
            --data-binary @openapi.yaml \
            https://validator.swagger.io/validator          
      - name: Generate SDK
        run: |
          mkdir -p sdk-go
          oapi-codegen \
            --config sdk-config.yaml \
            openapi.yaml \
            > sdk-go/client.gen.go          
      - name: Initialize Go module
        run: |
          cd sdk-go
          go mod init github.com/myorg/my-sdk-go || true
          go mod tidy          
      - name: Run tests on generated code
        run: |
          cd sdk-go
          go vet ./...          
      - name: Create release
        if: success()
        run: |
          TAG="sdk-v$(date +%Y.%m.%d-%H%M)"
          git config user.name "SDK Bot"
          git config user.email "[email protected]"
          git add sdk-go/
          git commit -m "chore: generate SDK ${TAG}"
          git tag "${TAG}"
          git push origin "${TAG}"          

This workflow ensures that every time your API spec changes, a new, tested SDK is automatically generated and released. Your clients always have access to the latest version.

Real-World Considerations

API Versioning: Your OpenAPI spec might support multiple API versions. Consider generating separate SDK packages for each major version. This prevents breaking changes from surprising your users. Custom Types: Generated code is a starting point. Sometimes you want to add custom marshaling behavior or validation. Don’t be afraid to extend the generated code with hand-written wrappers. Documentation: Generated SDKs include docstrings from your OpenAPI spec, so please, please write good descriptions in your OpenAPI definitions. Future you will be grateful. Testing: The generated code itself is usually pretty solid—it’s mechanically produced. Your testing focus should be on integration tests that verify the generated client works with your actual API. Rate Limiting: If your API has rate limiting, consider building it into your SDK generation process. The generated client should be aware of your API’s limitations.

The Bottom Line

Building an automated SDK generation pipeline isn’t just about saving time on boring code writing—though that’s definitely a bonus. It’s about:

  • Consistency: Every generated SDK follows the same patterns
  • Reliability: Mechanical generation means fewer bugs than manual coding
  • Agility: API changes propagate to clients automatically
  • Developer experience: Your SDK users get natural, idiomatic Go code When you set this up right, you stop thinking about SDK maintenance and start thinking about what your API can do. And that’s when the real fun begins. The tools are there, they’re solid, and they’re waiting for you to use them. Go forth and generate.