Skip to main content
Unkey provides two TypeScript packages:
  • @unkey/api — Manage keys, APIs, and rate limits (server-side, requires root key)
  • @unkey/ratelimit — Standalone rate limiting (server-side, requires root key)
For framework-specific wrappers, see @unkey/nextjs and @unkey/hono.

Installation

npm install @unkey/api @unkey/ratelimit

Quick Start

Initialize the client

import { Unkey } from "@unkey/api";
import { Ratelimit } from "@unkey/ratelimit";

// For key management
const unkey = new Unkey({
  rootKey: process.env.UNKEY_ROOT_KEY!,
});

// For rate limiting
const limiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "my-app",
  limit: 10,
  duration: "60s",
});
Never expose your root key in client-side code. These SDKs are for server-side use only.

Verify an API Key

The most common operation — check if a user’s API key is valid:
import { Unkey } from "@unkey/api";

const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });

async function handleRequest(request: Request) {
  const apiKey = request.headers.get("x-api-key");

  if (!apiKey) {
    return new Response("Missing API key", { status: 401 });
  }

  try {
    const { meta, data } = await unkey.keys.verifyKey({
      key: apiKey,
    });

    if (!data.valid) {
      // Key is invalid, expired, rate limited, etc.
      return new Response(`Unauthorized: ${data.code}`, { status: 401 });
    }

    // Key is valid — access granted
    // data.identity?.externalId, data.meta, data.credits, etc. available
    return handleAuthenticatedRequest(request, data);
  } catch (err) {
    console.error(err);
    return new Response("Service unavailable", { status: 503 });
  }
}

Verification response

FieldTypeDescription
validbooleanWhether the key passed all checks
codestringStatus code (VALID, NOT_FOUND, RATE_LIMITED, etc.)
keyIdstringThe key’s unique identifier
namestring?Human-readable name of the key
metaobject?Custom metadata associated with the key
expiresnumber?Unix timestamp (in milliseconds) when the key will expire (if set)
creditsnumber?Remaining uses (if usage limits set)
enabledbooleanWhether the key is enabled
rolesstring[]?Roles attached to the key
permissionsstring[]?Permissions attached to the key
identityobject?Identity info if externalId was set when creating the key
ratelimitsobject[]?Rate limit states (if rate limiting configured)

Check permissions during verification

try {
  const { meta, data } = await unkey.keys.verifyKey({
    key: apiKey,
    permissions: "documents.write",  // Required permission
  });

  if (!data.valid && data.code === "INSUFFICIENT_PERMISSIONS") {
    return new Response("Forbidden", { status: 403 });
  }
} catch (err) {
  console.error(err);
  return new Response("Service unavailable", { status: 503 });
}

Create API Keys

Issue new keys for your users:
try {
  const { meta, data } = await unkey.keys.create({
    apiId: "api_...",

    // Optional but recommended
    prefix: "sk_live",                    // Visible prefix
    externalId: "user_123",              // Link to your user
    name: "Production key",               // Human-readable name

    // Optional limits
    expires: Date.now() + 30 * 24 * 60 * 60 * 1000,  // 30 days
    remaining: 1000,                      // Usage limit
    refill: {
      amount: 1000,
      interval: "monthly",                // Auto-refill
    },
    ratelimits: [{
      name: "requests",
      limit: 100,
      duration: 60000,                    // 100/minute
    }],

    // Optional metadata
    meta: {
      plan: "pro",
      createdBy: "admin",
    },
  });

  // Send data.key to your user (only time you'll see the full key!)
  console.log("New key:", data.key);
  console.log("Key ID:", data.keyId);
} catch (err) {
  console.error(err);
  throw new Error("Failed to create key");
}
The full API key is only returned once at creation. Unkey stores only a hash. Make sure to display it to your user immediately.

Update Keys

Modify an existing key’s configuration:
try {
  await unkey.keys.update({
    keyId: "key_...",

    // Any fields you want to change
    name: "Updated name",
    meta: { plan: "enterprise" },
    enabled: true,
    expires: Date.now() + 90 * 24 * 60 * 60 * 1000,
    ratelimits: [{
      name: "requests",
      limit: 1000,  // Upgraded limit
      duration: 60000,
    }],
  });
} catch (err) {
  console.error(err);
  throw new Error("Failed to update key");
}

Delete Keys

Permanently revoke a key:
try {
  await unkey.keys.delete({
    keyId: "key_...",
  });
} catch (err) {
  console.error(err);
  throw new Error("Failed to delete key");
}
Or disable temporarily (can re-enable later):
try {
  await unkey.keys.update({
    keyId: "key_...",
    enabled: false,
  });
} catch (err) {
  console.error(err);
  throw new Error("Failed to disable key");
}

Rate Limiting

Use @unkey/ratelimit for standalone rate limiting:
import { Ratelimit } from "@unkey/ratelimit";

const limiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "api",
  limit: 100,
  duration: "60s",
});

async function handleRequest(request: Request) {
  const userId = request.headers.get("x-user-id") ?? "anonymous";

  try {
    const { success, remaining, reset } = await limiter.limit(userId);

    if (!success) {
      return new Response("Rate limit exceeded", {
        status: 429,
        headers: {
          "Retry-After": Math.ceil((reset - Date.now()) / 1000).toString(),
          "X-RateLimit-Remaining": "0",
        },
      });
    }

    // Process request...
  } catch (err) {
    console.error(err);
    return new Response("Rate limit service unavailable", { status: 503 });
  }
}

Cost-based rate limiting

Expensive operations can consume more of the limit:
// Normal request costs 1
await limiter.limit(userId);

// Expensive request costs 10
await limiter.limit(userId, { cost: 10 });

Error Handling

Use try/catch to handle errors:
try {
  const { meta, data } = await unkey.keys.create({ ... });

  console.log("Request ID:", meta.requestId);
  console.log("Key ID:", data.keyId);
} catch (err) {
  console.error(err);
  throw new Error("Failed to create key");
}

Resilient verification

For production, handle edge cases:
import { Unkey } from "@unkey/api";

const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });

async function verifyApiKey(key: string): Promise<VerifyResult | null> {
  try {
    const { meta, data } = await unkey.keys.verifyKey({ key });

    return data;
  } catch (err) {
    // Network error, timeout, etc.
    console.error("Unkey verification error:", err);
    return null;  // Or return a default allow/deny
  }
}

TypeScript Types

The SDK is fully typed. Import types as needed:
import type { 
  VerifyKeyResult,
  Key,
  Api,
  RatelimitResponse,
} from "@unkey/api";

Framework Guides


Full Reference

GitHub

Complete auto-generated API reference
Last modified on February 6, 2026