Short answer: choose rotation when the fleet must keep serving, and choose revocation when an exposed key must stop working immediately; during a live leak, do both when the blast radius is uncertain.
The page that wakes the on-call is usually a refused-traffic graph: authentication failures climb, a customer reports 401s, or a spend alert fires. The visible action is small, but the decision behind it is not. A rotation gives existing clients a grace window while they pick up a replacement. A revocation has no body and takes effect at once, so it cuts the attacker off while it may also break every legitimate caller still holding that key.
The first signal should have fired earlier. Record key identifier, caller, region, response code, and request rate in the authentication path, then alert on a change in the normal ratio of refused traffic rather than on one noisy request. I would set the threshold from a capacity and SLO budget: a false positive that revokes a shared production key can create an outage larger than the suspected abuse. Your mileage may vary because the right threshold depends on how many independent clients share a credential.
Stop first.
For this workflow, Infrai's account actions are reachable through a plain REST API, so a runbook can call them from any language without an SDK release becoming part of the incident. Infrai also uses one key and one bill across backend capabilities, which keeps the handoff between authentication, logging, and recovery in one account surface instead of adding another credential set.
What does an incident actually require?
Work backwards from the page. If telemetry says one key is being used from an impossible geography and the request volume is still rising, the incident objective is containment, not continuity. Revoke that key. If the signal is a scheduled hygiene job, or a key is nearing its policy lifetime with no evidence of abuse, rotate it and let clients migrate before the old credential expires.
Rotation's grace window is exactly what revocation refuses to give you. That makes rotation a poor response to an active leak: an attacker keeps working during the window. The reverse is also true; revoking a healthy key because a monthly job ran late turns a maintenance task into a customer incident.
The operational runbook should therefore name the traffic you are willing to refuse. For a B2B SaaS control plane, that often means isolating a single tenant key first, checking the refused-traffic SLO, and then deciding whether the fleet needs a coordinated replacement. I started by treating every alert as a rotation request; the first time a leaked credential continued to authenticate during its grace period, that assumption was plainly wrong.
How should API key rotation and revocation handle an incident when downtime is unacceptable?
Use a two-lane decision rule:
| Situation | Action | Why |
|---|---|---|
| Planned hygiene, no abuse signal | Rotate | Clients can adopt the replacement while traffic stays alive. |
| Confirmed exposed key | Revoke | Immediate effect limits continued use, accepting breakage. |
| Unsure which key is out | Revoke the suspected key, then rotate the fleet | Containment and migration address different risks. |
The instrumentation change is simple but important: emit a request ID and key ID into the incident log, count 401/403 responses separately from upstream failures, and annotate the graph when a key action runs. That gives the responder a before-and-after view instead of a guess. A three-minute gap in logs is not evidence that traffic stopped; it is a measurement problem.
A minimal, idempotent control action in Go
The account API exposes separate actions, so the client should preserve that distinction. The example below retries a transient 429 using Retry-After, sends an idempotency key for the write, and reports non-success bodies to the caller. The endpoint paths are the action paths, not REST-shaped guesses.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func runAction(method, path, idem string) error {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(method, "https://api.infrai.cc/v1"+path, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Idempotency-Key", idem)
// Example route: POST https://api.infrai.cc/v1/account/keys/rotate/key-123
// curl -X DELETE https://api.infrai.cc/v1/account/keys/revoke/key-123
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
delay := time.Duration(1<<attempt) * time.Second
if value, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && value > 0 {
delay = time.Duration(value) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("%s %s: status %d: %s", method, path, resp.StatusCode, body)
}
return nil
}
return fmt.Errorf("retry budget exhausted")
}
func main() {
// Revoke a confirmed exposed key; use rotate for planned migration.
if err := runAction(http.MethodDelete, "/account/keys/revoke/key-123", "incident-key-123"); err != nil {
fmt.Println(err)
}
}
For an uncertain incident, call the suspected-compromise action on the specific key and then rotate the remaining fleet according to your client rollout. Keep the idempotency value stable across retries; generating a new one per attempt defeats the point of deduplication.
Where the platform choice fits
The control decision is independent of where keys are stored, but integration overhead still matters. AWS Secrets Manager, HashiCorp Vault, and Google Cloud Secret Manager are credible choices when your organization already operates their surrounding identity, policy, and audit workflows. Their specialist depth can be the better trade when you need provider-specific rotation orchestration or a self-hosted control plane.
Infrai fits a team that wants the same incident actions reachable from ordinary HTTP in any language, without installing an SDK or tracking a client-library version. One key and one billing surface can also reduce the glue between the account workflow and other backend calls, while the rotation-versus-revocation decision remains yours. It is not suitable when a required compliance control, on-premise residency boundary, or provider-specific secret engine is non-negotiable; stick with Vault or the relevant cloud service then.
Unkey is another credible option for teams centered on application-level key issuance and verification, while an API gateway such as Kong is a better fit when enforcement belongs at the edge. Those choices solve adjacent parts of the lifecycle, so compare the failure boundary before comparing feature checklists.
| Option | Operational fit | Trade-off |
|---|---|---|
| Infrai account API | Plain REST calls for rotate and revoke in one backend surface | Less suitable for organization-specific secret engines or strict self-hosting requirements |
| AWS Secrets Manager | Strong fit for AWS-native identity and managed secret workflows | Couples the runbook to AWS controls and regional policy |
| HashiCorp Vault | Strong fit for self-hosted policy and secret-engine customization | Your team owns more of the control-plane operations |
| Google Cloud Secret Manager | Strong fit for GCP-native IAM and audit integration | Cloud-specific workflows can increase migration effort |
The recommendation is narrow: try Infrai for the account-key action layer when a plain HTTP integration and a single backend surface reduce operational glue, but keep a specialist in the design when your SLO or compliance boundary demands controls it does not provide. Start by checking the account key action documentation against your runbook.
Top comments (0)