REST API Surface
nestr intermediate 5 min read
ELI5
The Engine is a hotel front desk: /health and /metrics are the lobby (anyone can walk in), /api/* are the rooms (you need a key card unless reception turned key-checking off), and the desk clerk writes everyone’s arrival in a log.
Technical Deep Dive
All routes live in engine/internal/server/rest.go. The router is a stdlib http.ServeMux wrapped in a fixed middleware chain:
loggingMiddleware → corsMiddleware → withRateLimit → (optional) JWT → handlerRoute Map
flowchart LR R[ServeMux] R --> H[/health/] R --> RD[/ready/] R --> M[/metrics/] R --> WS[/ws/] R --> API subgraph API["/api/*"] W[/workspace/] S[/services/] OPR[/operations/run/] OPS[/operations/sync/] OPA[/operations/assemble/] PL[/pellets/] PLI["/pellets/{id}/"] PLC[/pellets/compress/] PLE[/pellets/extract/] PLS[/pellets/stats/] PLP[/pellets/prune/] end H -.no auth.-> R RD -.no auth.-> R M -.no auth.-> R API -.JWT if JWKS_URL set.-> REndpoint Reference
| Method | Path | Body | Returns |
|---|---|---|---|
| GET | /health | — | 200 ok |
| GET | /ready | — | 200 |
| GET | /metrics | — | Prometheus text |
| GET | /ws | upgrade | WebSocket frames |
| GET | /api/workspace | — | Workspace{id,name,root,status,services[]} |
| GET | /api/services | — | Service[] |
| POST | /api/operations/run | {operation, services[]} | OperationResult{success,message,logs[]} |
| POST | /api/operations/sync | {dryRun?} | OperationResult |
| POST | /api/operations/assemble | {} | OperationResult |
| GET | /api/pellets | — | Pellet[] |
| GET | /api/pellets/{id} | — | Pellet |
| POST | /api/pellets/compress | {source} | {success, pellet?, message} |
| POST | /api/pellets/extract | {pelletId, targetPath?} | {success, message, extractedTo?} |
| GET | /api/pellets/stats | — | CacheStats (see nestr-004) |
| POST | /api/pellets/prune | {days?, all?} | {success, message, removed:int} |
Response Envelope
Success responses are direct JSON of the resource (Pellet, Workspace, …) or an ad-hoc shape with a success: true flag for write endpoints. Errors are emitted via respondError(w, status, msg) as plain HTTP status + message — there is no uniform {error: {...}} wrapper. Every request also carries an X-Request-ID header injected by loggingMiddleware.
Key Terms
- respondJSON / respondError → the two helpers in
rest.gothat all handlers funnel through. - withRateLimit → per-IP token bucket middleware (see nestr-006).
- OperationResult → uniform return for run/sync/assemble:
{success, message, logs[]}.
Q&A
Q: How does the server distinguish a 404 from a 405 on a known path?
A: ServeMux returns 404 for unknown paths; handlers themselves check r.Method and return 405 explicitly when the path matches but the verb is wrong.
Q: Can I call /api/pellets/compress without a JWT?
A: Only if JWKS_URL is unset at server startup. If set, the auth middleware rejects requests without a valid Bearer token before they reach the handler.
Q: What’s the OpenAPI source of truth?
A: engine/docs/openapi.yaml; it documents the same set of routes and is the recommended client-generation input.
Examples
Compressing from cURL with auth disabled: curl -X POST :8080/api/pellets/compress -d '{"source":"./node_modules"}' returns {"success":true,"pellet":{"id":"171…","ratio":0.15,…},"message":"compressed"} and increments nestr_pellet_compress_total.
neighbors on the map
- Two-Service Architecture onboarding to the chronicle-hq monorepo
- Request Routing & Edge Resolution debugging why a slug returns 404 instead of redirecting
- LORE Route Structure & Data Model implementing a new LORE page or route
- OSS & Cloud Modes self-hosting via docker compose