Guardrails for an agent calling paid APIs: key, cap, and what to log

An agent calling a paid media API needs three habits: an idempotency key on each write, a spend cap per run, and logs that hold ids, never signed URLs or keys.

4 min readSume
All posts

Give every paid write an idempotency key, give every run a spend cap, and log ids rather than URLs or keys. Those three habits, taken from Sume's safe-automation guidance, are what keep an unattended agent from charging twice, charging without bound or leaking a credential into a log (Sume docs: Safe automation, read 2026-10-06).

They matter more as agents run on schedules. A person who clicks a button twice notices. A cron job that retries on a timeout does not.

The three habits

One: an idempotency key on every paid call. A retry with the same key and the same body replays the first receipt with idempotency_hit true, and you pay once. A retry with the same key and a different body fails with 409 idempotency_conflict, which is the correct refusal. Derive keys from the business event, like season, episode and version, rather than from a random value, which defeats the purpose.

Two: a spend cap on every run. Agent Completions require generation_spend_cap_usd. Format runs accept a number up to 500, and without one the Format's own cap applies. Schedules default to $1.00 and a per-run number can only lower it.

Three: log ids only. Keep request ids, run ids and job ids. Do not log API keys or the signed media URLs in receipts, which grant access to files.

Read-only first, paid second

On hosted MCP, the safe-automation page recommends OAuth mcp:read for exploration and says to grant mcp:write, or use an API key, before an agent calls paid tools such as generate_image or avatars_create. Write calls and paid calls must send idempotency_key; the older allow_write and allow_paid arguments are optional (Sume docs: Safe automation, read 2026-10-06).

In practice that means an agent can browse a workspace with a scope that cannot spend anything, and you widen the scope only for the job that needs it. Keep exploration and spending in separate steps so a log reader can tell which step charged.

The same page lists what is safe to log: request ids, job ids when necessary, high-level status and sanitized media metadata. It lists as unsafe API keys, signed URLs, raw private media URLs and large amounts of user content or transcripts. Treat the second list as a lint rule for your logging code.

The workspace comes from the key

The workspace a call bills to is decided by the API key, not by a field in the body. That is a guardrail too: an agent cannot move a charge to another workspace by naming it. It also means a team Format needs a key issued in that team's workspace, and a personal key will not do.

Give each automation its own key where you can, so a leaked or misbehaving one can be revoked without touching the others.

A wrapper that enforces them

Put the habits in one function so the agent code cannot forget them. The wrapper below refuses a call without a key or a cap and logs only the id.

import os, requests

def paid_post(url, body, key, cap):
    if not key:
        raise ValueError("idempotency key required")
    if cap is None or cap <= 0:
        raise ValueError("positive spend cap required")
    body = {**body, "generation_spend_cap_usd": cap}
    r = requests.post(url, json=body, timeout=30, headers={
        "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
        "Idempotency-Key": key})
    data = r.json().get("data", {})
    print("status", r.status_code, "id", data.get("id"))
    return r

try:
    paid_post("https://api.sume.com/v1/agent/completions",
              {"instruction": "Draft a caption."}, "cap-demo-1", 1.5)
except KeyError:
    print("set SUME_API_KEY first")
Guardrail habits and what each prevents, read 2026-10-06 against Sume docs
HabitPreventsWhere Sume enforces it
Idempotency-KeyDouble charges on retryReplay, or 409 on a changed body
Spend capUnbounded spendCap field, clamped on schedules
Ids in logsLeaked keys and signed URLsYour logging, not Sume's

Review the keys quarterly

Keys age. List the ones your automations use, remove those without an owner, and rotate the rest on a schedule. A small, known set of keys is itself a guardrail.

What to do when a guardrail fires

A refusal is information. A 409 idempotency_conflict says your code changed a request it meant to repeat. A 400 about a missing cap says the wrapper was bypassed. A run that stops on its cap says the work was bigger than you planned.

Route each of these to a human or a ticket rather than a blind retry. Retrying a capped run with a larger cap in a loop turns a guardrail into a ratchet. Decide the new number once, on purpose.

Sources

Related posts

More in Agents

All Agents posts

Written by Sume