Skill for developing REST API endpoints in internal/server/. Covers routing, request validation, WebSocket streaming, and API conventions.
npx skills add https://github.com/actonos/actonos --skill actonos-api-dev
Use this skill when creating or modifying REST API endpoints in the internal/server/ package.
internal/server/
├── router.go # Chi router setup, global & auth middlewares, route tree
├── api_auth.go # Setup, login, logout, password change, auth status
├── api_dashboard.go # Dashboard aggregate metrics & summaries
├── api_agent.go # Agent CRUD, start/stop, chat, soul, memory-md, cron
├── api_tasks.go # Autonomous Task matrix CRUD, Heartbeat config & manual pulse triggers
├── api_conversations.go # Chat conversations and message history
├── api_plugins.go # WASM plugin upload, enable/disable, logs, configuration & vault secrets
├── api_vault.go # Hardware-bound vault secret management
├── api_integrations.go # Channel accounts, pairing codes, sender authorization
├── api_tools.go # MCP servers, skills, tool execution, hub marketplace
├── api_workspace.go # Workspace file browser, read/write/mkdir/upload
├── api_system.go # Metrics, token usage ledger history, keys, identity, HAL
├── api_setup.go # Legacy/standalone setup endpoints
├── layered_fs.go # Layered filesystem (/data/overrides/ → go:embed fallback)
├── static.go # Embedded static asset server
└── server_test.go # Comprehensive endpoint test suite
/api (all routes are prefixed with /api)/api/v1/ prefix until v1.0.0 is officially released.ActonOS uses Chi v5 with standard library net/http:
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
Success (s.respondJSON(w, http.StatusOK, data)):
{
"data": { ... }
}
Error (s.respondError(w, http.StatusBadRequest, "INVALID_REQUEST", "description")):
{
"error": {
"code": "INVALID_REQUEST",
"message": "Human-readable explanation of error"
}
}
Server// 1. Respond with JSON data wrapped in {"data": ...}
func (s *Server) respondJSON(w http.ResponseWriter, status int, data any)
// 2. Respond with standard error envelope wrapped in {"error": {"code": ..., "message": ...}}
func (s *Server) respondError(w http.ResponseWriter, status int, code, message string)
// 3. Decode request body with 1MB safety limit
func (s *Server) decodeJSON(r *http.Request, v any) error
GET /api/healthGET /api/modelsGET /api/notifications/push/vapid-keyGET /api/auth/statusPOST /api/auth/setupPOST /api/auth/loginPOST /api/auth/logoutAll other routes are nested inside r.Group with r.Use(s.RequireAuthMiddleware). Requests must include Authorization: Bearer <token> in the HTTP headers when authentication is initialized.
type UpdateSoulRequest struct {
SoulContent string `json:"soul_content"`
}
type SoulResponse struct {
AgentID string `json:"agent_id"`
Content string `json:"content"`
UpdatedAt string `json:"updated_at"`
}
*Serverfunc (s *Server) handleSaveSoul(w http.ResponseWriter, r *http.Request) {
agentID := chi.URLParam(r, "agentID")
if agentID == "" {
agentID = agent.DefaultSystemAgentID
}
var req UpdateSoulRequest
if err := s.decodeJSON(r, &req); err != nil {
s.respondError(w, http.StatusBadRequest, "INVALID_BODY", "failed to decode json body")
return
}
if err := s.profileMgr.SaveSoul(r.Context(), agentID, req.SoulContent); err != nil {
s.respondError(w, http.StatusInternalServerError, "SAVE_FAILED", err.Error())
return
}
s.respondJSON(w, http.StatusOK, SoulResponse{
AgentID: agentID,
Content: req.SoulContent,
UpdatedAt: time.Now().UTC().Format(time.RFC3339),
})
}
internal/server/router.gor.Route("/agents", func(r chi.Router) {
// ...
r.Route("/{agentID}", func(r chi.Router) {
// ...
r.Put("/soul", s.handleSaveSoul)
})
})
docs/API.md.agents/rules/source-registry.mdweb/src/lib/api.ts and interfaces to web/src/lib/types.tsFor streaming LLM tokens, reasoning thoughts, and tool call progress:
func (s *Server) handleChatStream(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
s.respondError(w, http.StatusInternalServerError, "STREAMING_UNSUPPORTED", "streaming not supported")
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
eventChan := make(chan agent.AgentStreamEvent, 64)
go func() {
_, _ = s.engine.ExecuteStepStreamWithHistory(
r.Context(), agentID, msg, history, eventChan,
)
}()
for ev := range eventChan {
data, _ := json.Marshal(ev)
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", ev.Type, data)
flusher.Flush()
}
}
The stream endpoint must flush live thought, token, tool_call,
tool_result, audit, done, and error events. It must not proxy to the
non-streaming JSON handler. Conversation messages are persisted before and
after the stream.
GET /api/realtime is a protected, same-origin WebSocket.actonos_token cookie set by setup/login; never put bearer tokens in WebSocket query strings.router.gos.respondError with appropriate HTTP statusweb/src/lib/types.ts and api.ts are synceddocs/API.md and .agents/rules/source-registry.md are updatedgo test ./internal/server/... passesapi_approvals.go owns durable exact-action approval decisions.api_runs.go exposes durable run summaries and ordered execution events.ToolRegistry.Execute and return HTTP 202 for ApprovalRequiredError.
requestAdminApproval and exact dispatch from api_approvals.go.
GET /api/system/audit/verify is the canonical audit-chain integrity check.Config.DataDir,Config.WorkspaceDir, Config.SkillsDir, or Config.WASMDir; handlers must
not assume the process working directory is the data root.
Server.vault; never write API keys into JSONor .key files. Preserve automatic legacy migration and fail closed without
Vault.
VACUUM INTO through the live database connection;never copy the main database file while WAL mode is active.
Take actonos/actonos-api-dev from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.