# Unkey: a Go platform for API keys, rate limiting and gateway routing

> Unkey bundles key issuance and verification, globally consistent rate limiting, RBAC and audit logs behind one developer platform, with a Go core you can read and self-host under the AGPL. The catch is that the project has paused external pull requests.

**unkeyed/unkey** — The Developer Platform for Modern APIs

- Repository: https://github.com/unkeyed/unkey
- Website: https://go.unkey.com
- Stars: 5,454 · Forks: 638
- Language: Go
- License: NOASSERTION
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/unkeyed-unkey

## The problem Unkey targets: keys, limits and permissions in one place

Most API teams end up assembling the same three things by hand. A table of hashed keys with a verification endpoint, a rate limiter that has to agree with itself across regions, and some ad hoc permission check bolted onto the key record. Unkey's README frames the product as a single platform covering exactly that ground: API keys that can be issued, verified and revoked, rate limiting described as globally consistent and durable for any identifier, and per-key permissions and roles.

The audience is the team that owns a public API and does not want to build key infrastructure as a side project. The topics list on the repository points the same way: api-keys, authentication, authorization, rate-limiter, gateway. If your product already has an identity provider handling end-user login, Unkey is not that. It sits in front of your API as the thing that decides whether a caller with a key may proceed.

## How the Go codebase is laid out

The repository is a single Go module, github.com/unkeyed/unkey, with the services split across top-level directories. The visible entries are cmd/, svc/, internal/, pkg/, proto/, gen/, build/, web/, dev/ and benchmarks/. Protobuf definitions live in proto/ with buf.yaml and buf.lock at the root, and generated code lands in gen/. That layout tells you the API surface between services is defined in protobuf and regenerated with buf, which the go.mod lists as a tool dependency alongside oapi-codegen and sqlc.

Storage is not a single database. The go.mod pulls in both github.com/ClickHouse/clickhouse-go/v2 and a MySQL driver, and the Makefile has a generate-sql target that runs drizzle-kit against web/internal/db and then splits the generated MySQL DDL into per-table files under pkg/mysql/schema. So the schema is authored in TypeScript under web/internal/db and emitted as SQL that the Go side consumes. ClickHouse is the natural read for the analytics and audit-log side of the feature list, given the README promises usage, latency and per-key insights across every request.

The Dockerfile is short and worth reading as a statement of intent. It builds ./build/cli with CGO disabled and copies the resulting binary into a distroless static image with a nonroot user. One binary, one entrypoint, no shell in the final image. That is a deliberate constraint: you debug through logs and the CLI, not by execing into a container.

## Installing and running Unkey from the repository

The README itself does not carry install steps. It links to unkey.com and the docs site, and the repository's own instructions live in the Makefile. The install target depends on install-go, which runs go mod download, and then installs the web workspace with pnpm.

```bash
go mod download
cd web && pnpm install --frozen-lockfile
```

The frozen lockfile flag matters: pnpm will fail rather than resolve a different dependency tree, so the web workspace is expected to match the committed lockfile exactly.

For a local cluster, the Makefile has install-brew-tools, which installs tilt, ctlptl, minikube, dprint and ngrok through Homebrew if they are missing. K8S_DEVELOPMENT.md at the repository root is the file to read next if you go that route; the Makefile only installs the tooling and does not describe the workflow.

If you would rather build the container, the Dockerfile compiles ./build/cli and produces an image whose entrypoint is /unkey. There is no documented set of required environment variables in the README, so the configuration surface has to come from the docs site or from reading cmd/ and svc/. That is a real gap for anyone trying to stand the service up from the repository alone.

## External contributions are closed, and that changes the calculus

The most consequential line in the README is the pull request policy. Unkey states plainly that it is not accepting external pull requests at this time, that contributions from outside the team will not be reviewed or merged, and that the pause is deliberate while the team focuses on platform direction and stability. Issues stay open for bug reports, feature requests and documentation feedback.

This is a self-hosting licence, not a collaboration licence. You can read the code, fork it under the AGPL and run it, but you cannot get a fix upstream through the normal path. If you find a bug in the key verification path, your options are a local patch you maintain against every release, or waiting. For a component that sits in the authentication path of your own API, that is the decision to weigh, not the feature list.

The README says the policy may be revisited and that any change will be announced there. Until then, treat the fork as the unit of maintenance.

## Licence and the NOASSERTION label

The repository metadata reports the licence as NOASSERTION, which means GitHub could not match the LICENSE file to a known template. The README defers to that file rather than naming a licence, while the pull request section refers to forking under the terms of the AGPL. Those two statements are not in conflict, but they are not the same statement either: one is a machine classification, the other is prose in the README.

For a component in your authentication path, read the LICENSE file directly before you plan a deployment. The AGPL's network clause is the part that typically matters for a self-hosted service that other people interact with over a network, and how it applies to your specific arrangement is a question for your own counsel. What can be said from the repository alone is that the licence is not declared in the metadata, and the README points at the file instead of summarising it.

## When Unkey is the wrong tool

Unkey is built around long-lived API keys with permissions attached. If what you actually need is short-lived access tokens issued to end users after a login flow, an identity provider covers that and Unkey adds a second system to operate. The README's feature list is keys, rate limits, permissions, analytics and audit logs; there is no session or user-login story in it.

The second mismatch is operational. The stack expects ClickHouse and MySQL, a protobuf toolchain for regeneration, and a web workspace built with pnpm. A team that wants a single binary with an embedded store will find the surface larger than the problem it is solving. The Dockerfile gives you one small runtime image, but the dependencies behind it are not small.

The third is contribution. If your organisation's policy is to run only software it can patch upstream, the paused pull request policy rules Unkey out regardless of how well the features fit.

## How this differs from rolling your own with a rate-limit library

The obvious alternative is to keep key storage in your application database and add a rate limiting library in front of it. That approach is smaller and has no new services to run, and for a single-region API with modest traffic it is often enough.

The difference is in what Unkey claims to make consistent. A library that counts requests in process memory gives each instance its own view of the limit, so a caller hitting four replicas effectively gets four times the allowance unless you move the counter into shared storage and accept the round trip. Unkey's README describes rate limiting as globally consistent and durable for any identifier, which is the property you are buying: one counter, agreed on across regions, plus the key record and its permissions living in the same system as the limit. The trade is a network hop to a service you now operate, and the dependency set described above.

A second alternative is to use your existing API gateway for authentication and limiting. That keeps one hop and one config language, but gateway rate limiting is usually per-route and per-IP rather than per-key, and it will not carry per-key roles. If your limits are naturally expressed per customer key, the gateway approach pushes that logic back into your application.

## Conclusion

Unkey fits teams that want API key verification, rate limiting and per-key permissions handled by one Go service instead of three internal tools, and that can live with an AGPL self-hosted path. It is a poor fit if you need to upstream patches, since the README states external pull requests will not be reviewed or merged, or if you only need short-lived session tokens. Before adopting, read the LICENSE file rather than trusting the NOASSERTION label, and try the docker build and the ./build/cli entrypoint to confirm the CLI matches your deployment model.

## FAQ

### Who is Unkey for?

It targets teams that own a public API and need key verification, globally consistent rate limiting and per-key permissions without building that infrastructure themselves. It is not aimed at end-user login or session management.

### How much does Unkey cost?

The README does not list prices. It links to unkey.com and the docs site, and the repository itself is source-available for forking and self-hosting under the AGPL, so cost depends on whether you run it yourself or use the hosted product.

### What does unkey mean?

The repository does not explain the name; it only uses Unkey as the product name. The README's own description is the developer platform for modern APIs.

### Is there an Unkey alternative I should consider?

No competing product is named in the repository. The realistic comparison is against keeping keys in your own database with a rate limiting library, or using your existing API gateway, and the difference is whether you want one globally consistent counter and per-key permissions in a separate service.

## Sources

- [Issues](https://github.com/unkeyed/unkey/issues)
- [Project website](https://go.unkey.com)
- [README](https://github.com/unkeyed/unkey/blob/main/README.md)
- [Releases](https://github.com/unkeyed/unkey/releases)
- [unkeyed/unkey on GitHub](https://github.com/unkeyed/unkey)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/unkeyed-unkey
