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.

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:
| Field | Meaning |
|---|---|
| spend_confirmation_mode | The mode that triggered the check, for example approve_for_me. |
| approve_for_me_threshold_usd_micros | Spend below this amount can go through without a prompt. |
| requested_usd_micros | What this one reserve would cost, in millionths of a USD. |
| studio_agent_thread_id | The thread the approval has to be bound to. |
| operation_type | The call that was refused, for example images.create. |
| spend_approval_request_id | Present 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
- Split a 10-minute TikTok into parts for 3 and 5-minute accounts
TikTok's API allows up to 10 minutes, but an account may be limited to 3 or 5. Split a 600-second video into 4 or 2 parts with Sume trim at $0.02 a job.
- Split a 40-second brief into four Omni prompts, one character block
A Python script that turns one 40-second brief into four 10-second Gemini Omni 1.1 Flash request bodies for Sume, with one shared character block.
- Spread Graph API calls evenly: pace a nightly Reel batch
Meta advises spreading queries evenly to avoid traffic spikes. Space publish calls across the hour, and size the Sume render wave from generation_limits.
- SQLite ledger: a restarted poller never resubmits a 30-second video
Save the Sume job id and Idempotency-Key in SQLite before you poll, so a crash resumes the same $17 render instead of paying twice. Python, stdlib.
Written by Sume