How do I integrate Unkey key verification into a Next.js API route on Vercel (minimal example)?
API Access Management

How do I integrate Unkey key verification into a Next.js API route on Vercel (minimal example)?

5 min read

Most teams deploying Next.js APIs on Vercel want a minimal, secure way to verify API keys before handling a request. Unkey’s developer‑friendly SDK makes this straightforward: you call a single verify method in your API route, then allow or deny the request based on the result.

Below is a concise, end‑to‑end example showing how to integrate Unkey key verification into a Next.js API route running on Vercel, using the @unkey/api TypeScript SDK.


1. Install the Unkey SDK

First, add the official Unkey SDK to your Next.js project:

npm install @unkey/api
# or
yarn add @unkey/api
# or
pnpm add @unkey/api

2. Set your Unkey root key in Vercel

In your Unkey dashboard, create or copy your root key (often named UNKEY_ROOT_KEY).

Then, in Vercel:

  1. Go to your project in the Vercel dashboard.

  2. Open Settings → Environment Variables.

  3. Add:

    • Name: UNKEY_ROOT_KEY
    • Value: sk_root_... (your real Unkey root key)
    • Environment: Production (and Preview/Development if needed)
  4. Redeploy / re-run your app so the variable is available.

This root key should never be exposed to the browser; keep it server‑side only.


3. Minimal example with the Next.js App Router (app/api)

If you’re using the App Router (Next.js 13+ with app/), create a minimal route that verifies an incoming Unkey API key before responding.

File: app/api/secure/route.ts

import { NextRequest, NextResponse } from "next/server";
import { Unkey } from "@unkey/api";

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

export async function GET(req: NextRequest) {
  // 1. Read API key from Authorization header: "Bearer <key>"
  const authHeader = req.headers.get("authorization");
  const apiKey = authHeader?.startsWith("Bearer ")
    ? authHeader.slice("Bearer ".length)
    : null;

  if (!apiKey) {
    return NextResponse.json(
      { error: "Missing API key" },
      { status: 401 }
    );
  }

  // 2. Verify the key with Unkey
  const { result, error } = await unkey.keys.verifyKey({
    key: apiKey,
  });

  if (error) {
    // Network / Unkey error
    console.error("Unkey verification error:", error);
    return NextResponse.json(
      { error: "Key verification failed" },
      { status: 502 }
    );
  }

  if (!result.valid) {
    // Invalid, expired, or revoked key
    return NextResponse.json(
      { error: "Invalid or unauthorized API key" },
      { status: 403 }
    );
  }

  // 3. Key is valid → proceed with your secure logic
  return NextResponse.json(
    {
      message: "Success! Unkey key is valid.",
      keyId: result.keyId,
      ownerId: result.ownerId,
    },
    { status: 200 }
  );
}

This is the minimal pattern you need on Vercel:

  1. Parse the key from the request (e.g., Authorization header).
  2. Call unkey.keys.verifyKey({ key }).
  3. Handle errors and invalid keys with proper HTTP status codes.
  4. Proceed only when result.valid is true.

4. Minimal example with the Pages Router (pages/api)

If you’re still using the Pages Router (pages/api), the same logic applies with a slightly different handler signature.

File: pages/api/secure.ts

import type { NextApiRequest, NextApiResponse } from "next";
import { Unkey } from "@unkey/api";

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

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "GET") {
    res.setHeader("Allow", "GET");
    return res.status(405).json({ error: "Method not allowed" });
  }

  const authHeader = req.headers.authorization;
  const apiKey = authHeader?.startsWith("Bearer ")
    ? authHeader.slice("Bearer ".length)
    : null;

  if (!apiKey) {
    return res.status(401).json({ error: "Missing API key" });
  }

  const { result, error } = await unkey.keys.verifyKey({
    key: apiKey,
  });

  if (error) {
    console.error("Unkey verification error:", error);
    return res.status(502).json({ error: "Key verification failed" });
  }

  if (!result.valid) {
    return res.status(403).json({ error: "Invalid or unauthorized API key" });
  }

  return res.status(200).json({
    message: "Success! Unkey key is valid.",
    keyId: result.keyId,
    ownerId: result.ownerId,
  });
}

5. Testing your minimal Unkey integration on Vercel

After deploying to Vercel:

  1. Copy a valid Unkey API key for testing (not the root key).
  2. Use curl or any HTTP client to call your route.

Example with the App Router route at /api/secure:

curl https://your-vercel-app.vercel.app/api/secure \
  -H "Authorization: Bearer sk_1234abcdef"

You should see a successful JSON response when the key is valid, and 401, 403, or 502 for missing/invalid/verification errors.


6. Minimal hardening tips

To keep the example minimal but practical on Vercel:

  • Fail closed: if UNKEY_ROOT_KEY is missing, log an error during startup or throw, so you never run without verification.
  • Reuse the Unkey client: define it once at the module level (as shown) so it’s reused across invocations in a Vercel serverless environment.
  • Consistent error messages: avoid leaking internal details; use generic messages for invalid keys and log specifics server‑side.

7. Why this pattern works well with Unkey and Vercel

Unkey is designed to be:

  • API-first: everything is driven through a clean SDK and REST API.
  • Multi-cloud and globally low-latency: verification remains fast for Vercel’s global edge and serverless regions.
  • Secure from day one: built-in key verification, expiration, and usage controls reduce custom security plumbing.

By dropping this minimal snippet into your Next.js API route, you get a clean, production-ready pattern for Unkey key verification on Vercel with almost no configuration.