accepted generation job refund: Keep accepted work separate from submission rejection
·
An accepted generation job refund decision depends on where the failure happened. A request can fail from the client’s perspective after the provider has already accepted work. Treating every unsuccessful HTTP exchange as an upstream rejection can produce duplicate generation and incorrect credit compensation.
Follow the service’s two operations
After reserving credits, the job service calls provider.submit once. Only a typed, confirmed rejection permits a failed record with the submission-failure code and submissionOutcome set to rejected, followed by a refund. A pending settlement can be retried for that evidence-bearing record. Historical failure codes without rejection evidence are not automatically reclassified or refunded.
If provider.submit returns a reference, the service then updates the job with running state, the upstream reference, the upstream model identifier, and start time. That later database update is outside the submission exception handler. A failure while saving the reference is deliberately not treated as a provider refusal.
There is another observable edge: an update can return no row, in which case the service can return its original queued record. A queued record without a reference is therefore not proof that upstream work never existed. The state and available evidence must be considered together.
Preserve an unresolved attempt
Your application should record its local attempt identifier before submitting, then attach any returned job identifier and error metadata. If the HTTP result is lost, retain the request fingerprint and timestamps. Avoid an automatic replacement submission until the acceptance status has been investigated.
Use an application label such as acceptance unknown to represent the investigation. That label belongs to your task system and should not be presented as a new gateway state. The gateway’s job states remain queued, running, succeeded, failed, and canceled.
When a job exists but its upstream reference is missing, gather the service logs and provider submission evidence through authorized operational access. An empty reference cannot be repaired by guessing an identifier from the prompt or timestamp.
Explain credit status accurately
An explicit recorded submission failure and creditsActual equal to zero support a settled zero-consumption outcome. A credit ledger record adds further evidence of the returned amount. A client timeout, however, supports only the statement that the client did not observe a completed response.
Do not promise a refund or a safe free retry based on uncertainty alone. Equally, do not describe an unresolved reservation as final successful consumption. Show the reserved amount and investigation status until evidence establishes the outcome.
Offline acceptance review
Walk through fixtures where validation fails, submission throws, submission succeeds and persistence throws, and the client loses a successful response. Require different application actions for these cases. The uncertain cases should preserve evidence and avoid duplicate work.
Read reservation lifecycle, duplicate request handling, and refund receipts when designing the operational handoff.
Unknown submission outcomes need reconciliation
Transport failures and malformed acceptance responses use
UPSTREAM_SUBMISSION_UNCERTAIN, preserve the queued reservation, and do not
trigger another creation request. Nonterminal jobs with an uncertain marker
or either missing upstream handle cannot be canceled into a refund: the route
returns HTTP 409 SUBMISSION_RECONCILIATION_REQUIRED, including when saving
the uncertainty marker failed. Legacy failure codes without rejection evidence
need manual investigation, not bulk balance changes. Read
the unknown-submission reference.
Continue with costs and billing
Where these facts come from
- codebase: src/ai/providers/fal-client.ts
- codebase: src/ai/api/credits.ts
- codebase: src/credits/credits.ts
- codebase: src/ai/jobs/service.ts
- codebase: src/ai/jobs/db.ts
- Editorial keyword hypothesis; search volume not measured