unslothai/unsloth · error · HTTPException

Invalid or expired video link.

Error message

Invalid or expired video link.

What it means

Raised by the signed gallery-video file route when the HMAC token query parameter fails verification or names a different video_id than the one in the URL path. The token is produced by _sign_video_id(video_id) and is short-lived, so an expired or copy-pasted/mangled link fails here. It is a 401 because the token IS the credential on this route (no bearer header; the URL must work as a plain <video src>).

Source

Thrown at studio/backend/routes/video.py:514

    Returned as a relative URL so it works behind any proxy the page itself is served through."""
    from core.inference import video_gallery

    path = await asyncio.to_thread(video_gallery.owned_video_path, video_id)
    if path is None:
        raise HTTPException(status_code = 404, detail = "Video not found.")
    token = _sign_video_id(video_id)
    return {"url": f"/api/inference/video/gallery/{video_id}/file-signed?token={token}"}


@router.get("/video/gallery/{video_id}/file-signed")
async def get_gallery_video_file_signed(video_id: str, token: str = Query(...)):
    """Stream one gallery MP4 gated by the HMAC token instead of the bearer, so it can be a plain
    <video src> and the browser can range-request it. Same ownership gate as the bearer route, and
    the token names the single clip it may serve."""
    from core.inference import video_gallery

    if _verify_video_link_token(token) != video_id:
        raise HTTPException(status_code = 401, detail = "Invalid or expired video link.")
    path = await asyncio.to_thread(video_gallery.owned_video_path, video_id)
    if path is None:
        raise HTTPException(status_code = 404, detail = "Video not found.")
    from fastapi.responses import FileResponse

    return FileResponse(
        path,
        media_type = "video/mp4",
        headers = {"Cache-Control": "private, max-age=31536000, immutable"},
    )


@router.get("/video/gallery/{video_id}/export")
async def export_gallery_video(
    video_id: str,
    format: str = "webm",
    current_subject: str = Depends(get_current_subject),
):

View on GitHub (pinned to 203007d190)

Solutions

  1. Re-fetch the gallery list so fresh signed URLs are minted, then retry the request.
  2. If the page persists URLs, store only the video_id and request a fresh signed URL on demand instead of persisting the token.
  3. Check that the frontend passes the token unescaped (use encodeURIComponent on the query param).
  4. If links die across restarts, make the signing secret persistent (env/config) rather than per-process.

Example fix

// before (persisted URL goes stale)
const url = clip.signedUrl; // stored days ago

// after (mint on demand)
const { url } = await api.get(`/video/gallery/${clip.id}/signed-url`);
videoEl.src = url;
Defensive patterns

Strategy: retry

Validate before calling

// Re-mint the signed URL right before assigning it to <video>
async function freshSignedUrl(videoId) {
  const r = await fetch(`/api/inference/video/gallery/${videoId}/signed-url`, { headers: auth });
  if (r.status === 404) return null; // clip gone
  const { url } = await r.json();
  return url;
}

Type guard

function isSignedUrl(u: string): boolean {
  try { const q = new URL(u, location.origin).searchParams; return !!q.get('token'); }
  catch { return false; }
}

Try / catch

videoEl.onerror = async () => {
  const fresh = await freshSignedUrl(videoId);
  if (fresh) videoEl.src = fresh;      // token expired: remint once
  else removeClipFromUI(videoId);      // clip gone: drop card
};

Prevention

When it happens

Trigger: GET /api/inference/video/gallery/{video_id}/file-signed?token=... where (a) the token expired, (b) the token was signed for a different video_id (e.g. template reused a stale URL after the gallery refreshed), (c) the token string was truncated/re-encoded by the frontend, or (d) the server restarted with a new signing secret, invalidating all previously issued links.

Common situations: A gallery page left open past the token TTL; a cached HTML/JS bundle embedding old signed URLs; a reverse proxy or middleware stripping/mangling the query string; server restart rotating the HMAC secret.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/80d9dd603c64fe5c. Report an issue: GitHub.