.agents/api-endpoints-and-auth.md
This guide covers how to add new API endpoints and properly integrate them with the auth/permissions system.
Authentication and authorization flow through three layers:
core/http/auth/middleware.go → auth.Middleware) — applied to every request in core/http/app.go. Handles session cookies, Bearer tokens, API keys, and legacy API keys. Populates auth_user and auth_role in the Echo context.auth.RequireFeature) — per-feature access control applied to route groups or individual routes. Checks if the authenticated user has the specific feature enabled.auth.RequireAdmin) — restricts endpoints to admin users only.When auth is disabled (no auth DB, no legacy API keys), all middleware becomes pass-through (auth.NoopMiddleware).
Write the endpoint handler in the appropriate package under core/http/endpoints/. Follow existing patterns:
// core/http/endpoints/localai/my_feature.go
func MyFeatureEndpoint(app *application.Application) echo.HandlerFunc {
return func(c echo.Context) error {
// Use auth.GetUser(c) to get the authenticated user (may be nil if auth is disabled)
user := auth.GetUser(c)
// Your logic here
return c.JSON(http.StatusOK, result)
}
}
Add routes in the appropriate file under core/http/routes/. The file you use depends on the endpoint category:
| File | Category |
|---|---|
routes/openai.go | OpenAI-compatible API endpoints (/v1/...) |
routes/localai.go | LocalAI-specific endpoints (/api/..., /models/..., /backends/...) |
routes/agents.go | Agent pool endpoints (/api/agents/...) |
routes/auth.go | Auth endpoints (/api/auth/...) |
routes/ui_api.go | UI backend API endpoints |
Choose the appropriate protection level:
Exempt paths bypass auth entirely. Add to isExemptPath() in middleware.go or use the /api/auth/ prefix (always exempt). Use sparingly — most endpoints should require auth.
The global middleware already handles this. API paths (/api/, /v1/, etc.) automatically require authentication when auth is enabled. You don't need to add any extra middleware.
router.GET("/v1/my-endpoint", myHandler) // auth enforced by global middleware
Pass adminMiddleware to the route. This is set up in app.go and passed to Register*Routes functions:
// In the Register function signature, accept the middleware:
func RegisterMyRoutes(router *echo.Echo, app *application.Application, adminMiddleware echo.MiddlewareFunc) {
router.POST("/models/apply", myHandler, adminMiddleware)
}
For endpoints that should be toggleable per-user, use feature middleware. There are two approaches:
Approach A: Route-level middleware (preferred for groups of related endpoints)
// In app.go, create the feature middleware:
myFeatureMw := auth.RequireFeature(application.AuthDB(), auth.FeatureMyFeature)
// Pass it to the route registration function:
routes.RegisterMyRoutes(e, app, myFeatureMw)
// In the routes file, apply to a group:
g := e.Group("/api/my-feature", myFeatureMw)
g.GET("", listHandler)
g.POST("", createHandler)
Approach B: RouteFeatureRegistry (preferred for individual OpenAI-compatible endpoints)
Add an entry to RouteFeatureRegistry in core/http/auth/features.go. The RequireRouteFeature global middleware will automatically enforce it:
var RouteFeatureRegistry = []RouteFeature{
// ... existing entries ...
{"POST", "/v1/my-endpoint", FeatureMyFeature},
}
When you need a new toggleable feature (not just a new endpoint under an existing feature):
Add to core/http/auth/permissions.go:
const (
// Add to the appropriate group:
// Agent features (default OFF for new users)
FeatureMyFeature = "my_feature"
// OR API features (default ON for new users)
FeatureMyFeature = "my_feature"
)
Then add it to the appropriate slice:
// Default OFF — user must be explicitly granted access:
var AgentFeatures = []string{..., FeatureMyFeature}
// Default ON — user has access unless explicitly revoked:
var APIFeatures = []string{..., FeatureMyFeature}
In core/http/auth/features.go, add to the appropriate FeatureMetas function so the admin UI can display it:
func AgentFeatureMetas() []FeatureMeta {
return []FeatureMeta{
// ... existing ...
{FeatureMyFeature, "My Feature", false}, // false = default OFF
}
}
In core/http/app.go:
myFeatureMw := auth.RequireFeature(application.AuthDB(), auth.FeatureMyFeature)
Then pass it to the route registration function.
If your feature gates standard API endpoints (like /v1/...), add entries to RouteFeatureRegistry in features.go instead of using per-route middleware.
import "github.com/mudler/LocalAI/core/http/auth"
func MyHandler(c echo.Context) error {
// Get the user (nil when auth is disabled or unauthenticated)
user := auth.GetUser(c)
if user == nil {
// Handle unauthenticated — or let middleware handle it
}
// Check role
if user.Role == auth.RoleAdmin {
// admin-specific logic
}
// Check feature access programmatically (when you need conditional behavior, not full blocking)
if auth.HasFeatureAccess(db, user, auth.FeatureMyFeature) {
// feature-specific logic
}
// Check model access
if !auth.IsModelAllowed(db, user, modelName) {
return c.JSON(http.StatusForbidden, ...)
}
}
Middleware can be composed at different levels. Here are the patterns used in the codebase:
// All routes in the group share the middleware
g := e.Group("/api/agents", poolReadyMw, agentsMw)
g.GET("", listHandler)
g.POST("", createHandler)
// Individual routes get middleware as extra arguments
router.POST("/models/apply", applyHandler, adminMiddleware)
router.GET("/metrics", metricsHandler, adminMiddleware)
// Build a middleware chain for a handler
chatMiddleware := []echo.MiddlewareFunc{
usageMiddleware,
traceMiddleware,
modelFilterMiddleware,
}
app.POST("/v1/chat/completions", chatHandler, chatMiddleware...)
Always use schema.ErrorResponse for auth/permission errors to stay consistent with the OpenAI-compatible API:
return c.JSON(http.StatusForbidden, schema.ErrorResponse{
Error: &schema.APIError{
Message: "feature not enabled for your account",
Code: http.StatusForbidden,
Type: "authorization_error",
},
})
Use these HTTP status codes:
401 Unauthorized — no valid credentials provided403 Forbidden — authenticated but lacking permission429 Too Many Requests — rate limited (auth endpoints)If your endpoint should be tracked for usage (token counts, request counts), add the usageMiddleware to its middleware chain. See core/http/middleware/usage.go and how it's applied in routes/openai.go.
The global auth middleware classifies paths as API paths or non-API paths:
/api/, /v1/, /models/, /backends/, /backend/, /tts, /vad, /video, /stores/, /system, /ws/, /metrics/api/auth/ prefix, anything in appConfig.PathWithoutAuthIf you add endpoints under a new top-level path prefix, add it to isAPIPath() in middleware.go to ensure it requires authentication.
When adding a new endpoint:
core/http/endpoints/core/http/routes/ filepermissions.go, metadata in features.go, middleware in app.goisAPIPath() in middleware.goRouteFeatureRegistryusageMiddleware added to middleware chainschema.ErrorResponse format