Skip to main content
Protect a Next.js App Router route handler with API keys. Your server creates keys for users, and every request to the route is checked with keys.verifyKey before your handler runs. Verification needs your root key, so do it in route handlers or server actions, never in client components.
You need a root key with the permissions listed on this page. Create one in the dashboard under Settings > Root Keys, and pass it as Authorization: Bearer <root key>. See Permission reference for every permission.
1

Create a root key and a keyspace

In the dashboard, go to Settings > Root Keys and create a root key with api.*.create_api, api.*.create_key, and api.*.verify_key. Put it in your server’s UNKEY_ROOT_KEY environment variable. Never send it to a browser or mobile app.Then create a keyspace, which holds your keys. Use Keyspaces (APIs) in the dashboard, or apis.createApi:
create a keyspace
Keep the data.apiId from the response. You need it to create keys. See Root keys for more on root keys.
2

Install the SDK

@unkey/api (version ) is the client for the whole API. @unkey/nextjs (version ) wraps a route handler and verifies for you. You only need it for the wrapper in step 4.
3

Create a key for a user

Create a key from your backend when a user signs up or asks for one. externalId links the key to the user, so verification tells you who’s calling. meta comes back on every verification. You get data.key only once: show it to the user and store only data.keyId.
Every field is described in Creating keys.
4

Verify the key in a route handler

The first handler calls keys.verifyKey directly, so you control the response. The second uses withUnkey, which reads the bearer token from the Authorization header, it, and puts the result in req.unkey. Pass handleInvalidKey to choose the response for a bad key.
withUnkey only calls onError for unexpected responses, such as a 502 from a proxy. A rejected root key, a throttled request, a 500, or a network failure is thrown instead, so your route fails with an unhandled error rather than a 503. The hand-written version catches all of these and returns 503.
Test it with the key from step 3:
5

Handle the outcome codes

keys.verifyKey returns HTTP 200 for every outcome. Check data.valid, then data.code for the reason, and return the matching status from your API:Remaining credits are in data.credits, and each checked rate limit is in data.ratelimits with remaining and reset. The call only fails with an HTTP error (which the SDKs throw) when the call itself is wrong, such as a bad root key or a malformed body. See Verifying keys for every field.
6

Next steps

Verifying keys

Every request field, the order of checks, and every response field.

Credits and refill

Meter usage per key and refill balances on a schedule.

Cookbook

Copy-ready recipes for rate limits, billing, and subscription tiers.
Last modified on September 29, 2026