> ## Documentation Index
> Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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 theme={"theme":"kanagawa-wave"}
      npx create-next-app@latest my-api
      cd my-api
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm create next-app@latest my-api
      cd my-api
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bunx create-next-app my-api
      cd my-api
      ```
    </CodeGroup>
  </Step>

  <Step title="Install the Unkey SDK">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm install @unkey/nextjs
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm add @unkey/nextjs
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      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 theme={"theme":"kanagawa-wave"}
    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 theme={"theme":"kanagawa-wave"}
    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 theme={"theme":"kanagawa-wave"}
      npm run dev
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm dev
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      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 theme={"theme":"kanagawa-wave"}
    curl -X POST http://localhost:3000/api/protected \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    You should see:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "message": "Hello!",
      "keyId": "key_..."
    }
    ```

    Now try without a key:

    ```bash Test without key theme={"theme":"kanagawa-wave"}
    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>
