# Inventory the API Endpoints of a Gin Service

Yes. An agent reads your Gin router groups and middleware order and writes a table of every endpoint with its handler, auth rule, parameters and tests.

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

## What the agent does

The agent clones the service into its own cloud sandbox and starts where Gin starts: the router. In a Gin API like gothinkster/golang-gin-realworld-example-app, `go.mod` declares `go 1.21` with `github.com/gin-gonic/gin v1.10.0`, `gorm.io/gorm v1.25.12` on the SQLite driver, `github.com/golang-jwt/jwt/v5` and `github.com/go-playground/validator/v10`. `hello.go` builds `gin.Default()`, turns `RedirectTrailingSlash` off with a comment explaining that a redirect would drop a POST body, and makes one group, `v1 := r.Group("/api")`. The registrations that follow are not one list: `users.UsersRegister(v1.Group("/users"))` comes before `v1.Use(users.AuthMiddleware(false))`, the anonymous article, tag and profile groups come after it, and `v1.Use(users.AuthMiddleware(true))` precedes `UserRegister`, `ProfileRegister` and `ArticlesRegister`. A `/api/ping` group sits outside `v1` entirely.

That ordering is the auth rule, so the agent records it per endpoint rather than per file. `users/middlewares.go` shows what the two modes mean: `extractToken` reads an `Authorization` header that starts with `Token ` or an `access_token` query parameter, `AuthMiddleware(false)` sets `my_user_id` to zero and continues when there is no token, and `AuthMiddleware(true)` aborts with 401. So `POST /api/users` and `POST /api/users/login` take no token, `GET /api/articles`, `GET /api/articles/:slug`, `GET /api/articles/:slug/comments`, `GET /api/tags` and `GET /api/profiles/:username` take an optional one, and everything registered after the second `Use`, from `GET /api/user` and `PUT /api/user` to `POST /api/profiles/:username/follow`, `GET /api/articles/feed`, `PUT /api/articles/:slug`, `DELETE /api/articles/:slug`, the favorite routes and `POST` and `DELETE` on comments, requires one.

For each handler the agent reads the function body for what it takes and returns. `ArticleList` in `articles/routers.go` reads `tag`, `author`, `favorited`, `limit` and `offset` from the query; `ArticleFeed` reads `limit` and `offset`; `ArticleRetrieve`, `ArticleUpdate` and `ArticleDelete` read the `slug` path parameter; `ArticleCreate` binds through `NewArticleModelValidator` and answers 422 with `common.NewValidatorError` on a bad body or `common.NewError("database", err)` on a save failure, then 201 with an `ArticleSerializer`. The same reading of `users/routers.go`, `users/validators.go`, `articles/validators.go` and both `serializers.go` files fills the validator and response columns. Registrations that exist twice, such as `POST ""` and `POST "/"` on `/users` and `PUT /:slug` beside `PUT /:slug/` on articles, are listed as one endpoint with both spellings, since `RedirectTrailingSlash` is off and both are reachable.

Then it cross-checks the table against two things that already exist. `gin.Default()` runs in debug mode and prints every registered route with its handler name at startup, so the agent builds and starts the binary with `PORT` set, as `scripts/run-api-tests.sh` does, and diffs that output against its table. And it maps tests to endpoints: `articles/unit_test.go` has 41 test functions that drive a `setupRouter` copy of the real groups through `httptest`, `users/unit_test.go` has 6 and `common/unit_test.go` has 16, while `api/Conduit.postman_collection.json` is the RealWorld collection that `run-api-tests.sh` runs with newman in the `api-test` job of `.github/workflows/ci.yml`. Each row gets a column for unit coverage and one for Postman coverage, with the test or request name.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Write an inventory of every HTTP endpoint in this Gin service as endpoints.md. Start from hello.go: list each router group, the order in which AuthMiddleware(false) and AuthMiddleware(true) are applied with v1.Use, and which registrations come before and after each. Read users/routers.go, articles/routers.go and users/middlewares.go, then for every route write a table row with method, path, handler function and package, auth rule (none, optional token or required token), path and query parameters read with c.Param and c.Query, the validator that binds the body, the serializer that shapes the response, and the status codes the handler returns. Treat duplicate registrations with and without a trailing slash as one endpoint and note both spellings. Build and start the server with PORT set, as scripts/run-api-tests.sh does, and compare the route list gin prints in debug mode against your table; report any difference. Add a column saying which test in articles/unit_test.go, users/unit_test.go or common/unit_test.go exercises each endpoint and which request in api/Conduit.postman_collection.json does, and finish with the endpoints no test touches. Do not change any file, commit, push or open a pull request.",
  "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 one file, `endpoints.md`, as an artifact on the session, with a download link that works for an hour and is refreshed on every read of the session. Its first part is the middleware map: the `/api` group, the two `Use` calls and which group registrations fall before and after each, so the auth rule for every path can be read off in one glance. Its second part is the table, one row per endpoint, in the order Gin registers them, with the handler and package, the auth rule, the parameters, the validator and serializer, the status codes and the two coverage columns.

The report ends with what the cross-checks found: any route the running binary printed that the table lacks or the reverse, and the list of endpoints no unit test and no Postman request reaches. In a service shaped like this one the interesting rows are the duplicate registrations, the `access_token` query parameter that the header-only Postman collection never uses, and the `/api/ping` route that lives outside the `/api` group's middleware.

A reviewer checks the auth column against the middleware order first, because that column is derived from `hello.go` rather than from annotations and is the one most worth getting right, then uses the untested list to decide what to write next or to hand to a follow-up run on the same session.

## Run it on every pull request that touches a routers.go file

The inventory goes stale the day someone adds a route, so run it where routes change. Add an automation to the Project with this prompt and a `github` trigger on `pull_request.opened` and `pull_request.synchronize`; the trigger adds the pull request event after the prompt, so the agent can diff the table it writes against the one on the default branch and post the rows that changed. The run clones the pull request's branch through the Rush GitHub App installation. An automation starts at most `max_runs_per_hour` runs in an hour, which is the cap to raise when a busy repository opens many pull requests at once.

From your own server, the same request starts a run with `resources` pointing at `https://github.com/your-org/your-app/tree/<branch>`, and a `request_id` derived from the commit SHA so a retried delivery returns the run it already started. Give the key a webhook URL and Rush POSTs `run.completed` with `data.session_url` when the turn ends; your handler reads the session, takes the `endpoints.md` artifact link and attaches it to the pull request as a comment.

## Why run it on Rush?

An endpoint table written from memory is wrong the first time someone reorders a `Use` call. The agent derives it from `hello.go` and the router files, then starts the real binary in its own sandbox and checks the printed route list against what it wrote. That second step needs a built Go toolchain, a writable `./data/gorm.db` path and a free port, which the sandbox has and a shared staging box may not.

The proof travels with the report. The transcript at `GET /o/{org}/sessions/{id}/messages` keeps the `go build`, the server start with its debug route listing and every file the agent read, so a reviewer can see where each row came from. Several services, or several branches of one, run at once in separate sandboxes, each producing its own table.

## Frequently asked questions

### Does it need the server to run to write the table?

No. The table comes from reading `hello.go` and the `routers.go` files. Starting the binary is a cross-check: Gin in debug mode prints every registered route with its handler name, and the agent diffs that against its table so a route registered somewhere it did not read still shows up.

### How does it decide the auth rule for a route?

From the order of calls in `hello.go`. A group registered before `v1.Use(users.AuthMiddleware(false))` has no auth middleware, one registered between the two `Use` calls gets an optional token, and one registered after `v1.Use(users.AuthMiddleware(true))` requires a token. The agent writes that order down in the report so the rule can be checked rather than trusted.

### What does the coverage column mean?

Two things, kept apart. Unit coverage names the `Test` function in `articles/unit_test.go`, `users/unit_test.go` or `common/unit_test.go` that sends a request to that path through `httptest`. Postman coverage names the request in `api/Conduit.postman_collection.json` that the `api-test` CI job runs with newman against the real server. An endpoint can have one without the other.

### Can it also produce an OpenAPI document?

Send a follow-up on the same session asking for `openapi.yaml`, and it builds one from the same table, with the validator structs as request schemas and the serializer structs as responses. This prompt stops at the Markdown table so the first result is quick to review.
