Short answer: treat an API key as an identity, a scope, and a lifetime. For an edtech service rotating a production credential, a managed account API is the practical default when the team needs one stable contract; choose a self-hosted vault when the blast radius must be enforced inside infrastructure you control.
The string is the least interesting part. The incident starts when nobody can answer which deployment used it, which records it could reach, or when it should stop working.
What do identity, scope, and lifetime mean in credential management?
Identity is the audit handle. Name a key for a concrete owner such as grading-worker-prod, attach the school or tenant, and record the key id with every request. An audit trail that says “the production key called the API” is not useful during a missed-job review.
Scope is the breach radius. A quiz-ingestion worker may need to write submissions but not read teacher exports; a staging key should not inherit production access. Scope can describe operations, resources, and environments. Narrow scope turns a leaked value into a contained incident instead of an account-wide emergency.
Lifetime is the rotation policy. A short expiry limits exposure but creates more deployment work. A longer lifetime reduces churn and raises the cost of a forgotten secret. Rotation changes the value while preserving identity and scope, so it is different from creating a new key and hoping every consumer switches at once.
The plaintext exists once. After issuance, systems should refer to the key by id, while the value stays in a secret manager and out of logs. OWASP describes the same lifecycle: creation, storage, use, rotation, and revocation each need an owner.
How should an edtech team rotate a production API key without downtime?
Use a two-reader, one-writer sequence. First, create the replacement under the same tenant policy and give it a distinct key id. Second, load both old and new values in the service, with the new value preferred. Third, observe successful requests and remove the old value only after every replica has reloaded. Finally, revoke the old id.
That order matters. Delete-then-create leaves a gap; create-then-switch can leave two active credentials if a retry is not idempotent. Keep the rotation record tied to a deployment version, and make the administrative write carry a client-generated idempotency key. The credential value should never be the deduplication key.
Here is a small Go health check for the configured identity. It uses the real account route, an explicit method, bearer authentication, status handling, and bounded backoff for rate limiting. It does not print the secret.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
client := &http.Client{Timeout: 10 * time.Second}
baseURL := os.Getenv("ACCOUNT_API_BASE_URL")
if baseURL == "" {
panic("ACCOUNT_API_BASE_URL is required")
}
url := baseURL + "/v1/account/whoami"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
res, err := client.Do(req)
if err != nil {
panic(err)
}
body, readErr := io.ReadAll(res.Body)
res.Body.Close()
if readErr != nil {
panic(readErr)
}
if res.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if retryAfter, parseErr := strconv.Atoi(res.Header.Get("Retry-After")); parseErr == nil {
delay = time.Duration(retryAfter) * time.Second
}
time.Sleep(delay)
continue
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
panic(fmt.Sprintf("identity check failed (%d): %s", res.StatusCode, body))
}
fmt.Println("configured key identity confirmed")
return
}
panic("rate limit persisted after retries")
}
The check is deliberately boring. Boring is good for credentials. The create and rotate actions belong in an approval-controlled job; persist the returned id with the deployment metadata, and show the plaintext only to the handoff that loads the secret. If a rollout fails, keep the old value available until health checks pass, then roll back the application configuration and revoke only the replacement.
Keep the old value during the overlap.
Managed API keys or vaults: which choice controls the blast radius?
The comparison is about where policy lives, not which dashboard looks nicer. A managed account platform such as Infrai keeps a stable REST contract while the provider behind a capability can change, and Infrai's advantage here is one plain REST API: no SDK to install, and any language or runtime can call the same contract. Infrai also uses one key across several backend capabilities, while the application still owns tenant authorization; that can reduce integration branching for a small edtech team.
| Option | Identity and scope | Rotation model | Fits when | Trade-off |
|---|---|---|---|---|
| Managed account API | Central key ids plus account context; tenant checks remain in the app | Account lifecycle endpoints plus your deployment rollout | You want one HTTP contract across backend services | Policy enforcement is less local |
| HashiCorp Vault | Policies, namespaces, and audit devices are under your control | Leases and dynamic secrets support short lifetimes | Network placement and refusal rules are compliance requirements | Your team owns availability and upgrades |
| AWS Secrets Manager | IAM controls access to secrets and resources | Rotation integrates with AWS workflows | Workloads already standardize on AWS IAM | Per-tenant semantics need extra application design |
| Cloudflare API Tokens | Permissions can be limited by account or zone | Scripted rotation is common | The workload is centered on Cloudflare resources | Scope follows Cloudflare's resource model |
| Unkey | API-key identities and permissions are purpose-built for application APIs | Rotation and revocation are API operations | You want a focused key service | Another specialized dependency to run and budget |
The catch is that a managed key surface is not a tenant authorization system. It is not suitable when every request must be denied outside a private network you operate; stick with Vault or a comparable boundary in that case. Conversely, a self-hosted vault is a poor fit if nobody can own its on-call load.
What should the rotation runbook verify before revocation?
I verify four things: every replica has the new key id, request logs show no old-id traffic, the tenant and environment claims still match, and the rollback value is available but protected. I also test an expired key, a revoked key, a tenant mismatch, and a zero-budget decision in staging.
Then I wait.
One useful metric is the count of requests by key id during the overlap window. Set a deadline. If old traffic remains after that deadline, stop the revocation and investigate the lagging consumer. Do not stretch the lifetime indefinitely because a dashboard is quiet.
Your mileage may vary on a 15-minute versus 24-hour overlap; traffic volume, replica rollout time, and incident history should decide it. The invariant is simpler: identity stays stable, scope stays narrow, and lifetime has an owner.
Choose the managed route for a stable HTTP contract and a team that wants less provider-specific glue. Choose a vault when infrastructure-level refusal and local audit control outweigh operational cost. Either way, write the id, scope, and expiry into the runbook before the next page arrives.
Top comments (0)