# Next.js

> Add API key authentication to your Next.js API routes using Unkey. Verify keys in route handlers to protect your server-side endpoints.

## 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

- [Unkey account](https://app.unkey.com/auth/sign-up) (free)
- [Keyspace created](https://app.unkey.com/apis) in your Unkey dashboard

<Steps titleSize="h3">

<Step title="Create a Next.js app">

Skip this if you have an existing project.

<CodeGroup>

```bash npm
npx create-next-app@latest my-api
cd my-api
```

```bash pnpm
pnpm create next-app@latest my-api
cd my-api
```

```bash bun
bunx create-next-app my-api
cd my-api
```

</CodeGroup>

</Step>

<Step title="Install the Unkey SDK">

<CodeGroup>

```bash npm
npm install @unkey/nextjs
```

```bash pnpm
pnpm add @unkey/nextjs
```

```bash bun
bun add @unkey/nextjs
```

</CodeGroup>

</Step>

<Step title="Add your root key">

Get a root key from _Settings → Root Keys_ and add it to your environment:

```bash .env.local
UNKEY_ROOT_KEY="unkey_..."
```

<Warning>Never commit your root key. Add `.env.local` to `.gitignore`.</Warning>

</Step>

<Step title="Create a protected route">

Create a new API route that requires authentication:

```ts 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! },
);
```

<Tip>
  The `withUnkey` wrapper handles key extraction, verification, and error
  responses automatically. Invalid keys never reach your handler.
</Tip>

</Step>

<Step title="Start your server">

<CodeGroup>

```bash npm
npm run dev
```

```bash pnpm
pnpm dev
```

```bash bun
bun dev
```

</CodeGroup>

</Step>

<Step title="Test it">

First, create a test key in your [Unkey dashboard](https://app.unkey.com), then:

```bash Test with valid key
curl -X POST http://localhost:3000/api/protected \
  -H "Authorization: Bearer YOUR_API_KEY"
```

You should see:

```json
{
  "message": "Hello!",
  "keyId": "key_..."
}
```

Now try without a key:

```bash Test without key
curl -X POST http://localhost:3000/api/protected
```

You'll get a `401 Unauthorized` response.

</Step>

</Steps>

## What's in `req.unkey`?

After verification, `req.unkey.data` contains:

| Field         | Type        | Description                                                         |
| ------------- | ----------- | ------------------------------------------------------------------- |
| `valid`       | `boolean`   | Whether the key passed all checks                                   |
| `code`        | `string`    | Status code (`VALID`, `NOT_FOUND`, `RATE_LIMITED`, etc.)            |
| `keyId`       | `string`    | The key's unique identifier                                         |
| `name`        | `string?`   | Human-readable name of the key                                      |
| `meta`        | `object?`   | Custom metadata associated with the key                             |
| `expires`     | `number?`   | Unix timestamp (in milliseconds) when the key will expire. (if set) |
| `credits`     | `number?`   | Remaining uses (if usage limits set)                                |
| `enabled`     | `boolean`   | Whether the key is enabled                                          |
| `roles`       | `string[]?` | Permissions attached to the key                                     |
| `permissions` | `string[]?` | Permissions attached to the key                                     |
| `identity`    | `object?`   | Identity info if `externalId` was set when creating the key         |
| `ratelimits`  | `object[]?` | Rate limit states (if rate limiting configured)                     |

<Note>
  `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`.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card
    title="Add rate limiting"
    icon="gauge-high"
    href="/platform/apis/features/ratelimiting/overview"
  >
    Limit requests per key
  </Card>
  <Card
    title="Set usage limits"
    icon="calculator"
    href="/platform/apis/features/remaining"
  >
    Cap total requests per key
  </Card>
  <Card
    title="Add permissions"
    icon="user-shield"
    href="/platform/apis/features/authorization/introduction"
  >
    Fine-grained access control
  </Card>
  <Card title="Next.js SDK Reference" icon="book" href="/libraries/ts/nextjs">
    Full SDK documentation
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Getting 401 even with a valid key?">
    - 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
  </Accordion>

  <Accordion title="Environment variable not loading?">
    - Restart your dev server after adding `.env.local` - Make sure the file is
    in your project root - Check for typos in the variable name
  </Accordion>
</AccordionGroup>
