sipi.bot integration
sipi.bot + Stripe
sipi.bot integrates directly with Stripe to enforce per-charge merchant allowlists, transaction velocity caps, and daily spend ceilings. The agent's intent is policy-checked before the Stripe charge is created.
The one integration where real money moves
Every other integration on this site guards inference spend — tokens that turn into an invoice at the end of the month. Stripe is different in kind. When an agent creates a charge, funds actually move, to a real counterparty, and reversing it is a business process rather than a code change.
That asymmetry should change your policy. Token spend that is slightly too high is a cost problem you fix next sprint. A payment sent to the wrong destination is a different category of event. It is reasonable to run tighter rules here than anywhere else in the same system.
Where the allowlist finally earns its place
On a Claude or Gemini integration there is one counterparty, so a merchant allowlist has almost nothing to do. On Stripe it is the central control: the agent can pay anyone, and constraining who matters at least as much as constraining how much. A destination that is not on the list should not be a large payment that gets flagged — it should not be a payment at all.
| Rule type | What it does on this integration |
|---|---|
merchant_allow | The primary rule here. Constrains the set of destinations the agent may pay, rather than only the amount. |
approval_threshold | Human sign-off above a figure. On real money this should be set lower than instinct suggests. |
per_transaction | A hard ceiling no single charge may exceed under any circumstances. |
velocity | Catches the duplicate-charge loop, where each payment is individually valid and the sequence is not. |
Check before you create the intent
The decision belongs before the payment object exists — not after creation and not after confirmation. Once a charge is created you are managing a payment rather than preventing one.
import requests, stripe
def guarded_charge(amount_cents, destination, reason):
d = requests.post(
"https://sipi.bot/v1/transactions/evaluate",
headers={"Authorization": f"Bearer {SIPI_KEY}"},
json={"amount": amount_cents / 100, "merchant": destination,
"category": "payment", "description": reason},
timeout=5,
).json()
if d["decision"] == "BLOCKED":
raise PaymentBlocked(d["reason"])
if d["decision"] == "FLAGGED":
return queue_for_human(d["transaction_id"], amount_cents, destination)
return stripe.PaymentIntent.create(
amount=amount_cents, currency="usd",
metadata={"sipi_transaction_id": d["transaction_id"]},
)
Carrying transaction_id into the payment metadata is worth the one
line. It links the policy decision to the payment record permanently, which is what
turns an audit trail into something you can actually reconcile against Stripe.
Retries and double counting
Stripe's idempotency keys stop a retried request creating two charges. They do not stop a retried request consuming your daily ceiling twice — that is a separate system with separate state. If you retry around the firewall call, reuse the original decision rather than requesting a new one, or you will exhaust a budget on payments that never happened.
What this is not
- It is not fraud detection. Stripe's own risk tooling looks at signals — device, history, network — that a spend policy never sees. The two are complementary and one does not replace the other.
- It does not reverse anything. Every control here is preventive; once a charge exists, refunds are Stripe's domain.
- It does not validate that the counterparty is who you think. An allowlist enforces the list you wrote, including its mistakes.
Questions
Should an agent be able to move money at all?
A fair question and worth answering deliberately rather than by default. Where the answer is yes, the useful posture is a narrow allowlist, a low approval threshold, and human sign-off on anything unusual — autonomy inside a small, explicitly drawn box.
Does the check slow down checkout?
It adds a round trip before payment creation. On agent-initiated payments there is no user waiting, so the latency is not the constraint people assume it is.