Request Freshness
Request freshness protects against replay attacks where a captured API request is re-sent by an attacker. Keel validates request age and uniqueness using two headers on execution routes.
Request freshness is enabled by default in production. Hosted Keel deployments (api.keelapi.com) and any non-dev self-hosted deployment require valid timestamp and nonce headers on execution routes. Deployments that explicitly disable freshness validation in a non-dev environment fail closed at startup. Local, dev, and test environments keep freshness off unless an operator enables it.
Headers
| Header | Purpose | Requirements |
|---|---|---|
X-Keel-Timestamp | Declares when the request was created | Unix epoch seconds. Must be within ±300 seconds of server time. |
X-Keel-Nonce | Unique value for this request | Minimum 16 characters. Unique per API key. Tracked for 24 hours. |
Both headers are required on execution-route requests when freshness is active. Read and audit routes, and POST /v1/permits/dry-run, do not require them. Permit creation (POST /v1/permits) can also be freshness-gated independently of the global execution flag.
Timestamp validation
Keel compares the declared timestamp against the server’s current time. If the difference exceeds 300 seconds (5 minutes) in either direction, the request is rejected. This prevents old captured requests from being replayed after the freshness window expires.
Nonce validation
Keel checks the nonce value against a per-API-key nonce store. If the nonce has been seen before within the 24-hour tracking window, the request is rejected as a duplicate. Nonces older than 24 hours are automatically purged.
Error codes
| HTTP status | Error code | Meaning |
|---|---|---|
401 | request_not_fresh | Timestamp is outside the ±300 second freshness window, or headers are missing/malformed. |
409 | nonce_reuse | Nonce has already been used for this API key within the 24-hour tracking window. |
Example
curl -X POST https://api.keelapi.com/v1/permits \
-H "Authorization: Bearer keel_sk_your_key" \
-H "Content-Type: application/json" \
-H "X-Keel-Timestamp: $(date +%s)" \
-H "X-Keel-Nonce: $(openssl rand -hex 16)" \
-d '{...}'Recommendations
- Use both headers together for strongest replay protection. Timestamp alone prevents old replays. Nonce alone prevents duplicates within 24 hours. Together they cover both attack vectors.
- Generate nonces with a cryptographic random source, not sequential counters. UUIDs or
secrets.token_hex(16)work well. - Synchronize client clocks via NTP. The 300-second window is generous, but significant clock drift will cause false rejections.
SDK support
All Keel SDKs include optional freshness header injection. Enable it via the requestFreshness configuration option:
from keel_sdk import KeelClient, ClientConfig
client = KeelClient(ClientConfig(
base_url="https://api.keelapi.com",
api_key="keel_sk_...",
request_freshness=True, # Enables automatic header injection
))