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

# Bun

> Add API key authentication to your Bun server with Unkey. Verify keys on each request to protect your endpoints in a few lines of code.

## What you'll build

A Bun HTTP server that requires a valid API key on every request. Invalid or missing keys get rejected with a 401.

**Time to complete:** \~3 minutes

## Prerequisites

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

<Card title="Want to skip ahead?" icon="github" href="https://github.com/unkeyed/examples/tree/main/bun-server">
  Clone the complete example and run it locally.
</Card>

<Steps titleSize="h3">
  <Step title="Create a new Bun project">
    ```bash theme={"theme":"kanagawa-wave"}
    mkdir unkey-bun && cd unkey-bun
    bun init -y
    ```
  </Step>

  <Step title="Install the SDK">
    ```bash theme={"theme":"kanagawa-wave"}
    bun add @unkey/api
    ```
  </Step>

  <Step title="Add your root key">
    Create a `.env` file with your credentials from the [Unkey dashboard](https://app.unkey.com/settings/root-keys):

    ```bash .env theme={"theme":"kanagawa-wave"}
    UNKEY_ROOT_KEY="unkey_..."
    ```
  </Step>

  <Step title="Create your server">
    Replace the contents of `index.ts`:

    ```ts index.ts theme={"theme":"kanagawa-wave"}
    import { Unkey } from "@unkey/api";

    const unkey = new Unkey({ rootKey: Bun.env.UNKEY_ROOT_KEY! });

    const server = Bun.serve({
      async fetch(req) {
        // 1. Extract the key from the Authorization header
        const key = req.headers.get("Authorization")?.replace("Bearer ", "");

        if (!key) {
          return Response.json({ error: "Missing API key" }, { status: 401 });
        }

        // 2. Verify with Unkey
        try {
          const { data } = await unkey.keys.verifyKey({ key });

          if (!data.valid) {
            // Key is invalid, expired, rate limited, etc.
            return Response.json(
              { error: "Invalid API key", code: data.code },
              { status: 401 },
            );
          }

          // 3. Key is valid, return your response
          return Response.json({
            message: "Hello from protected endpoint!",
            keyId: data.keyId,
            identity: data.identity,
          });
        } catch (err) {
          console.error(err);
          return Response.json({ error: "Verification failed" }, { status: 500 });
        }
      },
      port: 3000,
    });

    console.log(`Server running at http://localhost:${server.port}`);
    ```
  </Step>

  <Step title="Run your server">
    ```bash theme={"theme":"kanagawa-wave"}
    bun run index.ts
    ```
  </Step>

  <Step title="Test it">
    Create a test key in your [Unkey dashboard](https://app.unkey.com), then:

    ```bash Test with valid key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000 \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    You should see:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "message": "Hello from protected endpoint!",
      "keyId": "key_...",
      "identity": null
    }
    ```

    Try without a key:

    ```bash Test without key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000
    ```

    You'll get:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "error": "Missing API key"
    }
    ```
  </Step>
</Steps>

## What's in `data`?

After successful verification:

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

## Adding routes

Bun's built-in server uses a single `fetch` handler. For multiple routes, pattern match on the URL:

```ts index.ts theme={"theme":"kanagawa-wave"}
import { Unkey } from "@unkey/api";

const unkey = new Unkey({ rootKey: Bun.env.UNKEY_ROOT_KEY! });

// Helper to verify key
async function authenticate(req: Request) {
  const key = req.headers.get("Authorization")?.replace("Bearer ", "");
  if (!key) return { valid: false, error: "Missing API key" };

  try {
    const { data } = await unkey.keys.verifyKey({ key });
    return data;
  } catch (err) {
    console.error(err);
    return { valid: false, error: "Verification failed" };
  }
}

const server = Bun.serve({
  async fetch(req) {
    const url = new URL(req.url);

    // Public route
    if (url.pathname === "/") {
      return Response.json({ message: "Welcome! Try /api/secret" });
    }

    // Protected routes
    if (url.pathname.startsWith("/api/")) {
      const auth = await authenticate(req);

      if (!auth.valid) {
        return Response.json(
          { error: auth.error || "Unauthorized" },
          { status: 401 },
        );
      }

      // Route to specific handlers
      if (url.pathname === "/api/secret") {
        return Response.json({ secret: "data", keyId: auth.keyId });
      }

      if (url.pathname === "/api/user") {
        return Response.json({ user: auth.identity });
      }
    }

    return Response.json({ error: "Not found" }, { status: 404 });
  },
  port: 3000,
});
```

## Next steps

<CardGroup cols={2}>
  <Card title="Add rate limiting" icon="gauge-high" href="/quickstart/ratelimiting/bun">
    Protect endpoints from abuse
  </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="SDK Reference" icon="book" href="/libraries/ts/api">
    Full TypeScript SDK docs
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Getting 401 even with a valid key?">
    * Ensure the key hasn't expired or been revoked - Verify the header format:
      `Authorization: Bearer YOUR_KEY` (note the space)
  </Accordion>

  <Accordion title="Environment variables not loading?">
    Bun automatically loads `.env` files. Make sure: - The `.env` file is in your
    project root - You're using `Bun.env.VAR_NAME` not `process.env.VAR_NAME` -
    Restart the server after changing `.env`
  </Accordion>

  <Accordion title="TypeScript errors?">
    Run `bun init -y` to ensure you have a proper `tsconfig.json`. Bun handles
    TypeScript natively, no extra setup needed.
  </Accordion>
</AccordionGroup>
