---
name: restful-api-designer
description: Use when designing or refactoring AgentTeam Go/Gin REST API handlers, typed handler adapters, route ownership, request/response DTOs, or API package boundaries under internal/api.
---

# RESTful API Designer

## Core rule

Keep route registration, HTTP adapter mechanics, handler behavior, and DTO definitions separate:

- `internal/api/<domain>/routes.go`: route assembly only.
- `internal/api/<domain>/api_*.go`: one feature-focused `Handler` struct per file.
- `internal/api/dto/<domain>_*.go`: all request and response structs.
- `internal/api/handler_adapter.go`: typed Gin adapter toolkit only; not a domain DTO/error hub.

## Typed handler adapter

For new or migrated authenticated/workspace routes, prefer typed handlers that return `(api.Response, error)` and are adapted in `routes.go`:

```go
func (h *MessageHandler) Create(r api.WorkspaceScopedJSON[dto.MessageCreateRequest]) (api.Response, error) {
	body := r.Body
	channelID := r.Gin.Param("channelId")
	// ...
	return api.Created(response), nil
}

func (h *MessageHandler) Get(r api.WorkspaceScopedRequest) (api.Response, error) {
	messageID := r.Gin.Param("messageId")
	// ...
	return api.OK(response), nil
}
```

Canonical request context types:

- `api.AuthenticatedRequest`: auth-scoped base request.
- `api.WorkspaceScopedRequest`: workspace-scoped base request; embeds `AuthenticatedRequest`.
- `api.AuthenticatedJSON[T]`: auth-scoped JSON request; embeds `AuthenticatedRequest` and exposes `Body T`.
- `api.WorkspaceScopedJSON[T]`: workspace-scoped JSON request; embeds `WorkspaceScopedRequest` and exposes `Body T`.

Rules:

- Name the handler request parameter `r`, not `apiRequest` or another long name.
- Base request structs keep an explicit named `Gin *gin.Context` field. Do **not** anonymously embed `*gin.Context`.
- Use `r.Gin.Param(...)`, `r.Gin.Query(...)`, `r.Gin.GetHeader(...)`, etc. If repeated, assign `c := r.Gin` locally.
- Do not add custom `Param`, `Query`, `Header`, `Bind`, or similar facade methods to typed request structs.
- Base requests are not generic. Do not introduce `AuthenticatedRequest[T]`, `WorkspaceScopedRequest[T]`, or `NoBody` placeholder request types.
- Raw `func(c *gin.Context)` handlers are acceptable for middleware, public/auth flows, streaming/webhook endpoints, and unmigrated routes; do not use raw Gin for new migrated workspace/auth CRUD handlers without a reason.

Naming note: JSON wrappers intentionally use `AuthenticatedJSON[T]` / `WorkspaceScopedJSON[T]` because DTO types often already carry a `Request` suffix. Keep base names `AuthenticatedRequest` / `WorkspaceScopedRequest`; do not rename those to adjectives.

## JSON body rules

Typed JSON handlers receive a single request object and read the decoded payload from `r.Body`:

```go
func (h *ChannelHandler) Update(r api.WorkspaceScopedJSON[dto.ChannelUpdateRequest]) (api.Response, error) {
	body := r.Body
	// ...
}
```

- Do not use the old two-parameter JSON shape `func(api.WorkspaceScopedRequest, dto.SomeRequest)`.
- Migrated JSON handlers must not call `ShouldBindJSON`, `Bind`, `BindJSON`, or manual body decoding. Binding belongs in `HandleAuthJSON[T]` / `HandleWorkspaceScopedJSON[T]` or a domain wrapper around them.
- Preserve legacy wire behavior when migrating: invalid JSON messages, status codes, and domain error classifiers must stay stable unless the task explicitly changes them.

## Handler shape

Each `api_*.go` file owns one feature area and defines one independent handler struct with narrow, consumer-side interfaces:

```go
type MessageHandler struct {
	store    MessageStore
	waiters  MessageWaiter
	hub      MessageNotifier
	realtime MessageRealtime
	logger   *slog.Logger
}

func (h *MessageHandler) Create(r api.WorkspaceScopedJSON[dto.MessageCreateRequest]) (api.Response, error) {}
func (h *MessageHandler) Get(r api.WorkspaceScopedRequest) (api.Response, error) {}
func (h *MessageHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {}
func (h *MessageHandler) Delete(r api.WorkspaceScopedRequest) (api.Response, error) {}
```

Define consumer-side interfaces in the same handler file, scoped to what that handler actually needs — not what the underlying package exposes. Use action names that match REST/tool semantics (`Create`, `Get`, `List`, `Update`, `Delete`, plus domain verbs like `Claim`, `Pause`, or `Upload` when clearer).

## Handler comments

Every handler method that serves an API endpoint must have a godoc comment with a route line plus a short description block:

```go
// MethodName handles METHOD /api/path.
// Short description of what the handler does. It may span multiple comment lines
// when a brief paragraph is clearer than a single sentence.
func (h *HandlerName) MethodName(r api.WorkspaceScopedRequest) (api.Response, error) {}
```

Rules:

- **Line 1**: `// MethodName handles METHOD /api/path.` — method name and exact URI path as registered in `routes.go`, including placeholders such as `:taskId` or `:agentId`.
- **Description block**: one or more `//` lines after line 1 describing behavior, validation, or results.
- HTTP method and path must match route registration exactly, including `/api/...` or `/internal/...` prefix.
- Private helper methods not wired to routes do not need this comment format.

## Dependencies

Each handler must depend on narrow interfaces, never on a shared `Service` god struct.

- Define consumer-side interfaces in the handler file (`api_messages.go` owns `MessageStore`, `MessageNotifier`, etc.).
- Interfaces are scoped to what the handler actually calls — do not mirror the full underlying package API.
- Domain-local adapter wrapper functions live directly in `routes.go` next to route registration when needed; do not create helper-only `adapter_helpers.go` / `http_helpers.go` files for wrappers.
- `routes.go` receives a `Dependencies` struct for wiring and constructs each handler with its specific interfaces at registration time.
- Do not add package-level god objects or setter injection.

## DTOs

Put all API request and response structs in `internal/api/dto`:

- Use exported names such as `AgentTaskCreateRequest` or `AgentMeshXAuthorizationResponse`.
- Do not define request/response structs inside handler files.
- Do not use anonymous inline request structs in handlers.
- Keep JSON tags stable when moving DTOs.

## Routes

`routes.go` wires handler methods through the appropriate adapter:

```go
func RegisterRoutes(r gin.IRoutes, deps Dependencies) {
	messages := &MessageHandler{
		store: deps.MessageStore, waiters: deps.MessageWaiter,
		hub: deps.MessageNotifier, realtime: deps.MessageRealtime,
		logger: deps.Logger,
	}

	workspaceScoped := r.Group("/api/v1/workspaces/:workspaceId")
	workspaceScoped.GET("/channels/:channelId/messages", api.HandleWorkspaceScoped(messages.List))
	workspaceScoped.POST("/channels/:channelId/messages", api.HandleWorkspaceScopedJSON[dto.MessageCreateRequest](messages.Create))
}
```

Use domain wrappers only to preserve legacy behavior or reduce repeated adapter options, for example `handleChannelWorkspaceJSON[T](handler.Create)` with a domain-specific invalid JSON message. Define those wrappers in `routes.go`, not in a separate helper-only file.

Do not use meaningless import aliases. Prefer natural package names like `agent`, `router`, and `dto`; rename local variables instead of aliasing imports.

## Tests and guardrails

For every moved route group:

- Add or update a route ownership test for method + path parity.
- Handler unit tests fake only the narrow interfaces the handler depends on — no need to construct a full `store.Store` or shared `Service`.
- Run focused handler/router tests first, then compile/build/vet as appropriate.
- Run `git diff --check` and strict conflict-marker grep before review.
- Record exact environmental failures instead of summarizing them as generic test failures.

Useful grep guardrails for typed adapter migrations:

- Old server/current-server symbols should be gone from migrated code: `RequireCurrentServer`, `SetCurrentServer`, `CurrentServerFromContext`, `ServerRequest`, `HandleServer`, `HandleServerJSON`.
- No custom typed request facade methods named `Param`, `Query`, `Header`, `Bind`, etc.
- No anonymous `*gin.Context` embed in typed request structs; keep `Gin *gin.Context` explicit.
- No generic base request or `NoBody` request placeholder.
- No old `func(Request, T)` JSON handler shape in migrated domains.
- No long typed handler parameter names such as `apiRequest`; use `r`.
- Migrated JSON handlers should use `r.Body` and should not call `r.Gin.ShouldBindJSON`, `r.Gin.Bind*`, `r.ShouldBindJSON`, or `r.Bind*`.
