Developer docs
GatewayStack is a set of composable npm modules for user-scoped AI governance. Each module can run standalone or as part of a six-layer pipeline.
Quickstart
Install the identity module and wire it into Express:
npm install @gatewaystack/identifiabl express
import express from "express";
import { identifiabl } from "@gatewaystack/identifiabl";
const app = express();
app.use(identifiabl({
issuer: process.env.OAUTH_ISSUER!,
audience: process.env.OAUTH_AUDIENCE!,
}));
app.get("/api/me", (req, res) => {
res.json({ user: req.user.sub, scopes: req.user.scope });
});
app.listen(8080);
Every request now requires a valid RS256 JWT. req.user contains the verified identity.
Modules
All six governance layers are live on npm. Each has a -core package (framework-agnostic) and an Express middleware wrapper.
| Module | npm | What it does |
|---|---|---|
@gatewaystack/identifiabl |
npm | RS256 JWT verification, identity normalization |
@gatewaystack/transformabl |
npm | PII detection, redaction, safety classification |
@gatewaystack/validatabl |
npm | Deny-by-default policy engine, scope/permission enforcement |
@gatewaystack/limitabl |
npm | Rate limits, budget tracking, agent guard |
@gatewaystack/proxyabl |
npm | Auth mode routing, SSRF protection, identity-aware proxy |
@gatewaystack/explicabl |
npm | Structured audit logging, health endpoints |
Full stack example
Wire all six layers together. Each is optional — use only what you need.
npm install @gatewaystack/identifiabl @gatewaystack/transformabl \
@gatewaystack/validatabl @gatewaystack/limitabl \
@gatewaystack/proxyabl @gatewaystack/explicabl \
@gatewaystack/request-context express
import express from "express";
import { runWithGatewayContext } from "@gatewaystack/request-context";
import { identifiabl } from "@gatewaystack/identifiabl";
import { transformabl } from "@gatewaystack/transformabl";
import { validatabl } from "@gatewaystack/validatabl";
import { limitabl } from "@gatewaystack/limitabl";
import { createProxyablRouter, configFromEnv } from "@gatewaystack/proxyabl";
import { createConsoleLogger, explicablLoggingMiddleware } from "@gatewaystack/explicabl";
const app = express();
app.use(express.json());
// 1. Establish request context for downstream layers
app.use((req, _res, next) => {
runWithGatewayContext(
{ request: { method: req.method, path: req.path } },
() => next()
);
});
// 2. Log every request
app.use(explicablLoggingMiddleware(createConsoleLogger()));
// 3. Require verified RS256 token
app.use(identifiabl({
issuer: process.env.OAUTH_ISSUER!,
audience: process.env.OAUTH_AUDIENCE!,
}));
// 4. Detect PII and classify content safety
app.use("/tools", transformabl({ blockThreshold: 80 }));
// 5. Enforce authorization policies
app.use("/tools", validatabl({
requiredPermissions: ["tool:read"],
}));
// 6. Apply rate limits and budget caps
app.use("/tools", limitabl({
rateLimit: { windowMs: 60_000, maxRequests: 100 },
budget: { maxSpend: 500, periodMs: 86_400_000 },
}));
// 7. Route /tools to your tool/model backends
app.use("/tools", createProxyablRouter(configFromEnv(process.env)));
app.listen(8080, () => {
console.log("GatewayStack running on :8080");
});
Architecture
The gateway pipeline processes each request through six composable layers:
user
→ identifiabl (who is calling?)
→ transformabl (prepare, clean, classify, anonymize)
→ validatabl (is this allowed?)
→ limitabl (how much can they use? pre-flight constraints)
→ proxyabl (where does it go? execute)
→ llm provider (model call)
→ [limitabl] (deduct usage - optional accounting phase)
→ explicabl (what happened?)
→ response
Each module intercepts the request, adds or checks metadata, and guarantees that the call is identified, transformed, validated, constrained, routed, and audited.
For the full architecture document, see architecture.md on GitHub.
OAuth identity flow
This is the end-to-end flow when a user triggers a tool call through ChatGPT or an MCP client. The control plane intercepts the call, verifies the user’s identity, enforces scopes, and injects user context before forwarding to your backend.
The key insight: your backend receives x-user-uid (the verified sub claim) on every request. It never sees a shared API key — it always knows exactly which user the LLM is acting for.
Guides
Module details
identifiabl — user identity & authentication
Every model call must be tied to a real user, tenant, and context. identifiabl verifies identity, handles OIDC/Apps SDK tokens, and attaches identity metadata to each request.
Live: OIDC/JWT via JWKS, claim mapping, multi-audience support Roadmap: Apps SDK tokens, multi-IdP federation
transformabl — content transformation & safety
Before a request can be validated or routed, its content must be transformed into a safe, structured form. transformabl handles PII detection and redaction, content classification (legal, sensitive, unsafe), jailbreak detection, and metadata extraction.
Live: Regex PII detection (email, phone, SSN, CC, IP, DOB), redaction, content classification, regulatory flags Roadmap: ML/NER detection, pseudonymization, sentiment/intent analysis
validatabl — access & policy enforcement
Once a request is tied to a user, validatabl ensures it follows your rules: tool- and model-level permissions, org-level policies, schema and safety checks, tenant boundaries, scopes and roles.
Live: Deny-by-default policy engine, scope/role checking, tool/model access control, tenant boundaries Roadmap: Safety integration, policy caching, YAML policy definitions
limitabl — rate limits, quotas, and spend controls
Every user, org, or agent needs usage constraints. limitabl enforces per-user rate limits, per-org quotas, budget ceilings, and cost anomaly detection. It runs in two phases — pre-flight checks before execution and usage accounting after the model responds.
Live: Sliding-window rate limits, budget tracking (preflight + accounting), agent guard Roadmap: Redis backend, persistent storage, anomaly detection
proxyabl — routing & execution
proxyabl is the gateway execution layer — the in-path request processor that chooses providers or models, forwards traffic, injects identity and policy metadata, applies routing rules, and respects constraints returned by limitabl.
Live: Auth mode routing (api_key, forward_bearer, service_oauth, user_oauth), SSRF protection, HTTP forwarding with identity injection Roadmap: Multi-provider selection, routing rules, failover, streaming
explicabl — observability & audit
The control plane must record who did what, what model they used, what policy decisions were triggered, what it cost, and what happened internally. explicabl provides the audit logs, traces, and metadata needed for trust, security, debugging, and compliance.
Live: HTTP audit logging, Auth0 webhook integration, health endpoints Roadmap: Model/cost tracking, policy decision logging, OpenTelemetry
Integration notes
OAuth2 / OIDC
GatewayStack works with any OIDC-compliant identity provider: Auth0, Okta, Cognito, Entra ID, Keycloak. identifiabl discovers JWKS endpoints automatically and validates RS256 tokens.
ChatGPT Apps SDK
GatewayStack validates Apps SDK identity tokens and threads user identity through to your backend. The reference implementation shows a complete ChatGPT integration with live demo.
Model Context Protocol (MCP)
GatewayStack threads identity through MCP tool calls. Agentic Control Plane Cloud provides a managed MCP gateway with tenant-scoped tool endpoints.
Any LLM provider
proxyabl routes to OpenAI, Anthropic, Google, Azure, or internal models. It acts as a drop-in governance layer without requiring changes to your application logic.
Prerequisites
- Node.js 20+
- npm 10+ (or pnpm 9)
- An OIDC provider issuing RS256 access tokens
Links
- GitHub repository
- npm packages
- Reference implementation
- Agentic Control Plane Cloud
- Enterprise contact
Troubleshooting
Common issues when integrating the identity and governance pipeline.
Token is encrypted (JWE), not signed (JWS)
Symptom: 401 response with access_token_is_encrypted_jwe.
Your identity provider is issuing opaque or encrypted tokens (JWE — 5 base64 segments) instead of signed JWTs (JWS — 3 segments). The control plane needs to verify the signature locally, which requires a signed RS256 token.
Fix: Configure your IdP to issue RS256-signed access tokens. In Auth0, go to APIs > your API > Settings > JSON Web Token Profile and select “RS256.”
Audience mismatch
Symptom: 401 response with jwt_verify_failed.
The aud claim in the token doesn’t match the OAUTH_AUDIENCE your gateway expects. This usually happens when the client requests a token for a different API identifier.
Fix: Ensure the audience parameter in your OAuth request matches the API identifier configured in your IdP. Check for trailing slashes — https://api.example.com and https://api.example.com/ are different audiences.
Insufficient scope
Symptom: 403 response with insufficient_scope.
The user’s token doesn’t include the scopes required by the tool’s scope allowlist. The WWW-Authenticate header in the response tells you which scopes are needed.
Fix: Check that your IdP grants the required scopes to the user or client. In Auth0, verify the API permissions and any Rules or Actions that modify token claims.
Token expired
Symptom: 401 response with token_expired or jwt_verify_failed.
The token’s exp claim is in the past. This happens with short-lived tokens or clock skew between the IdP and your server.
Fix: Check your token lifetime settings in the IdP. Default Auth0 access tokens expire in 24 hours, but custom APIs may have shorter lifetimes. If you see intermittent failures, check for clock drift on your server.
Identity claim missing
Symptom: Request passes authentication but the backend receives no user context.
The token verifies successfully but doesn’t contain a sub (subject) claim. Without sub, the gateway can’t inject a user identifier into the downstream request.
Fix: Ensure your IdP includes the sub claim in access tokens. Some providers only include sub in ID tokens by default. In Auth0, check that your API is configured with “RBAC Settings” enabled if you need role claims.