Genace

Video generation internal error: interpret a failed job response

·

A video generation internal error is a symptom to investigate, rather than a universal provider error code. First identify whether the message came from submission, a job read, the render outcome, or downloading an already generated file. Save the exact response before deciding that another generation is necessary.

A video generation API failed job response can arrive with HTTP 200. The server successfully retrieved the job; the job itself finished unsuccessfully. An HTTP client that throws only for non-success status codes will miss this outcome unless your application inspects state after parsing the response.

Genace serializes a job’s id, state, outputs, error, timestamps, and accounting fields. Missing outputs and errors are represented as null. The error object on a failed job is diagnostic data, not a replacement for checking state.

Preserve two failure channels

ChannelExampleMeaning
Confirmed rejectionHTTP 502 with UPSTREAM_SUBMISSION_FAILED and error.job_idCheck persisted rejection evidence and accounting
Unknown acceptanceHTTP 502 with UPSTREAM_SUBMISSION_UNCERTAIN and error.job_idKeep the reservation; reconcile instead of resubmitting
HTTP read rejection401, 403, 404, or 429You did not receive a normal job snapshot
Job terminal failureHTTP 200 with state failedThe render has a failed outcome
Local delivery failureDownload returns an errorRendering may already have succeeded

These cases deserve different retry policies. Retrying a read asks about the same job. Retrying delivery copies an existing artifact. Generating again creates new work and needs a separate budget decision.

Check an internal submission error before retrying

Before calling the provider, reserveJob creates the queued job and reserves its credits in one transaction. The balance deduction, FIFO credit consumption, USAGE record, and job insertion roll back together if reservation fails. A provider submission is attempted only after that transaction succeeds.

Only a confirmed rejection from provider.submit permits a failed job carrying UPSTREAM_SUBMISSION_FAILED and submissionOutcome: "rejected", followed by its refund. A generic exception is insufficient evidence. The confirmed-rejection HTTP error includes a recoverable local identifier:

{
  "error": {
    "type": "upstream_error",
    "code": "UPSTREAM_SUBMISSION_FAILED",
    "message": "Upstream submission failed. Check the job for credit reconciliation.",
    "job_id": "saved-job-id"
  }
}

This response has HTTP status 502. Save error.job_id and recover it through GET /api/v1/jobs/{id}. After a normal accepted submission, save the top-level job_id instead. Either path leads to a snapshot whose identifier is id; the snapshot’s error object is separate from the HTTP error envelope. A provider error may lack an upstream request identifier, so preserve what was returned rather than inventing one.

The compensation catch covers the provider submission call, not every later database operation. If the provider accepted a job but saving its reference fails, the service does not automatically refund it as a rejected submission. Other unhandled exceptions can still return HTTP 500 with error code INTERNAL. A failure to persist the submission-failure marker also needs manual reconciliation; the new path does not establish an upstream outcome that the available records cannot prove.

Keep the timestamp, model, application operation identifier, and exact response. Do not silently resubmit: refund idempotency prevents duplicate credit repayment, while generation creation still has no published request idempotency contract. The upstream failure reference explains the response shapes and credit-handling limits. Genace does not expose customer callback URLs for this recovery workflow.

Parse a saved failure safely

The following Node.js function accepts a response object from an existing job read. It retains the complete structured job for your application’s persistence layer instead of reducing the result to a generic exception string.

async function interpretJobRead(response) {
  const body = await response.json();
  if (!response.ok) {
    return { kind: 'read_rejected', status: response.status, error: body.error };
  }
  if (body.state === 'failed') {
    return {
      kind: 'render_failed',
      job: body,
      code: body.error?.code ?? 'UNSPECIFIED_JOB_FAILURE',
      message: body.error?.message ?? 'The render did not complete',
    };
  }
  if (body.state === 'canceled') return { kind: 'canceled', job: body };
  return { kind: 'snapshot', job: body };
}

Use your own label for an unspecified failure only in your local interpretation; do not claim that label came from Genace. Store the original error object separately so a later investigation can see exactly what the gateway returned.

Interpret actual adapter failures

The Seedance adapter treats an upstream completed status without a video URL as failed and supplies UPSTREAM_EMPTY_RESULT. An unrecognized upstream status produces UPSTREAM_UNKNOWN_STATUS. These failures require inspecting provider behavior and the saved request context, rather than assuming the prompt was rejected.

When the normal job service settles a failed or canceled outcome, it refunds held credits and records credits_actual: 0. A persisted submission failure can already have state: "failed" while its refund remains pending and credits_actual is null. State alone is therefore insufficient evidence that accounting has finished.

For UPSTREAM_SUBMISSION_FAILED with submissionOutcome: "rejected" and unsettled accounting, a GET can retry credit compensation without calling the provider again. The worker also retries eligible marked jobs using the stored backoff schedule. The configured cron currently runs daily; neither the scheduled timestamp nor a failed state promises a refund within a minute. Recheck the same job and its credit history rather than creating another generation to recover status.

Refund settlement uses the deterministic job-refund:{id} identifier and a database transaction with a locked balance row, an atomic balance increment, and a REFUND entry. Its positive remainingAmount makes returned credits available for later consumption. Repeated attempts recognize prior settlement. Internal accounting also sums matching legacy refunds, including accumulated partial amounts, and only adds the outstanding difference; this does not expose a public partial-refund operation.

Automatic compensation requires the rejection evidence as well as the failure marker. Historical codes without that evidence are not automatically refunded. It does not refund unknown queued jobs based on age, rewrite historical production outcomes, or repair every failed job. The manual reconcile-jobs --fail command still changes state without issuing a refund. If the failure marker itself could not be saved, preserve the local ID for operator investigation. A transport error before receiving a snapshot is not equivalent evidence of a refund.

Common mistakes include showing a success toast before checking state, blindly accessing outputs[0], dropping the model and request identifier from logs, and automatically trying another model for every failure. The last mistake can turn a diagnostic issue into repeated paid work.

Verification checklist

Exercise an HTTP 502 submission error containing error.job_id, a generic HTTP 500, a failed job with unsettled credits, a failed job with credits_actual: 0, a failed job without error details, a canceled job, and a succeeded job whose download later fails. Confirm that the accepted and error response identifiers both lead to the same GET path, that each outcome reaches the appropriate handler, and that only a deliberate new generation action can create another job.

Related guides cover job states, model fallback, and observability.

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 production operations

Where these facts come from

  • codebase: src/ai/providers/fal-client.ts
  • codebase: src/ai/jobs/http.ts
  • codebase: src/ai/providers/types.ts
  • codebase: src/ai/providers/seedance.ts
  • codebase: src/ai/jobs/service.ts
  • codebase: src/ai/jobs/db.ts
  • codebase: src/ai/api/credits.ts
  • codebase: src/credits/credits.ts
  • codebase: src/ai/api/errors.ts
  • codebase: scripts/reconcile-jobs.ts
  • codebase: vercel.json
  • Bing autocomplete: video generation api error seed, observed 2026-10-24; search volume not measured