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 permit-creation and execution routes.
Hosted Keel (api.keelapi.com) always requires request freshness. It cannot be turned off for a project, and a Sandbox project on the hosted API requires the headers too. Keel is offered only as a hosted service; there is no self-hosted deployment with a different setting.
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. |
On hosted Keel, both headers are required on every request to:
POST /v1/permits(permit creation)POST /v1/execute,POST /v1/execute/{execution_id}/resume, andPOST /v1/executions- every
/v1/proxy/*route, including the provider-SDK paths such as/v1/proxy/openai/chat/completionsand/v1/proxy/anthropic/v1/messages - managed MCP execution:
POST /v1/mcp/servers/{server_ref}/tools/{tool_name}:calland:prepare GET /v1/authoring-contextandGET /v1/verification-profile
Other read and audit routes, and POST /v1/permits/dry-run, do not require them.
Exception: a POST /v1/permits request that carries an agent exact-request proof may omit the headers. Keel verifies the proof and records the nonce bound inside it instead.
A stock provider client (for example an OpenAI-compatible SDK) pointed at a Keel base URL does not send these headers, so hosted Keel rejects its requests with 401 request_not_fresh. Such a client works with hosted Keel only if you add both headers to every HTTP attempt yourself, as the Works With recipes do with a per-request fetch hook. Clients that cannot add per-request headers are not supported on hosted Keel.
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
The official Python and JavaScript SDKs add fresh timestamp and nonce headers automatically by default. Raw curl and HTTP callers must add them explicitly. Generate new headers for every retry, including after correcting a validation error.