AI agents · Vendor isolation · Credential scope · Security architecture
AI agent vendor isolation: scoping credentials per agent, user, and vendor
Vendor isolation for AI agents is the principle that each agent run should have its own scoped credential for each vendor API it calls — not a shared production API key. Without isolation, a runaway agent has access to your entire Stripe account: all customers, all payment methods, no spend limit. With vendor isolation, any single agent failure is bounded to the scope of its credential: a specific set of allowed endpoints, a daily spend cap, an expiration time. This is not about trusting the LLM — it's about building systems that fail safely regardless of what the LLM does.
TL;DR
Vendor isolation has four dimensions: (1) credential separation — one key per agent run, not one key per application; (2) endpoint allowlisting — the key can only call specific API paths; (3) spend caps — a hard ceiling on cost per run; (4) time bounds — the key expires after the run's expected duration. All four must be present for true isolation — a per-run key with no spend cap and no endpoint allowlist is only marginally better than a shared key.
The blast radius problem
When you give an AI agent a production Stripe API key, the blast radius of any failure is your entire Stripe account. Consider what a runaway agent can do with unrestricted access:
- Create payment intents for any amount against any customer
- Issue full or partial refunds on any recent charge
- List all customers, payment methods, and charge history
- Create or modify subscription schedules
- Delete webhook endpoints (breaking your own system's event processing)
This isn't a theoretical threat. LLM reasoning errors, prompt injection in user-supplied tool arguments, stuck loops, and framework bugs have all caused real damage in production agent deployments. The question isn't whether your agent will ever behave unexpectedly — it's what the worst case looks like when it does.
Blast radius as a formula:
blast_radius = accessible_resources × accessible_actions × spend_ceiling
# With shared production key:
blast_radius = all_customers × all_stripe_endpoints × no_ceiling
# = unlimited
# With an isolated vault key:
blast_radius = policy.allowed_merchant_ids × policy.allowed_endpoints × policy.daily_usd_cap
# = bounded, auditable, revocable
The four dimensions of vendor isolation
1. Credential separation (per-run keys)
Each agent run gets its own credential, distinct from all other runs and from the production application key. This enables:
- Attribution: every vendor API call is associated with a specific agent run, not "the application"
- Revocation: kill one agent's access without affecting any other concurrent agent
- Audit: query "what did agent run X actually do" rather than "what did the application do"
# Without credential separation:
# All agent runs use the same key → can't distinguish their activity
stripe_key = os.environ["STRIPE_SECRET_KEY"] # Shared by all
# With credential separation:
# Each run issues its own key at start, revokes at end
vault_key = await keybrake.issue(
label=f"agent-run-{run_id}",
vendor="stripe",
...
)
2. Endpoint allowlisting
An isolated credential should only be able to call the specific API endpoints the agent actually needs for its task — not the full API surface. Map each agent capability to its minimum required endpoints:
| Agent capability | Required Stripe endpoints | NOT required |
|---|---|---|
| Create payment | /v1/payment_intents |
Refunds, subscriptions, customers, webhooks |
| Check charge status | /v1/charges/{id} |
Write endpoints of any kind |
| Create subscription | /v1/subscriptions, /v1/subscription_schedules |
Payment intents, refunds, webhook management |
| Send invoice | /v1/invoices, /v1/invoices/{id}/send |
Customers, payment methods, subscriptions |
An allowlist that includes /v1/refunds for an agent that only needs to create payment intents is unnecessary blast radius. Define allowlists as narrowly as the agent's actual task requires.
3. Spend caps
A per-run spend cap is the single most important isolation control for financial vendors. It converts the worst-case failure from "unlimited" to "bounded":
# Calculate the spend cap for a given agent run:
# expected_max_spend = max_legitimate_action × max_legitimate_action_count
# cap = expected_max_spend × safety_margin (1.5–2×)
# Example: billing agent that creates one invoice per customer per month
# Max invoice = $500, max customers processed per run = 10
expected_max_spend = 500 * 10 # = $5,000
cap = expected_max_spend * 1.5 # = $7,500 (50% buffer for edge cases)
vault_key = await keybrake.issue(
vendor="stripe",
daily_usd_cap=cap,
...
)
# Now if the agent loops and creates 1,000 invoices, the damage is $7,500 max
# — not your entire Stripe account balance
4. Time bounds (TTL)
Credentials that expire cannot be used after the agent run completes. TTL is the safety net for cases where explicit revocation fails:
| Agent pattern | TTL recommendation | Rationale |
|---|---|---|
| Single HTTP request (synchronous tool call) | 5–15 minutes | Longer than any single call, shorter than overnight |
| Multi-step workflow (Temporal / Prefect) | SLA + 20% | Outlives the expected workflow duration with buffer |
| Nightly batch agent | 6–8 hours | Batch should complete within one business night |
| Long-running autonomous agent | 24 hours (with human checkpoint) | Daily renewal forces a human-visible audit event |
Isolation matrix: per dimension, per vendor
Vendor isolation applies per vendor, not globally. A single agent run may call three vendors, each with different isolation requirements:
async def setup_isolated_credentials(run_id: str, task: AgentTask) -> dict[str, str]:
"""Issue one vault key per vendor, scoped to the task's requirements."""
credentials = {}
if task.requires_stripe:
stripe_key = await keybrake.issue(
label=f"{run_id}-stripe",
vendor="stripe",
daily_usd_cap=task.max_stripe_spend,
allowed_endpoints=task.required_stripe_endpoints,
expires_in=task.expected_duration_with_buffer
)
credentials["stripe"] = stripe_key["token"]
if task.requires_twilio:
twilio_key = await keybrake.issue(
label=f"{run_id}-twilio",
vendor="twilio",
daily_usd_cap=task.max_twilio_spend, # SMS cost per message
allowed_endpoints=["/2010-04-01/Accounts/*/Messages"], # Send only
expires_in=task.expected_duration_with_buffer
)
credentials["twilio"] = twilio_key["token"]
if task.requires_resend:
resend_key = await keybrake.issue(
label=f"{run_id}-resend",
vendor="resend",
daily_usd_cap=task.max_email_count * 0.001, # $0.001/email
allowed_endpoints=["/emails"],
expires_in="30m" # Email tasks are short
)
credentials["resend"] = resend_key["token"]
return credentials
Isolation vs. authentication
Vendor isolation is often confused with authentication. They're different problems:
| Concern | Authentication | Vendor isolation |
|---|---|---|
| Question answered | "Is this caller allowed to use the API at all?" | "How much damage can this caller cause?" |
| Who controls it | Vendor (Stripe validates the key) | You (via the proxy + credential policy) |
| Granularity | Key-level (all-or-nothing for most vendor APIs) | Per-run, per-endpoint, per-dollar |
| What it stops | Unauthorized callers | Authorized callers doing more than intended |
A production Stripe key authenticates your application — Stripe trusts all requests made with it. Vendor isolation limits what your own agents can do with that trust. Both are necessary; neither replaces the other.
Implementing isolation without a proxy
Partial vendor isolation is possible without a dedicated proxy using vendor-native features:
- Stripe Restricted API Keys: allow endpoint-level restrictions but no per-key spend caps and no TTL. You can create one restricted key per user via the Stripe API, but managing 1,000 user keys at the vendor level is operationally painful. See Stripe restricted API keys.
- Twilio subaccounts: provide billing isolation but require creating a Twilio subaccount per user — significant overhead for large user counts.
- Resend: no per-key restrictions as of 2026 — API keys are all-or-nothing access to the account.
The gap vendor-native features leave: no per-run spend caps (only plan-level billing limits), no TTL on credentials, and no cross-vendor unified audit log. A proxy layer fills these gaps across all vendors with a consistent credential API.
Related questions
Is vendor isolation necessary if my agent only reads from Stripe (no writes)?
For read-only agents, the spend cap dimension of vendor isolation is less critical (reads don't create charges), but credential separation and endpoint allowlisting still matter. An agent with read access to all Stripe customers, all payment methods, and all charge history can exfiltrate PCI-scoped data if it encounters a prompt injection attack or reasoning error. A credential scoped to GET /v1/charges/{id} can only read specific charge records, not enumerate the full customer base. Least-privilege applies to reads as much as writes — especially for data that has regulatory sensitivity under PCI DSS or GDPR.
How does vendor isolation interact with multi-tenant SaaS architectures?
In a multi-tenant SaaS where each tenant has their own Stripe account, vendor isolation has an additional dimension: per-tenant credential separation. Each agent run should use a credential for the specific tenant's Stripe account, not a super-admin credential with access to all tenants. Keybrake's merchant_allowlist policy field handles this: issue one vault key per agent run with merchant_allowlist: [tenant.stripe_account_id] so the credential can only act within that tenant's Stripe account, even if the underlying Keybrake token has access to multiple accounts. See multi-tenant isolation for the full pattern.
What's the minimum viable vendor isolation I can implement today without a proxy?
Without a proxy, the highest-impact minimum viable isolation steps are: (1) use Stripe Restricted API Keys instead of the secret key — restrict to only the endpoints your agent calls; (2) create one restricted key per user or per agent deployment, not one global key; (3) set up a Stripe webhook for high-value events (payment_intent.succeeded, charge.refunded) with a Slack/email alert so you get immediate visibility into unexpected activity. This doesn't give you TTL, spend caps, or a unified audit log, but it dramatically reduces the blast radius of a key compromise or runaway loop compared to sharing a single secret key.
Further reading
- AI agent API key lifecycle — issuance, active enforcement, expiration, and revocation: the four phases of vendor-isolated credential management.
- Multi-tenant isolation — per-tenant vendor isolation in SaaS architectures where multiple customers run agents against their own accounts.
- AI agent zero trust — the broader zero trust security model for AI agent architectures, of which vendor isolation is one pillar.
- AI agent compliance — how vendor isolation satisfies SOC 2, PCI DSS, and GDPR least-privilege requirements for autonomous agent systems.
- AI agent circuit breaker — spend-aware circuit breakers that complement vendor isolation by halting loops before spend caps are reached.