Frameworks

Generate an SDK from a Go API with swag or huma

Go has no built-in OpenAPI generator, and no single dominant framework decides the path for you. If you already have a router (Gin, Echo, chi, net/http) and want to document it without a rewrite, swaggo/swag parses source comments into a spec — but it only outputs Swagger 2.0, which needs an upgrade pass before most modern SDK tooling accepts it. If you're starting fresh, huma is a full framework that generates OpenAPI 3.1 natively, no annotations required. Either way, the spec then runs through a generator or a hosted pipeline like Sourced to become an installable SDK.

Which path fits your Go API?

Situation Use
Existing Gin/Echo/chi/net-http app, don't want to change the framework swaggo/swag — comment annotations, no rewrite
New service, or willing to adopt a framework huma — OpenAPI 3.1 generated as you write handlers

Extracting a spec with swaggo/swag

swag reads specially formatted comments above your handler functions and above main(), then writes a spec to disk.

Step Command / annotation What it does
1. Install the CLI go install github.com/swaggo/swag/cmd/swag@latest (Go 1.19+) Installs the swag binary
2. Add general API info @title, @version, @description, @host, @BasePath above main() Populates the spec's info and server fields
3. Annotate each handler @Summary, @Param, @Success, @Failure, @Router Documents one operation per handler
4. Generate swag init (or swag init -g http/api.go if annotations live elsewhere) Writes docs/swagger.json, docs/swagger.yaml, and docs/docs.go
5. Serve (optional) Import the generated docs package and mount ginSwagger.WrapHandler (or the chi/echo/net-http equivalent) Exposes a Swagger UI

A handler annotation looks like this:

// @Summary      Show an account
// @Param        id   path      int  true  "Account ID"
// @Success      200  {object}  model.Account
// @Failure      404  {object}  httputil.HTTPError
// @Router       /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) { ... }

swag supports plugins for gin, echo, buffalo, net/http, gorilla/mux, chi, fiber, atreugo, and hertz, so the annotation step is the same regardless of router.

Gotcha 1: swag outputs Swagger 2.0, not OpenAPI 3.x

swag's own repository description is explicit: it generates "Swagger 2.0" documentation. There's no flag to emit OpenAPI 3.x directly. definitions instead of components/schemas, host/basePath instead of a servers array, and consumes/produces instead of per-content-type content maps — all of that needs converting before a 3.x-only generator or hosted docs pipeline will accept the file. See OpenAPI 3.1 vs Swagger 2.0: upgrade first for why you should go straight to 3.1 rather than stopping at 3.0.

Gotcha 2: operationId is opt-in via @id

swag supports an @id annotation to set a stable operationId per operation, but nothing forces you to add it. Skip it, and the operation ships without one — spec-legal, but it leaves your downstream SDK generator to invent a method name from the verb and path. See how to fix missing operationId errors for what that fallback looks like and the naming convention that survives regeneration.

Extracting a spec with huma (OpenAPI 3.1 native)

huma is a Go framework, not an annotation layer — you register operations against a router adapter (humachi, humago, humafiber, and others), and the OpenAPI document is built from the Go types and struct tags you already wrote:

huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *struct {
    Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
}) (*GreetingOutput, error) {
    resp := &GreetingOutput{}
    resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
    return resp, nil
})

The spec is available immediately at /openapi.json and /openapi.yaml on a running app, with interactive docs at /docs via Stoplight Elements — no separate build step.

Gotcha: convenience methods auto-generate operationId

huma.Get, huma.Post, and the other verb-named convenience functions generate an operationId for you based on the path — huma.Get(api, "/things/{thing-id}", ...) becomes get-things-by-thing-id. That's stable as long as the path doesn't change, but it's still generator-chosen, not yours. Use huma.Register(api, huma.Operation{OperationID: "...", ...}, handler) directly when you want to set the name explicitly — the same fix pattern covered in missing operationId errors, just applied before the fallback ever fires.

From a Go spec to hosted docs and typed SDKs

Once you have a clean 3.x file — from huma directly, or from swag after the Swagger 2.0 upgrade — turning it into an installable SDK is a separate step. With Sourced:

  1. Point Sourced at your repo or spec file. Connect from GitHub, or upload openapi.json directly.
  2. Preview the TypeScript and Python SDKs. Method names, package structure, and typed errors render before anything publishes — so a get-things-by-thing-id-style fallback name is visible in preview, not a customer's IDE.
  3. Get hosted docs and llms.txt from the same pass. No separate docs deploy — the hosted docs preview and llms.txt come from the same spec.
  4. Publish when it's clean. Free unlimited previews and up to 2 hosted noindex docs review sites and one hosted MCP server, no credit card, cover steps 1-3.

Comparison: swag vs. huma vs. a full SDK pipeline

Need swag gives you huma gives you What neither gives you
A generated OpenAPI spec Yes, Swagger 2.0 from comments Yes, OpenAPI 3.1 natively Nothing — both are real and free
Works with an existing router Yes, via plugins No — huma is its own framework
Interactive API browsing Swagger UI via a router plugin Built-in docs at /docs A docs site customers install SDKs from
A typed client SDK Nothing built in Nothing built in Generated TypeScript or Python SDK
Agent-readable docs Nothing built in Nothing built in llms.txt derived from the spec
A diff before a breaking release Nothing built in Nothing built in A compatibility report

OSS generators for other languages

Sourced's generated SDKs focus on TypeScript and Python today — the two ecosystems where teams publish first, not Go. For the SDK itself, a clean spec from either tool — huma's 3.1 output directly, or swag's Swagger 2.0 upgraded to 3.x — works with the standard per-language OpenAPI Generator ecosystem: Go, Java, Ruby, PHP, C#, Kotlin, Rust, and Swift all have their own walkthroughs. Sourced still fits on that same spec for hosted docs, llms.txt, and validation. The five-minute generate-an-SDK guide covers running a local generator against any spec.

Other backend frameworks with their own spec-extraction paths: FastAPI, Rails, NestJS, Express, Django, Spring Boot, Laravel, and .NET.

Honest scope: when swag or huma alone is enough

If your Go API only has internal Go consumers, a generated Swagger UI (from either tool) plus a thin shared HTTP client is a reasonable stopping point — you don't need a codegen pipeline for teammates who can read the handler code directly. huma in particular is worth adopting even without ever generating an SDK, purely for the request validation and typed handlers it gives you for free. Reach for full SDK generation once you have external customers, a second language to support, or a docs site that needs to outlive an internally-hosted Swagger UI.

FAQ

Does Go generate OpenAPI automatically?

No, not from the standard library or from most routers on their own. You need either an annotation tool like swag (which parses comments into a spec) or a framework like huma (which builds the spec from your handler code as you write it).

What OpenAPI version does swag output?

Swagger 2.0 only, as of this writing. There's no built-in option to emit OpenAPI 3.x — you need a separate upgrade step before feeding a swag-generated file to 3.x-only tooling.

Does huma support OpenAPI 3.1?

Yes, natively — huma's repository describes itself as a "REST/HTTP API Framework for Golang with OpenAPI 3.1." The spec is served at /openapi.json and /openapi.yaml with no extra generation step.

Why does my huma SDK have a method named get-things-by-thing-id?

That's huma's auto-generated operationId from the convenience methods (huma.Get, huma.Post, etc.) — it derives the ID from the HTTP method and path. Use huma.Register with an explicit OperationID field if you want to choose the name yourself.

Can I use swag without switching frameworks?

Yes — that's its main advantage over huma. swag has plugins for gin, echo, buffalo, net/http, gorilla/mux, chi, fiber, atreugo, and hertz, so you annotate your existing handlers rather than rewriting them.

Do I have to convert a swag spec to OpenAPI 3.x before generating an SDK?

For most modern SDK generators and hosted docs pipelines, yes. Swagger 2.0 lacks components, a servers array, and JSON Schema composition keywords like oneOf — see OpenAPI 3.1 vs Swagger 2.0 for the exact structural differences and why to go straight to 3.1.

Ship it

Whether your spec comes from swag (after the Swagger 2.0 upgrade) or huma's native 3.1 output, the next step is the same. Start free on Sourced to preview a TypeScript and Python SDK plus hosted docs in one pass, or run the file through the OpenAPI validator first to catch operationId and schema gaps before you generate anything.