spend_confirmation_required 402 on Sume: why retrying will not help

A 402 spend_confirmation_required means a person must approve the spend first. It is not a balance error: retryable is false and next_action is fix_input.

4 min readSume
All posts

spend_confirmation_required is a 402 that says an interactive approval is missing, not that your balance is empty. The envelope marks it category: quota, stage: usage_reservation, retryable: false and next_action: fix_input, so a blind retry loop will fail the same way every time. Every Sume error uses one envelope: error.code, message, request_id, category, stage, retryable, retry_after_seconds, public_reason, next_action and optional details.

You will meet it when a script runs on a credential that belongs to a Studio Agent thread and asks for a paid generation. The API compares the request's cost with the thread's spend-confirmation mode and its approve-for-me threshold. If the cost is over the line and no unused approval grant matches, the reserve is refused before any money moves.

What the error body gives you

The details object is the useful part. Per the published OpenAPI example it carries the mode, the threshold, the cost of this request, the thread and the operation:

Fields in details for spend_confirmation_required (Sume OpenAPI example and API source, read 2026-10-05)
FieldMeaning
spend_confirmation_modeThe mode that triggered the check, for example approve_for_me.
approve_for_me_threshold_usd_microsSpend below this amount can go through without a prompt.
requested_usd_microsWhat this one reserve would cost, in millionths of a USD.
studio_agent_thread_idThe thread the approval has to be bound to.
operation_typeThe call that was refused, for example images.create.
spend_approval_request_idPresent when a pending approval record was saved for a person to approve.

Why it is not insufficient_credits

insufficient_credits carries next_action: add_funds and the required and available credits. This one carries neither, because the wallet may be fine. A grant is checked against the thread, the owner, the amount and the idempotency key. A grant that is expired, already used, for another thread or too small for the request does not count, and the request is refused again.

That last point matters for retries. Keep the same Idempotency-Key when you resend after approval, because the grant check compares it. The jobs and results guide explains why one key per intent is the safe default.

A handler that stops instead of looping

Branch on error.code before you look at the status, and hand the approval back to a person. This runs as is:

import json

BODY = '''{"error": {"code": "spend_confirmation_required", "retryable": false,
 "next_action": "fix_input", "details": {"requested_usd_micros": 4500000,
 "approve_for_me_threshold_usd_micros": 3000000}}}'''

def decide(status, body):
    err = body["error"]
    if err["code"] == "spend_confirmation_required":
        d = err["details"]
        usd = d["requested_usd_micros"] / 1_000_000
        return f"STOP: ask a person to approve ${usd:.2f}, then resend with the same Idempotency-Key"
    if err["retryable"]:
        return "retry later"
    return "fix the request"

print(decide(402, json.loads(BODY)))

Limits of this advice

If you call the API with an ordinary workspace key from your own backend, this code is not part of your path. Check error.code first. A plain insufficient_credits needs funds, and it is a different fix. The Errors and credits page lists the public error classes.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume