# Brokle API Routes

> Use this skill when creating, modifying, or reviewing API endpoints and routes. This includes SDK routes (/v1/*), Dashboard routes (/api/v1/*), request/response structures, input validation, OpenAPI documentation, or API-related middleware.

- Skill: `majiayu000/brokle-api-routes` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/brokle-api-routes`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/brokle-api-routes/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/brokle-api-routes

---


# Brokle API Routes Skill

Expert guidance for API endpoint development following Brokle's dual-route architecture.

## Dual Route Architecture

### SDK Routes (`/v1/*`) - API Key Authentication
**Target**: SDK integration, programmatic access
**Auth**: API keys (`bk_{40_char_random}`)
**Rate Limiting**: API key-based

**Examples**:
- `POST /v1/chat/completions` - OpenAI-compatible chat
- `POST /v1/embeddings` - Embeddings
- `POST /v1/traces` - OTLP traces ingestion (OpenTelemetry standard)
- `GET /v1/models` - Available models
- `POST /v1/route` - AI routing decisions

### Dashboard Routes (`/api/v1/*`) - JWT Authentication
**Target**: Web dashboard, administrative access
**Auth**: Bearer JWT tokens
**Rate Limiting**: IP-based + user-based

**Examples**:
- `/api/v1/auth/*` - Authentication & sessions
- `/api/v1/users/*` - User management
- `/api/v1/organizations/*` - Organization management
- `/api/v1/projects/*` - Project management
- `/api/v1/analytics/*` - Metrics & reporting

## URL Structure Standards

```
/api/v1/{domain}/{resource}[/{id}][/{sub-resource}]

Examples:
GET    /api/v1/auth/users                    # List users
POST   /api/v1/auth/users                    # Create user
GET    /api/v1/auth/users/{id}               # Get user
PUT    /api/v1/auth/users/{id}               # Update user
DELETE /api/v1/auth/users/{id}               # Delete user
GET    /api/v1/auth/users/{id}/sessions      # Get user sessions
```

## Complete Handler Pattern

```go
package http

import (
    "github.com/gin-gonic/gin"
    authDomain "brokle/internal/core/domain/auth"
    "brokle/internal/transport/http/middleware"
    "brokle/pkg/response"
    "brokle/pkg/ulid"
    "brokle/pkg/apperrors"
)

type UserHandler struct {
    userService authDomain.UserService
}

func NewUserHandler(userService authDomain.UserService) *UserHandler {
    return &UserHandler{userService: userService}
}

// RegisterRoutes sets up all user-related routes
// Note: Called from server setup where middleware is already applied to parent group
func (h *UserHandler) RegisterRoutes(r *gin.RouterGroup) {
    users := r.Group("/users")
    {
        users.POST("", h.CreateUser)
        users.GET("", h.ListUsers)
        users.GET("/:id", h.GetUser)
        users.PUT("/:id", h.UpdateUser)
        users.DELETE("/:id", h.DeleteUser)
    }
}

// @Summary      Create a new user
// @Description  Create a new user account with the provided information
// @Tags         Users
// @Accept       json
// @Produce      json
// @Param        request  body      CreateUserRequest  true  "User creation data"
// @Success      201      {object}  CreateUserResponse
// @Failure      400      {object}  response.ErrorResponse
// @Failure      409      {object}  response.ErrorResponse
// @Router       /api/v1/auth/users [post]
// @Security     ApiKeyAuth
func (h *UserHandler) CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.Error(c, appErrors.NewValidationError("Invalid request body", err))
        return
    }

    resp, err := h.userService.CreateUser(c.Request.Context(), &req)
    if err != nil {
        response.Error(c, err)
        return
    }

    response.Created(c, resp)
}
```

## Request/Response Patterns

### Create Request
```go
type CreateUserRequest struct {
    Email    string `json:"email" binding:"required,email" example:"user@example.com"`
    Name     string `json:"name" binding:"required,min=2,max=100" example:"John Doe"`
    Password string `json:"password" binding:"required,min=8" example:"secure123"`
}
```

### Update Request (Pointers for Optional)
```go
type UpdateUserRequest struct {
    Name   *string `json:"name,omitempty" binding:"omitempty,min=2,max=100"`
    Email  *string `json:"email,omitempty" binding:"omitempty,email"`
    Status *string `json:"status,omitempty" binding:"omitempty,oneof=active inactive"`
}
```

### List Request (Query Params)
```go
type ListUsersRequest struct {
    Page    int    `form:"page" binding:"omitempty,min=1" example:"1"`
    Limit   int    `form:"limit" binding:"omitempty,min=1,max=100" example:"20"`
    Search  string `form:"search" binding:"omitempty,max=100" example:"john"`
    Status  string `form:"status" binding:"omitempty,oneof=active inactive all"`
    SortBy  string `form:"sort_by" binding:"omitempty,oneof=created_at name email"`
    SortDir string `form:"sort_dir" binding:"omitempty,oneof=asc desc"`
}
```

### Response with Pagination
```go
type ListUsersResponse struct {
    Users      []*User     `json:"users"`
    Pagination *Pagination `json:"pagination"`
}

type Pagination struct {
    Page      int   `json:"page" example:"2"`
    PageSize  int   `json:"page_size" example:"20"`
    Total     int64 `json:"total" example:"156"`
    TotalPage int   `json:"total_page" example:"8"`
    HasNext   bool  `json:"has_next" example:"true"`
    HasPrev   bool  `json:"has_prev" example:"true"`
}
```

## Error Handling in Handlers

```go
// Input validation
if err := c.ShouldBindJSON(&req); err != nil {
    response.Error(c, appErrors.NewValidationError("Invalid request body", err))
    return
}

// Parameter validation
id, err := ulid.Parse(c.Param("id"))
if err != nil {
    response.Error(c, appErrors.NewValidationError("Invalid ID format", "id must be a valid ULID"))
    return
}

// Service layer errors (automatic HTTP mapping)
resp, err := h.service.Method(c.Request.Context(), req)
if err != nil {
    response.Error(c, err)  // Maps AppErrors to HTTP status
    return
}
```

## OpenAPI Documentation

```go
// @Summary      Brief description (1 line)
// @Description  Detailed description
// @Tags         Group endpoints logically
// @Accept       json
// @Produce      json
// @Param        id       path      string             true   "Resource ID"
// @Param        request  body      CreateUserRequest  true   "Request body"
// @Param        page     query     int                false  "Page number"  minimum(1)
// @Success      200      {object}  GetUserResponse
// @Success      201      {object}  CreateUserResponse
// @Failure      400      {object}  response.ErrorResponse
// @Failure      404      {object}  response.ErrorResponse
// @Router       /api/v1/auth/users/{id} [get]
// @Security     ApiKeyAuth
```

## Middleware Setup

### SDK Routes
```go
sdkRoutes := r.Group("/v1")
sdkRoutes.Use(sdkAuthMiddleware.RequireSDKAuth())
sdkRoutes.Use(rateLimitMiddleware.RateLimitByAPIKey())
{
    sdkRoutes.POST("/chat/completions", chatHandler.Completions)
    sdkRoutes.POST("/embeddings", embeddingsHandler.Create)
}
```

### Dashboard Routes (`internal/transport/http/server.go:219-223`)
```go
// Note: Middleware is injected via DI container (not package-level functions)
protected := router.Group("")
protected.Use(s.authMiddleware.RequireAuth())           // Instance method - JWT validation
protected.Use(s.csrfMiddleware.ValidateCSRF())          // Instance method - CSRF protection
protected.Use(s.rateLimitMiddleware.RateLimitByUser()) // Instance method - User rate limiting
{
    protected.POST("/users", userHandler.CreateUser)
    protected.GET("/users", userHandler.ListUsers)
}
```

## HTTP Status Code Mapping

| AppError Type | HTTP Status | Description |
|---------------|-------------|-------------|
| ValidationError | 400 | Invalid input |
| UnauthorizedError | 401 | Auth required |
| ForbiddenError | 403 | Insufficient permissions |
| NotFoundError | 404 | Resource not found |
| ConflictError | 409 | Already exists |
| RateLimitError | 429 | Rate limit exceeded |
| InternalError | 500 | Unexpected error |

## References

- `docs/development/API_DEVELOPMENT_GUIDE.md` - Complete API patterns
- `docs/development/PAGINATION_GUIDE.md` - Pagination standards

