Neutrino Docs
API ReferenceGateway

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.

PrefixServiceDescription
/api/v1/iamiamNNO IAM service — authentication, sessions, organizations, roles, grants
/api/v1/platformsregistryNNO Registry service — platforms, tenants, resources, feature activations
/api/v1/billingbillingNNO Billing service — Stripe subscriptions, metering, invoicing. Billing mounts routes at / internally (no prefix re-prepend after stripping).
/api/v1/provisioningprovisioningNNO Provisioning service — Cloudflare resource job state machine
/api/v1/stacksstack-registryNNO Stack Registry service — versioned NNO-authored stack template catalogue
/api/v1/clicliNNO CLI Service — platform GitHub repo management and CF Pages build triggers
/api/v1/onboardingregistryNNO 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_KEY is not set, all requests pass through unauthenticated. Never set AUTH_API_KEY in .env to enable this bypass.

Middleware Pipeline

Middleware runs in this order on every request:

MiddlewareDescription
securityHeadersSets X-Content-Type-Options, X-Frame-Options, and Referrer-Policy on all responses
errorHandlerCatches unhandled errors and wraps them in the NNO error envelope
requestIdReads X-Request-Id from the incoming request, or generates a UUID if absent; stores as c.get("requestId")
requestLoggerStructured request logging using the request ID
timingHono timing middleware
rateLimiterCloudflare Rate Limiting API via RATE_LIMITER binding with in-memory fallback
corsCORS middleware — see CORS configuration below
tracingDistributed tracing headers (X-Nno-Trace-Id)
authAuthenticates all /api/* routes via the three paths above
platformLifecycleChecks platform suspension status after auth

Health Endpoint

GET /health

Returns 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.