API Key Authentication¶
API keys allow external clients to bypass PIN authentication when accessing protected *.nominate.ai endpoints.
Overview¶
With API Key:
Request + X-API-Key header → NGINX → /auth/verify → validate key → 200 → backend
Without API Key (browser):
Request → NGINX → /auth/verify → 401 → PIN pad → cookie → 200 → backend
Creating API Keys¶
# Activate environment
source ~/.pyenv/versions/nominates/bin/activate
cd /home/bisenbek/projects/nominate/cbauth
# Create a new API key
python -m app.cli create-key --name "partner-api" --note "For external integration"
Output:
Created API key: partner-api
Prefix: cbauth_xK9m...
Note: For external integration
============================================================
API KEY (save this - shown only once!):
cbauth_xK9mPq2wR7nL4jF8vBnM3kH5tY9uW1qE6rT0pL2s
============================================================
Usage:
curl -H 'X-API-Key: cbauth_xK9m...' https://api.nominate.ai/...
Important: The full API key is only shown once at creation. Store it securely.
Managing Keys¶
List Keys¶
# Active keys only
python -m app.cli list-keys
# Include revoked keys
python -m app.cli list-keys --all
Output:
Name Prefix Active Last Used Note
------------------------------------------------------------------------------------------
partner-api cbauth_xK9m... yes 2026-01-06T18:55:17 For external integration
mobile-app cbauth_bT4n... yes never iOS app backend
old-integration cbauth_mL2p... REVOKED 2026-01-05T10:30:00 Deprecated
Revoke Keys¶
# By name
python -m app.cli revoke-key --name "partner-api"
# By prefix
python -m app.cli revoke-key --prefix "cbauth_xK9m"
Revoked keys immediately stop working. The action cannot be undone.
Using API Keys¶
Header Options¶
# Option 1: X-API-Key header (recommended)
curl -H "X-API-Key: cbauth_xK9mPq2wR7nL4jF8..." https://models.nominate.ai/health
# Option 2: Authorization Bearer
curl -H "Authorization: Bearer cbauth_xK9mPq2wR7nL4jF8..." https://models.nominate.ai/health
Example: Python¶
import requests
API_KEY = "cbauth_xK9mPq2wR7nL4jF8..."
response = requests.get(
"https://models.nominate.ai/health",
headers={"X-API-Key": API_KEY}
)
Example: JavaScript¶
const API_KEY = "cbauth_xK9mPq2wR7nL4jF8...";
fetch("https://models.nominate.ai/health", {
headers: { "X-API-Key": API_KEY }
})
Security Notes¶
- Key Storage: Keys are SHA256 hashed before storage in
data/api_keys.json - Constant-Time Comparison: Key validation uses
hmac.compare_digestto prevent timing attacks - Usage Tracking:
last_used_atis updated on each successful authentication - No Expiration: Keys don't expire automatically - revoke manually when needed
- Key Format:
cbauth_prefix + 43 characters of URL-safe base64
Storage Location¶
Keys are stored in /home/bisenbek/projects/nominate/cbauth/data/api_keys.json
This file is excluded from git via .gitignore.
Troubleshooting¶
Key returns 401¶
- Check key is not revoked:
python -m app.cli list-keys --all - Verify key format starts with
cbauth_ - Check NGINX is passing headers (see below)
NGINX not passing headers¶
Ensure /etc/nginx/snippets/pin-gate-auth.conf includes:
Then reload: sudo nginx -t && sudo systemctl reload nginx