Balance Hit Zero Mid-Call: What the Client Sees, What Gets Billed, and How to Resume on the Same Key
A step-by-step trace of what happens when a prepaid USDC balance runs out mid-request on an OpenAI-compatible endpoint: the error shape the client receives, how partial usage is metered and billed, and a safe pattern for topping up and resuming on the same API key without duplicating work.
Why this happens at all
On a prepaid aggregator you top up with USDC on Base and spend down per request. A long generation can start while the balance is sufficient and finish after it is not. The gateway cannot always know the full cost in advance, so the request is admitted, then metered as tokens stream.
This is normal for any prepaid, pay-per-token API. What matters is knowing exactly what the client sees, what you are charged for, and how to continue cleanly.
What the client sees
Because the endpoint is OpenAI-compatible, failures arrive in the same shape as any upstream error: a non-2xx HTTP status with a JSON error object, typically {"error": {"message": ..., "type": ..., "code": ...}}. Treat the status code and error.code as your control signal, not the prose in the message.
Two distinct moments matter:
- Rejected before generation. If the balance is already exhausted when the request arrives, the call fails immediately and no tokens are produced. This is the easy case.
- Exhausted mid-generation. If the balance runs out while tokens are being produced, a streaming response is cut off. The stream terminates without a normal finish signal, or ends with an error event depending on the client library. A non-streaming call returns an error response instead of a completion.
In both cases the client should treat the response as a failure, not as a truncated-but-valid answer. A stream that stops without a proper finish reason is ambiguous by design; do not persist it as a completed result.
Are partial tokens billed?
Yes, the tokens actually produced and delivered before the cutoff are metered. Usage is counted as it is consumed, so the balance is drawn down by the work already done. You are not charged for tokens that were never generated, and you are not charged a penalty for the failure itself.
The practical consequence: a mid-call failure is not free, but it is not a double charge either. The balance you spent reflects the partial output. Any retry is billed as a new request.
This is why retrying blindly is the expensive mistake. Each attempt consumes balance, and a client that retries a long generation several times can burn a meaningful amount of USDC on partials.
The safe resume pattern
1. Top up first, then retry
Resume only after the top-up settles. The balance is prepaid credit; there is no queued execution that waits for funds. A retry issued before the new balance lands will fail the same way.
2. Fetch the balance out of band
When you see an insufficient-balance error, stop the retry loop and check the account balance through the API or dashboard rather than guessing. Do not implement exponential backoff against a balance error; backoff cannot create funds.
3. Do not trust a truncated stream
If the stream was cut, discard it. Do not concatenate it with a later continuation and present the result as one answer, and do not cache it. Partial output that stops mid-sentence or mid-tool-call is a corrupt artifact.
4. Make the retry idempotent at your layer
Track a request identifier for each logical unit of work. If the previous attempt failed on balance, the retry is a new request with new billing; your own bookkeeping should mark the first attempt as failed rather than as a completed call. This prevents your application from showing users a partial answer and prevents downstream steps from consuming an incomplete result.
5. Prefer smaller units of work
A single enormous generation is the worst case for a balance cutoff: you pay for a partial and get nothing usable. Splitting long jobs into smaller requests means a cutoff costs you only the current chunk, and completed chunks are durable results you keep. For very long outputs, checkpoint between chunks.
6. Keep one key; do not rotate
Resuming on the same API key is the correct pattern. The key is an identity, not a balance; topping up the account restores capacity for the same key. There is no need to mint a new key, and doing so fragments your usage history and contributor accounting.
Pattern sketch
- Send request. On success, persist the result and its usage.
- On an insufficient-balance error, halt and surface it to the operator or user.
- Top up USDC on Base; wait for settlement.
- Re-issue the logical unit of work as a fresh request on the same key, with the same parameters.
- If the work is long, break it into chunks and resume from the first unfinished chunk, not from the beginning.
What not to do
- Do not loop retries against a balance error; it cannot succeed until funds arrive.
- Do not treat a cutoff stream as a completed answer.
- Do not assume the failure was free; partial tokens are billed.
- Do not open a second key to work around a balance problem; a top-up fixes the balance for the key you already have.
Where the ×1.3 and contributor credit fit
Billing is the official price multiplied by 1.3 for callers, so the partial spend above is calculated on the same basis as a completed call, applied to tokens actually consumed. Contributors whose keys are used earn credit at official price × 1.1 (× 1.2 for premium), paid in USDC. A mid-call cutoff does not change these multipliers; it only changes how many tokens were metered before the stop.
Summary
A balance cutoff mid-call is a clean failure, not a corrupt charge: the client gets a standard OpenAI-style error or a truncated stream, the tokens already produced are billed, and the fix is to top up, discard the partial, and re-issue the work on the same key. Chunk long jobs so a cutoff costs you one chunk rather than the whole run.