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
- Re-fetch the gallery list so fresh signed URLs are minted, then retry the request.
- If the page persists URLs, store only the video_id and request a fresh signed URL on demand instead of persisting the token.
- Check that the frontend passes the token unescaped (use encodeURIComponent on the query param).
- 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
- Never persist signed URLs across sessions; store video_id only
- Mint the signed URL at render time, not at list-fetch time, when TTLs are short
- Keep the signing secret stable across restarts if links must survive them
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
- lockfile not found: {path}
- Invalid or expired token
- Invalid or expired API key
- Invalid token payload
- GitHub {endpoint} returned {r.status_code} {r.reason}. Token
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/80d9dd603c64fe5c.
Report an issue: GitHub.