Unkey vs Zuplo vs Kong Gateway 2026
TL;DR: Unkey is the open-source API key management platform — sub-millisecond key verification, built-in rate limiting, usage analytics, and temporary keys with no gateway to deploy. Zuplo is the programmable API gateway built on Cloudflare Workers — edge-deployed, TypeScript policies, OpenAPI-native, and developer portal included. Kong Gateway is the enterprise API gateway — plugin ecosystem, service mesh, multi-protocol support, and the most mature self-hosted option. In 2026: Unkey for API key management without a full gateway, Zuplo for edge-native programmable gateways, Kong for enterprise API infrastructure.
Key Takeaways
- Unkey: Open-source (Apache 2.0), key management focused. API key creation/verification, rate limiting, usage tracking, temporary keys. Not a full gateway — integrates into your existing API. Best for adding API key auth and rate limiting without deploying infrastructure
- Zuplo: Cloud-native, edge-deployed. TypeScript request/response policies, OpenAPI-first, built-in developer portal, Cloudflare Workers runtime. Best for API products needing a gateway with developer portal and edge performance
- Kong Gateway: Open-source + enterprise. Plugin architecture, service mesh (Kuma), multi-protocol (REST, gRPC, GraphQL, WebSocket), Kubernetes-native. Best for enterprise API infrastructure with complex routing and plugin needs
Unkey — API Key Management
Unkey gives you API key management, rate limiting, and usage tracking without deploying a gateway — just verify keys inline.
Creating and Managing API Keys
import { Unkey } from "@unkey/api";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
// Create an API key
const { result } = await unkey.keys.create({
apiId: process.env.UNKEY_API_ID!,
prefix: "sk_live", // Key prefix for identification
name: "Acme Corp Production",
ownerId: "customer_42", // Link to your customer
meta: {
plan: "enterprise",
company: "Acme Corp",
},
// Rate limiting
ratelimit: {
type: "fast", // "fast" (local) or "consistent" (global)
limit: 1000,
refillRate: 100, // 100 tokens per interval
refillInterval: 1000, // every 1 second
},
// Auto-expire after 90 days
expires: Date.now() + 90 * 24 * 60 * 60 * 1000,
// Usage limits
remaining: 1000000, // Max 1M requests total
});
console.log(`Key: ${result.key}`); // sk_live_abc123...
console.log(`Key ID: ${result.keyId}`);
Verifying API Keys
import { verifyKey } from "@unkey/api";
// Verify in your API handler — sub-millisecond
async function authenticateRequest(req: Request): Promise<{
valid: boolean;
ownerId?: string;
meta?: Record<string, unknown>;
ratelimit?: { remaining: number };
}> {
const apiKey = req.headers.get("Authorization")?.replace("Bearer ", "");
if (!apiKey) return { valid: false };
const { result, error } = await verifyKey({
key: apiKey,
apiId: process.env.UNKEY_API_ID!,
});
if (error || !result.valid) {
return { valid: false };
}
return {
valid: true,
ownerId: result.ownerId,
meta: result.meta,
ratelimit: result.ratelimit
? { remaining: result.ratelimit.remaining }
: undefined,
};
}
// Express middleware
function unkeyAuth(req: Request, res: Response, next: NextFunction) {
authenticateRequest(req).then(({ valid, ownerId, meta, ratelimit }) => {
if (!valid) {
return res.status(401).json({ error: "Invalid API key" });
}
if (ratelimit && ratelimit.remaining <= 0) {
return res.status(429).json({ error: "Rate limit exceeded" });
}
req.customerId = ownerId;
req.plan = meta?.plan;
next();
});
}
app.use("/api/*", unkeyAuth);
Rate Limiting
// Standalone rate limiting (without API keys)
import { Ratelimit } from "@unkey/ratelimit";
const limiter = new Ratelimit({
rootKey: process.env.UNKEY_ROOT_KEY!,
namespace: "api.requests",
limit: 100,
duration: "60s", // 100 requests per 60 seconds
});
app.use("/api/*", async (req, res, next) => {
const identifier = req.ip || req.headers["x-forwarded-for"];
const { success, remaining, reset } = await limiter.limit(identifier);
res.setHeader("X-RateLimit-Remaining", remaining);
res.setHeader("X-RateLimit-Reset", reset);
if (!success) {
return res.status(429).json({
error: "Rate limit exceeded",
retryAfter: Math.ceil((reset - Date.now()) / 1000),
});
}
next();
});
// Tiered rate limiting based on plan
async function tierLimiter(req: Request, res: Response, next: NextFunction) {
const plan = req.plan || "free";
const limits = {
free: { limit: 100, duration: "60s" },
pro: { limit: 1000, duration: "60s" },
enterprise: { limit: 10000, duration: "60s" },
};
const config = limits[plan];
const ratelimit = new Ratelimit({
rootKey: process.env.UNKEY_ROOT_KEY!,
namespace: `api.${plan}`,
...config,
});
const { success, remaining } = await ratelimit.limit(req.customerId);
if (!success) return res.status(429).json({ error: "Rate limit exceeded" });
res.setHeader("X-RateLimit-Remaining", remaining);
next();
}
Key Analytics and Management
// List keys for a customer
const { result: keys } = await unkey.keys.list({
apiId: process.env.UNKEY_API_ID!,
ownerId: "customer_42",
});
for (const key of keys.keys) {
console.log(`${key.name}: ${key.start}... (${key.remaining} remaining)`);
}
// Update a key
await unkey.keys.update({
keyId: keyId,
ratelimit: {
type: "fast",
limit: 5000, // Upgrade rate limit
refillRate: 500,
refillInterval: 1000,
},
meta: { plan: "enterprise-plus" },
});
// Revoke a key
await unkey.keys.delete({ keyId: keyId });
// Get usage analytics
const { result: analytics } = await unkey.keys.getVerifications({
keyId: keyId,
start: Date.now() - 7 * 24 * 60 * 60 * 1000, // Last 7 days
granularity: "day",
});
for (const day of analytics.verifications) {
console.log(`${day.time}: ${day.success} ok, ${day.rateLimited} limited`);
}
Zuplo — Edge-Native Programmable Gateway
Zuplo is a programmable API gateway deployed to 300+ edge locations — TypeScript policies, OpenAPI-first design, and a built-in developer portal.
Gateway Configuration
// routes.oas.json — OpenAPI-based route configuration
{
"openapi": "3.1.0",
"info": { "title": "Acme API", "version": "2.0" },
"paths": {
"/v2/projects": {
"get": {
"operationId": "listProjects",
"x-zuplo-route": {
"handler": {
"module": "$import(@zuplo/runtime)",
"export": "urlForwardHandler",
"options": {
"baseUrl": "https://api-internal.acme.com"
}
},
"policies": {
"inbound": [
"api-key-auth",
"rate-limit-inbound",
"request-validation"
],
"outbound": [
"remove-internal-headers"
]
}
}
}
}
}
}
Custom TypeScript Policies
// modules/policies/custom-auth.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export default async function customAuth(
request: ZuploRequest,
context: ZuploContext,
options: { requiredScopes?: string[] },
policyName: string
) {
const apiKey = request.headers.get("authorization")?.replace("Bearer ", "");
if (!apiKey) {
return new Response(JSON.stringify({ error: "Missing API key" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// Verify key against your auth system
const keyInfo = await context.cache.get(`key:${apiKey}`);
if (!keyInfo) {
// Cache miss — verify against backend
const verification = await fetch("https://auth.acme.com/verify", {
method: "POST",
body: JSON.stringify({ key: apiKey }),
});
if (!verification.ok) {
return new Response(JSON.stringify({ error: "Invalid API key" }), {
status: 401,
});
}
const data = await verification.json();
await context.cache.set(`key:${apiKey}`, JSON.stringify(data), 300); // 5 min cache
}
// Check scopes
if (options.requiredScopes) {
const scopes = keyInfo.scopes || [];
const hasScope = options.requiredScopes.every((s) => scopes.includes(s));
if (!hasScope) {
return new Response(JSON.stringify({ error: "Insufficient scope" }), {
status: 403,
});
}
}
// Attach user info to request for downstream handlers
request.user = keyInfo;
return request; // Continue to next policy/handler
}
Request/Response Transformation
// modules/policies/transform-response.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export default async function transformResponse(
response: Response,
request: ZuploRequest,
context: ZuploContext,
options: {},
policyName: string
) {
const body = await response.json();
// Transform the response
const transformed = {
data: body.results,
pagination: {
total: body.total_count,
page: body.page,
perPage: body.per_page,
nextCursor: body.next_cursor,
},
meta: {
requestId: context.requestId,
timestamp: new Date().toISOString(),
},
};
return new Response(JSON.stringify(transformed), {
status: response.status,
headers: {
"Content-Type": "application/json",
"X-Request-Id": context.requestId,
"Cache-Control": "public, max-age=60",
},
});
}
Built-in Developer Portal
// zuplo.jsonc — developer portal configuration
{
"developerPortal": {
"enabled": true,
"pageTitle": "Acme API Documentation",
"faviconUrl": "/public/favicon.ico",
"logoUrl": "/public/logo.svg",
"theme": {
"primary": "#0D9373",
"background": "#0a0a0a"
},
"authentication": {
"provider": "auth0",
"issuer": "https://acme.auth0.com/",
"clientId": "abc123"
},
"generateExamples": true,
"enableKeyManagement": true
}
}
Kong Gateway — Enterprise API Infrastructure
Kong Gateway is the enterprise-grade API gateway — plugin ecosystem, service mesh integration, and multi-protocol support.
Kubernetes Ingress Configuration
# Kong Ingress Controller — Kubernetes-native
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: rate-limiting
config:
minute: 100
policy: redis
redis:
host: redis.default.svc.cluster.local
port: 6379
plugin: rate-limiting
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: key-auth
plugin: key-auth
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: cors
config:
origins: ["https://app.acme.com"]
methods: ["GET", "POST", "PUT", "DELETE"]
headers: ["Authorization", "Content-Type"]
max_age: 3600
plugin: cors
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway
annotations:
konghq.com/plugins: rate-limiting, key-auth, cors
konghq.com/strip-path: "true"
spec:
ingressClassName: kong
rules:
- host: api.acme.com
http:
paths:
- path: /v2/projects
pathType: Prefix
backend:
service:
name: project-service
port:
number: 8080
- path: /v2/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
Admin API
// Kong Admin API — manage configuration
const KONG_ADMIN = "http://kong-admin:8001";
// Create a service
await fetch(`${KONG_ADMIN}/services`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "project-service",
url: "http://project-svc.default.svc.cluster.local:8080",
retries: 3,
connect_timeout: 5000,
read_timeout: 30000,
}),
});
// Create a route
await fetch(`${KONG_ADMIN}/services/project-service/routes`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "projects-route",
paths: ["/v2/projects"],
methods: ["GET", "POST", "PUT", "DELETE"],
strip_path: true,
protocols: ["https"],
}),
});
// Enable plugins on a service
await fetch(`${KONG_ADMIN}/services/project-service/plugins`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "rate-limiting",
config: {
minute: 1000,
hour: 50000,
policy: "redis",
redis: { host: "redis", port: 6379 },
},
}),
});
// JWT authentication plugin
await fetch(`${KONG_ADMIN}/services/project-service/plugins`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "jwt",
config: {
claims_to_verify: ["exp"],
key_claim_name: "iss",
header_names: ["Authorization"],
},
}),
});
// Request transformation
await fetch(`${KONG_ADMIN}/services/project-service/plugins`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "request-transformer",
config: {
add: {
headers: ["X-Request-ID:$(uuid)"],
querystring: ["version:v2"],
},
remove: {
headers: ["X-Internal-Token"],
},
},
}),
});
Custom Plugin (Lua)
-- kong/plugins/custom-auth/handler.lua
local CustomAuth = {
PRIORITY = 1000,
VERSION = "1.0.0",
}
function CustomAuth:access(conf)
local api_key = kong.request.get_header("X-API-Key")
if not api_key then
return kong.response.exit(401, { message = "Missing API key" })
end
-- Verify against external service
local httpc = require("resty.http").new()
local res, err = httpc:request_uri(conf.auth_url, {
method = "POST",
body = kong.table.new(0, 1),
headers = {
["Content-Type"] = "application/json",
},
})
if not res or res.status ~= 200 then
return kong.response.exit(403, { message = "Invalid API key" })
end
-- Set consumer headers for upstream
local body = require("cjson").decode(res.body)
kong.service.request.set_header("X-Consumer-ID", body.customer_id)
kong.service.request.set_header("X-Consumer-Plan", body.plan)
end
return CustomAuth
Feature Comparison
| Feature | Unkey | Zuplo | Kong Gateway |
|---|---|---|---|
| Type | API key management | Programmable gateway | Full API gateway |
| Deployment | Cloud (no infra) | Edge (300+ PoPs) | Self-hosted or cloud |
| License | Apache 2.0 | Proprietary | Apache 2.0 + Enterprise |
| API Key Management | ✅ (core feature) | ✅ (built-in) | Plugin (key-auth) |
| Rate Limiting | ✅ (global + local) | ✅ (edge-native) | Plugin (Redis-backed) |
| Request Routing | ❌ (not a gateway) | ✅ | ✅ |
| Request Transformation | ❌ | ✅ (TypeScript) | ✅ (plugins) |
| Authentication | API keys | API keys, JWT, OAuth | API keys, JWT, OAuth, OIDC, LDAP |
| Developer Portal | ❌ | ✅ (built-in) | Kong Portal (Enterprise) |
| Custom Logic | N/A | TypeScript policies | Lua plugins |
| OpenAPI Support | ❌ | ✅ (OpenAPI-first) | Spec-based routes |
| gRPC Support | ❌ | ❌ | ✅ |
| WebSocket Support | ❌ | ✅ | ✅ |
| Service Mesh | ❌ | ❌ | ✅ (Kuma) |
| Analytics | ✅ (key usage) | ✅ (request analytics) | ✅ (Vitals - Enterprise) |
| Kubernetes Native | ❌ | ❌ | ✅ (Ingress Controller) |
| Plugin Ecosystem | N/A | Policies (TypeScript) | 100+ plugins |
| Latency Overhead | <1ms (key verify) | <5ms (edge) | 1-10ms |
| Complexity | Very low | Low | Medium-High |
| Best For | API key auth/limits | Edge API gateway | Enterprise infrastructure |
When to Use Each
Choose Unkey if:
- You need API key management without deploying a gateway
- Sub-millisecond key verification with built-in rate limiting is important
- Temporary keys with auto-expiry and usage caps fit your use case
- You already have an API and just need auth + rate limiting added
- Per-key analytics and usage tracking drive billing or monitoring
Choose Zuplo if:
- You're building an API product and need a gateway with developer portal
- Edge deployment (300+ locations) for low-latency global access matters
- TypeScript-based request/response policies give your team flexibility
- OpenAPI-first design with automatic validation is important
- You want a modern gateway that deploys like a Cloudflare Worker
Choose Kong Gateway if:
- Enterprise API infrastructure with 100+ plugins is needed
- Multi-protocol support (REST, gRPC, GraphQL, WebSocket) is important
- Kubernetes Ingress Controller for service mesh integration matters
- Self-hosted deployment for compliance and data residency is required
- You need service mesh capabilities (Kuma) alongside API gateway
Ecosystem and Community Health
Unkey is open-source (Apache 2.0) and has built a strong following among API-first startups and developer tool companies. Founded by Andreas Thomas and now backed by funding from several investor groups, Unkey is developing rapidly. The GitHub repository serves both as the source code for the cloud service and as a deployable self-hosted option, which gives developers full transparency. The TypeScript SDK is the primary integration path, and the quality and documentation of that SDK are consistently praised in the developer community.
Zuplo's position changed significantly when Cloudflare made it a preferred gateway partner for Workers deployments. Zuplo runs natively on Cloudflare's infrastructure, meaning the gateway has the same global footprint (300+ edge locations) as Cloudflare Workers without any additional configuration. This tight integration makes Zuplo the natural choice for API companies already deployed on Cloudflare. The built-in developer portal — which auto-generates from your OpenAPI spec and handles key management — removes the need for a separate developer portal product.
Kong Gateway has the longest history and the deepest enterprise adoption. Banks, telecoms, and large government deployments run Kong to handle billions of API calls per day. The plugin ecosystem — over 100 official plugins and a large marketplace of community plugins — means almost any API management requirement can be addressed without custom code. Kong's Kubernetes Ingress Controller (KIC) is particularly popular for teams that have moved to Kubernetes and want API gateway behavior to live alongside their service definitions.
API Gateway and the Broader API Infrastructure Stack
API gateways exist within a broader infrastructure context. For Node.js applications, the gateway sits in front of the application servers and handles cross-cutting concerns — rate limiting, authentication validation, request logging, response caching — so that the application code doesn't need to. The application server (whether running Hono, Express, Fastify, or NestJS) focuses on business logic while the gateway handles infrastructure concerns.
The choice of API gateway often follows the choice of deployment infrastructure. Teams deploying to Cloudflare Workers are natural Zuplo users. Teams running Kubernetes deployments at scale tend toward Kong or AWS API Gateway. Teams building SaaS products that need to issue and manage API keys for their customers are natural Unkey users. The "right" gateway is the one that aligns with your infrastructure and team's operational expertise.
For teams building the API layer that sits behind these gateways, the Hono vs Elysia 2026 comparison covers the TypeScript-native HTTP framework options. For authentication middleware that integrates with gateway-level JWT validation, WorkOS vs Stytch vs FusionAuth covers the identity platform options that pair with API gateway authentication plugins.
Rate limiting and API key management are just two dimensions of the API platform problem. Observability — understanding which endpoints are called, by whom, at what rate, and with what error rates — is the third. All three gateways (Unkey, Zuplo, Kong) provide analytics dashboards, but the depth and real-time nature of that analytics differs significantly. Unkey's analytics are key-centric (per-key usage, per-key rate limit status); Zuplo's analytics integrate with Cloudflare's analytics platform for edge-level visibility; Kong's analytics scale to billions of events with plugin integrations for DataDog, Prometheus, and ElasticSearch. Choosing a gateway also means choosing an observability approach, which is worth evaluating alongside the gateway's core rate limiting and routing capabilities. Teams that already have a preferred observability platform should verify that their chosen gateway integrates with it before committing.
Real-World Adoption
Unkey's value proposition resonates most strongly with SaaS companies launching an API product for the first time. When you need to give customers API keys, track usage per key, enforce rate limits by tier, and rotate keys without downtime — Unkey handles all of this in a few hours of integration rather than weeks of custom infrastructure. Companies like OpenAI have popularized the sk_live prefix model for API keys, and Unkey lets you implement exactly that pattern with full observability.
Zuplo is popular with API-first companies building developer platforms: fintech APIs, payment gateways, and data APIs that expose their service through a documented HTTP API with a developer portal. The combination of edge performance, TypeScript-native policy code, and the built-in developer portal makes Zuplo particularly strong for teams that want to iterate on API behavior without infrastructure changes.
Kong's adoption spans industries but is especially deep in financial services and healthcare. The compliance requirements in these industries — audit logging, request/response inspection, mutual TLS, custom authentication protocols — are areas where Kong's extensibility and enterprise support contracts provide genuine risk reduction. Kong Mesh (built on Kuma) extends this to service-to-service communication within Kubernetes clusters, giving platform teams a single tool for both north-south traffic (internet to cluster) and east-west traffic (service to service).
Developer Experience Deep Dive
Unkey's developer experience is intentionally minimal. The dashboard is clean, key creation via the API takes a single HTTP request, and the TypeScript SDK is well-typed with full IntelliSense support. The verifyKey function returns consistent structured data — valid boolean, remaining quota, rate limit info — that's easy to build middleware around. Documentation covers every SDK function, and the interactive playground on the website lets you test key creation before writing code.
The operational simplicity of Unkey deserves emphasis. You add two environment variables, three lines of middleware code, and you have API key authentication with rate limiting and per-key analytics. There's no gateway to deploy, no configuration files, no infrastructure to manage. For a small team that wants to ship an API product, this is genuinely transformative.
Zuplo's developer experience centers on the routes.oas.json file and the policy system. If your team knows OpenAPI, the configuration model will feel natural. The TypeScript policy API is clean — your policy function receives the request, has access to a cache, can make fetch calls, and returns either the modified request or an error response. Local development works via Zuplo's own CLI, which runs a local gateway instance that matches production behavior closely.
Kong's configuration surface area is large, which is both its strength and its friction. The Admin API is comprehensive but requires understanding Kong's data model: services, routes, consumers, plugins. The KIC (Kubernetes Ingress Controller) hides some of this complexity behind Kubernetes annotations, which is the recommended path for new Kong deployments in 2026.
Performance Analysis
Unkey's key verification latency is remarkable. The global distribution of verification nodes means that even without caching in your application, most key verifications complete in under 10ms from anywhere in the world. The "fast" rate limit type uses a local token bucket that makes rate limiting decisions in under 1ms without a network call, at the cost of some accuracy across multiple instances. The "consistent" rate limit uses a distributed counter that's accurate globally but adds 10-20ms per request.
Zuplo's edge deployment on Cloudflare eliminates the round-trip penalty that server-based gateways introduce. When your users are distributed globally and your gateway runs at the edge closest to them, the added gateway latency is typically under 5ms. For APIs where every millisecond matters — financial data APIs, gaming APIs, real-time services — edge deployment is not a nice-to-have but a requirement.
Kong Gateway, running self-hosted, typically adds 1-10ms of latency depending on the number of plugins active. Each plugin in the request pipeline adds processing overhead. Benchmark testing on Kong's own hardware has shown sub-1ms overhead with minimal plugins, scaling to 10ms+ with a full plugin stack including logging, authentication, rate limiting, and request transformation. For most enterprise use cases, this is acceptable.
Migration and Getting Started
For Unkey, the fastest path is: create an account at unkey.com, create an API and note the API ID, generate a root key, install @unkey/api, and add the middleware snippet from the documentation. The whole process takes under an hour for a working integration. If you already have API key management and want to migrate, Unkey's import API lets you bulk-import existing key hashes (it stores hashed keys, not plaintext).
For Zuplo, start with the Zuplo CLI: npm install -g zuplo, create a new project, configure your first route in routes.oas.json pointing to a backend service, add the built-in api-key-auth policy to the route, and deploy. The Zuplo dashboard then handles the developer portal automatically from your OpenAPI spec.
For Kong, the recommended starting point in 2026 is the Kubernetes Ingress Controller. Add Kong to your cluster via Helm, convert your existing Ingress resources to use the Kong ingress class, and start adding KongPlugin annotations to your Ingress definitions. This incremental approach lets you adopt Kong gradually without a big-bang migration.
Final Verdict 2026
Choose Unkey when you're adding API key management to an existing API without wanting to deploy gateway infrastructure. If you're a SaaS company that needs to expose a customer-facing API with per-customer rate limits, key rotation, and usage analytics, Unkey is the fastest and most focused solution.
Choose Zuplo when you're building an API product from scratch and want the developer portal, edge deployment, and TypeScript policy system in one package. For developer-facing APIs where documentation and key management are part of the product experience, Zuplo is the most complete modern option.
Choose Kong Gateway when you're operating enterprise-scale API infrastructure with Kubernetes, complex multi-protocol requirements, or compliance needs that require a self-hosted, auditable, extensively plugin-extended gateway.
Methodology
Feature comparison based on Unkey, Zuplo, and Kong Gateway (OSS + Enterprise) documentation as of March 2026. Unkey evaluated on key management API, rate limiting, and verification latency. Zuplo evaluated on edge performance, policy system, and developer portal. Kong evaluated on plugin ecosystem, protocol support, and Kubernetes integration. Code examples use official SDKs and REST APIs.
Related: