API overview
Hono on Bun, base URL https://lumen-api.zenmail.my.id. Auth is JWT in the Authorization header. Responses are JSON. Chat is SSE.
Auth header
All protected endpoints require:
Authorization: Bearer <access-token>
Access tokens last 15 minutes. The web client auto-refreshes on any 401 using the refresh token (7-day lifetime). If the refresh fails, the client clears tokens and redirects to /login.
Response shapes
Success
{
"users": [...], // resource-named key
"total": 42
}
or
{
"user": { ... },
"accessToken": "...",
"refreshToken": "..."
}
Error
Two shapes depending on source:
Handler-thrown (400/401/403/404/409/500):
{ "error": "<code>", "message": "<human text>" }
Zod validation failure (400):
{
"success": false,
"error": {
"issues": [
{ "path": ["email"], "message": "Invalid email", "code": "invalid_string" }
],
"name": "ZodError"
}
}
The web client's api.ts extracts a readable string for both shapes:
- Prefer
messagefield - Fall back to
errorif it's a string - For Zod errors, format as
"<path>: <message>"(e.g.,email: Invalid email)
Never surface [object Object] to the user — that's a bug in the error extractor.
Core endpoint groups
| Prefix | Purpose | Auth |
|---|---|---|
| /auth/* | Login, refresh, logout, me | Public (login/refresh) or JWT |
| /bootstrap/* | First-run wizard | Public |
| /users/* | Users CRUD | admin/superadmin |
| /departments/* | Departments + members + grants | admin/superadmin + CEO/manager for own dept |
| /groups/* | Groups under department | admin/superadmin + dept manager |
| /projects/* | Projects CRUD + grants + members | resolver-based |
| /projects/:id/grants | Create/update/delete grant | full tier on project |
| /documents/* | Upload, list, delete | resolver-based (tier varies) |
| /chat/* | SSE chat, conversations, messages | resolver-based (use tier) |
| /share-requests/* | Request flow | admin/superadmin |
| /share/:token | Guest share page | Public |
| /audit-log | Admin audit query | admin/superadmin |
| /providers/* | LLM provider config | engineer/superadmin (write), any auth (read /models) |
| /memories/* | Project memory | resolver-based |
Rate limits
Not enforced currently. Dev phase, single tenant. Production will need:
/auth/login— 10 req/min per IP/bootstrap/init— 3 req/min per IP/chat/*SSE — connection cap per user/documents/*POST — 50MB per file, 100 files/hour per user
CORS
Web origin (https://ai-kb.zenmail.my.id) is whitelisted. Preflight OPTIONS is auto-handled by Hono.
OpenAPI
Not auto-generated. This doc is the source of truth. When you add an endpoint:
- Add it to
apps/api/src/routes/<area>.ts - Register it in
apps/api/src/index.tsif it's a new route file - Document it in the appropriate
/engineering/api/*page - Add integration test if the endpoint has auth/permission gates
Observability
- Access log:
docker logs lumen-api --follow - Slow queries: Prisma
$queryRawhasconsole.logwrappers for the vector search only - Errors:
console.errorwith stack — piped through Dokploy log retention
No structured logging / tracing yet. When we outgrow single-tenant dev, add OpenTelemetry via Hono middleware.