Skip to content
Agentic Control Plane

Developer docs

Agentic Control Plane (ACP) is the identity and governance layer for AI agents. There are two ways to use it:

ACP Cloud — Managed multi-tenant gateway. Sign up, connect tools, create agents from the dashboard or REST API. Identity, governance, and audit are built in. No code required.

GatewayStack — Open-source (MIT) npm modules you self-host. Six composable middleware layers for Express. Deploy anywhere Node.js runs.

Both use the same architecture: every AI-initiated request is identified, authorized, scanned for PII, rate-limited, routed securely, and audited. How it all works →


Quickstart

ACP Cloud

# Trigger a governed agent with one HTTP request
export ACP_KEY="your-api-key-here"

curl -X POST https://api.agenticcontrolplane.com/<workspace>/agents/<profileId>/run \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACP_KEY" \
  -d '{"input": "Summarize open support tickets tagged urgent"}'

Create your first agent in 5 minutes → · Agent HTTP Triggers API →

GatewayStack (self-hosted)

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.

Full getting-started guide → · Reference architecture →


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

Reference architecture → · Architecture 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.

User LLM / MCP IdP (Auth0) Control Plane Backend 1. OAuth login (PKCE) 2. RS256 JWT (sub, scope, aud) 3. Prompt + Bearer token 4. Tool call + Bearer token 5. Verify JWT JWKS fetch (cached) 6. Check scope 7. PII + policy + limits 8. Forward + x-user-uid 9. Response 10. Audit log 11. Tool result

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

Identity providers

AI client integrations

Agent frameworks

Governance and compliance

Developer tools

View all 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, Firebase Auth, Google, and PingIdentity. identifiabl discovers JWKS endpoints automatically and validates RS256 tokens. Set up Auth0 → · Set up Okta → · Set up Entra ID →

ChatGPT Apps SDK

GatewayStack validates Apps SDK identity tokens and threads user identity through to your backend. ChatGPT + GitHub guide → · Reference implementation →

Model Context Protocol (MCP)

ACP exposes a standard MCP endpoint. Point any MCP-compatible client — Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Zed — at your workspace URL. Identity and governance are enforced on every request. Claude Desktop guide → · Connect external MCP servers →

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

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.