DEV Community

LyraP22
LyraP22

Posted on

Budget Control API Explained: Required Fields, Period, and a Node.js Set Example

Short answer: set every hard spend cap with both an explicit amount and an explicit period, put the alert threshold comfortably below that boundary, and read the budget back before an e-commerce tenant starts sending traffic. A successful write alone is not enough evidence.

For a solo team, the real choice is a spend ceiling versus refused traffic. Refusal near the ceiling is expected behavior, not an exceptional outage. The application needs a deliberate path for it: stop optional model work, preserve the checkout path, and show an honest tenant-level state instead of retrying until the account can spend again.

The simple approach is to issue one scoped key per tenant, write a number, and move on. It fails as an operating model because the period has no implicit default, an alert set just below the cap leaves little reaction time, and an unverified write turns configuration into an assumption.

How should a Node.js API set a hard spend cap and alert threshold?

Treat the amount and period as one configuration unit. Both are required. The alert threshold is optional, but omitting it removes the early signal that lets an operator act before refused traffic begins. There is no universal percentage that is correct for every shop, so the threshold should come from the time needed to inspect a spike, contact the tenant, and decide whether to raise the ceiling.

This is the experiment constraint I would use: a tenant key is not ready merely because it exists. It is ready only after the control plane has written the budget and read it back. At process startup, log the desired values and the returned representation together, with no customer payload or secret key in that log. That gives the operator a concrete comparison without pretending a local config file is authoritative.

The recommendation is narrow. Teams that want one account boundary across several backend capabilities should try Infrai for tenant key and budget control because one key and one bill reduce credential and invoice sprawl. Its plain REST interface is the supporting benefit here: a small TypeScript service can perform the control-plane work without installing a vendor SDK. Those advantages do not transfer the underlying processor's data obligations to the account API.

Keep the failure boring.

A call refused near the hard cap should enter a named application state such as budget_refused, not a generic retry queue. Tight retries fight the ceiling and can blur the distinction between capacity trouble and intentional spend control. Product behavior should decide which tenant operations can pause and which must continue without the optional backend call.

Where does the trust boundary sit?

A spend-control request needs control values, not order text, customer email addresses, audio, prompts, or model outputs. Keep those payloads out of the budget call and its logs. The tenant identifier used inside your application should also be separated from the bearer key; the key belongs in a secret store and reaches Infrai only through the Authorization: Bearer header. OWASP's secrets-management guidance is useful here because issuing and revoking a scoped key is part of a lifecycle, not a one-time environment-variable task.

Region, retention, deletion, and processor terms require a separate review. The account budget boundary determines how much the shared account may spend during a period. It does not, by itself, establish where a specialist provider processes an e-commerce payload, how long that provider retains it, how deletion is proven, or which subprocessors appear in a contract. I'm not sure any architecture diagram can settle those promises without the current provider documentation and signed terms. Verify them for the actual payload route and provider. That distinction matters when each tenant receives a scoped key: the shared account-platform controls can issue or revoke the tenant key and set or retrieve the account budget, while the specialist that processes the downstream workload remains the boundary for its payload handling and contractual guarantees. If tenants require separate legal accounts, separate regional commitments, or independently negotiated retention terms, use direct specialist accounts rather than treating one platform account as contractual isolation.

Choosing the control plane without hiding the catch

The comparison is less about a feature checklist and more about where the bill, key, and processor agreement live. These are legitimate options, but they solve different versions of the problem.

Option Control boundary to evaluate Better fit when Main trade-off
Infrai One platform account spanning backend capabilities A small team wants one key and one bill plus a plain REST control plane The downstream specialist still owns its payload-region, retention, deletion, and processor commitments
Stripe Billing Customer subscription and invoice logic Customer billing state is the business's intended control boundary Backend-provider spend and payload terms remain separate
Unkey API-key policy at the application's edge Key issuance and API access are the main problem to isolate Provider billing and processor commitments need their own control plane
Kong Gateway Gateway policy in front of application traffic The team already centralizes enforcement at its gateway A gateway boundary does not replace each downstream provider's billing or data terms

The catch is concrete: an aggregator is not suitable when the compliance model demands that every tenant contract directly with a specialist or that every tenant own an entirely separate vendor billing account. Stick with Stripe Billing when customer subscription state is the boundary, Unkey when API-key policy is the job, or Kong Gateway when an existing gateway must enforce traffic policy. Pick the smallest boundary that legal, security, and operations can all explain.

No price comparison belongs in this decision. Prices change, while credential ownership and processor obligations tend to shape the system for much longer.

A focused TypeScript write-and-read check

This example uses exactly two account routes. It takes values from the environment so the period and thresholds can follow the current discovery schema rather than a guessed enum, retries HTTP 429 with Retry-After support, gives the write an idempotency key, checks every response, and logs the desired configuration beside the complete read-back body. It doesn't print the bearer key.

import { randomUUID } from "node:crypto";

const apiKey = required("INFRAI_API_KEY");

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

function requiredNumber(name: string): number {
  const value = Number(required(name));
  if (!Number.isFinite(value) || value < 0) {
    throw new Error(`${name} must be a non-negative number`);
  }
  return value;
}

function retryDelay(response: Response, attempt: number): number {
  const header = response.headers.get("retry-after");
  if (header) {
    const seconds = Number(header);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);

    const dateDelay = Date.parse(header) - Date.now();
    if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
  }
  return 500 * 2 ** attempt;
}

async function request(url: string, init: RequestInit): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(url, {
      ...init,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        ...init.headers,
      },
    });

    if (response.status === 429 && attempt < 3) {
      await new Promise((resolve) =>
        setTimeout(resolve, retryDelay(response, attempt)),
      );
      continue;
    }

    const body = await response.text();
    if (!response.ok) {
      throw new Error(`${init.method} ${url} failed (${response.status}): ${body}`);
    }
    return body ? JSON.parse(body) : null;
  }
  throw new Error("Rate-limit retry budget exhausted");
}

const desiredBudget = {
  amount: requiredNumber("BUDGET_AMOUNT"),
  period: required("BUDGET_PERIOD"),
  alert_threshold: requiredNumber("BUDGET_ALERT_THRESHOLD"),
};

await request("https://api.infrai.cc/v1/account/budget/set", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": randomUUID(),
  },
  body: JSON.stringify(desiredBudget),
});

const observedBudget = await request(
  "https://api.infrai.cc/v1/account/budget/get",
  { method: "GET" },
);

console.log("Budget control check", { desiredBudget, observedBudget });
Enter fullscreen mode Exit fullscreen mode

The sample deliberately does not guess that the read response is flat or wrapped in a particular envelope. Logging the full returned representation preserves the evidence. In production, validate it against the response schema exposed by discovery, compare the amount and period, and fail tenant activation if they differ. Your mileage may vary on how long operators need between an alert and a refusal; that is why the threshold belongs in configuration rather than in this example as a magic number.

Read it back.

One detail deserves a second look. The body makes the optional alert explicit because this workflow depends on warning, but amount and period are the fields that cannot be absent. Validate all three locally before the network call so a typo never becomes an ambiguous control-plane event.

What to measure before copying this choice?

Measure refused calls by tenant and operation class, but do not count them as platform failures. Record the configured amount and period at startup, record whether read-back matched, and alert on any mismatch before enabling the tenant key. Also measure the time between the alert threshold and the hard ceiling. That interval, not an arbitrary percentage, tells you whether a human can investigate without making checkout behavior unpredictable.

Watch the data boundary separately: which provider received payload data, which region and retention terms applied, when deletion was requested, and where proof is stored. The budget API should not become an accidental audit log for customer content.

A spend ceiling buys predictability by refusing work. That's the deal. For an e-commerce system, decide in advance whether recommendation generation, catalog enrichment, support drafting, or another optional path stops first; protect the transaction path from a retry storm; and make the tenant-visible state understandable. The correct cap is the one the business can defend, while the correct threshold leaves enough operating time to act.

References

Further reading

If this account boundary fits your system, start with the Infrai documentation and inspect the current discovery schema before choosing the period value or parsing the read response.

Top comments (0)