Unkey
ServicesApi

API

This document only covers v2 of the Unkey API. The v1 API on Cloudflare Workers is deprecated and will be removed in the future. It was too hard to selfhost anyways.

Our API runs on AWS containers, in multiple regions behind a global load balancer to ensure high availability and low latency.

The source code is available on GitHub.

Quickstart

To get started, you need go1.24+ installed on your machine.

Clone the repository:

git clone git@github.com:unkeyed/unkey.git
cd unkey/go

Build the binary:

go build -o unkey .

Run the binary:

unkey api --database-primary="mysql://unkey:password@tcp(localhost:3306)/unkey?parseTime=true"

You should now be able to access the API at

$ curl http://localhost:7070/v2/liveness
{"message":"we're cooking"}%

Configuration

You can configure the Unkey API using command-line flags or environment variables. For each flag shown below, there's an equivalent environment variable.

For example, --http-port=8080 can also be set using the environment variable UNKEY_HTTP_PORT=8080.

Basic Configuration

These options control the fundamental behavior of the API server.

--platformstring

Identifies the cloud platform where this node is running. This information is primarily used for logging, metrics, and debugging purposes.

Environment variable: UNKEY_PLATFORM

Examples:

  • --platform=aws - When running on Amazon Web Services
  • --platform=gcp - When running on Google Cloud Platform
  • --platform=hetzner - When running on Hetzner Cloud
  • --platform=docker - When running in Docker (e.g., local or Docker Compose)

--http-port | UNKEY_HTTP_PORTint

HTTP port for the API server to listen on. This port must be accessible by all clients that will interact with the API. In containerized environments, ensure this port is properly exposed.

Examples:

  • --http-port=7070 - Default port
  • --http-port=8080 - Common alternative for local development
  • --http-port=80 - Standard HTTP port (requires root privileges on Unix systems)

--region | UNKEY_REGIONstring

Geographic region identifier where this node is deployed. Used for logging, metrics categorization, and can affect routing decisions in multi-region setups.

Examples:

  • --region=us-east-1 - AWS US East (N. Virginia)
  • --region=eu-west-1 - AWS Europe (Ireland)
  • --region=us-central1 - GCP US Central
  • --region=dev-local - For local development environments

Database Configuration

The Unkey API requires a MySQL database for storing keys and configuration. For global deployments, a read replica endpoint can be configured to offload read operations.

--database-primary | UNKEY_DATABASE_PRIMARY_DSN
Required
string

Primary database connection string for read and write operations. This MySQL database stores all persistent data including API keys, workspaces, and configuration. It is required for all deployments.

For production use, ensure the database has proper backup procedures in place. Unkey is using PlanetScale

Examples:

  • --database-primary=mysql://root:password@localhost:3306/unkey
  • --database-primary=mysql://user:password@mysql.example.com:3306/unkey?tls=true
  • --database-primary=mysql://unkey:password@mysql.default.svc.cluster.local:3306/unkey

--database-readonly-replica | UNKEY_DATABASE_READONLY_DSNstring

Optional read-replica database connection string for read operations. When provided, most read operations will be directed to this read replica, reducing load on the primary database and latency for users.

This is recommended for high-traffic deployments to improve performance and scalability. The read replica must be a valid MySQL read replica of the primary database.

Unkey is using PlanetScales global read replica endpoint.

Examples:

  • --database-readonly-replica=mysql://readonly:password@replica.mysql.example.com:3306/unkey?tls=true
  • --database-readonly-replica=mysql://readonly:password@mysql-replica.default.svc.cluster.local:3306/unkey

Analytics & Monitoring

These options configure analytics storage and observability for the Unkey API.

--clickhouse-url | UNKEY_CLICKHOUSE_URLstring

ClickHouse database connection string for analytics. ClickHouse is used for storing high-volume event data like API key validations, http request logs and historically aggregated analytics.

This is optional but highly recommended for production environments. If not provided, analytical capabilities will be omitted but core key validation will still function.

Examples:

  • --clickhouse-url=clickhouse://localhost:9000/unkey
  • --clickhouse-url=clickhouse://user:password@clickhouse.example.com:9000/unkey
  • --clickhouse-url=clickhouse://default:password@clickhouse.default.svc.cluster.local:9000/unkey?secure=true

--otel-otlp-endpoint | UNKEY_OTEL_OTLP_ENDPOINTstring

OpenTelemetry collector endpoint for metrics, traces, and logs. When provided, the Unkey API will send telemetry data (metrics, traces, and logs) to this endpoint using the OTLP protocol.

The endpoint should be an OpenTelemetry collector capable of receiving OTLP data. The implementation is currently configured for Grafana Cloud integration but is compatible with any OTLP-compliant collector.

Examples:

  • --otel-otlp-endpoint=http://localhost:4317 - Local collector
  • --otel-otlp-endpoint=https://otlp.grafana-cloud.example.com - Grafana Cloud
  • --otel-otlp-endpoint=https://api.honeycomb.io:443 - Honeycomb.io

--color | UNKEY_COLORboolean

Enable ANSI color codes in log output. When enabled, log output will include ANSI color escape sequences to highlight different log levels, timestamps, and other components of the log messages.

This is useful for local development and debugging but should typically be disabled in production environments where logs are collected by systems that may not properly handle ANSI escape sequences.

Examples:

  • --color=true - Enable colored logs (good for local development)
  • --color=false - Disable colored logs (default, best for production)

Clustering Configuration

Unkey supports clustering for high availability and distributed rate limiting. These options configure how nodes in a cluster discover and communicate with each other.

--cluster | UNKEY_CLUSTERboolean

Enable cluster mode to connect multiple Unkey API nodes together. When enabled, this node will attempt to form or join a cluster with other Unkey nodes. Clustering provides high availability, load distribution, and consistent rate limiting across nodes.

For production deployments with multiple instances, set this to true. For single-node setups (local development, small deployments), leave this disabled.

When clustering is enabled, you must also configure:

  1. An address advertisement method (static or AWS ECS metadata)
  2. A discovery method (static addresses or Redis)
  3. Appropriate ports for RPC and gossip protocols

Examples:

  • --cluster=true - Enable clustering
  • --cluster=false - Disable clustering (default)

--cluster-node-id | UNKEY_CLUSTER_NODE_IDstring

Unique identifier for this node within the cluster. Every node in a cluster must have a unique identifier. This ID is used in logs, metrics, and for node-to-node communication within the cluster.

If not specified, a random id with 'node_' prefix will be automatically generated. For ephemeral nodes (like in auto-scaling groups), automatic generation is appropriate. For stable deployments, consider setting this to a persistent value tied to the instance.

Examples:

  • --cluster-node-id=node_east1_001 - For a node in East region, instance 001
  • --cluster-node-id=node_replica2 - For a second replica node
  • --cluster-node-id=node_dev_local - For local development

Node Address Advertisement

You must configure exactly one method for advertising this node's address to other nodes in the cluster.

--cluster-advertise-addr-static | UNKEY_CLUSTER_ADVERTISE_ADDR_STATICstring

Static IP address or hostname that other nodes can use to connect to this node. This is required for clustering when not using AWS ECS discovery. The address must be reachable by all other nodes in the cluster.

For on-premises or static cloud deployments, use a fixed IP address or DNS name. In Kubernetes environments, this could be the pod's DNS name within the cluster.

Examples:

  • --cluster-advertise-addr-static=10.0.1.5 - Direct IP address
  • --cluster-advertise-addr-static=node1.unkey.internal - DNS name
  • --cluster-advertise-addr-static=unkey-0.unkey-headless.default.svc.cluster.local - Kubernetes DNS

--cluster-advertise-addr-aws-ecs-metadata | UNKEY_CLUSTER_ADVERTISE_ADDR_AWS_ECS_METADATAboolean

Enable automatic address discovery using AWS ECS container metadata. When running on AWS ECS, this flag allows the container to automatically determine its private DNS name from the ECS metadata service. This simplifies cluster configuration in AWS ECS deployments with dynamic IP assignments.

Do not set cluster-advertise-addr-static if this option is enabled. This option is specifically designed for AWS ECS and won't work in other environments.

Examples:

  • --cluster-advertise-addr-aws-ecs-metadata=true - Enable AWS ECS metadata-based discovery
  • --cluster-advertise-addr-aws-ecs-metadata=false - Disable (default)

Communication Ports

--cluster-rpc-port | UNKEY_CLUSTER_RPC_PORTnumber

Port used for internal RPC communication between cluster nodes. This port is used for direct node-to-node communication within the cluster for operations like distributed rate limiting and state synchronization.

The port must be accessible by all other nodes in the cluster and should be different from the HTTP and gossip ports to avoid conflicts. In containerized environments, ensure this port is properly exposed between containers.

Examples:

  • --cluster-rpc-port=7071 - Default RPC port
  • --cluster-rpc-port=9000 - Alternative port if 7071 is unavailable

--cluster-gossip-port | UNKEY_CLUSTER_GOSSIP_PORTnumber

Port used for cluster membership and failure detection via gossip protocol. The gossip protocol is used to maintain cluster membership, detect node failures, and distribute information about the cluster state.

This port must be accessible by all other nodes in the cluster and should be different from the HTTP and RPC ports to avoid conflicts. In containerized environments, ensure this port is properly exposed between containers.

Examples:

  • --cluster-gossip-port=7072 - Default gossip port
  • --cluster-gossip-port=9001 - Alternative port if 7072 is unavailable

Node Discovery Methods

You must configure exactly one method for discovering other nodes in the cluster.

--cluster-discovery-static-addrs | UNKEY_CLUSTER_DISCOVERY_STATIC_ADDRSstring[]

List of seed node addresses for static cluster configuration. When using static discovery, these addresses serve as initial contact points for joining the cluster. At least one functioning node address must be provided for initial cluster formation.

This flag is required for clustering when not using Redis discovery. Each address should be a hostname or IP address that's reachable by this node. It's not necessary to list all nodes - just enough to ensure reliable discovery.

Examples:

  • --cluster-discovery-static-addrs=10.0.1.5,10.0.1.6
  • --cluster-discovery-static-addrs=node1.unkey.internal,node2.unkey.internal
  • --cluster-discovery-static-addrs=unkey-0.unkey-headless.default.svc.cluster.local

--cluster-discovery-redis-url | UNKEY_CLUSTER_DISCOVERY_REDIS_URLstring

Redis connection string for dynamic cluster discovery. Redis-based discovery enables nodes to register themselves and discover other nodes through a shared Redis instance. This is recommended for dynamic environments where nodes may come and go frequently, such as auto-scaling groups in AWS ECS.

When specified, nodes will register themselves in Redis and discover other nodes automatically. This eliminates the need for static address configuration. The Redis instance should be accessible by all nodes in the cluster and have low latency to ensure timely node discovery.

Examples:

  • --cluster-discovery-redis-url=redis://localhost:6379/0
  • --cluster-discovery-redis-url=redis://user:password@redis.example.com:6379/0
  • --cluster-discovery-redis-url=redis://user:password@redis-master.default.svc.cluster.local:6379/0?tls=true

Deployment Examples

Single-Node

unkey api \
  --database-primary="mysql://root:password@localhost:3306/unkey?parseTime=true" \
  --color=true \
  --http-port=8080 \
  --region=dev-local

Docker Compose Setup

services:
  api:
    deploy:
      replicas: 3
      endpoint_mode: vip
    command: ["api"]
    image: ghcr.io/unkeyed/unkey:latest
    depends_on:
      - mysql
      - redis
      - clickhouse
    environment:
      UNKEY_HTTP_PORT: 7070
      UNKEY_CLUSTER: true
      UNKEY_CLUSTER_GOSSIP_PORT: 9090
      UNKEY_CLUSTER_RPC_PORT: 9091
      UNKEY_CLUSTER_DISCOVERY_REDIS_URL: "redis://redis:6379"
      UNKEY_DATABASE_PRIMARY_DSN: "mysql://unkey:password@tcp(mysql:3900)/unkey?parseTime=true"
      UNKEY_CLICKHOUSE_URL: "clickhouse://default:password@clickhouse:9000"

AWS ECS Production Cluster

unkey api \
  --platform="aws" \
  --region="us-east-1" \
  --cluster=true \
  --cluster-advertise-addr-aws-ecs-metadata=true \
  --cluster-discovery-redis-url="redis://user:password@redis.example.com:6379" \
  --database-primary="mysql://user:password@primary.mysql.example.com:3306/unkey?parseTime=true" \
  --database-readonly-replica="mysql://readonly:password@replica.mysql.example.com:3306/unkey?parseTime=true" \
  --clickhouse-url="clickhouse://user:password@clickhouse.example.com:9000/unkey" \
  --otel-otlp-endpoint="https://your-grafana-endpoint.com"

Last updated on