Service-account 402: daily, monthly or per-end-user spend cap hit

Three different 402 codes mean three different caps. Read details.cap_usd_micros and current_usd_micros to see which window blocked the request.

4 min readSume
All posts

If your Sume key is a service-account key, a paid generation can be refused with one of three 402 codes: service_account_daily_spend_cap_exceeded, service_account_monthly_spend_cap_exceeded or service_account_end_user_spend_cap_exceeded. The code tells you which window ran out, and details gives the cap, what the account has already used and what this request would cost, all in millionths of a USD. The refusal happens before a reserve, so nothing is billed for the refused request.

Which cap fires first

The source checks the caps in a fixed order, and returns the first one that would be crossed. Each check is current usage plus the requested amount against the cap, so a request can be refused while usage is still under the cap:

Spend caps on a service-account key, in check order (Sume API source, read 2026-10-05)
OrderCodeWindowOnly checked when
1service_account_daily_spend_cap_exceededDaily total for the service accountA daily cap is set
2service_account_monthly_spend_cap_exceededMonthly total for the service accountA monthly cap is set
3service_account_end_user_spend_cap_exceededDaily total for one end userA per-end-user cap is set and the x-sume-source-user-id-hash header is present

What retrying does

In the current source these 402s do not have a dedicated mapping, so the envelope falls to the generic branch: category: validation, retryable: false, next_action: fix_input. That wording is misleading, because the body of the request is fine. What fixes it is time, a smaller request, or a change to the key's policy by whoever manages it. A same-body retry in the same window returns the same code.

The per-end-user cap exists only when you pass the end-user hash header. If your product serves many users through one key, send a stable hash of the user id (never the raw id) so one heavy user hits their own cap and not the shared daily total.

Branch on the code, not on 402

A plain 402 insufficient_credits means add funds. These three do not. Sort by code and show the right message:

import json

BODY = '''{"error": {"code": "service_account_end_user_spend_cap_exceeded",
 "details": {"cap_usd_micros": 2000000, "current_usd_micros": 1900000,
 "requested_usd_micros": 300000}}}'''

WINDOW = {
    "service_account_daily_spend_cap_exceeded": "daily",
    "service_account_monthly_spend_cap_exceeded": "monthly",
    "service_account_end_user_spend_cap_exceeded": "per-user daily",
}

err = json.loads(BODY)["error"]
d = err["details"]
left = (d["cap_usd_micros"] - d["current_usd_micros"]) / 1e6
print(f"{WINDOW[err['code']]} cap: ${left:.2f} left, need ${d['requested_usd_micros']/1e6:.2f}")

A practical sizing checklist

Before you ship a batch job on a capped key, work out the budget from the numbers the error already gives you, and keep the logic in one place so that the three codes are handled the same way.

  • Log code, cap_usd_micros, current_usd_micros and requested_usd_micros with the request_id from the envelope, so support can find the refusal.
  • Split a large batch so that one request never costs more than the smallest cap on the key.
  • For a daily cap, queue the rest until the next day instead of retrying every few minutes.
  • For a per-end-user cap, show that one user a plain message and keep serving everyone else.
  • Never retry with a different Idempotency-Key to get around a cap: a new key is a new intent and meets the same cap.

Limits

Service-account keys are a policy layer on a key and are not created by every account. Format runs and Agent Completions, for example, are not available to them (see the related posts). Check what your key is allowed to do before you build on it. The general error envelope is on the Errors and credits page.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume