nexu-io/open-design · error · Error
openrouter video submit ${submitResp.status}: ${truncate(sub
Error message
openrouter video submit ${submitResp.status}: ${truncate(submitText, 240)} What it means
Thrown by renderOpenRouterVideo when the POST to submit an async video generation job (with frame_images for i2v when ctx.imageRef is set) returns non-2xx. The error embeds submitResp.status and the first 240 chars of the body. Unlike the image chat-completion path, video uses a dedicated async submit endpoint and returns a job id plus polling_url on success.
Source
Thrown at apps/daemon/src/media/index.ts:2029
];
}
// ── Step 1: Submit the generation request ──────────────────────────
const submitResp = await fetch(`${baseUrl}/videos`, withMediaRequestInit(ctx, {
method: 'POST',
headers: {
'authorization': `Bearer ${credentials.apiKey}`,
'content-type': 'application/json',
// OpenRouter attribution headers per
// https://openrouter.ai/docs/app-attribution
'HTTP-Referer': 'https://opendesign.dev',
'X-Title': 'Open Design',
},
body: JSON.stringify(body),
}));
const submitText = await submitResp.text();
if (!submitResp.ok) {
throw new Error(
`openrouter video submit ${submitResp.status}: ${truncate(submitText, 240)}`,
);
}
let submitData: any;
try {
submitData = JSON.parse(submitText);
} catch {
throw new Error(`openrouter video non-JSON: ${truncate(submitText, 200)}`);
}
const jobId = submitData?.id;
const pollingUrl = submitData?.polling_url;
if (!jobId || !pollingUrl) {
throw new Error(
`openrouter video submit returned no job id or polling_url: ${truncate(submitText, 200)}`,
);
}
View on GitHub (pinned to 5be4028344)
Solutions
- Read status+body: 402→add credits, 404→fix model slug, 413→reduce image size, 429→slow down, 5xx→retry/backoff.
- Confirm wireModel is a video-capable OpenRouter slug and that the account is enabled for it.
- For i2v, verify ctx.imageRef resolved to a valid base64 data URL within the provider's size limits.
- Retry once on 5xx; video backends are flaky under load.
Example fix
// before
if (!submitResp.ok) {
throw new Error(`openrouter video submit ${submitResp.status}: ${truncate(submitText, 240)}`);
}
// after — status hints
if (!submitResp.ok) {
const hint = submitResp.status === 402 ? ' (no credits)'
: submitResp.status === 413 ? ' (image too large)'
: submitResp.status === 404 ? ' (unknown video model)'
: submitResp.status === 429 ? ' (rate limited)'
: '';
throw new Error(`openrouter video submit ${submitResp.status}${hint}: ${truncate(submitText, 240)}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
function describeOpenRouterVideoSubmitError(status: number, body: string): string {
const hint = status === 401 ? 'bad key'
: status === 402 ? 'no credits for premium video model'
: status === 404 ? 'unknown video model slug'
: status === 413 ? 'frame_images payload too large for i2v'
: status === 429 ? 'rate limited'
: status >= 500 ? 'upstream video provider outage — retry'
: 'see body';
return `openrouter video submit ${status} (${hint}): ${truncate(body, 240)}`;
}
function isRetryableSubmitStatus(status: number): boolean {
return status === 429 || status >= 500;
} Type guard
function isOpenRouterVideoModelSlug(model: string): boolean {
return /seedance|kling|wan|sora|veo|cogvideo|hailuo|ltx-video|minimax/i.test(model);
} Try / catch
for (let attempt = 0; attempt < 2; attempt++) {
const r = await submitOpenRouterVideo(ctx, credentials);
if (r.ok) { /* parse and poll */ }
if (attempt === 0 && isRetryableSubmitStatus(r.status)) { await sleep(2000); continue; }
throw new Error(describeOpenRouterVideoSubmitError(r.status, await r.text()));
} Prevention
- Verify the account has credits; video models are premium and 402 is common on free keys.
- Keep ctx.imageRef base64 within the provider's i2v size cap to avoid 413.
- Confirm the slug is video-capable in the OpenRouter catalogue.
- Retry on 5xx/429 with backoff before surfacing.
When it happens
Trigger: 401/403 (bad key), 402 (no credits — video models are typically premium), 404 (model slug not enabled for video on the key), 413/422 (frame_images base64 too large or malformed for i2v), 429 (rate limit), or upstream 5xx from the underlying video provider.
Common situations: Account has no credits for the premium video model, ctx.imageRef base64 exceeds the provider size cap, model id not opted into video, or a provider-side (Seedance/Kling/etc.) incident.
Related errors
- openrouter image ${resp.status}: ${truncate(text, 240)}
- openrouter poll ${pollResp.status}: ${truncate(pollText, 240
- nano-banana image ${resp.status}: ${truncate(text, 240)}
- no OpenRouter API key — configure it in Settings or set OPEN
- openrouter video non-JSON: ${truncate(submitText, 200)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/fb6630b4f2239c42.
Report an issue: GitHub.