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

> ## Agent Instructions
> Unkey is two separate products. Compute builds, deploys, and runs apps behind a gateway. API Management issues API keys, enforces rate limits, manages identities and permissions, and reports usage. Say which product a page belongs to; a reader can use either without the other.
> Every Unkey API endpoint is an HTTP POST to https://api.unkey.com/v2/{service}.{procedure} with a root key in the Authorization: Bearer header. Root keys are workspace scoped.
> Error codes have the form err:{system}:{category}:{specific} and each has a page at /errors/{system}/{category}/{specific}.
> The word environment means production or preview in Compute. Rate limiting has four meanings on this site; the glossary lists them.

# @unkey/hono

> Hono middleware that verifies API keys for you.

export const versions = {
  cli: "2.0.150",
  tsApi: "2.5.1",
  tsRatelimit: "2.1.4",
  tsHono: "2.0.0",
  tsNextjs: "2.0.0",
  tsCache: "1.5.0",
  tsNuxt: "1.1.15",
  goSdk: "v3.0.1",
  pySdk: "3.0.3"
};

`@unkey/hono` (version {versions.tsHono}) is a [Hono](https://hono.dev) middleware that <Tooltip tip="Here: checking an API key on a request with keys.verifyKey. Not domain verification.">verifies</Tooltip> the API key on each request and puts the result on the context. It needs `hono` 4.6 or later.

<Note>
  You need a root key with the permissions listed on this page. Create one in the dashboard under **Settings > Root Keys**. See [Permission reference](/docs/platform/root-keys/permissions-legacy) for every permission.
</Note>

## Install

```bash theme={"system"}
npm install @unkey/hono
```

## Protect routes

```typescript theme={"system"}
import { Hono } from "hono";
import { type UnkeyContext, unkey } from "@unkey/hono";

const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

app.use("*", unkey({ rootKey: process.env.UNKEY_ROOT_KEY ?? "" }));

app.get("/protected", (c) => {
  const verification = c.get("unkey");
  if (!verification.data.valid) {
    return c.json({ error: "unauthorized" }, 401);
  }
  return c.json({ keyId: verification.data.keyId, permissions: verification.data.permissions });
});
```

The middleware reads the bearer token from the `Authorization` header, calls `keys.verifyKey`, and puts the full result in `c.get("unkey")`, so your handler can read `data.valid`, `data.code`, `data.keyId`, `data.meta`, and the rest. A request with no key gets `401 {"error":"unauthorized"}`.

<Warning>
  Unless you pass `handleInvalidKey`, an invalid key doesn't stop the request: the middleware stores the failed verification and calls the next handler. Check `data.valid` in your handler, or set `handleInvalidKey` to reject centrally.
</Warning>

## Options

<ParamField body="rootKey" type="string" required>
  Root key used to call `keys.verifyKey`.
</ParamField>

<ParamField body="permissions" type="string">
  A permission query the key must satisfy for `data.valid` to be `true`, for example `"documents.read"`.
</ParamField>

<ParamField body="tags" type="string[]">
  Tags recorded with the verification for later filtering in analytics.
</ParamField>

<ParamField body="getKey" type="(c: Context) => string | undefined | Response">
  Read the key from somewhere else, such as a query parameter. Return a `Response` to stop there. Return nothing to get a 401.
</ParamField>

<ParamField body="handleInvalidKey" type="(c: Context, result: UnkeyContext) => Response | Promise<Response>">
  Called when the key is present but `data.valid` is `false`. Return the response the client should get.
</ParamField>

<ParamField body="onError" type="(c: Context, err: errors.APIError) => Response | Promise<Response>">
  Called only for unexpected responses from the verify call, such as a 502 from a proxy. Return the response the client should get. Without it, these become a Hono `HTTPException` with status 500.
</ParamField>

## Reject invalid keys centrally

```typescript theme={"system"}
app.use(
  "*",
  unkey({
    rootKey: process.env.UNKEY_ROOT_KEY ?? "",
    permissions: "documents.read",
    getKey: (c) => c.req.query("api_key"),
    handleInvalidKey: (c, result) => c.json({ error: "invalid key", code: result.data.code }, 401),
    onError: (c, err) => {
      console.error("unkey error", err.message);
      return c.json({ error: "authentication unavailable" }, 503);
    },
  }),
);
```

`onError` doesn't catch everything. A rejected root key (401), a throttled request (429), a 500, or a connection failure or timeout is thrown instead. Catch those in Hono's `app.onError` if you want one response for every authentication failure.

## Next steps

<Columns cols={2}>
  <Card title="Verifying keys" icon="key" href="/docs/api-management/keys/verifying-keys">
    What `valid`, `code`, permissions, and rate limits mean in the result.
  </Card>

  <Card title="@unkey/api" icon="js" href="/docs/api-management/sdks/typescript/api">
    Call any other endpoint from the same app.
  </Card>
</Columns>
