Create a new API key, update your services, and revoke the old key without dropping traffic.
Regular key rotation limits the blast radius of a leaked credential. The recommended rotation interval is 90 days; rotate immediately if you suspect a key has been exposed.
Programmatic rotation uses the Management API, authenticated with a management key (mk-…) — create one in Dashboard → Settings → Management Keys. The management key manages your API keys (sk-…); an API key sent to the management endpoints answers 401. You can also rotate manually in Dashboard → Keys.
Every key is named by its hash: the SHA-256 of the key, in lowercase hex. The create call returns it, and so does every list.
Rotation is create new, cut over, delete old. Never delete the old key before the new one is in place.
1. POST /v1/keys → new key (key: sk-..., data.hash)
2. Deploy new key to all services
3. Verify traffic is flowing on the new key
4. DELETE /v1/keys/{old_hash}curl -X POST https://opendunes.com/v1/keys \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "production-v2"
}'Response (201 Created):
{
"data": {
"hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"name": "production-v2",
"label": "sk-a1B2c…",
"disabled": false,
"limit": null,
"usage": 0,
"created_at": "2026-09-24T08:00:00+00:00"
},
"key": "sk-…"
}The answer carries more fields than shown here — see API Keys.
Note. The
keyvalue is shown once only at creation. Store it immediately in your secret manager (Vault, AWS Secrets Manager, etc.) — it cannot be retrieved again.
curl https://opendunes.com/v1/keys \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY"{
"data": [
{ "hash": "5e884898…", "name": "production-v1", "disabled": false, "usage": 1840.25, "usage_daily": 12.5 },
{ "hash": "9f86d081…", "name": "production-v2", "disabled": false, "usage": 0, "usage_daily": 0 }
]
}usage is what the key has spent, in DA. Once it rises on the new key, the new key is receiving traffic and the old one can go.
import os, time, requests
BASE = "https://opendunes.com/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['OPENDUNES_MANAGEMENT_KEY']}",
"Content-Type": "application/json",
}
def create_key(name: str) -> dict:
r = requests.post(f"{BASE}/keys", json={"name": name}, headers=HEADERS)
r.raise_for_status()
return r.json() # {"data": {"hash": ..., ...}, "key": "sk-..."}
def delete_key(key_hash: str) -> None:
r = requests.delete(f"{BASE}/keys/{key_hash}", headers=HEADERS)
r.raise_for_status()
def wait_for_first_use(key_hash: str, timeout: int = 300) -> bool:
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f"{BASE}/keys/{key_hash}", headers=HEADERS)
r.raise_for_status()
if r.json()["data"]["usage"] > 0:
return True
time.sleep(10)
return False
# Step 1: create
new = create_key("production-v2")
print(f"New key: {new['key']}") # store this in your secret manager NOW — shown once only
# Step 2: deploy new['key'] to all services, then wait for traffic
print("Deploy the new key and press Enter...")
input()
# Step 3: verify
if wait_for_first_use(new["data"]["hash"]):
print("New key is receiving traffic. Deleting old key.")
delete_key(os.environ["OLD_KEY_HASH"])
print("Done.")
else:
print("Timed out waiting for new key traffic. Check your deployment.")To rotate with zero downtime:
usage on the new key confirms it is active.Deleting is permanent. If you are not sure everything has cut over, pause the old key instead — in Dashboard → Keys, or with PATCH /v1/keys/{hash} and { "disabled": true }. It stops working immediately, exactly as a delete would, but you can switch it back on with { "disabled": false } if something you had forgotten starts failing. Delete it once you are confident.
Use descriptive, versioned names so you can identify keys in the export and analytics:
production-v1, production-v2service-auth-v1, service-batch-v1staging-2026-q3Avoid default, test, or key1 — they become ambiguous in the activity export.
If you suspect a key is compromised:
curl -X DELETE "https://opendunes.com/v1/keys/$COMPROMISED_KEY_HASH" \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY"Deletion is immediate. Any request using the deleted key after this call answers HTTP 401. Replace it with a new key as quickly as possible to restore service.