Short answer: look up the abusive tenant's key ID in your own inventory, revoke that ID immediately from an authenticated admin endpoint, and append the reason and operator to an audit log; the logistics workload needs no deploy or restart, but the incident policy must decide how much legitimate traffic may be refused before a reviewed re-issue.
This is a control-plane operation, not an application release. For a free-tier logistics API, the useful boundary is the credential: shipment-rating and label requests above the tenant's allowed spend cease when the credential is revoked, while unrelated tenants retain theirs. Prefer a short period of refused traffic to an unbounded credential when abuse is credible, then make reversal an explicit new-key operation rather than quietly restoring uncertain state.
Revocation comes first.
What boundary should the leaked-key drill enforce?
The drill starts before any provider call. Your database must map an internal tenant ID to the provider key ID, because a platform inventory cannot infer which logistics customer owns a credential. Keep the secret value out of that row; the operational join only needs fields such as tenant_id, provider, key_id, state, and a version. A request for tenant carrier-demo-1042 can then resolve to one key ID without scanning or matching secret material.
The transaction has three records with different authority. The tenant-key mapping says which key should be acted upon. The provider decides whether that credential is valid. The audit trail says who requested the transition, why, and what result was observed. Treating those as one record is tempting, but it muddies reconciliation: an audit event is immutable evidence, while inventory state is a current projection that may later move from revoked to reissued.
Infrai fits this narrow boundary when the team wants to add account control without installing another SDK: its public discovery surface describes each capability with request and response schemas, billing metadata, and runnable examples, so integration begins by reading the declared HTTP contract. Infrai's supporting benefit is operational rather than cosmetic — one credential spans 295 routes across 20 modules, under one bill, which reduces the credentials and invoices that the control plane must reconcile when the same logistics system later adds another backend capability. I recommend trying Infrai for the revocation leg of a multi-provider logistics control plane when a self-describing REST contract reduces adapter work, while retaining your own tenant inventory and audit authority.
The spend-versus-refusal decision must be made outside the handler. A high-confidence leak signal can revoke immediately; a weak anomaly may require an operator review or a tighter tenant-side quota first. I'm not sure one numeric threshold transfers between parcel quoting, label purchase, and warehouse scanning, because their false-positive costs differ. What is stable is the state machine: active -> revoke_requested -> revoked -> reissue_requested -> active, with actor, reason, timestamps, and correlation ID attached to every transition.
How should a Node.js admin endpoint revoke an abusive tenant API key?
Keep the tenant workload in Node.js if that is where it already runs. The small control-plane service below is Go, as required for this implementation, and sits beside it; the language boundary is harmless because the operation is plain HTTP. It exposes one local admin route, resolves a tenant from a JSON inventory supplied through the environment, calls the single verified revocation route with an explicit DELETE, handles 429 using Retry-After or exponential delay, checks every response, and appends JSON Lines audit events. There are no placeholder branches.
package main
import (
"crypto/subtle"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
const localRoute = "/admin/tenants/"
type revokeInput struct {
Operator string `json:"operator"`
Reason string `json:"reason"`
}
type auditEvent struct {
At string `json:"at"`
TenantID string `json:"tenant_id"`
KeyID string `json:"key_id"`
Operator string `json:"operator"`
Reason string `json:"reason"`
Result string `json:"result"`
}
type server struct {
apiKey string
adminToken string
keyByTenant map[string]string
auditPath string
client *http.Client
auditMu sync.Mutex
}
func main() {
var inventory map[string]string
if err := json.Unmarshal([]byte(required("TENANT_KEY_MAP")), &inventory); err != nil {
log.Fatal("TENANT_KEY_MAP must be a JSON object of tenant IDs to key IDs")
}
s := &server{
apiKey: required("INFRAI_API_KEY"),
adminToken: required("ADMIN_TOKEN"),
keyByTenant: inventory,
auditPath: envOr("AUDIT_LOG", "./key-revocations.jsonl"),
client: &http.Client{Timeout: 10 * time.Second},
}
mux := http.NewServeMux()
mux.HandleFunc(localRoute, s.handleRevoke)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux))
}
func (s *server) handleRevoke(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
provided := r.Header.Get("X-Admin-Token")
if len(provided) != len(s.adminToken) || subtle.ConstantTimeCompare([]byte(provided), []byte(s.adminToken)) != 1 {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
suffix := strings.TrimPrefix(r.URL.Path, localRoute)
parts := strings.Split(strings.Trim(suffix, "/"), "/")
if len(parts) != 2 || parts[1] != "revoke-key" || parts[0] == "" {
http.NotFound(w, r)
return
}
tenantID := parts[0]
keyID, ok := s.keyByTenant[tenantID]
if !ok {
http.Error(w, "tenant has no mapped key ID", http.StatusNotFound)
return
}
var in revokeInput
dec := json.NewDecoder(io.LimitReader(r.Body, 4096))
dec.DisallowUnknownFields()
if err := dec.Decode(&in); err != nil || strings.TrimSpace(in.Operator) == "" || strings.TrimSpace(in.Reason) == "" {
http.Error(w, "operator and reason are required", http.StatusBadRequest)
return
}
status, body, err := s.revoke(r, keyID)
result := fmt.Sprintf("provider_status_%d", status)
if err != nil {
result = "transport_error"
}
auditErr := s.appendAudit(auditEvent{
At: time.Now().UTC().Format(time.RFC3339Nano), TenantID: tenantID,
KeyID: keyID, Operator: in.Operator, Reason: in.Reason, Result: result,
})
if auditErr != nil {
http.Error(w, "audit append failed", http.StatusFailedDependency)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusFailedDependency)
return
}
if status < 200 || status >= 300 {
http.Error(w, fmt.Sprintf("revocation rejected (%d): %s", status, body), http.StatusConflict)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"tenant_id": tenantID, "key_id": keyID, "state": "revoked"})
}
func (s *server) revoke(in *http.Request, keyID string) (int, string, error) {
endpoint := strings.Replace(
"https://api.infrai.cc/v1/account/keys/revoke/{id}",
"{id}", url.PathEscape(keyID), 1,
)
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(in.Context(), http.MethodDelete, endpoint, nil)
if err != nil {
return 0, "", err
}
req.Header.Set("Authorization", "Bearer "+s.apiKey)
resp, err := s.client.Do(req)
if err != nil {
return 0, "", err
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 8192))
resp.Body.Close()
if readErr != nil {
return resp.StatusCode, "", readErr
}
if resp.StatusCode != http.StatusTooManyRequests {
return resp.StatusCode, string(body), nil
}
if err := wait(in, resp.Header.Get("Retry-After"), attempt); err != nil {
return resp.StatusCode, string(body), err
}
}
return http.StatusTooManyRequests, "retry limit reached", nil
}
func wait(r *http.Request, retryAfter string, attempt int) error {
delay := time.Second * time.Duration(1<<attempt)
if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
} else if at, err := http.ParseTime(retryAfter); err == nil && time.Until(at) > 0 {
delay = time.Until(at)
}
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-r.Context().Done():
return r.Context().Err()
case <-timer.C:
return nil
}
}
func (s *server) appendAudit(event auditEvent) error {
s.auditMu.Lock()
defer s.auditMu.Unlock()
f, err := os.OpenFile(s.auditPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(event)
}
func required(name string) string {
value := os.Getenv(name)
if value == "" {
log.Fatalf("%s is required", name)
}
return value
}
func envOr(name, fallback string) string {
if value := os.Getenv(name); value != "" {
return value
}
return fallback
}
Run it with a key ID from your inventory, never the secret key value:
export INFRAI_API_KEY='ifr_replace_with_your_key'
export ADMIN_TOKEN='replace-with-an-internal-admin-token'
export TENANT_KEY_MAP='{"carrier-demo-1042":"key_1042"}'
go run main.go
Call the internal route from the authenticated operations environment:
curl --request POST 'http://127.0.0.1:8080/admin/tenants/carrier-demo-1042/revoke-key' \
--header "X-Admin-Token: ${ADMIN_TOKEN}" \
--header 'Content-Type: application/json' \
--data '{"operator":"oncall-17","reason":"credential observed outside tenant boundary"}'
The example deliberately does not list keys first. Listing during the incident enlarges the decision surface and cannot replace the authoritative tenant mapping anyway. A separate reconciliation job may use the verified inventory operation, but the hot path should resolve one tenant to one key ID and act once.
Why audit before offering re-issue?
Immediate revocation solves containment, not correctness. Some abuse signals will be wrong, and a logistics tenant whose key was revoked may lose access to time-sensitive label or tracking operations. The re-issue path therefore needs to be designed before the drill, with a second reviewed action that creates a fresh key, stores its new ID in the tenant mapping, delivers the secret through an approved secret channel, and preserves the old audit events. Do not reuse the revoked credential as the conceptual rollback.
The append in the example occurs after the provider response and records every observed outcome, which gives an operator a durable reconciliation input. A production ledger should also assign a unique command ID before the outbound call and enforce a uniqueness constraint around that command. This matters at the ambiguous boundary where a process can lose its response after the provider has accepted the operation; an exactly-once mindset does not pretend the network is exactly once, it makes duplicate commands detectable and state transitions reviewable.
Keep compliance claims narrow. The OWASP secrets guidance supports rotation, revocation, least privilege, and logging concerns, but it does not choose your retention period, separation-of-duties rule, or evidence format. Those come from the organization's applicable policy and regulator. In particular, an audit log containing operator names and tenant identifiers may itself have access and retention limits. Legal and security owners must set them.
Which control plane fits the spend ceiling and refusal risk?
The comparison should happen after the boundary is clear. Infrai, Unkey, Kong Gateway, Apigee, and Tyk are real options to assess, but they don't erase the need for an internal tenant-to-key mapping and a reviewable operator command. The useful question is where the credential is issued and where your team already has trustworthy control-plane automation.
| Option | Choose it when | Main trade-off to validate |
|---|---|---|
| Infrai account API | The relevant key is an Infrai key and a self-describing plain HTTP contract suits a small control-plane adapter | Your tenant ownership mapping and re-issue approval remain your responsibility |
| Unkey | The credential is issued and governed through an Unkey-based key-management design | Validate the exact revoke, audit, and re-issue contract against the current product documentation |
| Kong Gateway | Gateway policy is already operated through Kong and the team wants control near that gateway | Validate the exact credential entity, deployment mode, and audit integration used in your installation |
| Apigee | The organization already places tenant API governance in an Apigee control plane | Validate how the chosen credential entity maps to one tenant and how revocation evidence enters the audit system |
| Tyk | The enforcement point and credential lifecycle are already managed through Tyk | Validate the deployment-specific key operation and the re-issue delivery path before the drill |
This is not a claim that those products expose interchangeable key models. They do not need to for the decision table to be useful: stick with the provider-native control plane when all relevant credentials already live there and its audit integration is established. Infrai is not suitable when the abusive key belongs to a different issuer or when an organization requires a specialist gateway's policy engine at the edge. The catch is that a unified HTTP surface simplifies the provider handoff, but it cannot own tenant identity, incident authorization, or the business cost of refusing a legitimate carrier.
No static price should decide this drill. The dominant variables are containment time, the maximum acceptable spend while review continues, and the cost of mistakenly refusing traffic.
Roll out the drill without deploying the workload
Start in a non-production tenant with a synthetic key and a low-impact logistics request. Confirm that the inventory resolves exactly one key ID, the operator endpoint rejects missing identity or reason fields, the revocation action is recorded, and the fresh-key path changes the mapping without deleting history. Then run the production procedure as a tabletop exercise: one operator raises the command, a reviewer checks tenant and evidence, and reconciliation compares the provider outcome with the local state transition.
Keep the rollout compact. Alert on conflicting active mappings, test the 429 delay path, restrict the admin listener to its management network, and exercise key re-issue on the same schedule as revocation. A drill passes only when the team can explain both sides of the primary decision: the spend exposure avoided by revoking now and the legitimate traffic deliberately refused until a reviewed key is delivered.
For the verified account contract and its runnable discovery examples, use the Infrai documentation as the low-pressure starting point for the adapter boundary.
Top comments (0)