API Key Management

Secure the lifecycle of API keys from generation to revocation. Covers key generation best practices, rotation policies, scope limiting, usage monitoring, and the patterns that prevent API key compromise from becoming a security incident.

API keys are a lightweight, stateless mechanism for authenticating clients to APIs. They are most effective when a service exposes a public or internal API to a diverse set of clients—mobile apps, third-party integrations, internal microservices—where simplicity and scalability outweigh the need for full OAuth flows. API key management is the process of generating, distributing, securing, rotating, and revoking keys across these clients, ensuring that each request carries a valid key that grants access to specific scopes, tiers, or resources.

Key Lifecycle Management

API keys are not static identifiers. They are created, assigned, and updated over time. The core operations are key creation, assignment to clients, and revocation.

To create a key, a client sends a POST request to /api/v1/keys with a JSON body specifying the key’s name, description, and optional scopes:

{
  "name": "mobile-app-prod",
  "description": "Production mobile app, v2.1.0",
  "scopes": ["read:users", "write:orders"],
  "rate_limit": 1000
}

The server responds with a 201 Created and the fully populated key:

{
  "id": "key-1a2b3c4d",
  "key": "sk_abc123xyz456def",
  "created_at": "2024-05-15T10:00:00Z",
  "expires_at": "2025-05-15T10:00:00Z",
  "scopes": ["read:users", "write:orders"],
  "rate_limit": 1000,
  "last_used": null,
  "status": "active"
}

The key field is the actual API key string, which clients use as a Authorization header:

GET /api/v1/users HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_abc123xyz456def

Keys can be deactivated via PATCH /api/v1/keys/{id}:

{
  "status": "revoked",
  "revoked_at": "2024-05-16T08:30:00Z"
}

When a client sends a request with a revoked key, the server returns 401 Unauthorized with a WWW-Authenticate header:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Key has been revoked"

Key Rotation and Rotation Strategies

Rotating API keys is critical for reducing the window of exposure when a key is compromised. Keys can be rotated manually or automatically via a schedule.

A common rotation strategy is rolling keys: a new key is issued while the old key remains active, allowing gradual migration.

To initiate rotation, a client calls:

POST /api/v1/keys/rotate HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_admin_secret
Content-Type: application/json

{
  "old_key": "sk_abc123xyz456def",
  "new_key": "sk_789uvw345rst678",
  "duration": "7d"
}

The server responds:

{
  "old_key": "sk_abc123xyz456def",
  "new_key": "sk_789uvw345rst678",
  "valid_from": "2024-05-16T08:30:00Z",
  "valid_until": "2024-05-23T08:30:00Z"
}

Clients are expected to use both keys during the transition. The API server checks for the key in a database table with columns: key, active, valid_from, valid_until, revoked_at.

When a request arrives with a key not active, the server returns:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Key not yet valid"

Keys can also be rotated automatically via a cron job that runs every 30 days:


#!/bin/sh
curl -X POST \
  -H "Authorization: Bearer sk_scheduler_secret" \
  -H "Content-Type: application/json" \
  -d '{"rotation_interval": "30d"}' \
  https://api.example.com/api/v1/keys/rotate

Scoping and Access Control

API keys are often scoped to grant fine-grained access. A key can be assigned one or more scopes, such as read:users, write:orders, admin:settings.

The server validates scopes by checking the scopes field in the key record. When a client makes a request, the server performs a scope check:

GET /api/v1/orders?status=pending HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_abc123xyz456def

The API server checks whether the key has the read:orders scope. If not, it responds:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "insufficient_scope",
  "scope": "read:orders",
  "required_scope": "read:orders"
}

Multiple scopes are evaluated using a comma-separated list in the Authorization header:

Authorization: Bearer sk_abc123xyz456def, scope=read:users,write:orders

The server extracts the scope parameter and validates against the key’s scopes array. If any required scope is missing, the 403 response includes a WWW-Authenticate header:

WWW-Authenticate: Bearer error="insufficient_scope", error_description="Missing required scope: write:orders"

Rate Limiting and Throttling

API keys can be associated with rate limits, enforced at the API gateway layer. Each key has a rate_limit field, specifying the maximum number of requests per time window.

The gateway (e.g., NGINX, Traefik, or a custom service) tracks requests per key using an in-memory store or Redis. The default time window is 1 minute, but it can be configured.

When a client exceeds the limit, the server returns:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715894400

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit of 1000 requests per minute exceeded"
}

The X-RateLimit-* headers are standard and consumed by clients to adapt behavior. The Retry-After header provides a hint for when the client can retry.

Keys can have dynamic rate limits based on tier:

The API server reads the key’s tier field and applies the corresponding limit.

Key Validation and Error Handling

Validating API keys involves checking presence, format, and metadata.

The most common failure occurs when a client sends an invalid Authorization header:

Authorization: Bearer sk_abc123xyz456def

The server extracts the sk_abc123xyz456def string and queries the database for a key with that value. If no match is found, it returns:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="No such API key"

The error_description field provides context. If the key is expired or revoked, the error includes more detail:

WWW-Authenticate: Bearer error="invalid_token", error_description="Key expired", error_uri="https://api.example.com/docs/errors#key-expired"

Keys can be invalidated by setting an expires_at timestamp. When a request arrives after expires_at, the server returns:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Key has expired", expires_at="2024-05-15T10:00:00Z"

Clients must check expires_at and refresh keys proactively.

Key Distribution and Client Onboarding

Distributing keys to clients involves providing a setup guide, often in the form of a code snippet or a configuration file.

A typical onboarding flow includes:

  1. Create a key via API.
  2. Receive the key string and base_url.
  3. Configure the client with the key.

For a Node.js client:

const client = new ApiClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'sk_abc123xyz456def'
});

// Usage
const users = await client.get('/users');

For a Python client:

from example_api import Client

client = Client(api_key='sk_abc123xyz456def', base_url='https://api.example.com')

users = client.get('/users')

Clients can also use environment variables:

export EXAMPLE_API_KEY=sk_abc123xyz456def
export EXAMPLE_API_BASE_URL=https://api.example.com

When a client fails to authenticate, the error stack trace often reveals a missing or malformed key. A common mistake is using Bearer instead of sk_ in the header:

Authorization: Bearer sk_abc123xyz456def

This is correct. But using Authorization: Bearer abc123xyz456def (without sk_) leads to a 401 with error_description="No such API key".

Another silent failure occurs when keys are not properly validated across domains. A key created for api.example.com may not be recognized by app.example.com if the gateway does not share the key store.

Configuration and Operational Patterns

API key management is backed by a database with a api_keys table:

CREATE TABLE api_keys (
  id UUID PRIMARY KEY,
  key TEXT NOT NULL UNIQUE,
  name TEXT,
  description TEXT,
  scopes TEXT[],
  rate_limit INTEGER,
  tier TEXT,
  created_at TIMESTAMP WITH TIME ZONE,
  expires_at TIMESTAMP WITH TIME ZONE,
  revoked_at TIMESTAMP WITH TIME ZONE,
  status TEXT CHECK (status IN ('active', 'revoked', 'expired')),
  last_used TIMESTAMP WITH TIME ZONE
);

The scopes column uses a PostgreSQL array type, but the same pattern works with JSON arrays in any database.

Keys are managed via a CLI tool or a web dashboard. A common command-line interface uses curl and jq:

# List all keys
curl -H "Authorization: Bearer sk_admin_secret" \
  https://api.example.com/api/v1/keys | jq

# Create a new key
curl -X POST \
  -H "Authorization: Bearer sk_admin_secret" \
  -H "Content-Type: application/json" \
  -d @key.json \
  https://api.example.com/api/v1/keys

A key.json file might contain:

{
  "name": "internal-service",
  "scopes": ["read:logs", "write:metrics"],
  "rate_limit": 5000,
  "tier": "enterprise"
}

The server validates the JSON schema and stores it in the database. A failed request returns a 400 Bad Request with a detailed error:

{
  "error": "invalid_request",
  "message": "Invalid JSON in request body",
  "details": [
    {
      "field": "name",
      "error": "required_field_missing"
    },
    {
      "field": "scopes",
      "error": "array_expected"
    }
  ]
}

Failure Modes and Observations

API key management fails silently in production.

Summary

API key management is a foundational security practice for APIs. Keys are not just tokens; they are the primary mechanism for authentication, authorization, rate limiting, and onboarding. The pattern is repeatable across systems: create a key, assign scopes, enforce limits, distribute via header, and validate with error responses. The success of an API depends on how consistently these mechanisms are applied.

This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.