Skip to main content

What you’ll build

A Next.js API route that requires a valid API key on every request. Invalid or missing keys get rejected with a 401. Time to complete: ~5 minutes

Prerequisites

1

Create a Next.js app

Skip this if you have an existing project.
npx create-next-app@latest my-api
cd my-api
2

Install the Unkey SDK

npm install @unkey/nextjs
3

Add your root key

Get a root key from Settings → Root Keys and add it to your environment:
.env.local
UNKEY_ROOT_KEY="unkey_..."
Never commit your root key. Add .env.local to .gitignore.
4

Create a protected route

Create a new API route that requires authentication:
app/api/protected/route.ts
import { NextRequestWithUnkeyContext, withUnkey } from "@unkey/nextjs";

export const POST = withUnkey(
  async (req: NextRequestWithUnkeyContext) => {
    // The key has already been verified at this point
    // Access verification details via req.unkey.data
        
    // Your API logic here
    // 
    // req.unkey.data contains all verification details.
    return Response.json({
      message: "Hello!",
      keyId: req.unkey.data.keyId,
      // If you set an externalId when creating the key:
      externalId: req.unkey.data.identity?.externalId,
    });
  },
  { rootKey: process.env.UNKEY_ROOT_KEY! }
);
The withUnkey wrapper handles key extraction, verification, and error responses automatically. Invalid keys never reach your handler.
5

Start your server

npm run dev
6

Test it

First, create a test key in your Unkey dashboard, then:
Test with valid key
curl -X POST http://localhost:3000/api/protected \
  -H "Authorization: Bearer YOUR_API_KEY"
You should see:
{
  "message": "Hello!",
  "keyId": "key_..."
}
Now try without a key:
Test without key
curl -X POST http://localhost:3000/api/protected
You’ll get a 401 Unauthorized response.

What’s in req.unkey?

After verification, req.unkey.data contains:
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[]?Permissions 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)
data.meta is your custom key metadata (set via meta when creating the key). This is different from the response’s top-level meta which contains requestId.

Next steps

Troubleshooting

  • Ensure the key hasn’t expired or been revoked
  • Verify the Authorization header format: Bearer YOUR_KEY (note the space)
  • Check that your root key has the verify_key permission
  • Restart your dev server after adding .env.local
  • Make sure the file is in your project root
  • Check for typos in the variable name
Last modified on February 16, 2026