If a lip-sync job fails on Sume, is the reserved money refunded?
Yes. Sume reserves the price at admit, captures on completion and refunds on failure. For a 5-second H3 Max lip-sync at 768p the reservation is $0.50.

Yes. For the MiniMax H3 Max lip-sync route, the API reserves the amount at admit, captures it when the job completes and refunds it if the job fails. A 5-second clip at 768p reserves $0.50, which is 5 x $0.08 x 1.25.
What the three steps mean
The price is ceil(duration_seconds) x the provider list rate per second at the chosen resolution x the 1.25 house margin. List rates are $0.05 at 480p, $0.08 at 768p and $0.16 at 1080p.
- Reserve: when the job is accepted, the price is held against your balance.
- Capture: if the job completes, the held amount is the charge.
- Refund: if the job fails, the held amount is released.
Reservation by length
duration_seconds is a required field on this route and is the reservation basis. The API refuses a value outside 5 to 14.8 seconds with invalid_request and does not clamp it, because a clamped reservation would render a clip shorter than your audio.
| Resolution | List per second | Sume per second | 5 s clip | 14.8 s clip (ceil 15 s) |
|---|---|---|---|---|
| 480p | $0.05 | $0.0625 | $0.3125 | $0.9375 |
| 768p | $0.08 | $0.10 | $0.50 | $1.50 |
| 1080p | $0.16 | $0.20 | $1.00 | $3.00 |
What fails before the money moves
Several refusals happen at submit, so no job exists and nothing is reserved: audio that is not on Sume media (unsupported_audio_source), audio over 10 MB (audio_too_large), a duration outside the window, and a body that sets model, endpoint or provider_endpoint. Fix the request and send it again.
Check the outcome yourself
After you submit, poll GET /v1/jobs/{id}/status. The submit response carries usage.billable_amount_usd_micros, so you can compare the amount you expected with what the API reserved. This script submits one job and prints the reserved amount.
import json, os, urllib.request
body = {
"avatar_handle": os.environ["AVATAR_HANDLE"],
"audio_url": os.environ["AUDIO_URL"],
"duration_seconds": 5.84,
"resolution": "768p",
}
req = urllib.request.Request(
"https://api.sume.com/v1/minimax/h3-max/lip-sync",
data=json.dumps(body).encode(),
method="POST",
headers={
"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": "lipsync-refund-check-001",
},
)
with urllib.request.urlopen(req, timeout=30) as r:
data = json.load(r)
print(data["job"]["id"], data["usage"]["billable_amount_usd_micros"])What to do
Do not build your own refund logic. Submit with an Idempotency-Key, poll or use a webhook for the terminal state, and read the amount from the response. Retry a failed job with the same key only if it is an exact retry.
Sources
Related posts
More in Developers
- fal retries failed requests up to 10 times; on Sume the retry is yours
fal's queue retries transient errors up to 10 times unless you send X-Fal-No-Retry. On Sume the retry is yours: reuse the Idempotency-Key, never resubmit blind.
- fal never drops queued requests; Sume can answer 429 queue_full
fal's queue docs say queued requests are never dropped. Sume caps accepted jobs per plan and returns 429 queue_full when full. Plan for the gap.
- Fallback chain for a 30-second AI video: first catalog row that fits
Kling 4.0 is rolling out in stages. Pick the first Sume video id whose catalog row lists 30 seconds, and stop with an error if none does.
- Fan out one Sume clip to three platforms: one webhook, three task keys
Receive one job.completed webhook per Sume job, then enqueue a task per platform. Key each task by job id plus platform so a retry never double-posts.
Written by Sume