Claude Code Plugins

Community-maintained marketplace

Feedback

brokle-api-routes

@brokle-ai/brokle
2
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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/otlp/traces - OTLP traces ingestion
  • 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 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