Safeguard
Product•

Control LLM spend in Safeguard Console: budgets, credits, usage

How to cap, attribute and audit LLM spend in Safeguard Console: prepaid credit, holds, key and workspace limits, usage by key, the credit ledger and monthly statements.

Safeguard Security Team
40 min read

Safeguard Console stops LLM spend before it happens, not after the bill arrives. Every request first reserves the most it could cost, and that reservation has to fit three gates in order: the workspace's available prepaid credit, the calling key's monthly spend limit and the workspace's monthly spend limit. A request that fails any gate is refused with HTTP 402 before a provider is called, so it is never charged. Attribution then comes from per key and per model usage tables, a metadata only request log, an append only credit ledger and downloadable monthly usage statements.

This guide is for the AI platform owner who runs LLM features in production, the FinOps lead who has to explain the number at month end, and the CISO who wants runaway consumption treated as a risk rather than a surprise. Safeguard Console is what the docs call the API Platform: Safeguard's OpenAI-compatible inference API and the console around it. It is switched on per workspace. Every fact below about the Console comes from docs.safeguard.sh; other facts come from the primary sources listed at the end. Where we recommend something the docs do not prescribe, we label it a suggestion.

If you are moving existing OpenAI client code across, the 5 Nov guide on moving from the OpenAI API to Safeguard Console (it publishes 5 Nov) covers base URLs, keys, model ids and the cutover. This post starts where that one stops: how the money side works in detail, and how to run it as a monthly process.

Key takeaways

  • Credit is a hard stop. The inference API is prepaid. When available credit cannot cover a request's worst case, the answer is 402 insufficient_credit and nothing reaches a provider.
  • Holds, not guesses. Before each call the gateway reserves the most the request could cost. The reservation shows as Reserved, and only the actual cost is charged when the answer completes.
  • Two monthly limits. Each key can carry a monthly spend limit set by its owner. The workspace can carry one set by a tenant admin or super admin. Both count UTC calendar months and include what in flight requests hold.
  • Keys are your cost centres. Keys belong to the whole workspace and cannot be scoped to an organization or project, so the docs' own advice for keeping teams' spend apart is one key per team with its own limit, compared under Usage, By key.
  • Rate limits do not cap spend. The requests per minute limit stops runaway loops; the dollar limits cap money.
  • Every change is on record. The credit ledger is append only, key limit changes and gateway setting changes write audit events with old and new values, and monthly usage statements download as CSV.
  • The Console issues no invoices for inference credit. Your record is the ledger and the statements.

Why LLM spend needs controls before the invoice

Model usage is metered in tokens, and tokens are cheap one at a time and expensive in aggregate. The FinOps Foundation's working group on FinOps for AI puts it plainly: AI providers mostly price by consumption, with "usage metrics such as tokens generated, API calls, or processing time". That makes LLM cost a variable you cannot read off an architecture diagram.

The security community has a name for the failure mode. The OWASP Top 10 for LLM Applications lists Unbounded Consumption as LLM10:2025, and one of its scenarios is Denial of Wallet: "By initiating a high volume of operations, attackers exploit the cost-per-use model of cloud-based AI services, leading to unsustainable financial burdens on the provider and risking financial ruin." Among the MITRE ATLAS techniques the page lists for the risk is AML.T0034, Cost Harvesting. The mitigations it lists include rate limiting and user quotas, resource allocation management so no single user or request consumes excessive resources, and logging and monitoring to "detect and respond to unusual patterns of resource consumption."

Not every runaway bill is an attack. More often it is a retry loop with no ceiling, an agent calling a model inside a loop, or a test suite pointed at production keys. The fix is the same: a cap checked before money is spent, attribution precise enough to find the culprit in minutes, and a record good enough to explain the month to finance.

The FinOps Framework describes the practice in three phases, Inform, Optimize and Operate. The Console's controls line up with them closely enough to use the framework as a checklist:

FinOps phaseWhat it asks forConsole control
InformVisibility and allocation of costDashboard, Usage totals, By model and By key tables, request log, usage statements
OptimizeReduce waste per unit of workmax_tokens on every call, model choice per request, usage.cost on every response
OperateGovern continuously, with ownershipKey and workspace monthly limits, the 402 refusals, role based gateway settings, audit events, credit ledger

How money moves: balance, hold and charge

Everything else in this post follows from one mechanism, so it is worth reading slowly.

The four figures

Open Usage in the Console and scroll to Credits and limits. Four figures describe your workspace's money at any moment:

FigureWhat it means
BalanceCredit after every charge so far
ReservedCredit held for requests that are still running
AvailableBalance minus Reserved; new requests draw on this
Spent this monthWhat requests have cost in the current UTC month, with the workspace's monthly limit and the share used, or No monthly limit

Available is the number that matters for admission. A workspace can have a healthy Balance and still refuse requests if a burst of long running calls has reserved most of it.

The hold

The gateway cannot know what a request will cost until the provider answers, so before calling the provider it holds the most the request could cost. The Limits page explains how that worst case is built:

  • Input. It estimates input tokens from your messages, generously, at about one token for every three characters plus a little for each message.
  • Output. It assumes the full output cap: your max_completion_tokens or max_tokens, or a server default output cap (or the model's maximum, if lower) when you set none.
  • Price. It prices both at the model's per million token prices. When fallbacks are allowed, it uses the most expensive of the models it might try.

That amount shows as Reserved while the request runs.

The charge

When the answer is complete, the hold is released and the actual cost is charged: the provider's own token counts times the model's per million token price, input and output priced separately. Costs are worked out to eight decimal places, so a request that costs a fraction of a cent is charged exactly that. The ledger gets one Usage entry for the request.

Cached input tokens are billed at the cached input price when the model catalog lists one, and at the input price otherwise. If a provider reports no token counts, for example when a stream ends early, the gateway bills an estimate based on the length of the text; the response's usage says estimated: true and the request log marks the row est.

When nothing is charged

Two cases cost nothing, and they differ in where you will see them:

  • A request the gateway refuses before calling a provider (bad key, platform off, unknown or unavailable model, too little credit, a spend limit, a rate limit, an invalid body) is not charged and is not logged in the Console. Your client receives the error directly.
  • A request that reaches a provider but fails before the provider produces anything is not charged, and does appear in the request log with its error code.

When you pay even though you stopped

Closing the connection does not stop the charge. When a streaming client goes away, the gateway keeps reading the provider's stream to the end so it can bill the provider's own counts, and the request log shows client_disconnected. Stopping a run in the Workbench works the same way. If a streamed answer broke partway because of a provider error, the tokens already produced are charged.

This matters for any UI with a Stop button. Users pressing Stop does not save money on Console; a lower output cap does.

Edge cases worth knowing

  • A hold that is never settled, for example because a request was cut off inside Safeguard, is released after about 30 minutes. Until then it still counts against Available.
  • Rarely, a provider reports more input tokens than the gateway estimated. Those tokens were used and billed by the provider, so they are charged, and the balance can dip below zero. Every new request is then refused until credit is added.
  • Requests in flight at the same time each hold their own amount. Ten concurrent long requests reserve ten worst cases.

The practical consequence: a request can be refused while a little credit or limit headroom is left, because the gate compares the worst case, not the likely cost. The single biggest lever you control is the output cap. A smaller max_tokens makes every hold smaller, so more requests fit in the same headroom.

The three gates, in order

Before each request, the hold must pass three checks in this order. The first one that fails decides the error.

OrderCheckPasses whenRefused withWho can fix it
1Prepaid creditAvailable credit covers the hold; at a balance of zero or less every request is refused402 insufficient_creditA tenant admin or super admin by card where card top-up is switched on, or Safeguard by grant
2Key monthly limit, if setKey's spend this month plus its in flight holds plus this hold stays within the limit402 key_spend_limit_exceededThe key's owner, under API keys, Edit
3Workspace monthly limit, if setThe same sum for the whole workspace, Workbench runs included, stays within the limit402 account_spend_limit_exceededA tenant admin or super admin, under Usage, Credits and limits, Gateway settings

Months are UTC calendar months, so monthly spend starts again at 00:00 UTC on the 1st. A key or workspace with no monthly limit is limited only by credit. Workbench runs spend the workspace's credit and count toward the workspace limit, but they use no key, so no key limit applies to them.

What the refusal looks like

All three refusals are HTTP 402 with the error type insufficient_quota, in OpenAI's error body shape so existing SDKs raise their usual exceptions:

{
  "error": {
    "message": "This API key's monthly spend limit would be exceeded.",
    "type": "insufficient_quota",
    "code": "key_spend_limit_exceeded",
    "param": null
  }
}

The code field is what your handler should branch on. The Console's own message for insufficient_credit already points at the two remedies: "Add credit in the Console, or lower max_tokens."

A note on the status code itself. RFC 9110, the HTTP semantics standard, says only that "The 402 (Payment Required) status code is reserved for future use." It defines no standard meaning, which is exactly why a gateway can use it to separate money problems from load problems. The Console does that: 402 always means a credit or limit decision, while 429 means a rate limit. Retrying a 402 unchanged rarely helps; it keeps failing until credit is added, a limit changes, or in flight requests finish and release what they hold.

The money gates come late in the request lifecycle: the key, the platform switch, the body, the rate limit and the model are checked first. So a 401 or 403 is configuration, not budget, and a 402 is always money.

Design a budget structure before the first production request

The Console gives you two levels of limit and one unit of attribution, the key. The docs are explicit about the shape this forces:

  • An inference key belongs to the whole workspace. It cannot be limited to an organization, project, product or agent.
  • Credit, monthly limits, usage and the request log are kept per workspace and per key, not per organization.
  • Every key can call every model available to the workspace.
  • To keep teams' spending apart, give each team its own keys, each with its own monthly limit, and compare them under Usage, By key.

So your cost model is the key list. Design it on purpose.

A three layer structure (suggestion)

  1. Workspace ceiling. One monthly limit, <workspace_budget>, that represents the most the organization is willing to spend on inference in a month under any circumstances. This is the backstop for the case where every individual key is set too generously.
  2. Per service and environment keys. One key per application per environment, for example checkout-assistant-prod, checkout-assistant-staging, support-summariser-prod. Each carries its own monthly limit, <service_budget>. Production limits are sized to expected peak; staging and CI limits are deliberately small, because nothing in staging should cost much.
  3. Per team grouping by name. Since keys cannot be scoped to an organization, encode the team in the key name so By key reads like a cost report: payments.checkout-assistant.prod, support.summariser.prod.

Remember the sums. Key limits do not have to add up to the workspace limit, and usually should not: if they do, one team can never borrow headroom from a quiet month elsewhere. If they add up to more, the workspace limit is what will actually stop traffic, and it stops every team at once. Decide which behaviour you want and write it in the runbook.

Choosing where to put the limit

You wantUseWhy
A hard organization wide ceilingWorkspace monthly limitApplies to every key and to Workbench runs
One service never to exceed its budgetThat service's key limitOnly that key is refused when it runs out
Developers to experiment safelySeparate low limit keys per developer or sandboxA runaway notebook stops itself
CI tests that call a modelA dedicated CI key with a small limitA loop in a test cannot touch production budget
To stop a single misbehaving client nowRevoke its key, or set its limit lowA key limit change reaches the gateway within 30 seconds
Fewer requests per minute from a clientThe key's requests per minute limitRate limits stop loops; they are not a spend cap

Ownership is part of the budget

Keys belong to people. A key is created by, and for, the signed in member; nobody can create one on someone else's behalf, Safeguard staff included. Deactivating a member revokes their keys at once, and reactivating them does not bring the keys back. So only the owner can raise a production key's limit in the Console, and offboarding the owner stops the key. Suggestion: create production keys from a service owner account your team controls, and record each key's owner in the runbook.

Step by step: set up spend control

Step 1: confirm the platform is on and credit is in place

Safeguard switches the API Platform on per workspace. Two switches matter: API Platform (Console), which puts the Console in your product switcher, and console:inference_gateway, which connects the Console to the inference gateway, lets members create inference keys and lets the API accept your workspace's keys. Safeguard turns both on for your workspace; when it changes a feature for your workspace, the change appears in Organization activity, naming the feature.

Then check Credits and limits under Usage. Credit reaches the workspace in two ways, both visible in the ledger. A Grant is credit Safeguard adds (contact hi@safeguard.sh), shown with the reason in the Note column; Safeguard also records which of its staff made it. A Topup is credit a tenant admin or super admin buys by card where card top-up is switched on for your workspace: Add credit, enter Credit to add (USD), Continue to payment, then pay on Stripe's checkout page. The credit appears once Stripe confirms the payment, and each payment is credited once. Other members see "A tenant administrator can add credit." instead of the button.

Step 2: set the workspace ceiling

A tenant admin or super admin opens Usage, goes to Credits and limits, and under Gateway settings fills in Workspace monthly spend limit (USD), then selects Save settings. In the Console's words: "Requests that would pass it are refused." Leave it empty for no limit. The limit applies from the next request, and the Spent this month card shows how much of it is used.

Gateway settings also hold Store prompts and outputs, Allow retries and fallbacks, and Only use models whose provider offers zero data retention. Two of those affect cost: allowing fallbacks makes holds larger (the hold is priced at the most expensive model the request might try), and the zero data retention setting narrows the models available. Change them knowing that.

Step 3: create keys with limits

In the web app, open the product switcher and choose API Platform (Console). On API keys, select Create key and fill in a Name, the optional Monthly spend limit (USD) and, if needed, a requests per minute value. The Console takes dollars and cents, above zero; leave the limit empty for no limit. To change it later, select Edit on the key's row. A change to a key's limit reaches the gateway within 30 seconds.

The This month column on API keys shows each key's spend against its limit, with a bar that turns red as the key nears its limit. Note that API keys lists only your own keys. Usage covers everyone's.

Step 4: cap output on every call

Set max_tokens (or max_completion_tokens) on every request. It is both a quality control and a budget control: it shrinks the hold, so more requests fit within credit and limits, and it bounds the worst case of any single call.

from openai import OpenAI
import os

client = OpenAI(
    base_url="https://api.safeguard.sh/gpt/v1",
    api_key=os.environ["SAFEGUARD_INFERENCE_KEY"],
    max_retries=0,  # suggestion: handle retries yourself, by error code
)

resp = client.chat.completions.create(
    model="openai/gpt-6-sol",
    messages=[{"role": "user", "content": "Summarise this ticket in three bullets: ..."}],
    max_tokens=300,  # bounds the hold and the bill
)

print(resp.usage)  # includes cost in US dollars on Safeguard

Step 5: read your budget from code

GET /key returns the calling key's state, so a service can know its own headroom without a person opening the Console:

curl -s https://api.safeguard.sh/gpt/v1/key \
  -H "Authorization: Bearer $SAFEGUARD_INFERENCE_KEY" | jq .data
Field in dataWhat it is
monthly_spend_limit_usdThe key's monthly limit as a string, or null for none
month_spent_usdWhat this key has spent in the current UTC month
available_credit_usdThe workspace's available credit
monthThe current month, as YYYY-MM

The response also carries the key's name, prefix, scopes and rate_limit_rpm. Two details to code around: if the billing service cannot be reached, the spend and credit fields are left out, so treat them as optional; and the workspace limit is not part of this response, so a service cannot see the workspace ceiling from its own key.

A small budget probe (suggestion) that your service runs at start up and on a timer, emitting a metric and warning before the key is refused:

import os, httpx
from decimal import Decimal

KEY_URL = "https://api.safeguard.sh/gpt/v1/key"
WARN_SHARE = Decimal(os.environ["BUDGET_WARN_SHARE"])  # your choice, as a fraction of the limit

def budget_status() -> dict:
    r = httpx.get(KEY_URL, headers={"Authorization": f"Bearer {os.environ['SAFEGUARD_INFERENCE_KEY']}"}, timeout=10)
    r.raise_for_status()
    d = r.json().get("data", {})
    status = {"key": d.get("name"), "month": d.get("month")}
    limit = d.get("monthly_spend_limit_usd")
    spent = d.get("month_spent_usd")
    credit = d.get("available_credit_usd")
    if spent is None or credit is None:
        status["state"] = "unknown"  # billing not reachable: do not guess
        return status
    status["spent"] = Decimal(str(spent))
    status["credit"] = Decimal(str(credit))
    if limit is None:
        status["state"] = "no_key_limit"
    else:
        status["limit"] = Decimal(str(limit))
        share = status["spent"] / status["limit"]
        status["state"] = "warn" if share >= WARN_SHARE else "ok"
    return status

The warning share is yours to choose; the Console does not prescribe one. Publish spent, limit and credit as gauges to whatever monitoring you already run, and alert on the trend, not only the level: a key that spent a normal week's budget in an afternoon is the signal OWASP's monitoring advice is about.

Step 6: handle 402 as a money event, not a retry

Branch on the error code. A 402 should page a person or degrade the feature gracefully; it should never feed a retry loop.

import openai

def complete(messages, **kw):
    try:
        return client.chat.completions.create(messages=messages, **kw)
    except openai.APIStatusError as e:
        code = (e.body or {}).get("error", {}).get("code") if isinstance(e.body, dict) else None
        if e.status_code == 402:
            if code == "insufficient_credit":
                alert("finops", "Workspace credit cannot cover requests; add credit")
            elif code == "key_spend_limit_exceeded":
                alert("service-owner", "This key reached its monthly limit")
            elif code == "account_spend_limit_exceeded":
                alert("platform-admin", "Workspace monthly limit reached")
            return fallback_response()  # your degraded mode, not a retry
        raise

The insufficient_credit message itself suggests the one retry that can help: if the request is close to the line, the same request with a smaller max_tokens holds less and may fit. Suggestion: only do this for features where a shorter answer is acceptable, and log that you did.

In the Workbench these errors come with a shortcut to the right place: Open limits for a limit, and Add credit for credit where card top-up is on. Suggestion: copy the Who can fix it column of the gates table above into your runbook with real names against each role.

Rate limits are a different control

Each key also has a requests per minute limit, counted over a rolling window; past it, requests get 429 rate_limited with a Retry-After header. The docs say it "guards against runaway clients and is approximate, so it is not a way to cap spend": each gateway server process applies it separately, so short bursts can go somewhat over. Use both controls. The rate limit catches a tight loop in seconds; the spend limit catches the slow leak that stays under any sensible rate but runs all month. OWASP's guidance pairs them the same way: rate limiting and quotas, plus monitoring of consumption.

Attribute spend: Dashboard, Usage and the request log

The Dashboard sums up the last 14 days in UTC days: requests with their total cost, input and output tokens, your own active keys, and a Requests per day bar chart that makes an out of pattern day easy to spot. Usage is the reporting view.

Usage: totals, By model and By key

Usage lets you pick the period at the top of the page: Last 7 days, Last 14 days, Last 30 days or Last 90 days. Periods are UTC days ending today.

  • Totals. Requests, Input tokens, Output tokens and Cost for the period. Requests includes requests that ended with an error.
  • By model. A row per model id, each with Requests, Input, Output and Cost. When a fallback model answered a request, the request counts under the fallback model, not the one you asked for.
  • By key. A row per key, shown by its name and masked form, such as production backend · sg-api-1a2b3c4d…9f8e. The name and mask are the ones the key had when the requests were made, so a renamed or revoked key stays recognizable. Workbench runs use no key, so they are grouped as Console workbench. A key that no longer exists shows as Deleted key. The Console never shows a key's numeric id.

Usage, the request log and the credit figures cover the whole workspace. Every member who can open the Console sees all of it, not only their own requests. That is useful for transparency and worth remembering before you put customer names in key names.

The request log

The request log lists requests newest first, 50 at a time, and Load older pages back through the whole history whatever period you picked. The Console notes above the table: "Metadata only. Prompts and outputs are not shown here."

ColumnWhat it shows
TimeWhen the gateway recorded the request, at the end of the answer, in your browser's time zone (sent in UTC)
ModelThe model that answered; for a failed request, the model you asked for
KeyThe key used, named as in By key; Console workbench for a Workbench run
InputInput tokens, as the provider counted them
OutputOutput tokens; est. means the provider reported no usage and the gateway estimated
CostWhat the request was charged, in USD, with more decimal places for small amounts
LatencyMilliseconds from the start of the provider call to the end of the answer, including any retry or fallback
StatusOK, or the error code

Statuses with a cost story:

StatusCost behaviour
upstream_error, upstream_timeout, upstream_auth, upstream_rate_limitedNot charged if no answer was produced; if a streamed answer broke partway, the tokens already produced are charged
upstream_rejectedThe provider refused the request as invalid; not charged
client_disconnectedYour client closed a stream early; the gateway read the answer to the end and charged for all of it

The Console never shows prompts or outputs. Safeguard stores them only if a tenant admin or super admin turns on Store prompts and outputs, and deleting stored prompts is itself an audited action.

What the Console cannot see, and your code can

Refusals before the provider call never reach the log, by design. A key that is being refused with 402 all afternoon leaves no rows. Your client is the only place those appear. Suggestion: count responses by HTTP status and error.code per route in your own metrics, so a sudden run of key_spend_limit_exceeded is visible even though it costs nothing.

Per request cost for finer attribution

Keys are the finest unit the Console attributes to. If you need cost per feature, per tenant of your own product or per user, attribute it in your code from the response. A non streamed response carries its cost in US dollars in usage.cost; for a stream, set stream_options.include_usage and the last chunk carries it. Each response also has a gen- generation id (in the body and the X-Generation-Id header) that matches the Console's records.

import json, logging, time

log = logging.getLogger("llm.cost")

def tracked(feature: str, tenant: str, **kw):
    t0 = time.monotonic()
    resp = client.chat.completions.create(**kw)
    usage = resp.usage.model_dump() if resp.usage else {}
    log.info(json.dumps({
        "feature": feature,
        "tenant": tenant,            # your customer, not Safeguard's workspace
        "generation_id": resp.id,    # gen- id, quote it to support
        "model": resp.model,
        "prompt_tokens": usage.get("prompt_tokens"),
        "completion_tokens": usage.get("completion_tokens"),
        "cost_usd": usage.get("cost"),
        "estimated": usage.get("estimated", False),
        "latency_ms": round((time.monotonic() - t0) * 1000),
    }))
    return resp

Sum your logs by feature and compare the monthly total with the key's spend in Usage. They should agree closely; a gap usually means a code path that is not instrumented, or streams whose usage you never read because the client disconnected first.

Close the month: ledger, statements and invoices

The credit ledger

Credit ledger, on the same Credits and limits page, lists every change to the balance, newest first, and Load older pages back through all of them. Entries are only ever added, never edited.

ColumnWhat it shows
TimeWhen the entry was recorded
EntryGrant, Topup or Usage
AmountPositive for credit added, negative for a charge
Balance afterThe balance once the entry applied
NoteThe reason for a grant, or Card top-up for a card payment

A Usage entry is one request's charge. A request that was not charged has no ledger entry. Because every entry carries Balance after, the ledger reconciles itself: each balance should equal the previous one plus the amount.

Usage statements

Where statements are switched on for your workspace, Usage lists every month with activity, newest first, with requests, input and output tokens, usage cost, top-ups, grants and adjustments. Any member can download a month as CSV. The file holds:

  • one line per day, model, provider route, key and whether the request ran on your own provider key, with requests, tokens and cost;
  • then the month's top-ups, grants and adjustments from the ledger.

A statement is a record of usage, not a tax invoice. Text cells that start with =, +, - or @ are prefixed with ' so a spreadsheet does not run them as formulas. That matters more than it sounds: key names are user supplied text, and OWASP's CSV Injection page lists those leading characters among the ones that make a spreadsheet treat a cell as a formula, with prepending a single quote among its mitigations. If you load the statement into a script, strip the leading quote only for display, never before writing the data back into another spreadsheet.

Invoices

The Console does not issue invoices for inference credit. A card top-up is a one-time payment on Stripe's checkout page, and Stripe emails its own receipt for it. Your record is the credit ledger: every grant, top-up and charge, each with the balance after it. For anything else about billing, contact hi@safeguard.sh.

A month end routine (suggestion)

  1. On the first working day after the UTC month ends, download the previous month's statement.
  2. Group the usage lines by key name and map each key to a team and cost centre using your naming pattern.
  3. Compare each key's total with its monthly limit. Keys that ended well under their limit for several months can be tightened; keys that hit their limit need either more budget or a look at why.
  4. Check the credit section of the statement against the ledger: grants and top-ups in the statement should match Grant and Topup entries.
  5. Pull the audit events for key limit changes and gateway setting changes in the month (see below) and attach them to the report, so every limit raise has a name and a timestamp.
  6. Send the report to finance with the receipts for any card top-ups.

Who can change money settings, and how it is audited

Spend control is only as strong as the control over who can change the limits.

ActionWho
Use Dashboard, Workbench, API keys and UsageMembers of a workspace where the API Platform is on
Create an inference keyAny active member except an auditor-only account, signed in as themselves
Edit or revoke a key in the ConsoleThe key's owner; API keys lists only your own keys
Run prompts in the WorkbenchMembers, except auditor-only accounts; charged to workspace credit
See usage, the request log, credit figures and the ledgerAny member; all of it covers the whole workspace
Change Gateway settings, including the workspace monthly limitTenant admins and super admins
Add credit by card, where top-up is onTenant admins and super admins
Delete stored promptsTenant admins and super admins
Add credit as a grantSafeguard
Deactivate a member, which revokes their keysTenant admins, and organization admins for members of organizations they administer

Safeguard's billing service makes these checks itself, so they do not depend on the Console's screens. Everyone else sees the gateway settings read only, with "A tenant administrator can change these." Safeguard's authentication service also lets a tenant admin change or revoke another member's key, but the Console's API keys page lists only your own; to stop another member's keys from the Console, deactivate the member under Settings, Members.

The audit events that matter for spend

ActionRecorded asWith
Create a keyauth.api.key.createdThe key's id, prefix and name
Change a key's name or limitsauth.api.key.updatedOnly the fields that changed, each with old and new value (<field>From and <field>To); a save that changes nothing records nothing
Revoke a keyauth.api.key.revokedThe key's id
Deactivate a memberauth.account.disabledHow many of the member's keys were revoked
Change Gateway settingsinference.settings.updatedOnly the settings that changed, with old and new values, and the role used
Start a card top-upinference.credits.checkout_startedThe credit and charge amounts, and the role
Delete stored promptsinference.prompts.deletedHow many were deleted, and the role

Every record also holds who acted, the workspace, the time, the IP address and browser, and whether the action succeeded. The key itself is never recorded. Refused attempts are recorded as failures too, for example an auditor-only account trying to create a key, or a member trying to change someone else's key.

To read them, open Settings, Security activity in ESSCM if your workspace has it. My activity shows your own events; Organization activity shows everyone's and only tenant admins see it. Type auth.api.key in the action filter and select Apply to list key events, or inference. to list gateway settings, top-ups and deletions of stored prompts. Security activity shows IP addresses masked, with the country and region.

For a CISO, this is the evidence trail: every raise of a spend limit has an actor, a time, an old value and a new value. Suggestion: review auth.api.key.updated and inference.settings.updated events in the monthly routine above, and investigate any limit raise that does not match a change request.

Patterns for common situations (suggestions)

  • Batch jobs. Give the job its own key and limit, cap output on every call, and have the job call GET /key before each chunk of work so it stops cleanly instead of being refused half way through.
  • Agents that call models in a loop. This is where Denial of Wallet most often happens by accident. Bound the loop in code (steps and output per run), run the agent on a dedicated key with a tight limit, and alert on the key's trend. The spend limit is the last line, not the first.
  • Developers, notebooks and staging. Small per developer and staging keys are cheap insurance, and a staging 402 is a useful signal that a test is looping. Workbench experiments count against the workspace limit with no key limit of their own.
  • Fallbacks. With Allow retries and fallbacks on, every hold is priced at the most expensive model the request might try, so 402s can appear earlier near a limit even when the primary model answers. Keep output caps tight on those routes.

Common mistakes

  • Treating the requests per minute limit as a budget. It is approximate, per process, and the docs say it is not a way to cap spend. Set dollar limits.
  • Leaving max_tokens unset. The gateway then holds a server default output cap for every call, which makes holds larger and 402s earlier, and leaves single calls unbounded up to that cap.
  • Retrying 402s. A 402 is a credit or limit decision. Retrying unchanged burns client time and changes nothing until someone acts.
  • One shared key for everything. The key is the only unit of attribution and the only per service limit. One key means one budget and no way to see who spent it.
  • Production keys owned by an individual. When that person leaves, their keys stop at once and do not come back on reactivation.
  • Reading only the Console. Refusals before the provider call are not logged there. Count your own 402s, 401s and 429s.
  • Expecting an invoice from the Console. It issues none for inference credit. Plan finance's process around the ledger, statements and card receipts.

FAQ

How do I set a monthly budget for an LLM API key in Safeguard Console?

On API keys, select Create key and fill in Monthly spend limit (USD), or select Edit on an existing key. Leave the field empty for no limit. The key's owner sets it, and a change reaches the gateway within 30 seconds. Requests that would take the key past its limit get 402 key_spend_limit_exceeded.

Can I cap total LLM spend for the whole company?

Yes. A tenant admin or super admin sets Workspace monthly spend limit (USD) under Usage, Credits and limits, Gateway settings. It covers every key and every Workbench run, applies from the next request, and refusals return 402 account_spend_limit_exceeded.

Why was my request refused when there was still credit left?

The gateway compares the most a request could cost, not its likely cost, and requests in flight each hold their own amount. Near the edge of credit or a limit, a request with a large output cap can fail while a smaller one fits. Lower max_tokens, or wait for in flight requests to finish.

Can I limit an API key to one project or organization?

No. An inference key belongs to the whole workspace and cannot be limited to an organization, project, product or agent, and every key can call every model available to the workspace. Give each team its own keys with their own limits and compare them under Usage, By key.

Is a requests per minute limit enough to control LLM cost?

No. The docs describe the per key rate limit as a guard against runaway clients that is approximate and "not a way to cap spend". A slow leak can stay under any sensible rate all month. Set key and workspace monthly spend limits as well.

How do I see LLM spend per team?

Name keys by team and service, then read Usage, By key for any period from the last 7 to the last 90 days, or download the monthly usage statement, which has a line per day, model, provider route and key.

Does Safeguard Console send invoices for API usage?

No invoices are issued for inference credit. Card top-ups are paid on Stripe's checkout page and Stripe emails a receipt. The credit ledger and monthly usage statements are the record; for anything else about billing, contact hi@safeguard.sh.

Who can change spend limits, and is it logged?

Key limits: the key's owner in the Console (Safeguard's authentication service also lets a tenant admin change or revoke another member's key), recorded as auth.api.key.updated with old and new values. Workspace limit and other gateway settings: tenant admins and super admins, recorded as inference.settings.updated with old and new values and the role used. Both are visible in Security activity.

The model bill is not the only supply chain

The service that calls the gateway is ordinary software: an SDK, an HTTP client, a JSON parser and a few dozen transitive packages, each of which can carry a vulnerability that matters far more than any month's token bill. Keeping those dependencies patched is the part of running LLM features that budgets do not cover.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Griffin, Safeguard's security model, opens a policy gated pull request that moves the affected dependency to the safe version, and your own CI runs your tests on it before anyone merges. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Auto-merge is a separate switch and stays off by default, so your team decides what merges on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The Console's spend controls live in the web app and in your code, so the demos below show the surfaces you are most likely to use beside them. None of them is a billing demo; each shows a flow that applies to the services that call the gateway.

The first demo connects Claude to the Safeguard MCP server with OAuth and shows how you choose which tools Claude can use. It is the same principle as one inference key per service with its own limit: give each client only what it needs, and keep the choice visible.

Click through it at your own pace, or open it full screen.

The second demo is a real Safeguard CLI session. The assistant reads a finding and its advisory, asks before any command that changes something, edits a dependency version and runs the install and tests. That is the flow you would use to upgrade the SDK and HTTP client in a service that calls the gateway.

Click through it at your own pace, or open it full screen.

The third demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. Scanning the services that hold inference keys tells you what they depend on before you raise their budgets.

Click through it at your own pace, or open it full screen.

To try Safeguard yourself, download the apps or start with the spend controls page in the docs.

Quick check

A key has a monthly limit and its This month figure is still below it, yet a long request gets 402 key_spend_limit_exceeded. Why?

The check adds the key's spend this month, what its in flight requests hold, and this request's hold, which is its worst case cost including the full output cap. That sum would pass the limit. A smaller max_tokens, fewer concurrent requests, or a higher limit will let it through.

Your team wants the Console to report spend for each of the three organizations in your workspace. What do you set up?

Keys cannot be scoped to organizations, and usage is kept per workspace and per key. Create keys per team or service with team names in the key name, give each its own monthly limit, and read Usage, By key or the monthly statement.

Finance asks for last month's invoice from the Console. What do you send?

The Console issues no invoices for inference credit. Send the monthly usage statement (CSV), the credit ledger entries for the month, and Stripe's receipts for any card top-ups.

Sources

Last reviewed: 6 November 2026. Console behaviour is taken from docs.safeguard.sh; check the linked pages for changes before you set budgets.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

We email you a link to confirm, and only then add you to the list. Unsubscribe in one click. Privacy

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.