API Gateway
The API Gateway is the single entry point for all client traffic in AEGIS. It is a Go reverse proxy that handles authentication, rate limiting, CORS, and request routing to the appropriate backend microservice.
Overview
Every request from the frontend (or any external client) passes through the API Gateway on port 8000 before reaching a backend service. The gateway validates the JWT (from the Authorization header or the aegis_token cookie), enforces rate limits, attaches CORS headers, and rewrites URL paths before proxying requests to the correct upstream service.
The gateway strips CORS headers from backend responses to ensure it is the single source of truth for cross-origin policy.
Port & Language
| Property | Value |
|---|---|
| Port | 8000 |
| Language | Go 1.21+ |
| Framework | net/http standard library |
| Entry point | cmd/gateway/main.go |
Middleware Stack
Requests pass through the middleware chain in this order (reordered in R42b — auth
now runs before the rate limiter so the limiter keys per-user; behind Caddy every
remote address is 127.0.0.1, so IP keying would collapse all users onto one bucket):
Client Request → CORS → Auth → Rate Limiter → Route Handler → Backend Service1. CORS Middleware
Adds permissive CORS headers to every response and short-circuits OPTIONS preflight requests with a 200 OK.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Backend CORS headers are stripped by the reverse proxy’s ModifyResponse hook to prevent conflicts.
2. Auth Middleware
Authenticates the request and authorizes it fresh-at-perimeter (R42b). Authentication is JWT only — the gateway no longer accepts the X-API-Key header.
Where the JWT is read from (in order):
Authorization: Bearer <token>header — the standard case for API clients.aegis_tokencookie — the browser case (httpOnly since R42b; also covers SSE/EventSource clients that cannot set headers).
The token is first validated locally using HMAC-SHA256 with the JWT_SECRET environment variable (fast reject). Then the gateway consults its resolve client (internal/resolver): an in-memory cache keyed sha256(token) fronting auth-service’s POST /auth/resolve.
- Cached results ≤5s old are served without a network call.
- Older entries are re-fetched with a 500ms timeout.
- If auth-service errors, a stale entry keeps being served up to 60s old (stale-while-revalidate — an auth-service restart does not take the platform down).
- Beyond 60s stale, the request fails closed with
401.
The middleware stamps X-User-Id, X-User-Email (R42d), X-Roles, X-Tenant-Id, and X-Permissions onto the proxied request from the resolve result (not raw JWT claims) — inbound copies of these headers are stripped on every path first, so clients cannot spoof them — and role changes and deactivation bind on live traffic within ~5 seconds. The gateway has no Postgres client — auth-service stays the sole identity SQL surface. Cache hit rates and stale-served counts are logged once a minute (resolve cache: …).
Since R42d the stamped headers are not merely informational: every Python service verifies them via aegis_shared.auth dependencies (mode-gated by AEGIS_AUTH_MODE), so the gateway is the first of two authorization layers. See Auth Flow — Service-Side Enforcement.
Unauthenticated routes (auth skipped):
/health/api/v1/auth/token/api/v1/auth/logout(so a client with an expired token can still clear its cookie)/api/v1/auth/invites/*(public invite validation/redemption)/api/v1/entity-types*/api/v1/relationship-rules*/api/v1/relationship-types*
/api/v1/auth/resolve is deliberately NotFound’d — internal-only, same pattern as rules/evaluate.
3. Rate Limiter
Per-user token bucket rate limiter using golang.org/x/time/rate:
- Rate: 600 requests per minute (10 req/sec)
- Burst: 100 requests (dashboard pages burst-fetch on mount)
- Key: authenticated
user_idfrom context (set by the auth middleware); remote address only for public paths
When the limit is exceeded, the gateway returns 429 Too Many Requests.
For local development, obtain a token by logging in with admin@aegis.local / aegis-dev-admin at POST /api/v1/auth/token. That user has admin, operator, and reviewer roles.
Routing Rules
The gateway maps public API paths (prefixed with /api/v1/) to internal service paths. Each upstream service is configured via environment variables with sensible localhost defaults.
Route Table
| Public Path | Upstream Service | Internal Path | Notes |
|---|---|---|---|
/api/v1/conversations* | Orchestration (8001) | /conversations* | |
/api/v1/execute | Orchestration (8001) | /execute | POST only (R42c; executeSync) |
/api/v1/agents/{id}/execute | Orchestration (8001) | /execute | POST only |
/api/v1/agents/{id}/stream/{conv_id} | Orchestration (8001) | /conversations/{conv_id}/stream | SSE streaming |
/api/v1/approvals* | Approval (8004) | /approvals* | POST .../decide requires reviewer or admin (R42c) |
/api/v1/graph/context/managed/{id} | Knowledge Graph (8003) | /context/assemble/managed/{id} | R27 managed entities |
/api/v1/graph/context/{well_api} | Knowledge Graph (8003) | /context/assemble/{well_api} | Legacy well context |
/api/v1/graph/entities/* | Knowledge Graph (8003) | /entities/* | Raw label lists |
/api/v1/graph/query-template | Knowledge Graph (8003) | /query/template | Template-only (R42c); raw /query not routed |
/api/v1/impact/* | Knowledge Graph (8003) | /impact/* | |
/api/v1/admin/* | Knowledge Graph (8003) | /admin/* | Requires admin role |
/api/v1/entity-types* | Knowledge Graph (8003) | /entity-types* | Public (no auth) |
/api/v1/relationship-types* | Knowledge Graph (8003) | /relationship-types* | Public (no auth) |
/api/v1/relationship-rules* | Knowledge Graph (8003) | /relationship-rules* | Public (no auth) |
/api/v1/detection-rules* | Knowledge Graph (8003) | /event-detection-rules* | Requires power_user or admin |
/api/v1/entities* | Knowledge Graph (8003) | /managed-entities* | R25 managed CRUD |
/api/v1/ingest/csv | Ingestion (8005) | /ingest/operator/csv | |
/api/v1/ingest/* | Ingestion (8005) | /ingest/* | |
/api/v1/compliance/summary | Orchestration (8001) | /compliance/summary | R1.2 dashboard |
/api/v1/compliance/matrix | Orchestration (8001) | /compliance/matrix | R1.2 dashboard |
/api/v1/compliance/charts/* | Orchestration (8001) | /compliance/charts/* | R1.2 dashboard |
/api/v1/compliance/assess* | Orchestration (8001) | /compliance/assess* | |
/api/v1/compliance/* | Compliance (8006) | /compliance/* | Legacy endpoints |
/api/v1/checklists* | Orchestration (8001) | /checklists* | R1.3 |
/api/v1/rules* | Orchestration (8001) | /rules* | R1.5 |
/api/v1/workspaces/* | Orchestration (8001) | /workspaces/* | R1.4 SSE streaming |
/api/v1/events* | Flaring (8007) | /events* | Operational events |
/api/v1/flare-events* | Flaring (8007) | /flare-events* | |
/api/v1/repair-obligations* | Flaring (8007) | /repair-obligations* | |
/api/v1/authorizations* | Flaring (8007) | /authorizations* | Collection root + subtree |
/api/v1/event-types* | Flaring (8007) | /event-types* | Event-type defs + guard rules; writes require power_user/admin (R42c) |
/api/v1/alerts/* | Flaring (8007) | /alerts/* | |
/api/v1/compliance/domains | Orchestration (8001) | /compliance/domains | Domain registry (R42c; #216) |
/api/v1/flaring/* | Flaring (8007) | /flaring/* | Legacy routes |
/api/v1/settings* | Agent Config (8010) | /settings* | Platform settings (tenant LLM runtime config) |
/api/v1/auth/* | Auth (8009) | /auth/* | Includes admin-only GET /auth/users (user directory) |
/health | Gateway (local) | — | Returns {"status":"ok"} |
Role-Based Access Control
Some routes enforce role checks before proxying:
/api/v1/admin/*— Requires theadminrole. Returns403 Forbiddenotherwise./api/v1/detection-rules*— Requirespower_useroradminrole.
Roles come from the DB-fresh resolve result and are placed into the request context by the auth middleware. These route-level checks are coarse; fine-grained permission checks (hitl.approve, filings.submit, …) happen in the services themselves since R42d.
Architecture
cmd/gateway/main.go # Entry point, middleware chain assembly
internal/
├── auth/
│ └── jwt.go # JWT parsing, validation, generation (HS256)
├── middleware/
│ ├── auth.go # Auth middleware (JWT only — header or cookie)
│ ├── cors.go # CORS header injection
│ └── ratelimit.go # Per-client token bucket rate limiter
└── proxy/
└── routes.go # Route registration, reverse proxy setupJWT Claims Structure
type Claims struct {
UserID string `json:"user_id"`
Roles []string `json:"roles"`
jwt.RegisteredClaims
}Tokens are signed with HS256 and expire after 24 hours. The issuer is set to aegis-gateway.
Dependencies
| Dependency | Version | Purpose |
|---|---|---|
github.com/golang-jwt/jwt/v5 | v5.2.1 | JWT parsing and generation |
golang.org/x/time | v0.5.0 | Token bucket rate limiter |
The gateway has no database dependencies. It proxies all persistence to backend services.
Service Dependencies
The gateway calls the auth-service (port 8009) for token generation. JWT validation is done locally using the shared JWT_SECRET. All other backend services are called only as reverse proxy targets.
Configuration
| Environment Variable | Default | Description |
|---|---|---|
API_GATEWAY_PORT | 8000 | Port the gateway listens on |
JWT_SECRET | aegis-local-dev-secret-change-in-production | HMAC secret for JWT signing/validation |
ORCHESTRATION_URL | http://localhost:8001 | Orchestration engine upstream |
MEMORY_URL | http://localhost:8002 | Memory service upstream |
KNOWLEDGE_GRAPH_URL | http://localhost:8003 | Knowledge graph service upstream |
APPROVAL_URL | http://localhost:8004 | Approval service upstream |
INGESTION_URL | http://localhost:8005 | Ingestion service upstream |
COMPLIANCE_URL | http://localhost:8006 | Compliance monitor upstream |
FLARING_URL | http://localhost:8007 | Flaring monitor upstream |
AUTH_URL | http://localhost:8009 | Auth service upstream |
Running Locally
cd services/api-gateway
go run ./cmd/gateway/The gateway will start on port 8000 and log the upstream service URLs it is configured to proxy to. Make sure the backend services you need are running before sending requests through the gateway.
The gateway must share the same JWT_SECRET value as the auth-service. If these values diverge, JWT validation will fail for all authenticated requests.