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.

$ Instalar

git clone https://github.com/brokle-ai/brokle /tmp/brokle && cp -r /tmp/brokle/.claude/skills/brokle-api-routes ~/.claude/skills/brokle

// tip: Run this command in your terminal to install the skill


name: brokle-api-routes description: 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.

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

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

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)

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)

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

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

// 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

// @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

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)

// 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 TypeHTTP StatusDescription
ValidationError400Invalid input
UnauthorizedError401Auth required
ForbiddenError403Insufficient permissions
NotFoundError404Resource not found
ConflictError409Already exists
RateLimitError429Rate limit exceeded
InternalError500Unexpected error

References

  • docs/development/API_DEVELOPMENT_GUIDE.md - Complete API patterns
  • docs/development/PAGINATION_GUIDE.md - Pagination standards