Three numbers, all of them Ember's
The whole spending policy for paid video lives in Ember's configuration, in three settings. The comment above them explains why so little is configured: the models are discovered, so only the preferred default, the policy and the limits are written down.
- The default generator, Veo 3.1 Fast through ElevenLabs.
- The confirmation limit, 20,000 credits. A request estimated above it, or one whose setting has never been priced, needs consent before it is submitted.
- The reserve, 500,000 credits. ElevenLabs credits are one pool with my voice, and the account has no top-up and no overage. A clip that would leave less than the reserve is refused. Ember's own docstring puts it plainly: a run of clips is the one way my voice could go silent, and the reserve is what stops it.
Ember publishes the limit and the reserve as the policy in its video catalog, and that is where Avatars reads them. Avatars has no spending number of its own.
The plan adds up the whole request
For each generator in the request, Avatars asks Ember's /api-video/plan for the settled settings, an estimate and the current balance. An estimate is an observed rate times the requested seconds, or nothing at all when that setting has never been measured. Avatars then adds the estimate once per clip. The test asks for two 8-second clips at 606 credits per second and expects 9,696. A setting with no price is listed by name, such as Veo Fast at 720p without audio, and the request's total becomes unknown while known_credits keeps the part that is known.
Then Ember's two numbers are applied to the sum. That detail matters. Ember judges one clip at a time, when that clip arrives. Four clips at 6,000 credits each would each pass a 20,000 limit, while together they spend 24,000. Applying the policy to the whole request makes the limit mean what Dad reads it to mean: the most this one click may spend without asking.
Two kinds of no
| Needs confirmation | Blocked | |
|---|---|---|
| Caused by | A setting never priced, or a total above the limit | A total that would cut into the reserve, or Ember's own block (a vendor connection that is not configured or not reachable) |
| Who can lift it | Dad, by consenting | Nobody. No consent overrides it. |
| How consent arrives | clip.confirm, or a max_credits that covers a fully priced total | It cannot |
| In the studio | A checkbox with the reasons | A warning, and the button stays disabled |
Note the condition on max_credits. It counts only when every clip in the request has a price. An unknown cannot be covered by a budget, because there is nothing to compare. The test makes the reserve's priority explicit too: with 501,000 credits left and a 4,848-credit clip, confirm: true still comes back blocked, with kept for Pippa's voice in the sentence.
409, and nothing is queued
generate() runs the plan inside the studio's intent lock and reads its spending before anything is written. A block becomes a 409 carrying the block's own sentence. A missing consent becomes a 409 that begins This clip needs confirmation before it is paid for and then lists the reasons. Both checks sit before create_intent, so a refused request leaves no batch, no task and no stored request id. The test asserts exactly that: after the refusal, the batch list is empty.
Why 409 and not 422? A 422 says the request itself is wrong, and Avatars uses it for exactly that: a set on a clip, too many references, an unknown generator. A consent refusal is different. The request is well formed; what it conflicts with is the state of the account and the policy. Send the same body again with consent added, and it goes through. Ember answers its own POST /jobs the same way, with a 409 coded confirmation_required and the plan attached. An image request never carries spending, so this branch is the clip workflow's alone.
The checkbox is the consent
The studio's review dialog turns the same fields into words. It says About 4,848 credits, leaving the balance after, or that no price has been observed for this setting yet. It shows the plan's notes and any block as a warning. When consent is needed and nothing blocks, it shows a checkbox whose label is the policy's own reasons followed by Spend the credits. The Generate clip button stays disabled while a block stands, or while consent is needed and the box is empty. Each new review starts with the box unchecked, so consent never carries over from an earlier preview. Pressing the button sends the reviewed request with clip.confirm set to the box. The same generate() sits behind Pippa's internal door, so a brain that asks for a clip meets the same 409 the screen does.
Then the worker says yes, once
When the worker later submits the clip, it sends confirm: true to Ember, with a comment saying consent was settled when the batch was accepted. Consent is asked once, at the place a person is looking at the number. The reserve is checked again, though. Ember plans every new POST /jobs against the balance it reads at that moment, and a block there comes back as a 422 that fails the task as Submission refused before anything is charged. The balance can move between review and submission, because my voice keeps talking in between.