Gateway API
NNO Gateway — single public API entry point. Routes to internal services via Cloudflare Service Bindings.
The NNO Gateway is the single public API entry point for the Neutrino platform. All frontend and external requests are routed through the gateway. Every route is prefixed /api/v1/*; the gateway strips the full prefix before forwarding the request to the upstream service.
Routing
The gateway proxies requests to internal services via Cloudflare Service Bindings (deployed) or HTTP fallback URLs (local dev). The prefix is stripped before forwarding — for example, GET /api/v1/platforms/abc arrives at the Registry worker as GET /abc.
| Prefix | Service | Description |
|---|---|---|
/api/v1/iam | iam | NNO IAM service — authentication, sessions, organizations, roles, grants |
/api/v1/platforms | registry | NNO Registry service — platforms, tenants, resources, feature activations |
/api/v1/billing | billing | NNO Billing service — Stripe subscriptions, metering, invoicing. Billing mounts routes at / internally (no prefix re-prepend after stripping). |
/api/v1/provisioning | provisioning | NNO Provisioning service — Cloudflare resource job state machine |
/api/v1/stacks | stack-registry | NNO Stack Registry service — versioned NNO-authored stack template catalogue |
/api/v1/cli | cli | NNO CLI Service — platform GitHub repo management and CF Pages build triggers |
/api/v1/onboarding | registry | NNO Registry service — onboarding flows proxied through the registry worker |
Authentication
The gateway supports three authentication paths, all implemented in src/middleware/auth.ts.
Path 1 — Service-to-service (static API key)
Authorization: Bearer {AUTH_API_KEY}Matched via constant-time HMAC comparison. Bypasses the platform suspension check.
Path 2 — User session (IAM Bearer token)
Authorization: Bearer {session_token}Validated by calling IAM /api/nno/session. On success, sets nnoUserId, nnoRole, and nnoPlatformId on the Hono context, which are forwarded to upstream services as X-Nno-User-Id, X-Nno-Role, and X-Nno-Platform-Id headers.
Path 3 — Programmatic API key
x-api-key: nno_{key}Validated by IAM /api/nno/apikey/validate. Always carries a platformId.
Local dev bypass: when
AUTH_API_KEYis not set, all requests pass through unauthenticated. Never setAUTH_API_KEYin.envto enable this bypass.
Middleware Pipeline
Middleware runs in this order on every request:
| Middleware | Description |
|---|---|
securityHeaders | Sets X-Content-Type-Options, X-Frame-Options, and Referrer-Policy on all responses |
errorHandler | Catches unhandled errors and wraps them in the NNO error envelope |
requestId | Reads X-Request-Id from the incoming request, or generates a UUID if absent; stores as c.get("requestId") |
requestLogger | Structured request logging using the request ID |
timing | Hono timing middleware |
rateLimiter | Cloudflare Rate Limiting API via RATE_LIMITER binding with in-memory fallback |
cors | CORS middleware — see CORS configuration below |
tracing | Distributed tracing headers (X-Nno-Trace-Id) |
auth | Authenticates all /api/* routes via the three paths above |
platformLifecycle | Checks platform suspension status after auth |
Health Endpoint
GET /healthReturns HTTP 200 with plain-text body ok. Used by Cloudflare health checks. No authentication required.
CORS Configuration
Allowed headers: Content-Type, Authorization, X-Request-Id.
The origin allowlist is environment-specific and configured via wrangler.toml vars. It is not hardcoded in source.
Rate Limiting
The rateLimiter middleware uses the Cloudflare Rate Limiting API via the RATE_LIMITER binding defined in wrangler.toml. Requests are keyed by {ip}:{path} — rate limited per IP per route.
Fallback: when the RATE_LIMITER binding is unavailable (local dev, unit tests), an in-memory Map<string, {count, windowStart}> is used instead.
Platform Suspension
The platformLifecycleMiddleware runs after authMiddleware. If the resolved platform is in a suspended state, the gateway returns HTTP 451 with the NNO error envelope:
{
"error": {
"code": "PLATFORM_SUSPENDED",
"message": "This platform has been suspended.",
"requestId": "<uuid>"
}
}Service-to-service requests authenticated via the static API key bypass this check.
Error Envelope
All errors returned by the gateway follow the NNO error envelope format:
{
"error": {
"code": "NOT_FOUND",
"message": "Human readable message",
"details": {},
"requestId": "<uuid>"
}
}The requestId field is always populated from c.get("requestId"). Upstream services receiving proxied requests also see the request ID forwarded as the X-Nno-Request-Id header.