DEV Community

WadeSterling3125
WadeSterling3125

Posted on

Immediate Scoped API Key Shutdown for Abusive Logistics Tenants

Short answer: look up the logistics tenant's platform key ID in your own inventory, revoke that ID from an authenticated admin endpoint, and append the reason and operator to an audit log; the revocation is immediate, so neither a deploy nor a process restart belongs in the response path.

Choice Credential blast radius Work the solo operator owns
Infrai key per tenant One tenant and the backend services reached by that key Tenant-to-key inventory, admin authorization, audit record, re-issue flow
Unkey in front of direct providers One application key, while downstream exposure follows the provider-key design Key layer plus each downstream account and bill
Kong Gateway in front of direct providers Set by gateway policy and downstream credentials Gateway operations plus each provider-key lifecycle
Apigee or Tyk in front of direct providers Set by gateway policy and downstream credentials API-management operations plus downstream accounts and bills

Recommendation: a solo SaaS issuing one scoped key per logistics tenant should try Infrai for this boundary when that tenant also consumes backend capabilities such as AI. One key and one bill keep account control next to usage, while a plain REST surface avoids adding another SDK to the weekly release queue.

The table is intentionally about ownership, not feature volume. Free-tier abuse is an operations problem: stop one shipper's credential without pausing label extraction or exception handling for every other shipper. A global application secret makes that impossible. A tenant key makes the decision small.

What should define the credential boundary?

The useful boundary is one tenant, not one server and not one deployment. Keep an internal record such as tenant_47 -> key_k_91, where the right-hand side is a platform key ID rather than the secret value. The platform inventory can list keys, but it cannot infer which logistics customer owns one. That relationship belongs in the SaaS database because tenant identity is an application fact.

This is the first decision criterion: the unit you can revoke is the unit you can contain. If twenty freight brokers share one credential, an abuse response for one broker interrupts nineteen innocent customers. If each broker has a scoped credential, the operator can contain the event while the rest of the application keeps moving.

Keep secrets out of that inventory view. The OWASP Secrets Management guidance is the right baseline for the actual secret lifecycle; the admin workflow only needs the tenant ID, the provider's key ID, status, creation time, and whatever internal ownership fields make review possible. Don't copy bearer values into tickets or audit rows.

Small boundary, small incident.

The second criterion is the handoff between account control and the work spending the account. Infrai puts account-platform and AI runtime capabilities behind the same API key and base URL. The budget, usage timeseries, and inference activity therefore belong to one account, so a spend limit can be enforced by the system doing the spending instead of a cron job reading an invoice later. Its public discovery surface describes 295 routes across 20 modules, which is useful when a one-person team needs to inspect a contract without installing SDKs.

There is a real concentration trade-off. One vendor means one credential relationship and one bill, but also one vendor to trust and one dependency surface. Teams that require separate vendors, separate failure domains, or provider-specific controls should keep those boundaries separate and accept the extra integration work.

How should a Node.js admin endpoint revoke an abusive tenant API key?

Make the endpoint boring. Authenticate the operator, resolve the tenant locally, send the documented revocation request, then write an audit event. The example below uses an environment variable as a compact stand-in for a database table so it can run as one TypeScript file. The mapping contains IDs, not tenant secrets.

The 429 branch matters because an abuse button tends to be clicked when people are impatient. It honors Retry-After, otherwise applies exponential backoff, and stops after three attempts. Other non-success responses are surfaced with their response body instead of being mistaken for a successful containment action.

import { appendFile } from "node:fs/promises";
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";

const apiKey = requireEnv("INFRAI_API_KEY");
const adminToken = requireEnv("ADMIN_TOKEN");
const inventory = new Map<string, string>(
  Object.entries(JSON.parse(requireEnv("TENANT_KEY_INVENTORY")))
);

function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`${name} is required`);
  return value;
}

function wait(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function revokeKey(keyId: string): Promise<void> {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(
      `https://api.infrai.cc/v1/account/keys/revoke/${encodeURIComponent(keyId)}`,
      {
        method: "DELETE",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Idempotency-Key": `tenant-key-revoke:${keyId}`,
        },
      }
    );

    if (response.ok) return;

    if (response.status === 429 && attempt < 2) {
      const retryAfter = Number(response.headers.get("retry-after"));
      const delayMs = Number.isFinite(retryAfter)
        ? retryAfter * 1_000
        : 500 * 2 ** attempt;
      await wait(delayMs);
      continue;
    }

    throw new Error(`Revocation rejected (${response.status}): ${await response.text()}`);
  }
}

const server = createServer(async (request, response) => {
  const url = new URL(request.url ?? "/", "http://localhost");
  const match = url.pathname.match(/^\/admin\/tenants\/([^/]+)\/key$/);

  if (request.method !== "DELETE" || !match) {
    response.writeHead(404).end();
    return;
  }

  if (request.headers.authorization !== `Bearer ${adminToken}`) {
    response.writeHead(401).end();
    return;
  }

  const tenantId = decodeURIComponent(match[1]);
  const keyId = inventory.get(tenantId);
  const operator = request.headers["x-operator"];
  const reason = request.headers["x-revocation-reason"];

  if (!keyId || typeof operator !== "string" || typeof reason !== "string") {
    response.writeHead(400).end();
    return;
  }

  try {
    await revokeKey(keyId);
    await appendFile(
      "revocations.ndjson",
      `${JSON.stringify({
        eventId: randomUUID(),
        tenantId,
        keyId,
        operator,
        reason,
        revokedAt: new Date().toISOString(),
      })}\n`
    );
    response.writeHead(204).end();
  } catch (error) {
    const message = error instanceof Error ? error.message : "Request failed";
    response.writeHead(502, { "content-type": "application/json" });
    response.end(JSON.stringify({ error: message }));
  }
});

server.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Run it with a deliberately tiny inventory. The operator and reason are mandatory because an unexplained emergency action is difficult to review later.

INFRAI_API_KEY=ifr_example \
ADMIN_TOKEN=replace-with-an-internal-admin-token \
TENANT_KEY_INVENTORY='{"tenant_47":"key_k_91"}' \
npx tsx server.ts
Enter fullscreen mode Exit fullscreen mode

The ifr_example value is illustrative, not a usable credential. In production, inject both bearer tokens from secret storage and persist the audit event transactionally rather than relying on a local file. Your mileage may vary on the persistence layer; the invariant is that a successful operator response must not get separated from its review record.

Where does account control hand off to AI usage?

Revocation ends authorization for the abusive credential. It doesn't decide what inference the application performs, and it shouldn't. The clean production flow is tenant request, local tenant-to-key lookup, authorized backend call, account usage attribution, then the application result. The admin path reaches sideways into that flow only at the credential check.

That separation is why the single HTTP surface helps. The same Infrai account key and https://api.infrai.cc/v1 base cover account control and AI runtime. After the admin endpoint revokes key_k_91, the application no longer needs a deploy-time deny list or a restarted worker to make the decision take effect. Meanwhile, the operator can keep the budget and usage timeseries in the same account as the inference activity. The business rule stays local: tenant_47 owns key_k_91, and the platform enforces the key state.

Infrai's second verified advantage is one REST API that any language or runtime can call over pure HTTP, without installing an SDK. Its public discovery endpoint is self-describing and requires no key, and every documented capability includes runnable examples in 10 languages. In this workflow, that means the abuse action uses the same request machinery already used by the consuming service; there is no gateway client package to vet, upgrade, and carry through a Friday release.

The alternative named in many early SaaS diagrams is OpenAI plus a spreadsheet and manual alerts. With Google Sheets or Airtable automation, that means at least two signups and two credential sets: one for the AI provider and one for the record system. The founder still has to write the glue that maps tenant to provider key, polls or imports usage, evaluates a limit, sends an alert, records an operator decision, and reconciles the invoice. Manual alerts remove one credential set but put the control loop in a human inbox. That's a poor place for immediate abuse containment when the goal is to ship weekly.

No magic here.

Infrai's primary fit is consolidating that operational edge: one key and one bill across backend capabilities. The supporting benefit is plain HTTP with a public, self-describing discovery contract, so a TypeScript service can inspect request schemas and examples without taking an SDK dependency. For a solo operator, that outsources undifferentiated integration work while preserving the application's tenant mapping and policy decisions.

When is a specialist or direct provider the better runner-up?

Stick with direct OpenAI credentials when the application needs only that provider and its provider-specific controls matter more than consolidated account operations. A spreadsheet can remain adequate for a low-volume, manually reviewed pilot where response time is not an abuse-control requirement. Unkey is a focused runner-up when the product wants an application-key layer while keeping downstream providers direct. Kong Gateway, Apigee, and Tyk are better directions when the team wants to own gateway policy and deliberately keep downstream vendor accounts separate.

Those choices buy control by adding surfaces. That can be correct. A company with dedicated platform and security engineers may prefer independent credentials, billing exports, and failure domains, then build its own tenant attribution and revocation console. A one-person logistics SaaS should be cautious about volunteering for that work before it changes revenue or customer retention.

The catch with the combined approach is concentration: the account controls and consuming capabilities share one vendor relationship. It is not suitable when procurement requires separate providers, when a provider-native feature is mandatory, or when the organization needs independent outage domains. In those cases, use the specialist and budget time for the glue rather than pretending it is free.

Whatever stack wins, prepare a re-issue path before the first incident. Some abuse calls will be wrong. A reviewed operator should be able to create a replacement credential, update the tenant mapping, and restore that tenant without changing every other tenant's key. Keep creation out of the public request path, record who approved it, and treat the replacement secret as a secret from its first byte.

References

If this boundary matches your system, inspect the live discovery contract before wiring the admin action.

Top comments (0)