Auto-recharge that can't become an unbounded card charge
trigger_balance, recharge_amount and the two caps that bound the worst case. Plus the failure counter that tells you it quietly stopped working.
Auto-recharge is the right default for anything running unattended on Infrai, and it’s also the setting most able to surprise your finance team. PUT /v1/account/autorecharge/configure takes a trigger_balance and a recharge_amount — and, importantly, max_per_day and max_per_month, which are what turn an automatic payment into a bounded one.
Configure the caps in the same call as the trigger. There’s no good reason to run without them.
Configure it
curl -sS -X PUT "https://api.infrai.cc/v1/account/autorecharge/configure" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"trigger_balance": 20,
"recharge_amount": 50,
"max_per_day": 100,
"max_per_month": 500
}'
{
"ok": true,
"data": {
"enabled": true,
"trigger_balance": 20.0,
"recharge_amount": 50.0,
"payment_method_id": "pm_1QxWvR4mBn8dTcYs",
"payment_method_summary": "visa ••4242",
"max_per_day": 100.0,
"max_per_month": 500.0,
"triggered_today": 0,
"triggered_this_month": 2,
"next_check_at": "2026-09-21T03:15:00Z",
"last_succeeded_at": "2026-09-14T11:02:41Z",
"last_failed_at": null,
"consecutive_failures": 0,
"configured": true,
"disabled_reason": null
}
}
Read that response carefully — it’s a diagnostic, not just an echo. triggered_today and triggered_this_month are your consumption against the caps; consecutive_failures and disabled_reason are how you find out it stopped.
Size the three numbers together
They interact, and getting one wrong makes the others useless.
trigger_balance should be at least a few days of runway, not a token amount. If you burn $0.58 a day and trigger at $2, a weekend of elevated traffic can outrun the recharge cycle; triggering at 10-14 days of spend gives the payment time to clear and you time to notice a failure. Pull the number from daily_avg_spend on GET /v1/account/balance rather than inventing it.
recharge_amount wants to be large enough that you’re not charging a card every few hours — each top-up is a payment that can fail — and small enough that one erroneous trigger isn’t your monthly budget.
import os
import requests
API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
def derive_autorecharge(trigger_days: int = 12, recharge_days: int = 30,
daily_cap_multiple: float = 2.0) -> dict:
"""Derive every number from measured burn. A trigger balance picked by feel is
the reason people wake up to either a frozen account or six charges."""
balance = SESSION.get(f"{API}/v1/account/balance", timeout=20)
balance.raise_for_status()
daily = max(0.05, balance.json()["data"]["daily_avg_spend"])
trigger = round(daily * trigger_days, 2)
amount = round(max(10.0, daily * recharge_days), 2)
body = {
"trigger_balance": trigger,
"recharge_amount": amount,
"max_per_day": round(amount * daily_cap_multiple, 2),
"max_per_month": round(amount * 6, 2),
}
resp = SESSION.put(f"{API}/v1/account/autorecharge/configure", json=body, timeout=25)
resp.raise_for_status()
return resp.json()["data"]
if __name__ == "__main__":
print(derive_autorecharge())
max_per_day at twice the recharge amount allows one retry and one legitimate second trigger, and refuses the third. That’s the shape you want: tolerant of a bad day, intolerant of a loop.
The two ways it bites
| Symptom | What’s happening | Where you see it |
|---|---|---|
| Repeated charges in a short window | trigger too low relative to burn | triggered_today climbing |
| Recharge refused | daily or monthly cap reached | ACCOUNT_AUTORECHARGE_LIMIT |
| Balance falls anyway | payment method failing | consecutive_failures, last_failed_at |
| Auto-recharge silently off | disabled after repeated failures | disabled_reason, enabled: false |
The last row is the dangerous one. An expired card produces a failure, then another, and eventually the platform stops trying — at which point you have no auto-recharge and no error in your own logs, because nothing in your code called anything.
So subscribe to the failure
curl -sS -X POST "https://api.infrai.cc/v1/account/webhooks/register" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://ops.example.com/hooks/billing",
"events": ["autorecharge.charged", "autorecharge.failed", "topup.failed", "wallet.low_balance"],
"description": "auto-recharge health",
"secret": "a-long-random-string-you-generate"
}'
autorecharge.failed is the event that matters most and the one nobody subscribes to until after the first incident. Pair it with a periodic read of the config, because a subscription that was itself auto-disabled won’t tell you it’s gone:
curl -sS "https://api.infrai.cc/v1/account/autorecharge/get" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
Changing the card
POST /v1/account/payment_method/set_default takes a payment_method_id and returns last4, brand and expires. That expires field is worth a calendar entry of its own — a card that expires next month is an auto-recharge that fails next month, and the cheapest fix is a reminder now.
Pair it with a hard ceiling from PUT /v1/account/budget/set so automatic payment and automatic spending are bounded independently. Auto-recharge without a budget cap is a pump with no float switch.
The limitation
There’s no per-tenant or per-key auto-recharge: it’s an account-level setting, so you can’t let one customer’s workload top itself up while another’s is capped. If your product resells capacity per tenant, the accounting for that stays in your own billing system — the platform gives you one wallet and one set of caps.
And it can’t validate that a recharge is justified, only that it’s within limits. That judgement is what the budget cap and the usage breakdown are for.
Everything around it is on the same credential, which is the part a single-purpose billing tool can’t offer: the failure event, the notification email through POST /v1/email/send, the daily health check on POST /v1/cron/create, and the spend it’s funding in GET /v1/account/usage — one key, one invoice, one usage view. All of those account routes report billing_class: free in discovery, so the guardrails themselves cost nothing. The rates being funded are live in GET /v1/account/balance (verified 2026-09-21) and trend downward over time, so read them rather than budgeting from a printed figure.