# Add OpenAPI Docs to a Gin API

Yes. An agent annotates your Gin handlers with swag comments, generates the OpenAPI spec, serves Swagger UI, checks it against the real routes and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/add-openapi-docs/gin/), in a Project that has your repository.

## What the agent does

The agent starts by building the list of routes from the code, not from a README, because a generated spec is only as true as the handlers it documents. In a Gin API like `gothinkster/golang-gin-realworld-example-app`, `hello.go` mounts everything under `/api`: `users.UsersRegister` on `/users`, then the optional `AuthMiddleware(false)` for the anonymous article, tag and profile routes, then `AuthMiddleware(true)` for `/user`, `/profiles/:username/follow` and the authenticated article routes, plus `/api/ping`. `users/routers.go` and `articles/routers.go` register each handler, often twice, with and without a trailing slash because `RedirectTrailingSlash` is off. `go.mod` says `go 1.21`, `gin v1.10.0`, `validator/v10` and `golang-jwt/jwt/v5`, and `users/middlewares.go` reads the token from an `Authorization` header that begins with `Token `.

Then it reads the shapes. Request bodies are the validator structs in `users/validators.go` and `articles/validators.go`, such as `UserModelValidator` with a nested `User` object whose fields carry `binding` tags like `required,min=4,max=255` and `email`. Responses are the serializer structs in `users/serializers.go` and `articles/serializers.go`, `UserResponse` and `ProfileResponse` among them, but the handlers wrap them in `gin.H{"user": ...}` or `gin.H{"article": ...}`, and errors come back as `common.CommonError` with an `errors` map from `common/utils.go`. Status codes are explicit in each handler: 201 on create, 404 for an unknown slug or username, 422 for validation and database errors, 401 on a failed login and 403 when the caller is not the author.

From that it adds `swag` annotations. `hello.go` gets the general block: title, version, `@BasePath /api` and an `apikey` security definition named for the `Authorization` header with a description that the value is `Token <jwt>`. Each handler gets `@Summary`, `@Tags`, `@Param`, `@Success` and `@Failure` lines with the right codes, and because `gin.H` has no schema, the agent defines small envelope types, `UserEnvelope`, `ArticleEnvelope`, `ArticlesEnvelope` with `articlesCount`, `CommentsEnvelope`, `TagsEnvelope` and an `ErrorEnvelope` for `CommonError`, so the generated schemas match the JSON the handlers emit. Only the canonical path of each duplicate route is annotated. It adds `github.com/swaggo/swag`, `github.com/swaggo/gin-swagger` and `github.com/swaggo/files` to `go.mod`, runs `swag init -g hello.go` to generate `docs/docs.go`, `docs/swagger.json` and `docs/swagger.yaml`, and mounts `GET /swagger/*any` with `ginSwagger.WrapHandler(swaggerFiles.Handler)` in `hello.go`.

Then it checks the spec against the real server and the repository's own gates. It starts the API, loads `/swagger/doc.json`, and compares every request in `api/Conduit.postman_collection.json` with the paths and methods in the spec, listing any route the collection calls that the spec lacks. It runs `scripts/run-api-tests.sh` with Newman as `.github/workflows/ci.yml` does to confirm the server still passes, `scripts/gofmt.sh`, `golangci-lint` with the `staticcheck`, `unused` and `misspell` linters from `.golangci.yml`, and `go test ./...`, and it excludes the generated `docs/` package from the linter rather than editing generated code. A CI step runs `swag init` and fails on `git diff --exit-code docs/`, so the committed spec cannot drift from the annotations.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Add OpenAPI documentation to this Gin API. Read hello.go, go.mod, users/routers.go, users/validators.go, users/serializers.go, users/middlewares.go, articles/routers.go, articles/validators.go, articles/serializers.go, common/utils.go, api/Conduit.postman_collection.json, scripts/run-api-tests.sh, .golangci.yml and .github/workflows/ci.yml first, and list every route with its method, auth requirement, request struct, response wrapper and status codes. Use swaggo/swag annotations on each handler, with a general block in hello.go that sets the base path and an API key security definition for the Authorization header whose value is Token followed by the JWT. Define named envelope types for the gin.H wrappers and the errors map so the generated schemas match the real JSON, and annotate only the canonical path of routes registered with and without a trailing slash. Add the swag, gin-swagger and files modules, run swag init, commit the generated docs package and serve Swagger UI at /swagger. Start the server, compare the spec's paths with the requests in the Postman collection and list any gaps, run the Newman API tests, gofmt, golangci-lint with the generated package excluded, and go test. Add a CI step that regenerates the docs and fails if they differ. Fix anything until everything passes, and open a pull request that lists the documented routes and where to find the UI.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch with the annotated handler files, the envelope types, the generated `docs/` package, the `/swagger/*any` route in `hello.go`, the module additions in `go.mod` and `go.sum`, the linter exclusion and the new CI step. The description lists every documented route grouped by tag with its auth requirement, names the handlers whose responses needed an envelope type, and reports the comparison with the Postman collection, including any route the collection calls that is still undocumented.

The transcript on the session keeps every command with its output: `swag init`, the server start, the fetch of `/swagger/doc.json`, the Newman run, `gofmt`, `golangci-lint`, `go test` and any linter complaint fixed on the way. The repository's own `CI` workflow runs on the pull request too, so the Go version matrix, the coverage run, `golangci-lint`, `gosec` and the API integration job report beside the new doc-drift step.

Before merging, a reviewer opens the Swagger UI from the branch, tries a request that needs the `Token` header through the authorize dialog, checks that the 422 error example matches what `common.NewValidatorError` really returns, and confirms the generated files are committed so the API serves the spec without `swag` installed. A follow-up message to the same session adds examples to specific fields or splits the spec by tag.

## Run it on every Linear issue labelled api-docs

A team that tracks documentation work in Linear starts one run per issue from an automation with a `linear` trigger on the `api-docs` label. The automation adds the issue after this prompt, so the agent reads which routes or fields the issue asks for and starts in the repository the automation names.

Each run opens its own pull request from its own sandbox, and an automation run has its own `max_runs_per_hour` cap. The pull request links back to the issue, and the transcript holds the `swag init` output and the collection comparison for that change.

## Why run it on Rush?

The tedious part of this job is reading every handler, every validator struct and every serializer to write annotations that match, then discovering that `gin.H` wrappers produce empty schemas and going back to add envelope types. Doing it for two dozen routes is an afternoon on a laptop, and checking the result means starting the server and clicking through the UI. In a Rush sandbox the agent runs the server, Newman and the linters itself, and the sandbox parks when the turn ends.

The pull request carries its own proof. The comparison between the spec and the Postman collection, the Newman run and the linter output are in the transcript beside the commands that produced them, so a reviewer can see that the documented routes are the ones the API serves, not the ones a README remembers.

## Frequently asked questions

### Why are there new envelope types?

Because the handlers return `gin.H{"user": serializer.Response()}` and similar maps, which carry no type information for `swag`. Small structs such as `UserEnvelope` with a `User UserResponse` field give the generator a schema that matches the JSON exactly. They are used only in annotations and optionally in the handlers, and the pull request says which.

### Do I need swag installed to run the API?

No. `swag init` output is committed as the `docs/` package, and `hello.go` imports it so the spec is compiled into the binary and served at `/swagger/doc.json`. `swag` is needed only to regenerate after an annotation changes, and the new CI step catches a forgotten regeneration.

### How is authentication described in the spec?

As an API key security scheme on the `Authorization` header, because `users/middlewares.go` expects `Token ` followed by the JWT rather than the `Bearer` scheme. The authorize dialog in Swagger UI then accepts the full `Token <jwt>` value, and each route that runs behind `AuthMiddleware(true)` is marked as requiring it.

### Will golangci-lint complain about the generated code?

It would, so the agent excludes the `docs/` directory in `.golangci.yml` instead of editing generated files. `gofmt.sh` and `go test ./...` run over the rest as before, and the transcript shows them passing.
