What happens when the balance hits zero, and seeing it coming

The freeze is a state, not an outage, and runway_days tells you about it days early. The fields to watch and the two ways back.

An Infrai account whose balance reaches zero stops being able to make billable calls, and the refusal is a typed INSUFFICIENT_CREDIT rather than anything ambiguous. Free routes — reads, management, the account endpoints themselves — keep working, so you can always see what happened and top up. GET /v1/account/balance carries everything you need to avoid getting there, and the single most useful field is runway_days.

It’s a state you can watch approaching. That’s the point of this page.

The fields that matter

curl -sS "https://api.infrai.cc/v1/account/balance" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}"
{
  "ok": true,
  "data": {
    "account_id": "acct_...275b",
    "balance": 24.03029009,
    "balance_usd": 24.03029009,
    "cap": 500.0,
    "available_room": 475.96970991,
    "runway_days": 41.79,
    "daily_avg_spend": 0.57507162,
    "tier": "standard",
    "expires_at": null,
    "expires_in_days": null,
    "is_in_grace_period": false,
    "grace_period_ends_at": null,
    "currency": "USD",
    "pending_topups": []
  }
}

runway_days is balance divided by your own daily_avg_spend. Forty-two days of runway is a calendar reminder; four is a page.

is_in_grace_period and grace_period_ends_at are the pair to alert on hardest — a grace period means the deadline has already passed once. And pending_topups is why you shouldn’t panic-top-up twice: a payment in flight is visible here before it lands.

Watch it, don’t poll it hopefully

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}"})

WARN_DAYS = 14
PAGE_DAYS = 3


def runway_state() -> tuple[str, dict]:
    """Classify the account from its own burn rate rather than a fixed dollar
    threshold, so the alarm moves when consumption does."""
    resp = SESSION.get(f"{API}/v1/account/balance", timeout=20)
    resp.raise_for_status()
    data = resp.json()["data"]

    if data.get("is_in_grace_period"):
        return "grace", data
    days = data.get("runway_days")
    if days is None:
        return "unknown", data
    if days <= PAGE_DAYS:
        return "page", data
    if days <= WARN_DAYS:
        return "warn", data
    return "ok", data


if __name__ == "__main__":
    level, snapshot = runway_state()
    print(f"{level}: {snapshot['runway_days']} day(s) at "
          f"${snapshot['daily_avg_spend']:.4f}/day, balance ${snapshot['balance']:.2f}")

Run it on a schedule you don’t maintain by hand — POST /v1/cron/create on the same key will call your own endpoint daily, which is one less piece of infrastructure than a separate scheduler.

Or let the platform tell you

Polling is fine; being pushed is better. The wallet events exist for exactly this:

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/wallet",
    "events": ["wallet.low_balance", "wallet.expiring_soon", "wallet.expired", "topup.failed"],
    "description": "wallet guardrails",
    "secret": "a-long-random-string-you-generate"
  }'

topup.failed is the one people forget to subscribe to. An auto-recharge that didn’t go through is the most common path to a surprise freeze, and it’s silent unless something is listening.

The two ways back

SituationActionNotes
Balance low, card on filePOST /v1/account/topupreturns a checkout_url when payment is needed
Balance low, recurringPUT /v1/account/autorecharge/configureset max_per_day and max_per_month
Payment in flightGET /v1/account/topup/statuscheck before topping up again
Frozen after zerotop up; the freeze clearsfree reads worked throughout
curl -sS -X POST "https://api.infrai.cc/v1/account/topup" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"amount_usd": 50, "currency": "USD", "return_url": "https://app.example.com/billing/done"}'

The response carries topup_id, status and a checkout_url when a payment step is needed. Poll GET /v1/account/topup/status?topup_id=... rather than assuming — and use the topup_id, because that’s what makes a retry idempotent instead of a double charge.

Design your own behaviour for the refusal

This is the part that separates a product that degrades from one that breaks. INSUFFICIENT_CREDIT is not retryable, so a worker that retries it in a loop turns a billing condition into a hot loop and a log flood.

Treat it as a circuit breaker: stop the paid work, keep serving whatever doesn’t need it, surface a banner to whoever can pay, and let a queued job wait rather than fail. POST /v1/queue/publish with a delay is a reasonable holding pattern, and the work resumes when the balance does.

What you shouldn’t do is swallow it. An agent that silently produces worse output because the paid call failed is harder to debug than one that stops.

Trial credit expires, paid credit doesn’t

One structural fact worth knowing: the credit a new account starts with is time-limited, and expires_at with expires_in_days on the balance read is where you see it. Credit you paid for isn’t taken away by the clock. is_refundable and is_transferable are also on that response, which saves asking.

The limitation is that none of this reaches your own infrastructure. A frozen account stops platform spend; it doesn’t stop your servers, your own database or the third-party APIs you call directly, so the blast radius of running out is exactly as wide as how much of your stack sits here.

That cuts the other way too, and it’s the honest version of the consolidation argument: because inference, storage, email, SMS and queues all draw on this one balance, one number predicts all of them at once. GET /v1/account/usage shows which capability is consuming it, and the alert email goes out through POST /v1/email/send on the same key — no second vendor, one invoice, one thing to watch.

Every route on this page reports billing_class: free in discovery, so monitoring your own balance never costs you anything. The live per-capability rates come back in the same balance response as affordable_uses_hint (verified 2026-09-21) — read them there, and expect them to fall over time rather than rise.

References

Browse more account developer guides